Partager via


Tutoriel : Utiliser des indicateurs de fonctionnalité variants dans une application Python

Dans ce tutoriel, vous utilisez un indicateur de fonctionnalité variant pour gérer les expériences pour différents segments d’utilisateurs dans un exemple d’application, Citation du jour. Vous utilisez l’indicateur de fonctionnalité de variante créé dans Utiliser des indicateurs de fonctionnalité de variante. Avant de continuer, veillez à créer l’indicateur de fonctionnalité variant nommé Salutations dans votre magasin App Configuration.

Prérequis

Configurer une application web Python Flask

Si vous disposez déjà d’une application web Python Flask, vous pouvez passer à la section Utiliser l’indicateur de fonctionnalité de variante.

  1. Créez un dossier de projet nommé QuoteOfTheDay.

  2. Créez un environnement virtuel dans le dossier QuoteOfTheDay.

    python -m venv venv
    
  3. Activez l’environnement virtuel.

    .\venv\Scripts\Activate
    
  4. Installez les dernières versions des packages suivants.

    pip install flask
    pip install flask-login
    pip install flask_sqlalchemy
    pip install flask_bcrypt
    

Créer l’application Citation du jour

  1. Créez un fichier nommé app.py dans le dossier QuoteOfTheDay avec le contenu suivant. Il configure une application web Flask de base avec l’authentification utilisateur.

    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)
    
  2. Créez un fichier nommé model.py dans le dossier QuoteOfTheDay avec le contenu suivant. Il définit une classe de données Quote et un modèle utilisateur pour l’application 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
    
  3. Créez un fichier nommé routes.py dans le dossier QuoteOfTheDay avec le contenu suivant. Il définit des routes pour l’application web Flask, gère l’authentification utilisateur, et affiche une page d’accueil avec une citation aléatoire.

    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"])
    def index():
        context = {}
        user = ""
        if current_user.is_authenticated:
            user = current_user.username
            context["user"] = user
        else:
            context["user"] = "Guest"
    
        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"))
    
  4. Créez un dossier nommé templates dans le dossier QuoteOfTheDay et ajoutez-y un nouveau fichier nommé base.html avec le contenu suivant. Il définit la page de disposition de l’application 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">
            &copy; 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>
    
  5. Créez un fichier nommé index.html dans le dossier templates avec le contenu suivant. Il étend le modèle de base et ajoute le bloc de contenu.

    {% 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 %}
    
  6. Créez un fichier nommé sign_up.html dans le dossier templates avec le contenu suivant. Il définit le modèle de la page d’inscription de l’utilisateur.

    {% 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 %}
    
  7. Créez un fichier nommé login.html dans le dossier templates avec le contenu suivant. Il définit le modèle de la page de connexion de l’utilisateur.

    {% 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 %}
    
  8. Créez un dossier nommé static dans le dossier QuoteOfTheDay et ajoutez-y un nouveau fichier nommé site.css avec le contenu suivant. Il ajoute des styles CSS pour l’application 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 */
    }
    

Utiliser l’indicateur de fonctionnalités de variantes

  1. Installez les dernières versions des packages suivants.

    pip install azure-identity 
    pip install azure-appconfiguration-provider
    pip install featuremanagement[AzureMonitor]
    
  2. Ouvrez le fichier app.py et ajoutez le code suivant à la fin du fichier. Il se connecte à App Configuration et configure la gestion des fonctionnalités.

    Vous utilisez le DefaultAzureCredential pour vous authentifier auprès de votre magasin App Configuration. Suivez les instructions pour attribuer à votre identifiant le rôle de lecteur de données de configuration de l'application. Veillez à laisser suffisamment de temps pour que l’autorisation se propage avant d’exécuter votre application.

    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)
    
  3. Ouvrez routes.py et mettez à jour le code suivant dans greeting_message afin d'obtenir la variante de fonctionnalité.

    from . import feature_manager
    
    # Update greeting_message to variant
    greeting = feature_manager.get_variant("Greeting", user)
    greeting_message = ""
    if greeting:
        greeting_message = greeting.configuration
    

Générer et exécuter l’application

  1. Définissez une variable d’environnement nommée AzureAppConfigurationEndpoint sur le point de terminaison de votre magasin App Configuration qui se trouve sous la Vue d’ensemble de votre magasin dans le portail Azure.

    Si vous utilisez l’invite de commandes Windows, exécutez la commande suivante et redémarrez l’invite pour que la modification soit prise en compte :

    setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
    

    Si vous utilisez PowerShell, utilisez la commande suivante :

    $Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
    

    Si vous utilisez macOS ou Linux, exécutez la commande suivante :

    export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
    
  2. À l’invite de commandes, dans le dossier QuoteOfTheDay, exécutez : flask run.

  3. Attendez que l’application démarre, puis ouvrez un navigateur et accédez à http://localhost:5000/.

  4. Une fois l’application en cours d’exécution affichée, sélectionnez Register en haut à droite pour inscrire un nouvel utilisateur.

    Capture d’écran de l’application Quote of the day montrant l’option Register.

  5. Inscrivez un nouvel utilisateur nommé usera@contoso.com.

    Remarque

    Il est important, dans le cadre de ce tutoriel, d’utiliser exactement ces noms. Tant que la fonctionnalité a été configurée comme prévu, les deux utilisateurs doivent voir différentes variantes.

  6. Sélectionnez le bouton Submit après avoir entré les informations utilisateur.

  7. Vous êtes connecté automatiquement. Vous devez constater que usera@contoso.com voit le message long lors de l’affichage de l’application.

    Capture d’écran de l’application Quote of the day, avec un message spécial présenté à l’utilisateur.

  8. Déconnectez-vous à l’aide du bouton Logout en haut à droite.

  9. Inscrivez un deuxième utilisateur nommé userb@contoso.com.

  10. Vous êtes connecté automatiquement. Vous devez constater que userb@contoso.com voit le message court lors de l’affichage de l’application.

    Capture d’écran de l’application Citation du jour, avec un message court présenté à l’utilisateur.

Étapes suivantes

Pour obtenir la liste complète des fonctionnalités de la bibliothèque de gestion des fonctionnalités Python, référez-vous au document suivant.