fancy-features
Headless feature management and metered-resource gating for Python. Feature flags, feature groups, per-plan quotas and billing-period usage — with no web framework, no ORM and no runtime dependencies at all.
The Python twin of particle-academy/laravel-fms
(PHP) and @particle-academy/fancy-features
(Node/TypeScript), and the owner of the shared feature contract
fancy-catalog consumes.
pip install fancy-features
In one minute
from fancy_features import create_features
features = create_features(
features={
"use-mcp": {"enabled": True},
"ai-tokens": {"type": "resource", "limit": 10_000},
},
groups=[
{"key": "pro", "features": ["sso"], "overrides": {"ai-tokens": {"limit": 250_000}}},
],
)
features.can_access("use-mcp", user) # True
features.remaining("ai-tokens", user) # 10000
features.try_consume("ai-tokens", user, 40) # True -- atomic check-and-increment
features.remaining("ai-tokens", user) # 9960
features.explain("ai-tokens", user) # AccessResult(source="config", used=40, ...)
Every method has an a-prefixed twin for async hosts — acan_access,
aremaining, atry_consume — and both drive one copy of the resolution
rules, so they cannot disagree.
How a verdict is reached
pre-strategies → gate → registry → groups (OR) → config → sources → deny
- pre-strategies and gate are authoritative: a non-
Noneanswer from either is final in both directions. That is what lets a billing service deny something a stray permission would allow. - everything after them is additive: a feature defined with
enabled=Falsedoes not block a group or a plan from turning it on.
For a resource feature:
pre-remaining strategies → MAX(group limit, source limit, feature limit) − usage
clamped at zero, with None meaning unlimited. MAX, because a plan should
be able to lift a base limit and never to lower one.
Adapters, all optional
| Contract | Default | What a host plugs in |
|---|---|---|
UsageStore |
InMemoryUsageStore |
its feature_usages table |
GroupStore |
InMemoryGroupStore |
its group-assignment table |
FeatureSource |
none | fancy-catalog, or its own entitlement service |
| gate | none | its permission system |
Every one may be synchronous or asynchronous. A dict-backed store returns an
int; a database-backed one returns a coroutine; both work.
Billing periods
from datetime import datetime, UTC
from fancy_features import BillingPeriod
january = BillingPeriod(
start=datetime(2026, 1, 1, tzinfo=UTC), end=datetime(2026, 2, 1, tzinfo=UTC)
)
features.try_consume("ai-tokens", user, 500, period=january)
features.remaining("ai-tokens", user, period=january)
features.reset_period(user, january) # the renewal reset
The period reaches the store on every quota path — reads and writes alike.
Composing with a Stripe catalog
from fancy_catalog import create_catalog
from fancy_catalog.features import create_catalog_feature_source
from fancy_features import create_features
catalog = create_catalog(stripe=stripe_client)
features = create_features(
sources=[create_catalog_feature_source(catalog, resolve_subscription=lookup)],
)
features.can_access("use-mcp", user) # resolved through the user's plan
features.remaining("ai-tokens", user) # the plan's included quantity, minus usage
The two packages share one definition of the contract, in
fancy_features.contract. The catalog imports it; there is no mirrored copy.
Guarding a route
from fancy_features import FeatureAccessDeniedError, require_feature
try:
require_feature(features, ["use-mcp", "use-agents"], user) # OR
except FeatureAccessDeniedError as denied:
return json_response({"features": denied.features}, status=denied.status)
No framework is imported and none is assumed.
One thing that will bite a porter
Every callback takes (subject, context) — check, enabled, limit,
usage, remaining. The Node package still publishes usage/remaining as
(key, subject, context). A three-parameter callback here raises, naming
the feature and the field, rather than binding subject to the key string and
quietly metering the wrong thing. Fewer parameters is fine: lambda: 30 works.
Requirements
Python 3.11+. No runtime dependencies.
License
MIT © Particle Academy
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fancy_features-0.2.0.tar.gz.
File metadata
- Download URL: fancy_features-0.2.0.tar.gz
- Upload date:
- Size: 49.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf58194c659e7e5eba1ca425293fb84f2f8be01f3f5ceedec202050b36c208f4
|
|
| MD5 |
0bd22c5a4cdf3af3e2e1b0ea8638997d
|
|
| BLAKE2b-256 |
768fee1e8b7b5c304a248c1497ce568857dde0f876f79ae5e307a088ad15176d
|
Provenance
The following attestation bundles were made for fancy_features-0.2.0.tar.gz:
Publisher:
publish.yml on Particle-Academy/fancy-features-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fancy_features-0.2.0.tar.gz -
Subject digest:
bf58194c659e7e5eba1ca425293fb84f2f8be01f3f5ceedec202050b36c208f4 - Sigstore transparency entry: 2519567256
- Sigstore integration time:
-
Permalink:
Particle-Academy/fancy-features-py@a3402c3af90834980a0e3e3df0a2da73019d6bcc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Particle-Academy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a3402c3af90834980a0e3e3df0a2da73019d6bcc -
Trigger Event:
push
-
Statement type:
File details
Details for the file fancy_features-0.2.0-py3-none-any.whl.
File metadata
- Download URL: fancy_features-0.2.0-py3-none-any.whl
- Upload date:
- Size: 35.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4f6c9b57a2f8a86823bd966aadbfb7af9a0bddd3c4150bdb383210cf066f5cd
|
|
| MD5 |
b706221c64c3626e63111ccc45dbb058
|
|
| BLAKE2b-256 |
1c2d758377c601a3ca494ed12c754b8de5d886b29390e3b44eba436eda00173c
|
Provenance
The following attestation bundles were made for fancy_features-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on Particle-Academy/fancy-features-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
fancy_features-0.2.0-py3-none-any.whl -
Subject digest:
d4f6c9b57a2f8a86823bd966aadbfb7af9a0bddd3c4150bdb383210cf066f5cd - Sigstore transparency entry: 2519567301
- Sigstore integration time:
-
Permalink:
Particle-Academy/fancy-features-py@a3402c3af90834980a0e3e3df0a2da73019d6bcc -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Particle-Academy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a3402c3af90834980a0e3e3df0a2da73019d6bcc -
Trigger Event:
push
-
Statement type: