Używanie flag funkcji wariantu w aplikacji języka Python
W tym samouczku użyjesz flagi funkcji wariantu do zarządzania środowiskami dla różnych segmentów użytkowników w przykładowej aplikacji — Cytat dnia. Używasz flagi funkcji wariantu utworzonej w temacie How to variant feature flags (Jak wariant flag funkcji). Przed kontynuowaniem upewnij się, że utworzono flagę funkcji wariantu o nazwie Greeting w magazynie App Configuration.
Wymagania wstępne
- Python 3.8 lub nowszy — aby uzyskać informacje na temat konfigurowania języka Python w systemie Windows, zobacz dokumentację języka Python w systemie Windows
- Postępuj zgodnie z samouczkiem How to variant feature flags (Jak wariant flagi funkcji) i utwórz flagę funkcji wariantu o nazwie Greeting (Powitanie).
Konfigurowanie aplikacji internetowej platformy Python Flask
Jeśli masz już aplikację internetową platformy Python Flask, możesz przejść do sekcji Używanie flagi funkcji wariantu.
Utwórz nowy folder projektu o nazwie QuoteOfTheDay.
Utwórz środowisko wirtualne w folderze QuoteOfTheDay .
python -m venv venv
Aktywuj środowisko wirtualne.
.\venv\Scripts\Activate
Zainstaluj najnowsze wersje następujących pakietów.
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
Tworzenie oferty aplikacji Day
Utwórz nowy plik o nazwie
app.py
w folderzeQuoteOfTheDay
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) from . import routes app.register_blueprint(routes.bp)
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 . 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
Utwórz nowy plik o nazwie routes.py w folderze QuoteOfTheDay z następującą zawartością. Definiuje trasy dla aplikacji internetowej platformy Flask, obsługując uwierzytelnianie użytkowników i wyświetlając stronę główną z losowym cudzysłowem.
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"))
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"> © 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>
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 %}
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 %}
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 %}
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żywanie flagi funkcji wariantu
Zainstaluj najnowsze wersje następujących pakietów.
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]
app.py
Otwórz plik i dodaj następujący kod na końcu pliku. Łączy się z usługą App Configuration i konfiguruje zarządzanie funkcjami.Użyj polecenia
DefaultAzureCredential
, aby uwierzytelnić się w magazynie usługi App Configuration. Postępuj zgodnie z instrukcjami, aby przypisać poświadczenia roli Czytelnik danych konfiguracji aplikacji. Przed uruchomieniem aplikacji należy zezwolić na wystarczający czas na propagację uprawnień.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)
Otwórz
routes.py
i dodaj następujący kod na końcu, aby odświeżyć konfigurację i pobrać wariant funkcji.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
Skompiluj i uruchom aplikację
Ustaw zmienną środowiskową o nazwie AzureAppConfigurationEndpoint na punkt końcowy sklepu App Configuration znalezionego w obszarze Przegląd sklepu w witrynie Azure Portal.
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 "<endpoint-of-your-app-configuration-store>"
Jeśli używasz programu PowerShell, uruchom następujące polecenie:
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
Jeśli używasz systemu macOS lub Linux, uruchom następujące polecenie:
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
W wierszu polecenia w folderze QuoteOfTheDay uruchom polecenie:
flask run
.Poczekaj na uruchomienie aplikacji, a następnie otwórz przeglądarkę i przejdź do
http://localhost:5000/
strony .Po wyświetleniu uruchomionej aplikacji wybierz pozycję Zarejestruj w prawym górnym rogu, aby zarejestrować nowego użytkownika.
Zarejestruj nowego użytkownika o nazwie usera@contoso.com.
Uwaga
Ważne jest, aby w tym samouczku używać dokładnie tych nazw. O ile funkcja została skonfigurowana zgodnie z oczekiwaniami, obaj użytkownicy powinni zobaczyć różne warianty.
Po wprowadzeniu informacji o użytkowniku wybierz przycisk Prześlij .
Zalogowano się automatycznie. Podczas wyświetlania aplikacji powinien zostać wyświetlony usera@contoso.com długi komunikat.
Wyloguj się przy użyciu przycisku Wyloguj w prawym górnym rogu.
Zarejestruj drugiego użytkownika o nazwie userb@contoso.com.
Zalogowano się automatycznie. Podczas wyświetlania aplikacji powinien zostać wyświetlony userb@contoso.com krótki komunikat.
Następne kroki
Aby zapoznać się z pełnym uruchomieniem funkcji biblioteki zarządzania funkcjami języka Python, zapoznaj się z następującym dokumentem.