Skip to content

📖 Quickstart

TL;DR

🖱 Click here if you want to see code only

Import feature flag

from fastfeatureflag.feature_flag import feature_flag

Adding a feature flag

By adding feature_flag() to a method, the method will raise an NotImplementedError if called.

def not_yet_finished_feature():
    ...
@feature_flag()
def not_yet_finished_feature():
    ...

By adding the feature without any parameters, it will disable the method/function/class.

You can specify the activation of a feature flag
@feature_flag(activation="off")

feature_flag() equals feature_flag("off")

Respond even if deactive

Sometimes a feature should be callable, but respond with a different response. Because it might not be finished yet, but other parts want to interact with it already. Providing a response to the feature flag can solve this issue.

>>> def not_yet_finished_feature():
...     return True
>>> not_yet_finished_feature()
True
>>> @feature_flag(response="I responded")
... def not_yet_finished_feature():
...     return True
>>> not_yet_finished_feature()
'I responded'

Activate a feature

If you want a flagged method/function/class to be active, configure the flag to be active.

>>> @feature_flag("on")
... def not_yet_finished_feature():
...     return True
...
>>> not_yet_finished_feature()
True

Name a feature

Deactivated

If you have several methods/functions you want to group, use a name for that feature.

>>> @feature_flag("off", name="awesome_feature", response="Not finished yet")
... def awesome_feature_1():
...     return "I am feature 1"
...
>>> @feature_flag(name="awesome_feature", response="Not finished yet")
... def awesome_feature_2():
...     return "I am feature 2"
...
>>> awesome_feature_1()
'Not finished yet'
>>> awesome_feature_2()
'Not finished yet'

These methods are now registered as a feature and changing the activation to "on" in the (first appearing) feature flag, changes the activation for all flagged objects which are grouped by name.

Activated

Typically the feature_flag(name="named_feature") flag would assume an off activation. However, as a group they share the activation.

Activate the feature by changing from off to on in the activation.

>>> @feature_flag("on", name="awesome_feature")
... def awesome_feature_1():
...     return "I am feature 1"
...
>>> @feature_flag(name="awesome_feature")
... def awesome_feature_2():
...     return "I am feature 2"
...
>>> awesome_feature_1()
'I am feature 1'
>>> awesome_feature_2()
'I am feature 2'

The first activation wins

If you have several objects flagged with with the same name and different activations, then the first activation wins. The first flag registers the name and activation. Any other flag will not change the already registered feature.

Cleaning the feature register

It is possible to clean all already registered features and update the configuration of all features.

from fastfeatureflag.feature_flag import feature_flag
import pprint
pp = pprint.PrettyPrinter(indent=4)

@feature_flag(name="test_clean_register", response="I am deactivated")
def broken_feature():
    return "I am broken"

pp.pprint(broken_feature())

pp.pprint(broken_feature.registered_features)
broken_feature.clean()

pp.pprint(broken_feature.registered_features)
pp.pprint(broken_feature())
Output:
'I am deactivated'
[   FeatureContent(activation='off',
                name='test_clean_register',
                response=None,
                shadow=None,
                func=<function broken_feature at 0x7fea25881ea0>,
                configuration=None,
                configuration_path=None)]
[]
'I am deactivated'

Be aware, that this only "unregister" a feature. It does not deactivate it. Meaning, e.g. a configured response is still forwarded. If you want to change and register new features dynamically, you can provide the feature configuration:

from fastfeatureflag.feature_flag import feature_flag
import pprint
pp = pprint.PrettyPrinter(indent=4)

@feature_flag(name="test_clean_register", response="I am deactivated")
def broken_feature():
    return "I am broken"

pp.pprint(broken_feature())

configuration = {"test_clean_register" : {"activation":"off", "response":"I am re-configured"}}
broken_feature.configuration = configuration
pp.pprint(broken_feature.configuration)

broken_feature()
Output:
'I am deactivated'
{'test_clean_register': {'activation': 'off', 'response': 'I am re-configured'}}
'I am re-configured'

Every flagged method has its own unique response

If you want that a deactivated object returns a custom response, you will have to define one and provide it to every feature flag individually.

Use a configuration file

Handling several features and their activation manually with every flag might lead to conflicting activations and responses. To manage them, you can use a configuration file. As a default, all feature flags are searching for a fastfeatureflag_config.toml within the current working directory.

Within this configuration file, you can easily specify the features (as titles) and their activation.

fastfeatureflag_config.toml
[feature_1]
activation="on"

[feature_2]
activation="off"

This file provides you a central location to switch off/on the features you need.

Another great option is the use of environment variables within the activation.

fastfeatureflag_config.toml
[feature_1]
activation="FEATURE_1"

By providing the name of the environment variable it is possible to read their status. You can now switch on/off the feature by providing "on"|"off" via the environment variable.

export FEATURE_1 = "on"

In combination with a .env file, you can save and specify the features for your local use case. Provide different .env files or other means of environment variables to easily handle feature flags within a CI/CD pipeline etc.

Environment variable standard

Typically, environment variables are written in upper case. However, the feature flag system searches for every word as an environment variable.

Take also care, that no environment variable with the key words on and off have been defined.


Last update: October 28, 2023
Created: October 28, 2023