Skip to main content

Local feature flags via database models.

Project description

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).

Project details


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.36.4.tar.gz (14.5 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.36.4-py3-none-any.whl (19.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: plain_flags-0.36.4.tar.gz
  • Upload date:
  • Size: 14.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","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.36.4.tar.gz
Algorithm Hash digest
SHA256 d211c3d6a422bb11c335dcc1a82d56ae2b7ae53c2d1856a56188746378245a8d
MD5 207cf8f0ad5ca38da9a1f8383a6eedc3
BLAKE2b-256 ea808605007f01eafce7c6fa586b954c0c840430e0fed60ee9ba86d6d6e9e386

See more details on using hashes here.

File details

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

File metadata

  • Download URL: plain_flags-0.36.4-py3-none-any.whl
  • Upload date:
  • Size: 19.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","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.36.4-py3-none-any.whl
Algorithm Hash digest
SHA256 651ee5ca8357cf63deccdea6ae1841702b4baa75a939a9e9dbfde36ae7e66092
MD5 2d3bb2e954e4f6dd245ee97651b3daa6
BLAKE2b-256 199816f83e7aa569c62a222befc7afda561fcc4772fcabfc3a676b5ec2382d9d

See more details on using hashes here.

Supported by

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