Skip to main content

An elegant application configurator for the more civilized age

Project description

envenom

pipeline status coverage report latest release

Introduction

envenom is an elegant application configurator for the more civilized age.

envenom is written with simplicity and type safety in mind. It allows you to express your application configuration declaratively in a dataclass-like format while providing your application with type information about each entry, its nullability and default values.

envenom is designed for modern usecases, allowing for pulling configuration from environment variables or files for more sophisticated deployments on platforms like Kubernetes.

Usage

How it works

An envenom config class looks like a regular Python dataclass - because it is one!

The config decorator really just creates a new dataclass by converting the config fields into their dataclass equivalents providing the relevant default_factory.

This also means it's 100% compatible with dataclasses - you can use the config class as part of a regular dataclass, or a regular dataclass as part of the config class!

envenom will automatically build the environment variable names for you, trying to populate the dataclass fields from the environment, optionally running a parser so that the field is automatically converted to a desired type (works out of the box with all simple types like Enum and UUID or with any custom class that can be instantiated from a single string). If using a static type checker the type deduction system will correctly identify mistakes if you declare fields, parsers or default values with mismatched types.

Because the environment's case-sensitivity is... questionable, to put it lightly, all interaction is case-insensitive - environment variable names are in full uppercase, and since _ is a common separator within environment variable names it uses __ to separate namespaces instead. This means

envenom also offers reading variable contents from file by specifying an environment variable <name>__FILE which contains the name of a file with the respective secret. This aims to facilitate a common deploy pattern where secrets are mounted as files (especially with Kubernetes and containers everywhere).

Quickstart guide

Install envenom with python -m pip install envenom.

from functools import cached_property

from envenom import config, optional, required, subconfig, with_default
from envenom.parsers import as_boolean


@config(namespace=("myapp", "postgres"))
class DatabaseCfg:
    host: str = required()
    port: int = with_default(int, default=5432)
    database: str = required()
    username: str | None = optional()
    password: str | None = optional()
    sslmode_require: bool = with_default(as_boolean, default=True)

    @cached_property
    def connection_string(self) -> str:
        authentication_part = (
            f"{self.username}:{self.password}" if self.password else self.username
        )
        query_string = ""
        if self.sslmode_require:
            query_string += "sslmode=require"

        return (
            f"postgresql+psycopg://{authentication_part}@{self.host}:{self.port}"
            f"/{self.database}?{query_string}"
        )


@config(namespace="myapp")
class AppCfg:
    secret_key: str = required()
    database: DatabaseCfg = subconfig(DatabaseCfg)


if __name__ == "__main__":
    cfg = AppCfg()

    print(f"myapp/secret_key: {repr(cfg.secret_key)} {type(cfg.secret_key)}")
    print(f"myapp/db/host: {repr(cfg.database.host)} {type(cfg.database.host)}")
    print(f"myapp/db/port: {repr(cfg.database.port)} {type(cfg.database.port)}")
    print(f"myapp/db/database: {repr(cfg.database.database)} {type(cfg.database.database)}")
    print(f"myapp/db/username: {repr(cfg.database.username)} {type(cfg.database.username)}")
    print(f"myapp/db/password: {repr(cfg.database.password)} {type(cfg.database.password)}")
    print(f"myapp/db/sslmode_require: {repr(cfg.database.sslmode_require)} {type(cfg.database.sslmode_require)}")
    print(f"myapp/db/connection_string: {repr(cfg.database.connection_string)} {type(cfg.database.connection_string)}")

Run the example with python -m envenom.examples.quickstart:

Traceback (most recent call last):
    ...
    raise MissingConfiguration(self.env_name)
envenom.errors.MissingConfiguration: 'MYAPP__SECRET_KEY'

Run the example again with environment set:

MYAPP__SECRET_KEY='}uZ?uvJdKDM+$2[$dR)).n4q1SX!A$0u{(+D$PVB' \
MYAPP__POSTGRES__HOST='postgres' \
MYAPP__POSTGRES__DATABASE='database-name' \
MYAPP__POSTGRES__USERNAME='user' \
MYAPP__POSTGRES__SSLMODE_REQUIRE='f' \
python -m envenom.examples.quickstart
myapp/secret_key: '}uZ?uvJdKDM+$2[$dR)).n4q1SX!A$0u{(+D$PVB' <class 'str'>
myapp/db/host: 'postgres' <class 'str'>
myapp/db/port: 5432 <class 'int'>
myapp/db/database: 'database-name' <class 'str'>
myapp/db/username: 'user' <class 'str'>
myapp/db/password: None <class 'NoneType'>
myapp/db/sslmode_require: False <class 'bool'>
myapp/db/connection_string: 'postgresql+psycopg://user@postgres:5432/database-name?' <class 'str'>

Next steps

See the wiki for more info.

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

envenom-1.0.4.tar.gz (21.6 kB view hashes)

Uploaded Source

Built Distribution

envenom-1.0.4-py3-none-any.whl (26.6 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page