跳转到主要内容

为Flask提供功能标志

项目描述

flask-pancake

GitHub Workflow Status (branch) Codecov branch PyPI

为Flask提供功能标志

这个库受到了django-waffle的极大启发。

安装

flask-pancake 依赖于 Redisflask-redis Python包。

$ python -m pip install flask-pancake
Successfully installed flask-pancake
from flask import Flask
from flask_pancake import FlaskPancake, Switch
from flask_redis import FlaskRedis

app = Flask(__name__)
app.secret_key = "s3cr!t"
pancake = FlaskPancake(app)
redis = FlaskRedis(app)

SWITCH_FEATURE = Switch("FEATURE", default=False)


@app.route("/")
def index():
    if SWITCH_FEATURE.is_active():
        return "Hello World!", 200
    else:
        return "Not found", 404

或者,如果您使用 create_app() 方法配置您的Flask应用,请使用 pancake.init_app()

from flask import Flask
from flask_pancake import FlaskPancake

pancake = FlaskPancake()


def create_app() -> Flask:
    app = Flask(__name__)
    app.secret_key = "s3cr!t"
    pancake.init_app(app)
    return app

使用

flask-pancake 提供了三种类型的标志

  • 开关,它们可以是全局激活或关闭。这些开关的常见用途是在整个系统中启用或禁用功能。例如,在依赖第三方服务的情况下,当该服务不可用时,使用全局开关禁用功能。

  • 标志 与开关类似,但可以针对单个组进行覆盖。要使用 标志,需要定义至少一个返回组唯一ID或 None 的函数。组可以是您希望用户分组的任何内容:用户ID(这将允许按用户启用/禁用功能)、用户的属性,如“is_superuser”或“is_staff”,或您能想到的任何其他内容。

    将按顺序尝试组。第一个匹配的将被使用。这意味着,更具体的函数应该首先定义,不具体的函数最后定义。

    from flask import request
    from flask_pancake import FlaskPancake
    
    def get_group_user():
        # If the `request` object has a `user` attribute and the `user` object
        # has a `uid` attribute, return that.
        return getattr(getattr(request, "user", None), "uid", None)
    
    def get_group_superuser():
        # If the `request` object has a `user` attribute and the `user` object
        # has an `is_superuser` attribute, return "y" if that is boolean `True`
        # or "n" if it isn't.
        return getattr(getattr(request, "user", None), "is_superuser", None) and "y" or "n"
    
    # Alternatively, instead of using `get_group_superuser()` one can use a
    # slightly more verbose class-based approach which has the added benefit
    # of adding additional value to the flask-pancake overview API view (see
    # below).
    class IsSuperuser(GroupFunc):
        def __call__(self) -> str:
            return getattr(getattr(request, "user", None), "is_superuser", None) and "y" or "n"
    
        def get_candidate_ids(self) -> List[str]:
            return ["yes", "no"]
    
    pancake = FlaskPancake(
        group_funcs={"user": get_group_user, "superuser": get_group_superuser}
        # alternatively if using the class-based approach:
        # group_funcs={"user": get_group_user, "superuser": IsSuperuser}
    )
    # Or, if importing a function from somewhere isn't possible, a string based
    # approach can be used.
    # Separate the the fully qualified module path from the function with a `:`
    pancake = FlaskPancake(
        group_funcs={
            "user", "my.app.account.utils:get_group_user",
            "superuser", "my.app.account.utils:get_group_superuser",
            # alternatively if using the class-based approach:
            "superuser", "my.app.account.utils:IsSuperuser",
        }
    )
    

    在示例中,每次检查 标志 时,FlaskPancake 都会按以下顺序检查是否设置了值

    1. 标志是为当前用户禁用/启用吗?
    2. 如果不是,标志是为超级用户/非超级用户禁用/启用吗?
    3. 如果没有,默认情况下标志是禁用/启用吗?
  • 示例具有全局“比例”0 - 100%。在请求中对示例进行第一次检查时,将检查这些范围内的随机值。如果它低于或等于设定的值,则是激活的,如果它更大,则是非激活的。

    由于随机性,示例将它们的状态存储在请求上下文中(Flask的g上下文对象)。此外,为了在请求之间为用户提供一致的行为,请求中使用的示例的值存储在用户的浏览器中的cookie中。然后在下一个请求中再次加载,从而在请求之间提供稳定的行为。

    这意味着,尽管涉及随机性,但此行为实际上是安全的

    def foo():
        if MY_SAMPLE.is_active():
            # do something
            pass
        ...
        if MY_SAMPLE.is_active():
            # do more
            pass
    

可以使用clear()方法清除所有三种类型功能标志的持久状态。

类似地,可以使用disable()enable()方法更改FlagSwitch的持久状态。Sample可以使用它们的set(value: float)方法进行更新。

当使用Flag时,有clear_group(group_id)clear_all_group(group_id)方法,用于清除当前组或所有用户的状态。同样,还有disable_group(group_id)enable_group(group_id)用于设置当前用户所属组的组状态。

Web API

flask-pancake提供了一个API端点,该端点在蓝图中的/overview路由下显示所有可用的FlagSampleSwitch及其对应的状态。它还提供了一个JSON API,用于在/status路由下获取当前用户的所有功能标志的状态。可以通过注册Flask蓝图来启用这些API。

from flask import Flask
from flask_pancake import FlaskPancake, blueprint

app = Flask(__name__)
app.secret_key = "s3cr!t"
pancake = FlaskPancake(app)
app.register_blueprint(blueprint, url_prefix="/pancakes")

警告:此API没有进行任何形式的保护!您应使用Flask的Blueprint.before_request()功能为/overview端点添加一些身份验证。请参阅complex_app.py以获取示例。

注意:/status API端点是供前端应用程序使用的,用于加载所有FlagSampleSwitch的状态,并在前端中轻松使用。如果不想通过/status端点暴露某些功能标志,则将功能标志分为两个FlaskPancake扩展实例,并且仅允许访问为前端功能标志服务的/status端点。

如上所述,Sample在请求之间将它们的状态存储在cookie中。默认情况下,cookie名称为扩展的名称,但可以在创建FlaskPancake()扩展时使用cookie_name参数显式设置。对于cookie选项也是如此:默认情况下,cookie将使用HttpOnlySameSite=Lax属性进行设置。cookie选项通过传递到Werkzeug的set_cookie()方法

from flask import Flask
from flask_pancake import FlaskPancake

app = Flask(__name__)
app.secret_key = "s3cr!t"
pancake = FlaskPancake(
    app,
    name="feature-flags",
    cookie_name="ff",
    cookie_options={"httponly": True, "samesite": "Lax", "secure": True},
)

命令行界面

flask-pancake提供了一个CLI,它可以与Flask自己的CLI集成。您可以使用flask run以开发模式启动应用程序的方式调用flask pancake。以下是一些示例

$ flask pancake
Usage: flask pancake [OPTIONS] COMMAND [ARGS]...

  Commands to manage flask-pancake flags, samples, and switches.

Options:
  --help  Show this message and exit.

Commands:
  flags
  samples
  switches

$ flask pancake flags list
DO_SOMETHING_ELSE: Yes (default: Yes)
FOO_CAN_DO: No (default: No)

$ flask pancake flags enable FOO_CAN_DO
Flag 'FOO_CAN_DO' enabled.

$ flask pancake flags list
DO_SOMETHING_ELSE: Yes (default: Yes)
FOO_CAN_DO: Yes (default: No)

项目详情


下载文件

下载适用于您平台的应用程序。如果您不确定选择哪个,请了解更多关于安装包的信息。

源分发

flask-pancake-0.5.2.tar.gz (27.0 kB 查看哈希值)

上传时间 源代码

构建分发版

flask_pancake-0.5.2-py3-none-any.whl (14.1 kB 查看哈希值)

上传时间 Python 3

支持