{"id":16945645,"url":"https://github.com/markush/flask-pancake","last_synced_at":"2025-09-14T22:23:16.340Z","repository":{"id":52456375,"uuid":"234965671","full_name":"MarkusH/flask-pancake","owner":"MarkusH","description":"Feature Flagging for Flask","archived":false,"fork":false,"pushed_at":"2021-10-23T15:32:41.000Z","size":112,"stargazers_count":4,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-11T15:18:17.064Z","etag":null,"topics":["featureflags","flask","hacktoberfest"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MarkusH.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.rst","contributing":null,"funding":"FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":["MarkusH"]}},"created_at":"2020-01-19T20:58:06.000Z","updated_at":"2023-07-06T13:23:13.000Z","dependencies_parsed_at":"2022-08-27T18:21:31.482Z","dependency_job_id":null,"html_url":"https://github.com/MarkusH/flask-pancake","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkusH%2Fflask-pancake","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkusH%2Fflask-pancake/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkusH%2Fflask-pancake/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MarkusH%2Fflask-pancake/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MarkusH","download_url":"https://codeload.github.com/MarkusH/flask-pancake/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248431749,"owners_count":21102261,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["featureflags","flask","hacktoberfest"],"created_at":"2024-10-13T21:23:14.906Z","updated_at":"2025-04-11T15:32:33.564Z","avatar_url":"https://github.com/MarkusH.png","language":"Python","funding_links":["https://github.com/sponsors/MarkusH"],"categories":[],"sub_categories":[],"readme":"# flask-pancake\n\n![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/MarkusH/flask-pancake/CI/master?style=for-the-badge)\n![Codecov branch](https://img.shields.io/codecov/c/gh/MarkusH/flask-pancake/master?style=for-the-badge)\n![PyPI](https://img.shields.io/pypi/v/flask-pancake?style=for-the-badge)\n\nFeature Flagging for Flask\n\nThis library was heavily inspired by\n[django-waffle](https://github.com/django-waffle/django-waffle).\n\n## Installation\n\n`flask-pancake` depends on [Redis](https://redis.io/) and the [flask-redis](https://pypi.org/project/flask-redis/) Python package.\n\n```bash\n$ python -m pip install flask-pancake\nSuccessfully installed flask-pancake\n```\n\n```python\nfrom flask import Flask\nfrom flask_pancake import FlaskPancake, Switch\nfrom flask_redis import FlaskRedis\n\napp = Flask(__name__)\napp.secret_key = \"s3cr!t\"\npancake = FlaskPancake(app)\nredis = FlaskRedis(app)\n\nSWITCH_FEATURE = Switch(\"FEATURE\", default=False)\n\n\n@app.route(\"/\")\ndef index():\n    if SWITCH_FEATURE.is_active():\n        return \"Hello World!\", 200\n    else:\n        return \"Not found\", 404\n```\n\nAlternatively, if you use a `create_app()` method to configure your Flask app,\nuse `pancake.init_app()`:\n\n```python\nfrom flask import Flask\nfrom flask_pancake import FlaskPancake\n\npancake = FlaskPancake()\n\n\ndef create_app() -\u003e Flask:\n    app = Flask(__name__)\n    app.secret_key = \"s3cr!t\"\n    pancake.init_app(app)\n    return app\n```\n\n## Usage\n\n`flask-pancake` provides three types of flags:\n\n* `Switch`es, which are either globally active or inactive. A common use case\n  for these are system-wide enabling or disabling of a feature. E.g. in the\n  context of a dependency on a third party service, disabling a feature with a\n  global switch when that service is unavailable.\n\n* `Flag`s are like Switches but can be overridden for individual groups. To\n  make use of `Flag`s, one needs to define at least one function that returns a\n  group's unique ID or `None`. Groups can be anything that you want users to be\n  grouped by: their user ID (which would allow per-user enabling/disabling of\n  features), a user's attribute, such as \"is_superuser\" or \"is_staff\", or\n  anything else that you can think of.\n\n  The groups are tried in order. The first one to match will be used. Meaning,\n  more specific functions should be defined first, less specific functions last.\n\n  ```python\n  from flask import request\n  from flask_pancake import FlaskPancake\n\n  def get_group_user():\n      # If the `request` object has a `user` attribute and the `user` object\n      # has a `uid` attribute, return that.\n      return getattr(getattr(request, \"user\", None), \"uid\", None)\n\n  def get_group_superuser():\n      # If the `request` object has a `user` attribute and the `user` object\n      # has an `is_superuser` attribute, return \"y\" if that is boolean `True`\n      # or \"n\" if it isn't.\n      return getattr(getattr(request, \"user\", None), \"is_superuser\", None) and \"y\" or \"n\"\n\n  # Alternatively, instead of using `get_group_superuser()` one can use a\n  # slightly more verbose class-based approach which has the added benefit\n  # of adding additional value to the flask-pancake overview API view (see\n  # below).\n  class IsSuperuser(GroupFunc):\n      def __call__(self) -\u003e str:\n          return getattr(getattr(request, \"user\", None), \"is_superuser\", None) and \"y\" or \"n\"\n\n      def get_candidate_ids(self) -\u003e List[str]:\n          return [\"yes\", \"no\"]\n\n  pancake = FlaskPancake(\n      group_funcs={\"user\": get_group_user, \"superuser\": get_group_superuser}\n      # alternatively if using the class-based approach:\n      # group_funcs={\"user\": get_group_user, \"superuser\": IsSuperuser}\n  )\n  # Or, if importing a function from somewhere isn't possible, a string based\n  # approach can be used.\n  # Separate the the fully qualified module path from the function with a `:`\n  pancake = FlaskPancake(\n      group_funcs={\n          \"user\", \"my.app.account.utils:get_group_user\",\n          \"superuser\", \"my.app.account.utils:get_group_superuser\",\n          # alternatively if using the class-based approach:\n          \"superuser\", \"my.app.account.utils:IsSuperuser\",\n      }\n  )\n  ```\n\n  In the example, whenever one checks for a `Flag`, FlaskPancake would check if\n  a value has been set in the following order:\n\n  1. Is the flag disable/enable for the current user?\n  1. If not, is the flag disable/enabled for superusers/non-superusers?\n  1. If not, is the flag disable/enabled by default?\n\n* `Sample`s, have a global \"ratio\" of 0 - 100%. On the first check of a sample\n  in a request, a random value is checked within these bounds. If it's lower or\n  equal the set value, it's active, if it's larger, it's inactive.\n\n  Due to the randomness, samples store their state in a request context (Flask's\n  `g` context object). Additionally, in order to provide consistent behavior for\n  a user between requests, the values of the used samples in a request are\n  stored in a cookie in the user's browser. They are then loaded on the next\n  request again and thus provide a stable behavior across requests.\n\n  That means, despite the randomness involved, this behavior is actually safe:\n\n  ```python\n  def foo():\n      if MY_SAMPLE.is_active():\n          # do something\n          pass\n      ...\n      if MY_SAMPLE.is_active():\n          # do more\n          pass\n  ```\n\nThe persisted state for all three types of feature flags can be cleared, using\nthe `clear()` method.\n\nSimilarly, one can change the persisted state for `Flag`s and `Switch`es using\ntheir `disable()` and `enable()` methods. `Sample`s can be updated using their\n`set(value: float)` method.\n\nWhen using `Flag`s, there are `clear_group(group_id)` and\n`clear_all_group(group_id)` methods, to clear the state for the current or all\nusers within a group. Along the same line, there are `disable_group(group_id)`\nand `enable_group(group_id)` to set the group's state the current user is part\nof.\n\n### Web API\n\n`flask-pancake` provides an API endpoint that shows all available `Flag`s,\n`Sample`s and `Switch`es and their corresponding states under the `/overview`\nroute within the blueprint. It also provides a JSON API to get the status for\nall feature flags for the current user under the `/status` route. The APIs can\nbe enabled by registering a Flask blueprint:\n\n```python\nfrom flask import Flask\nfrom flask_pancake import FlaskPancake, blueprint\n\napp = Flask(__name__)\napp.secret_key = \"s3cr!t\"\npancake = FlaskPancake(app)\napp.register_blueprint(blueprint, url_prefix=\"/pancakes\")\n```\n\n**WARNING:** The API is not secured in any way! You should use Flask's\n[`Blueprint.before_request()`](https://flask.palletsprojects.com/en/1.1.x/api/?highlight=register_blueprint#flask.Blueprint.before_request)\nfeature to add some authentication for the `/overview` endpoint. Check the\n[`complex_app.py`](https://github.com/MarkusH/flask-pancake/blob/master/examples/complex_app.py)\nfor an example.\n\n**NOTE:** The `/status` API endpoint is meant to be used by front-end\napplications to load the status of all `Flag`s,\n`Sample`s and `Switch`es and have them readily available in the front-end. If\none does not want to expose some feature flags to the front-end via the\n`/status` endpoint, separate the feature flags into two `FlaskPancake` extension\ninstances and only allow access to the `/status` endpoint serving the front-end\nfeature flags.\n\nAs noted above, `Sample`s store their state in cookies between requests. The\ncookie name defaults to the name of the extension, but can be set explicitly\nusing the `cookie_name` argument when instantiating the `FlaskPancake()`\nextension. The same goes for the cookie options: by default, cookies will be set\nwith the `HttpOnly` and `SameSite=Lax` attributes. The cookie options are passed\nthrough to [Werkzeug's `set_cookie()` method](https://werkzeug.palletsprojects.com/en/1.0.x/wrappers/?highlight=set_cookie#werkzeug.wrappers.BaseResponse.set_cookie):\n\n```python\nfrom flask import Flask\nfrom flask_pancake import FlaskPancake\n\napp = Flask(__name__)\napp.secret_key = \"s3cr!t\"\npancake = FlaskPancake(\n    app,\n    name=\"feature-flags\",\n    cookie_name=\"ff\",\n    cookie_options={\"httponly\": True, \"samesite\": \"Lax\", \"secure\": True},\n)\n```\n\n### Command Line Interface\n\n`flask-pancake` comes with a CLI that hooks into Flask's own CLI. The same way you can call `flask run` to start your application in development mode you can call `flask pancake`. Here are some examples:\n\n```console\n$ flask pancake\nUsage: flask pancake [OPTIONS] COMMAND [ARGS]...\n\n  Commands to manage flask-pancake flags, samples, and switches.\n\nOptions:\n  --help  Show this message and exit.\n\nCommands:\n  flags\n  samples\n  switches\n\n$ flask pancake flags list\nDO_SOMETHING_ELSE: Yes (default: Yes)\nFOO_CAN_DO: No (default: No)\n\n$ flask pancake flags enable FOO_CAN_DO\nFlag 'FOO_CAN_DO' enabled.\n\n$ flask pancake flags list\nDO_SOMETHING_ELSE: Yes (default: Yes)\nFOO_CAN_DO: Yes (default: No)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkush%2Fflask-pancake","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkush%2Fflask-pancake","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkush%2Fflask-pancake/lists"}