Usar sinalizadores de recursos de variantes em um aplicativo Python
Neste tutorial, você usará um sinalizador de recurso de grade para gerenciar experiências para diferentes segmentos de usuário em um aplicativo de exemplo, Citação do Dia. Use o sinalizador de recurso de variantes criado em Como usar sinalizadores de recursos de variantes. Antes de continuar, certifique-se de criar o sinalizador de recurso de grade chamado Saudação em seu repositório de Configuração de Aplicativos.
Pré-requisitos
- Python 3.8 ou posterior – para obter informações sobre como configurar o Python no Windows, confira a Documentação do Python no Windows
- Siga o tutorial Como usar sinalizadores de recursos de variantes e crie o sinalizador de recurso de variantes chamado Saudação.
Configurar um aplicativo Web Flask em Python
Se você já tiver um aplicativo Web Flask em Python, poderá pular para a seção Usar o sinalizador de recurso de variantes.
Crie uma pasta de projeto chamada QuoteOfTheDay.
Crie um ambiente virtual na pasta QuoteOfTheDay.
python -m venv venv
Ative o ambiente virtual.
.\venv\Scripts\Activate
Instale as versões mais recentes dos seguintes pacotes.
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
Criar o aplicativo Quote of the Day
Crie um arquivo chamado
app.py
na pastaQuoteOfTheDay
com o seguinte conteúdo. Ele configura um aplicativo Web Flask básico com autenticação de usuário.from flask_bcrypt import Bcrypt from flask_sqlalchemy import SQLAlchemy from flask_login import LoginManager from flask import Flask app = Flask(__name__, template_folder="../templates", static_folder="../static") bcrypt = Bcrypt(app) db = SQLAlchemy() db.init_app(app) login_manager = LoginManager() login_manager.init_app(app) from .model import Users @login_manager.user_loader def loader_user(user_id): return Users.query.get(user_id) with app.app_context(): db.create_all() if __name__ == "__main__": app.run(debug=True) from . import routes app.register_blueprint(routes.bp)
Crie um arquivo chamado model.py na pasta QuoteOfTheDay com o seguinte conteúdo. Ele define uma classe de dados
Quote
e um modelo de usuário para o aplicativo Web Flask.from dataclasses import dataclass from flask_login import UserMixin from . import db @dataclass class Quote: message: str author: str # Create user model class Users(UserMixin, db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(250), unique=True, nullable=False) password_hash = db.Column(db.String(250), nullable=False) def __init__(self, username, password): self.username = username self.password_hash = password
Crie um arquivo chamado routes.py na pasta QuoteOfTheDay com o seguinte conteúdo. Ele define rotas para o aplicativo Web Flask, manipulando a autenticação do usuário e exibindo uma home page com uma citação aleatória.
import random from flask import Blueprint, render_template, request, flash, redirect, url_for from flask_login import current_user, login_user, logout_user from . import db, bcrypt from .model import Quote, Users bp = Blueprint("pages", __name__) @bp.route("/", methods=["GET", "POST"]) def index(): context = {} user = "" if current_user.is_authenticated: user = current_user.username context["user"] = user else: context["user"] = "Guest" if request.method == "POST": return redirect(url_for("pages.index")) quotes = [ Quote("You cannot change what you are, only what you do.", "Philip Pullman"), ] greeting_message = "Hi" context["model"] = {} context["model"]["greeting_message"] = greeting_message context["model"]["quote"] = {} context["model"]["quote"] = random.choice(quotes) context["isAuthenticated"] = current_user.is_authenticated return render_template("index.html", **context) @bp.route("/register", methods=["GET", "POST"]) def register(): if request.method == "POST": password = request.form.get("password") hashed_password = bcrypt.generate_password_hash(password).decode('utf-8') user = Users(request.form.get("username"), hashed_password) try: db.session.add(user) db.session.commit() except Exception as e: flash("Username already exists") return redirect(url_for("pages.register")) login_user(user) return redirect(url_for("pages.index")) return render_template("sign_up.html") @bp.route("/login", methods=["GET", "POST"]) def login(): if request.method == "POST": user = Users.query.filter_by(username=request.form.get("username")).first() password = request.form.get("password") if user and bcrypt.check_password_hash(user.password_hash, password): login_user(user) return redirect(url_for("pages.index")) return render_template("login.html") @bp.route("/logout") def logout(): logout_user() return redirect(url_for("pages.index"))
Crie uma pasta chamada modelos na pasta QuoteOfTheDay e adicione um novo arquivo chamado base.html nela com o conteúdo a seguir. Ele define a página de layout do aplicativo Web.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>QuoteOfTheDay</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"> <link rel="stylesheet" href="{{ url_for('static', filename='site.css') }}"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"> </head> <body> <header> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <div class="container"> <a class="navbar-brand" href="/">QuoteOfTheDay</a> <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="navbar-collapse collapse d-sm-inline-flex justify-content-between"> <ul class="navbar-nav flex-grow-1"> <li class="nav-item"> <a class="nav-link text-dark" href="/">Home</a> </li> </ul> {% block login_partial %} <ul class="navbar-nav"> {% if isAuthenticated %} <li class="nav-item"> <a class="nav-link text-dark">Hello {{user}}!</a> </li> <li class="nav-item"> <a class="nav-link text-dark" href="/logout">Logout</a> </li> {% else %} <li class="nav-item"> <a class="nav-link text-dark" href="/register">Register</a> </li> <li class="nav-item"> <a class="nav-link text-dark" href="/login">Login</a> </li> {% endif %} </ul> {% endblock %} </div> </div> </nav> </header> <div class="container"> <main role="main" class="pb-3"> {% block content %} {% endblock %} </main> </div> </body> <footer class="border-top footer text-muted"> <div class="container"> © 2024 - QuoteOfTheDay </div> </footer> <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script> </body> </html>
Crie um arquivo chamado index.html na pasta modelos com o conteúdo a seguir. Ele estende o modelo base e adiciona o bloco de conteúdo.
{% extends 'base.html' %} {% block content %} <div class="quote-container"> <div class="quote-content"> {% if model.greeting_message %} <h3 class="greeting-content">{{model.greeting_message}}</h3> {% endif %} <br /> <p class="quote">“{{model.quote.message}}”</p> <p>- <b>{{model.quote.author}}</b></p> </div> <div class="vote-container"> <button class="btn btn-primary" onclick="heartClicked(this)"> <i class="far fa-heart"></i> <!-- Heart icon --> </button> </div> <form action="/" method="post"> </form> </div> <script> function heartClicked(button) { var icon = button.querySelector('i'); icon.classList.toggle('far'); icon.classList.toggle('fas'); } </script> {% endblock %}
Crie um arquivo chamado sign_up.html na pasta modelos com o conteúdo a seguir. Ele define o modelo da página de registro do usuário.
{% extends 'base.html' %} {% block content %} <div class="login-container"> <h1>Create an account</h1> <form action="#" method="post"> <label for="username">Username:</label> <input type="text" name="username" /> <label for="password">Password:</label> <input type="password" name="password" /> <button type="submit">Submit</button> </form> </div> {% endblock %}
Crie um arquivo chamado login.html na pasta modelos com o conteúdo a seguir. Ele define o modelo da página de logon do usuário.
{% extends 'base.html' %} {% block content %} <div class="login-container"> <h1>Login to your account</h1> <form action="#" method="post"> <label for="username">Username:</label> <input type="text" name="username" /> <label for="password">Password:</label> <input type="password" name="password" /> <button type="submit">Submit</button> </form> </div> {% endblock %}
Crie uma pasta chamada estático na pasta QuoteOfTheDay e adicione um novo arquivo chamado site.css nela com o conteúdo a seguir. Ele adiciona estilos CSS para o aplicativo Web.
html { font-size: 14px; } @media (min-width: 768px) { html { font-size: 16px; } } .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; } html { position: relative; min-height: 100%; } body { margin-bottom: 60px; } body { font-family: Arial, sans-serif; background-color: #f4f4f4; color: #333; } .quote-container { background-color: #fff; margin: 2em auto; padding: 2em; border-radius: 8px; max-width: 750px; box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); display: flex; justify-content: space-between; align-items: start; position: relative; } .login-container { background-color: #fff; margin: 2em auto; padding: 2em; border-radius: 8px; max-width: 750px; box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.2); justify-content: space-between; align-items: start; position: relative; } .vote-container { position: absolute; top: 10px; right: 10px; display: flex; gap: 0em; } .vote-container .btn { background-color: #ffffff; /* White background */ border-color: #ffffff; /* Light blue border */ color: #333 } .vote-container .btn:focus { outline: none; box-shadow: none; } .vote-container .btn:hover { background-color: #F0F0F0; /* Light gray background */ } .greeting-content { font-family: 'Georgia', serif; /* More artistic font */ } .quote-content p.quote { font-size: 2em; /* Bigger font size */ font-family: 'Georgia', serif; /* More artistic font */ font-style: italic; /* Italic font */ color: #4EC2F7; /* Medium-light blue color */ }
Usar o sinalizador de recurso de grade
Instale as versões mais recentes dos seguintes pacotes.
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]
Abra o arquivo
app.py
e adicione o seguinte código ao final do arquivo. Ele se conecta à Configuração de Aplicativos e configura o gerenciamento de recursos.Você usa a autenticação
DefaultAzureCredential
no repositório de Configuração de Aplicativos. Siga as instruções para atribuir à credencial a função Leitor de Dados de Configuração de Aplicativo. Certifique-se de permitir tempo suficiente para a permissão se propagar antes de executar seu aplicativo.import os from azure.appconfiguration.provider import load from featuremanagement import FeatureManager from azure.identity import DefaultAzureCredential ENDPOINT = os.getenv("AzureAppConfigurationEndpoint") # Updates the flask app configuration with the Azure App Configuration settings whenever a refresh happens def callback(): app.config.update(azure_app_config) # Connect to App Configuration global azure_app_config azure_app_config = load( endpoint=ENDPOINT, credential=DefaultAzureCredential(), on_refresh_success=callback, feature_flag_enabled=True, feature_flag_refresh_enabled=True, ) app.config.update(azure_app_config) # Create a FeatureManager feature_manager = FeatureManager(azure_app_config)
Abra
routes.py
e adicione o código a seguir ao final dele para atualizar a configuração e obter a variante do recurso.from featuremanagement.azuremonitor import track_event from . import azure_app_config, feature_manager ... # Update the post request to track liked events if request.method == "POST": track_event("Liked", user) return redirect(url_for("pages.index")) ... # Update greeting_message to variant greeting = feature_manager.get_variant("Greeting", user) greeting_message = "" if greeting: greeting_message = greeting.configuration
Compilar e executar o aplicativo
Defina a variável de ambiente chamada AzureAppConfigurationEndpoint como o ponto de extremidade do repositório da Configuração de Aplicativos, encontrado na Visão geral do seu repositório no portal do Azure.
Se você usar o prompt de comando do Windows, execute o comando a seguir e reinicie o prompt de comando para permitir que a alteração entre em vigor:
setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
Se você usar o PowerShell, execute o seguinte comando:
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
Se você usa macOS ou Linux, execute o comando a seguir:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
No prompt de comando, na pasta QuoteOfTheDay, execute:
flask run
.Aguarde até que o aplicativo seja iniciado e, em seguida, abra um navegador e navegue até
http://localhost:5000/
.Depois de exibir o aplicativo em execução, selecione Registrar na parte superior direita para registrar um novo usuário.
Registrar um novo usuário chamado usera@contoso.com.
Observação
É importante para a finalidade deste tutorial usar exatamente esses nomes. Desde que o recurso tenha sido configurado conforme o esperado, os dois usuários devem ver variantes diferentes.
Selecione o botão Enviar depois de inserir as informações do usuário.
Você será conectado automaticamente. Você deve ver que usera@contoso.com vê uma mensagem longa ao exibir o aplicativo.
Faça logoff usando o botão Fazer logoff no canto superior direito.
Registrar um segundo usuário chamado userb@contoso.com.
Você será conectado automaticamente. Você deve ver que userb@contoso.com vê uma mensagem curta ao exibir o aplicativo.
Próximas etapas
Para obter o resumo completo dos recursos da biblioteca de gerenciamento de recursos do Python, consulte o documento a seguir.