Samouczek: użycie flag wariantowych funkcji w aplikacji Python

W tym samouczku użyjesz flagi funkcji z wariantami do zarządzania różnymi wersjami działania aplikacji dla różnych segmentów użytkowników w przykładowej aplikacji Quote of the Day. Używasz flagi funkcji wariantowej utworzonej w artykule Jak tworzyć flagi funkcji wariantowych. Przed kontynuowaniem upewnij się, że utworzysz flagę funkcji typu wariant o nazwie Greeting w magazynie App Configuration.

Wymagania wstępne

Konfigurowanie aplikacji internetowej platformy Python Flask

Jeśli masz już aplikację internetową Flask w Pythonie, możesz przejść do sekcji Używanie flagi funkcji wariantu.

  1. Utwórz nowy folder projektu o nazwie QuoteOfTheDay.

  2. Utwórz środowisko wirtualne w folderze QuoteOfTheDay .

    python -m venv venv
    
  3. Aktywuj środowisko wirtualne.

    .\venv\Scripts\Activate
    
  4. Zainstaluj najnowsze wersje następujących pakietów.

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

Utwórz aplikację Cytat Dnia

  1. Utwórz nowy plik o nazwie app.py w folderze QuoteOfTheDay z następującą zawartością. Konfiguruje podstawową aplikację internetową platformy Flask z uwierzytelnianiem użytkownika.

    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)
    
    import routes
    app.register_blueprint(routes.bp)
    
  2. Utwórz nowy plik o nazwie model.py w folderze QuoteOfTheDay z następującą zawartością. Definiuje klasę Quote danych i model użytkownika dla aplikacji internetowej platformy Flask.

    from dataclasses import dataclass
    from flask_login import UserMixin
    from app 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. Utwórz nowy plik o nazwie routes.py w folderze QuoteOfTheDay z następującą zawartością. Definiuje trasy dla aplikacji internetowej we Flasku, obsługując uwierzytelnianie użytkowników i wyświetlając stronę główną z losowym cytatem.

    import random
    
    from flask import Blueprint, render_template, request, flash, redirect, url_for
    from flask_login import current_user, login_user, logout_user
    from app 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. Utwórz nowy folder o nazwie templates w folderze QuoteOfTheDay i dodaj w nim nowy plik o nazwie base.html z następującą zawartością. Definiuje stronę układu aplikacji internetowej.

    <!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. Utwórz nowy plik o nazwie index.html w folderze templates z następującą zawartością. Rozszerza szablon podstawowy i dodaje blok zawartości.

    {% 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. Utwórz nowy plik o nazwie sign_up.html w folderze templates z następującą zawartością. Definiuje szablon strony rejestracji użytkownika.

    {% 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. Utwórz nowy plik o nazwie login.html w folderze templates z następującą zawartością. Definiuje szablon strony logowania użytkownika.

    {% 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. Utwórz nowy folder o nazwie static w folderze QuoteOfTheDay i dodaj w nim nowy plik o nazwie site.css z następującą zawartością. Dodaje style CSS dla aplikacji internetowej.

    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 */
    }
    

Użyj flagi funkcji wariantu

  1. Zainstaluj najnowsze wersje następujących pakietów.

    pip install azure-identity 
    pip install azure-appconfiguration-provider
    pip install featuremanagement[AzureMonitor]
    
  2. Otwórz plik app.py . Dodaj następujący kod między bcrypt = Bcrypt(app) i db = SQLAlchemy(). Łączy się z usługą App Configuration i konfiguruje zarządzanie funkcjami.

    Używasz polecenia DefaultAzureCredential, aby uwierzytelniać się w magazynie App Configuration. Postępuj zgodnie z instrukcjami, aby przypisać poświadczeniu rolę Czytelnik danych konfiguracji aplikacji. Przed uruchomieniem aplikacji należy odczekać wystarczająco dużo czasu, aby uprawnienia się rozpropagowały.

    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. Otwórz routes.py i zaktualizuj następujący kod, greeting_message aby uzyskać wariant funkcji.

    from app import feature_manager
    
    # Update greeting_message to variant
    greeting = feature_manager.get_variant("Greeting", user)
    greeting_message = ""
    if greeting:
        greeting_message = greeting.configuration
    
  4. W portalu Azure przejdź do utworzonego wcześniej magazynu App Configuration i dodaj następujące wartości klucza. Utwórz parę klucz-wartość

Key Value Description
"SQLALCHEMY_DATABASE_URI" "sqlite:///db.sqlite" Używany przez Flask-SQLAlchemy do określenia, z którą bazą danych należy się połączyć
"SECRET_KEY" fałszywy-tajny-klucz Używany przez platformę Flask do podpisywania plików cookie sesji (ochrona integralności) na potrzeby funkcji uwierzytelniania

W app.pyload(...) funkcja pobiera dane konfiguracji z Azure App Configuration i przechowuje je w pliku azure_app_config. Następnie wywołanie app.config.update(azure_app_config) stosuje pobrane ustawienia konfiguracji do aplikacji Platformy Flask.

Skompiluj i uruchom aplikację

  1. Ustaw zmienną środowiskową o nazwie AzureAppConfigurationEndpoint i przypisz jej wartość punktu końcowego magazynu App Configuration, który znajdziesz na stronie Przegląd tego magazynu w portalu Azure.

    Jeśli używasz wiersza polecenia systemu Windows, uruchom następujące polecenie i uruchom ponownie wiersz polecenia, aby zezwolić na wprowadzenie zmian:

    setx AzureAppConfigurationEndpoint "<AppConfigurationEndpoint>"
    

    Jeśli używasz programu PowerShell, uruchom następujące polecenie:

    $Env:AzureAppConfigurationEndpoint = "<AppConfigurationEndpoint>"
    

    Jeśli używasz systemu macOS lub Linux, uruchom następujące polecenie:

    export AzureAppConfigurationEndpoint='<AppConfigurationEndpoint>'
    
  2. W wierszu polecenia w folderze QuoteOfTheDay uruchom polecenie: flask run.

  3. Poczekaj na uruchomienie aplikacji, a następnie otwórz przeglądarkę i przejdź do http://localhost:5000/strony .

  4. Po wyświetleniu uruchomionej aplikacji wybierz pozycję Zarejestruj w prawym górnym rogu, aby zarejestrować nowego użytkownika.

    Zrzut ekranu aplikacji Cytat dnia, pokazujący opcję „Zarejestruj”.

  5. Zarejestruj nowego użytkownika o nazwie usera@contoso.com.

    Uwaga

    Ważne jest, aby używać dokładnie tych nazw na potrzeby tego samouczka. O ile funkcja została skonfigurowana zgodnie z oczekiwaniami, obaj użytkownicy powinni zobaczyć różne warianty.

  6. Po wprowadzeniu informacji o użytkowniku wybierz przycisk Prześlij .

  7. Zalogowano się automatycznie. Powinieneś zobaczyć, że usera@contoso.com widzi długi komunikat podczas przeglądania aplikacji.

    Zrzut ekranu przedstawiający aplikację Oferty dnia z komunikatem specjalnym dla użytkownika.

  8. Wyloguj się za pomocą przycisku Wyloguj w prawym górnym rogu.

  9. Zarejestruj drugiego użytkownika o nazwie userb@contoso.com.

  10. Zalogowano się automatycznie. Powinno być widać, że userb@contoso.com widzi krótki komunikat podczas przeglądania aplikacji.

    Zrzut ekranu przedstawiający aplikację Cytat dnia z komunikatem dla użytkownika.

Następne kroki

Pełny przegląd funkcji biblioteki do zarządzania funkcjami dla Pythona znajdziesz w poniższym dokumencie.