Skip to main content

A simple yet powerful feature flag and multivariate testing platform built in Python

Project description

Novi

Novi is a simple yet powerful feature flag platform built in Python. Novi is "simple" because all of its core capabilities are built around 2 simple concepts.

  • Flags
  • and Activations

Concepts

Flags

Flags are associated with features. Flags turn a feature on or off. The on/off status of a flag is determined by Activations. Details about flags are store in the Flag table, which has 3 columns

  • A unique ID which is also the primary key for the flag
  • A unique name
  • A default status. If the default status is false, the flag is always turned off, irrespective of how the activations get evaluated.
id name status
1 Date and Random Activated Feature 1
2 Random Variant Feature 1
3 Combo AND 1
4 Combo OR 1

Activations

Activations determine the on/off status of a flag when specific conditions are met. Activations are what make novi a "dynamic" feature flag system, as the runtime conditions are evaluated against an Activations trigger logic to set the flag status. Some scenarios that Activations are useful:-

  • Features could be activated depending on the deployment environment such as production, development or test
  • Feature could be shown only to certain usernames and disabled for others.
  • Organizations may choose to show a feature to only traffic originating from company IP address ranges during testing
  • Features may be shown to a percentage of users selected randomly

The list of scenarios can be infinitely varied and complex. An activation table has four columns:-

  • A unique ID,
  • A descriptive name,
  • A python class name that is used to instantiate an activation object
  • A configuration
id name class_name config
1 Date Activated novi.client.activations.date_time_activation.DateTimeActivation {"startDateTime":"11/26/2023 12:00 AM","endDateTime":"11/28/2023 12:00 AM","format": "%m/%d/%Y %I:%M %p"}
2 Random Split Activated novi.client.activations.weighted_random_activation.WeightedRandomActivation { "splits":[100, 0, 0], "variations":["A", "B", "C"]}
3 Combo AND Activation novi.client.activations.and_activation.AndActivation [1,2]
4 Combo OR Activation novi.client.activations.or_activation.OrActivation [1,2]

Activation classes are discovered and registered with Novi using two approaches, users can choose which ever is more convenient:-

  • Using the novi_activations folder - Any python modules or packages found in the novi_activations folder on the PYTHON_PATH will be registered as an activation
  • Using the @register annotation - Activation classes can be annotated with @register to indicate to Novi to register the class as an activation.

For a class to be considered as a valid Activation class it should inherit from the Abstract base class BaseActivation.

Out of the box novi comes with a few activations:-

At the time of instantiation Novi passes the data from the configuration column to the class constructor. Configurations enable the business logic that drives activations. The configuration field provides the parameters necessary to do the evaluation, in the example of date range check, it could be the start and end date. Think of configurations as a free-form column, containing strings (typically json) that can be parsed by the Activation class to build its internal configuration object. Please ee Creating Activations for details on how to create your own custom activations.

Relationship between Flags and Activations

A given flag can have multiple activations and similarly a given activation can be associated with multiple flags (many to many) This relationship is captured in the flags_activations table

flag_id activation_id
1 1
1 2
2 2
3 3
4 4

Architecture

Components

novi.core

The core component implements the logic to discover and register activations

novi.client

The client component uses SQLAlchemy to retrieve the flags from any supported relational database and evaluates the status of a flag's associated activations

novi.web

The web component implements a simple flask API server to implement 4 endpoints

Retrieve the original flag status as defined in the database

  • [GET] /flags - Retrieve all flags
  • [GET] /flags/<flag_name> - Retrieve a specific flag by name

Retrieve the evaluated flag status

  • [POST] /evaluatedFlags
  • [POST] /evaluatedFlags/<flag_name>

Implementing Activations

Novi scans a folder by name "novi_activations" on the python path and registers all classes inheriting from BaseActivation found within this folder.

class BaseActivation(object):

    def __init__(self, cfg: Any = None):
        self.config = cfg

    def evaluate(self, context: dict = None) -> bool:
        pass

Novi's power comes from being able to implement pretty much any complex logic within these activation classes. A flag can be associated with multiple activations:-

Row Based Association

If the association is expressed as a many-to-many relationship in the flags_activations tables the status of evaluating each activation is 'AND'ed with the others to calculate the final status. In the example above Date and Random Activated Feature (id=1) is associated with 2 activations Date based and Random weight as capture in the flags_activations table.

A List of Activations

Flags can also be associated with Activations by inheriting from BaseCombinationActivation class

class BaseCombinationActivation(BaseActivation):

    def __init__(self, config: str = None):
        logging.getLogger(__name__).debug(f"{self.__module__} = {self.__class__}")
        activationIds: list[int] = json.loads(config)
        super().__init__(flag.get_activation_by_ids(activationIds))

    @abstractmethod
    def evaluate(self, context: dict = None) -> bool:
        pass

This approach gives implementers more fine grained ability to control how the final status of the flag is calculated. Rows 3 and 4 in the Activation table shown above are an example of ComboActivations, the configuration column is a list of activations that need to be combined They activations can be combined using any custom logic implemented in the def evaluate(..) method

Installation and Usage

Installation

pip install novi

Usage

There are a few ways to check the status of a flag and turn on/of features.

Using novi.client

The novi.client.flag retrieves feature flags from a database. Novi uses sqlalchemy to query from a variety of databases.

Configuration

Before you query a database, you need to tell Novi how to connect to your database. This is done in novi.ini. An example file is here. To create the tables you can either run the DDL SQL from the file schema.sql or use the novi.web. Instructions to create and seed the table using the sample_data.sql script are here.

Once the tables are created and seeded with your feature flag data. You can either use the decorator or the is_enabled method to check the status of a feature flag using it's name. The example python file demonstrates both ways

import logging.config
from novi.client import flag

dateToTest = "11/26/2023 12:00 AM"


@flag.enabled("Date Activated Feature", {
    'currentDateTime': dateToTest
})
def date_based_activated_func():
    print(f"Feature active as of {dateToTest}")


# Only the variationA will be evaluated as the split is [100,0, 0] in the database
#
@flag.enabled("Random Variant Feature", {
    "seed": 333,
    "variant": "A"
})
def variationA():
    print('variation A')


@flag.enabled("Random Variant Feature", {
    "seed": 333,
    "variant": "B"
})
def variationB():
    print('variation B')


def variationC():
    print('variation C')


if __name__ == '__main__':
    logging.config.fileConfig("logging.conf")
    date_based_activated_func()
    variationA()
    variationB()
    if flag.is_enabled("Random Variant Feature", {
        "seed": 333,
        "variant": "C"
    }):
        variationC()

Given the sample tables as shown above

The output will be

Feature active as of 11/26/2023 12:00 AM
variation A

Using API endpoints

novi.web exposes 4 endpoints.

  • The \flags and \flags\<flag_name> endpoints return the raw flag data as retrieved from the database table. The status of the flag is not updated. These endpoints are useful if you are integrating with an authoring tool, so as to allow users to view\edit the flag data as persisted in the database. These endpoints are restful and support GET, POST, PUT and DELETE http methods
  • The \evaluatedFlags and \evaluatedFlags\<flag_name> endpoints do the actual evaluation of the status flag by applying the context against each of the activations associated with the flag. The context is passed as part of the request body in the POST request. See novi.web for details

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

novi-0.4.2.tar.gz (24.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

novi-0.4.2-py3-none-any.whl (26.2 kB view details)

Uploaded Python 3

File details

Details for the file novi-0.4.2.tar.gz.

File metadata

  • Download URL: novi-0.4.2.tar.gz
  • Upload date:
  • Size: 24.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.7.1 CPython/3.11.5 Windows/10

File hashes

Hashes for novi-0.4.2.tar.gz
Algorithm Hash digest
SHA256 244d2047341f3c8955a7ef6b3464be9c94ef2439d9f03bb9df7dbbf6745db270
MD5 9779d576731bf9a8a4a64912b003c433
BLAKE2b-256 f80705f98d5bbe1aea505ebc7acf0bf17f0c1390ad49c736caf2d04487788d65

See more details on using hashes here.

File details

Details for the file novi-0.4.2-py3-none-any.whl.

File metadata

  • Download URL: novi-0.4.2-py3-none-any.whl
  • Upload date:
  • Size: 26.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.7.1 CPython/3.11.5 Windows/10

File hashes

Hashes for novi-0.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1310cd4bf06634e6cffbf7b358ecbedd69cf5627db9b42f6b6665788778c7afc
MD5 b9f82f6e6b8a56ac0db1564518747b4b
BLAKE2b-256 075ebd155d4f6280ebe02d9a3ec5d98842b3ee209da2c71f11193b08b4955bee

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