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).
Metadata
Release files for plain.flags 0.37.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| plain_flags-0.37.3.tar.gz | 16.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plain_flags-0.37.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 35.2 kB
Release files / plain_flags-0.37.3.tar.gz
| Download URL | plain_flags-0.37.3.tar.gz |
|---|---|
| Size | 16.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8ecb6ea959a315b3d91423db72be9c17f16f4e3d7b087aec204760bf58fd1e4f
|
|
BLAKE2b-256 checksum How to use checksums |
6988d35cf5598be5404c1acfd49cf692dd006c77c2412c3ef1e4882bf7ca33f2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}
|
Release files / plain_flags-0.37.3-py3-none-any.whl
| Download URL | plain_flags-0.37.3-py3-none-any.whl |
|---|---|
| Size | 18.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
07fe885d3f24284435e913829b7e60ef596f15fc258df0fac08c4e279fd2e942
|
|
BLAKE2b-256 checksum How to use checksums |
bf7049763b343288fbfc05927fa76a2bc53625a9c7dddf29921c45912a1560ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","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}
|