Compartir a través de


Uso de las marcas de características de variantes en una aplicación Python

En este tutorial, se usa una marca de característica de variante para administrar experiencias para distintos segmentos de usuarios en una aplicación de ejemplo, Cita del día. Utilice la marca de característica de variante creada en Cómo usar las marcas de característica de variantes. Antes de continuar, asegúrese de crear la marca de característica de variante llamada Saludo en su almacén de App Configuration.

Requisitos previos

Configuración de una aplicación web de Python Flask

Si ya tiene una aplicación web de Python Flask, puede ir directamente a la sección Usar la marca de característica de variante.

  1. Cree una carpeta de proyecto denominada QuoteOfTheDay.

  2. Cree un entorno virtual en la carpeta QuoteOfTheDay.

    python -m venv venv
    
  3. Active el entorno virtual.

    .\venv\Scripts\Activate
    
  4. Instale las versiones más recientes de los siguientes paquetes.

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

Creación de la aplicación Cita del día

  1. Cree un archivo nuevo denominado app.py en la carpeta QuoteOfTheDay con el siguiente contenido. Este configura una aplicación web básica de Flask con autenticación de usuario.

    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. Cree un nuevo archivo denominado model.py en la carpeta QuoteOfTheDay con el siguiente contenido. Este define una clase de datos de Quote y un modelo de usuario para la aplicación web de 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. Cree un nuevo archivo denominado routes.py en la carpeta QuoteOfTheDay con el siguiente contenido. Este define rutas para la aplicación web de Flask, para controlar la autenticación de usuarios y mostrar una página principal con una cita aleatoria.

    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"))
    
  4. Cree una nueva carpeta denominada templates en la carpeta QuoteOfTheDay y agregue un nuevo archivo denominado base.html en ella con el siguiente contenido. Este define la página de diseño de la aplicación 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. Cree un nuevo archivo denominado index.html en la carpeta templates con el siguiente contenido. Este extiende la plantilla base y agrega el bloque de contenido.

    {% 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. Cree un nuevo archivo denominado sign_up.html en la carpeta templates con el siguiente contenido. Este define la plantilla para la página de registro de usuario.

    {% 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. Cree un nuevo archivo denominado login.html en la carpeta templates con el siguiente contenido. Este define la plantilla para la página de inicio de sesión del usuario.

    {% 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. Cree una nueva carpeta denominada static en la carpeta QuoteOfTheDay y agregue un nuevo archivo denominado site.css en ella con el siguiente contenido. Este agrega estilos CSS para la aplicación 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 */
    }
    

Uso de la marca de característica de variante

  1. Instale las versiones más recientes de los siguientes paquetes.

    pip install azure-identity 
    pip install azure-appconfiguration-provider
    pip install featuremanagement[AzureMonitor]
    
  2. Abra el archivo app.py y agregue el código siguiente al final. Se conecta a App Configuration y configura la administración de características.

    Use DefaultAzureCredential para autenticarse en el almacén de App Configuration. Siga las instrucciones para asignar la credencial al rol Lector de datos de App Configuration. Asegúrese de dejar tiempo suficiente para que el permiso se propague antes de ejecutar la aplicación.

    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. Abra routes.py y agregue el código siguiente al final para actualizar la configuración y obtener la variante de característica.

    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 y ejecutar la aplicación

  1. Establezca una variable de entorno denominada AzureAppConfigurationEndpoint en el punto de conexión del almacén de App Configuration que se encuentra en la Información general del almacén en Azure Portal.

    Si usa el símbolo del sistema de Windows, ejecute el siguiente comando y reinícielo para que se aplique el cambio:

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

    Si usa PowerShell, ejecute el siguiente comando:

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

    Si usa macOS o Linux, ejecute el siguiente comando:

    export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
    
  2. En el símbolo del sistema, en la carpeta QuoteOfTheDay, ejecute: flask run.

  3. Espere a que se inicie la aplicación y, a continuación, abra un explorador y vaya a http://localhost:5000/.

  4. Una vez que vea la aplicación en ejecución, seleccione Registrar en la parte superior derecha para registrar un nuevo usuario.

    Captura de pantalla de la aplicación Cita del día, en la que se muestra Registrarse.

  5. Registre un nuevo usuario denominado usera@contoso.com.

    Nota:

    Es importante que este tutorial use estos nombres exactamente. Siempre que la característica se haya configurado según lo previsto, los dos usuarios deben ver variantes diferentes.

  6. Seleccione el botón Enviar después de escribir la información del usuario.

  7. La sesión se inicia automáticamente. Podrá comprobar que usera@contoso.com ve el mensaje largo al visualizar la aplicación.

    Captura de pantalla de la aplicación Cita del día, en la que se muestra un mensaje especial para el usuario.

  8. Cierre la sesión con el botón Cerrar sesión en la parte superior derecha.

  9. Registre un segundo usuario denominado userb@contoso.com.

  10. La sesión se inicia automáticamente. Podrá comprobar que userb@contoso.com ve el mensaje corto al visualizar la aplicación.

    Recorte de pantalla de la aplicación Cita del día, que muestra un mensaje para el usuario.

Pasos siguientes

Si quiere conocer todas las características de la biblioteca de administración de características de Python, consulte el siguiente documento.