Usar sinalizadores de recurso variante em um aplicativo Python
Neste tutorial, você usa um sinalizador de recurso variante para gerenciar experiências para diferentes segmentos de usuário em um aplicativo de exemplo, Cotação do Dia. Você utiliza o sinalizador de recurso variante criado em Como sinalizadores de recurso variante. Antes de continuar, certifique-se de criar o sinalizador de recurso variante chamado Saudação na sua loja de configuração de aplicativos.
Pré-requisitos
- Python 3.8 ou posterior - para obter informações sobre como configurar o Python no Windows, consulte a documentação do Python no Windows
- Siga o tutorial Como sinalizadores de recurso variante e crie o sinalizador de recurso variante chamado Saudação.
Configurar um aplicativo Web Python Flask
Se você já tiver um aplicativo web Python Flask, você pode pular para a seção Usar o sinalizador de recurso variante.
Crie uma nova 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 a aplicação Orçamento do Dia
Crie um novo arquivo nomeado
app.py
naQuoteOfTheDay
pasta 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 novo arquivo chamado model.py na pasta QuoteOfTheDay com o seguinte conteúdo. Ele define uma classe de
Quote
dados 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 novo arquivo chamado routes.py na pasta QuoteOfTheDay com o seguinte conteúdo. Ele define rotas para a aplicação web Flask, manipulando a autenticação do usuário e exibindo uma página inicial com uma cotaçã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 nova pasta chamada templates na pasta QuoteOfTheDay e adicione um novo arquivo chamado base.html nela com o seguinte conteúdo. Ele define a página de layout para o 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 novo arquivo chamado index.html na pasta de modelos com o seguinte conteúdo. 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 novo arquivo chamado sign_up.html na pasta de modelos com o seguinte conteúdo. Ele define o modelo para a 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 novo arquivo chamado login.html na pasta de modelos com o seguinte conteúdo. Ele define o modelo para a página de login 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 nova pasta chamada static na pasta QuoteOfTheDay e adicione um novo arquivo chamado site.css nela com o seguinte conteúdo. 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 variante
Instale as versões mais recentes dos seguintes pacotes.
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]
Abra o
app.py
arquivo e adicione o seguinte código ao final do arquivo. Ele se conecta à Configuração do aplicativo e configura o gerenciamento de recursos.Você usa o para autenticar em
DefaultAzureCredential
sua loja de configuração de aplicativos. Siga as instruções para atribuir à sua credencial a função de Leitor de Dados de Configuração do Aplicativo. Certifique-se de dar tempo suficiente para que a permissão se propague 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 seguinte código ao final para atualizar a configuração e obter a variante de 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 a aplicação
Defina uma variável de ambiente chamada AzureAppConfigurationEndpoint para o ponto de extremidade do seu repositório de Configuração de Aplicativo encontrado em Visão geral da sua loja no portal do Azure.
Se você usar o prompt de comando do Windows, execute o seguinte comando 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 seguinte comando:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
No prompt de comando, na pasta QuoteOfTheDay , execute:
flask run
.Aguarde até que a aplicação seja iniciada e, em seguida, abra um browser e navegue até
http://localhost:5000/
.Depois de visualizar o aplicativo em execução, selecione Registrar no canto superior direito para registrar um novo usuário.
Registre um novo usuário chamado usera@contoso.com.
Nota
É importante para o propósito deste tutorial usar esses nomes exatamente. 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ê está automaticamente conectado. Você deve ver que usera@contoso.com vê a mensagem longa ao visualizar o aplicativo.
Saia usando o botão Logout no canto superior direito.
Registre um segundo usuário chamado userb@contoso.com.
Você está automaticamente conectado. Você deve ver que userb@contoso.com vê a mensagem curta ao visualizar o aplicativo.
Próximos passos
Para obter o resumo completo dos recursos da biblioteca de gerenciamento de recursos do Python, consulte o documento a seguir.