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.4
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.4.tar.gz | 17.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plain_flags-0.37.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 36.4 kB
Release files / plain_flags-0.37.4.tar.gz
| Download URL | plain_flags-0.37.4.tar.gz |
|---|---|
| Size | 17.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
429af760aefa4e83f3b36b76c29dec1e00f5ac38e0d77d046ac6078422881f7a
|
|
BLAKE2b-256 checksum How to use checksums |
955fee3b145996f7259f9585c5acf48a278580ce7aa06515b0aac4e74d98fcca
|
| 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.4-py3-none-any.whl
| Download URL | plain_flags-0.37.4-py3-none-any.whl |
|---|---|
| Size | 19.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3f42515ca823da67604c9fe8b003205456b27b09e8ab69cbcb8eae0246c34991
|
|
BLAKE2b-256 checksum How to use checksums |
f0bdfd06f475c65956ee6e9f81717e1b19c531fa991219160ec8d4319e79aa8f
|
| 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}
|