Usare flag di funzionalità varianti in un'applicazione Python
In questa esercitazione si usa un flag di funzionalità variante per gestire le esperienze per segmenti utente diversi in un'applicazione di esempio, Offerta del giorno. Usare il flag di funzionalità variant creato in Come variantare i flag di funzionalità. Prima di procedere, assicurarsi di creare il flag di funzionalità variante denominato Greeting nell'archivio Configurazione app.
Prerequisiti
- Python 3.8 o versione successiva: per informazioni sulla configurazione di Python in Windows, vedere la documentazione di Python in Windows
- Seguire l'esercitazione Come variantare i flag di funzionalità e creare il flag di funzionalità variante denominato Greeting.
Configurare un'app Web Python Flask
Se si dispone già di un'app Web Python Flask, è possibile passare alla sezione Usare il flag di funzionalità variant.
Creare una nuova cartella di progetto denominata QuoteOfTheDay.
Creare un ambiente virtuale nella cartella QuoteOfTheDay .
python -m venv venv
Attivare l'ambiente virtuale.
.\venv\Scripts\Activate
Installare le versioni più recenti dei pacchetti seguenti.
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
Creare l'app Quote of the Day
Creare un nuovo file denominato
app.py
nellaQuoteOfTheDay
cartella con il contenuto seguente. Configura un'applicazione Web Flask di base con l'autenticazione utente.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)
Creare un nuovo file denominato model.py nella cartella QuoteOfTheDay con il contenuto seguente. Definisce una
Quote
classe di dati e un modello utente per l'applicazione 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
Creare un nuovo file denominato routes.py nella cartella QuoteOfTheDay con il contenuto seguente. Definisce le route per l'applicazione Web Flask, la gestione dell'autenticazione utente e la visualizzazione di una home page con un'offerta casuale.
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"))
Creare una nuova cartella denominata templates nella cartella QuoteOfTheDay e aggiungere un nuovo file denominato base.html al suo interno con il contenuto seguente. Definisce la pagina di layout per l'applicazione 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>
Creare un nuovo file denominato index.html nella cartella templates con il contenuto seguente. Estende il modello di base e aggiunge il blocco di contenuto.
{% 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 %}
Creare un nuovo file denominato sign_up.html nella cartella templates con il contenuto seguente. Definisce il modello per la pagina di registrazione dell'utente.
{% 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 %}
Creare un nuovo file denominato login.html nella cartella templates con il contenuto seguente. Definisce il modello per la pagina di accesso dell'utente.
{% 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 %}
Creare una nuova cartella denominata static nella cartella QuoteOfTheDay e aggiungere un nuovo file denominato site.css in esso con il contenuto seguente. Aggiunge stili CSS per l'applicazione 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 */ }
Usare il flag di funzionalità variant
Installare le versioni più recenti dei pacchetti seguenti.
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]
Aprire il
app.py
file e aggiungere il codice seguente alla fine del file. Si connette a Configurazione app e configura la gestione delle funzionalità.Usare per eseguire l'autenticazione
DefaultAzureCredential
nell'archivio Configurazione app. Seguire le istruzioni per assegnare le credenziali al ruolo lettore dati Configurazione app. Assicurarsi di consentire tempo sufficiente per la propagazione dell'autorizzazione prima di eseguire l'applicazione.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)
Aprire
routes.py
e aggiungere il codice seguente alla fine di esso per aggiornare la configurazione e ottenere la variante della funzionalità.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
Compilare ed eseguire l'app
Impostare una variabile di ambiente denominata AzureAppConfigurationEndpoint sull'endpoint dell'archivio Configurazione app disponibile in Panoramica dell'archivio nel portale di Azure.
Se si usa il prompt dei comandi di Windows, eseguire il comando seguente e riavviare il prompt per rendere effettiva la modifica:
setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
Se si usa PowerShell, eseguire il comando seguente:
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
Se si usa macOS o Linux, eseguire il comando seguente:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
Nel prompt dei comandi, nella cartella QuoteOfTheDay eseguire:
flask run
.Attendere l'avvio dell'app e quindi aprire un browser e passare a
http://localhost:5000/
.Una volta che si sta visualizzando l'applicazione in esecuzione, selezionare Registra in alto a destra per registrare un nuovo utente.
Registrare un nuovo utente denominato usera@contoso.com.
Nota
Ai fini di questa esercitazione è importante usare esattamente questi nomi. Se la funzionalità è stata configurata come previsto, i due utenti dovrebbero visualizzare varianti diverse.
Selezionare il pulsante Invia dopo aver immesso le informazioni utente.
L'accesso viene eseguito automaticamente. Verrà visualizzato usera@contoso.com il messaggio lungo quando si visualizza l'app.
Disconnettersi con il pulsante Disconnessione in alto a destra.
Registrare un secondo utente denominato userb@contoso.com.
L'accesso viene eseguito automaticamente. Si noterà che userb@contoso.com viene visualizzato il breve messaggio quando si visualizza l'app.
Passaggi successivi
Per il rundown completo delle funzionalità della libreria di gestione delle funzionalità Python, vedere il documento seguente.