Skip to main content

plain.flags

Local feature flags via database models.

Overview

You write custom flags as subclasses of Flag. Each flag defines a "key" (to identify who/what the flag applies to) and an initial value. The results are stored in the database, allowing you to override them later via the admin.

# app/flags.py
from plain.flags import Flag


class FooEnabled(Flag):
    def __init__(self, user):
        self.user = user

    def get_key(self):
        return self.user

    def get_value(self):
        # Initially all users will have this feature disabled
        # and we'll enable them manually in the admin
        return False

To check a flag, import it and access the .value property:

from app import flags

if flags.FooEnabled(user).value:
    # Feature is enabled for this user
    ...

You can also use flags directly in boolean expressions since they implement __bool__:

if flags.FooEnabled(user):
    # Feature is enabled
    ...

Usage in templates

You can use flags directly in HTML templates:

{% if flags.FooEnabled(get_current_user()) %}
    <p>Foo is enabled for you!</p>
{% else %}
    <p>Foo is disabled for you.</p>
{% endif %}

Usage in Python

from app import flags


# Check as a boolean
if flags.FooEnabled(user):
    print("Foo is enabled!")

# Get the actual value
print(flags.FooEnabled(user).value)

Advanced usage

You can do whatever you want inside of get_key and get_value. For example, you might want to check URL parameters to temporarily enable a feature during development:

class OrganizationFeature(Flag):
    url_param_name = ""

    def __init__(self, request=None, organization=None):
        # Both of these are optional, but will usually both be given
        self.request = request
        self.organization = organization

    def get_key(self):
        if (
            self.url_param_name
            and self.request
            and self.url_param_name in self.request.query_params
        ):
            return None

        if not self.organization:
            # Don't save the flag result for PRs without an organization
            return None

        return self.organization

    def get_value(self):
        if self.url_param_name and self.request:
            if self.request.query_params.get(self.url_param_name) == "1":
                return True

            if self.request.query_params.get(self.url_param_name) == "0":
                return False

        if not self.organization:
            return False

        # All organizations will start with False,
        # and I'll override in the DB for the ones that should be True
        return False


class AIEnabled(OrganizationFeature):
    pass

Settings

Setting Default Env var
FLAGS_MODULE "app.flags" PLAIN_FLAGS_MODULE

See default_settings.py for more details.

FAQs

How do flags get stored in the database?

When you first use a flag, plain.flags creates a Flag record in the database to track the flag itself, and a FlagResult record for each unique key. The FlagResult stores the computed value so subsequent calls return the cached result.

How do I override a flag value?

You can modify flag results directly in the database or through the admin interface. Each FlagResult has a value field that you can update to override the computed value.

What if I want to temporarily compute the value without storing it?

Return a falsy value (like None) from get_key(). When there's no key, the flag will compute the value fresh each time without storing it in the database.

How do I disable a flag entirely?

Each Flag record has an enabled field. Set it to False to disable the flag. In debug mode, accessing a disabled flag raises a FlagDisabled exception. In production, it logs an error and returns None.

Installation

Install the plain.flags package from PyPI:

uv add plain.flags

Add to your INSTALLED_PACKAGES:

INSTALLED_PACKAGES = [
    ...
    "plain.flags",
]

Create a flags.py at the top of your app (or configure FLAGS_MODULE to point to a different location).

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

plain_flags-0.37.1.tar.gz (15.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

plain_flags-0.37.1-py3-none-any.whl (18.6 kB view details)

Uploaded Python 3

File details

Details for the file plain_flags-0.37.1.tar.gz.

File metadata

  • Download URL: plain_flags-0.37.1.tar.gz
  • Upload date:
  • Size: 15.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_flags-0.37.1.tar.gz
Algorithm Hash digest
SHA256 9efe3fbd13821b1f10fea402d19bff1420a72931a45379703287854be33ad8d1
MD5 1c4bfa3e27d12874a7ab0aa32f822ffa
BLAKE2b-256 8e66e24f93b5af6ce2f2fd035e59ab38fb2fb215dd0aa17d65f591e4bd37b91b

See more details on using hashes here.

File details

Details for the file plain_flags-0.37.1-py3-none-any.whl.

File metadata

  • Download URL: plain_flags-0.37.1-py3-none-any.whl
  • Upload date:
  • Size: 18.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.1 {"installer":{"name":"uv","version":"0.12.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for plain_flags-0.37.1-py3-none-any.whl
Algorithm Hash digest
SHA256 129ef4b90273c06ef72d590783aa81019678202addfa39e1984e26cbe8558391
MD5 71d50c3d2830afd1e4b11a0ce9df53e2
BLAKE2b-256 93faef6c60a378740baf58b9c1374fcdef0aec62589b6c079e3b31b97e6a8fce

See more details on using hashes here.

Release history Release notifications | RSS feed

0.37.2

2 files

This release

0.37.1 This release

2 files

0.37.0

2 files

0.36.7

2 files

0.36.6

2 files

0.36.5

2 files

0.36.4

2 files

0.36.3

2 files

0.36.2

2 files

0.36.1

2 files

0.36.0

2 files

0.35.0

2 files

0.34.2

2 files

0.34.1

2 files

0.34.0

2 files

0.33.4

2 files

0.33.3

2 files

0.33.2

2 files

0.33.1

2 files

0.33.0

2 files

0.32.0

2 files

0.31.0

2 files

0.30.1

2 files

0.30.0

2 files

0.29.0

2 files

0.28.0

2 files

0.27.3

2 files

0.27.2

2 files

0.27.1

2 files

0.27.0

2 files

0.26.0

2 files

0.25.3

2 files

0.25.2

2 files

0.25.1

2 files

0.25.0

2 files

0.24.0

2 files

0.23.0

2 files

0.22.0

2 files

0.21.1

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.1

2 files

0.17.0

2 files

0.16.0

2 files

0.15.0

2 files

0.14.0

2 files

0.13.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.9.1

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.3

2 files

0.7.2

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

0.0.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page