Python アプリケーションでバリアント機能フラグを使用する
このチュートリアルでは、バリアント機能フラグを使用して、Quote of the Day というサンプル アプリケーションでさまざまなユーザー セグメントのエクスペリエンスを管理します。 「バリアント機能フラグを使用する方法」で作成したバリアント機能フラグを利用します。 作業を始める前に、App Configuration ストアに Greeting というバリアント機能フラグを作成していることを確認してください。
前提条件
- Python 3.8 以降 - Windows での Python の設定について詳しくは、Windows での Python に関するドキュメントをご覧ください
- 「バリアント機能フラグを使用する方法」チュートリアルに従って、Greeting というバリアント機能フラグを作成します。
Python Flask Web アプリを設定する
すでに Python Flask Web アプリをお持ちの場合は、「バリアント機能フラグを使用する」セクションに進むことができます。
QuoteOfTheDay という新しいプロジェクト フォルダーを作成します。
QuoteOfTheDay フォルダー内に仮想環境を作成します。
python -m venv venv
仮想環境をアクティブにします。
.\venv\Scripts\Activate
次のパッケージの最新バージョンをインストールします。
pip install flask pip install flask-login pip install flask_sqlalchemy pip install flask_bcrypt
Quote of the Day アプリを作成する
QuoteOfTheDay
フォルダー内に、次の内容を含むapp.py
という新しいファイルを作成します。 ユーザー認証を使用して基本的な Flask Web アプリケーションを設定します。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)
QuoteOfTheDay フォルダー内に、次の内容を含む model.py という新しいファイルを作成します。 Flask Web アプリケーションの
Quote
データ クラスとユーザー モデルを定義します。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
QuoteOfTheDay フォルダー内に、次の内容を含む routes.py という新しいファイルを作成します。 Flask Web アプリケーションのルートを定義し、ユーザー認証を処理し、ランダムな引用でホームページを表示します。
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"))
QuoteOfTheDay フォルダー内に templates という名前の新しいフォルダーを作成し、そのフォルダー内に次の内容を含む base.html という新しいファイルを追加します。 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>
templates フォルダー内に、次の内容を含む index.html という新しいファイルを作成します。 基本テンプレートを拡張し、コンテンツ ブロックを追加します。
{% 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 %}
templates フォルダー内に、次の内容を含む sign_up.html という新しいファイルを作成します。 ユーザー登録ページ用のテンプレートを定義します。
{% 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 %}
templates フォルダー内に、次の内容を含む login.html という新しいファイルを作成します。 ユーザー ログイン ページ用のテンプレートを定義します。
{% 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 %}
QuoteOfTheDay フォルダー内に static という名前の新しいフォルダーを作成し、そのフォルダー内に次の内容を含む site.css という新しいファイルを追加します。 Web アプリケーション用の CSS スタイルが追加されます。
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 */ }
バリアント機能フラグを使用する
次のパッケージの最新バージョンをインストールします。
pip install azure-identity pip install azure-appconfiguration-provider pip install featuremanagement[AzureMonitor]
app.py
ファイルを開き、ファイルの末尾に次のコードを追加します。 App Configuration に接続し、機能の管理を設定します。DefaultAzureCredential
を使って、App Configuration ストアに対する認証を行います。 手順に従って、資格情報に App Configuration データ閲覧者ロールを割り当てます。 アプリケーションを実行する前に、アクセス許可が伝わるのに十分な時間をおいてください。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)
routes.py
を開き、末尾に次のコードを追加して構成を更新し、機能バリアントを取得します。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
アプリをビルドして実行する
AzureAppConfigurationEndpoint という環境変数を、Azure portal でストアの [概要] にある App Configuration ストアのエンドポイントに設定します。
Windows コマンド プロンプトを使用する場合は、次のコマンドを実行してコマンド プロンプトを再起動し、変更が反映されるようにします。
setx AzureAppConfigurationEndpoint "<endpoint-of-your-app-configuration-store>"
PowerShell を使っている場合は、次のコマンドを実行します。
$Env:AzureAppConfigurationEndpoint = "<endpoint-of-your-app-configuration-store>"
macOS または Linux を使用する場合は、次のコマンドを実行します。
export AzureAppConfigurationEndpoint='<endpoint-of-your-app-configuration-store'
コマンド プロンプトで、QuoteOfTheDay 内で
flask run
を実行します。アプリが起動するまで待ち、ブラウザーを開いて
http://localhost:5000/
に移動します。実行中のアプリケーションを表示したら、右上にある [登録] を選択して新しいユーザーを登録します。
usera@contoso.com という名前の新しいユーザーを登録します。
Note
このチュートリアルの目的上、これらの名前を正確に使用することが重要です。 機能が想定どおりに構成されている限り、この 2 人のユーザーには異なるバリアントが表示されます。
ユーザー情報を入力した後、[送信] ボタンを選択します。
自動的にログインします。 usera@contoso.com がアプリを表示したときに長いメッセージが表示されることを確認できます。
右上の [ログアウト] ボタンを使用してログアウトします。
userb@contoso.com という名前の 2 番目のユーザーを登録します。
自動的にログインします。 userb@contoso.com がアプリを表示したときに短いメッセージが表示されることを確認できます。
次のステップ
Python 機能管理ライブラリの詳細な機能の説明については、次のドキュメントを参照してください。