Skip to main content

TypedConf

A lightweight, type-safe configuration management library powered by Pydantic. Following the 12-factor application guide, it centralizes your application configuration/settings by merging data from multiple sources with a defined priority: env > cli > json > toml > payload > defaults.

TypedConf combines the strengths of Pydantic and dynaconf: While it requires developers to create classes to reflect your configuration (unlike dynaconf), this approach reduces heavily your headaches 🤕🤕😠 from misstyped property names, wrong types or format, ... TypedConf offers comprehensive IDE support, type safety and runtime data validation, powered by pydantic.

Key Features

  • Type-Safe: Built on Pydantic, ensuring configuration values are validated at runtime.
  • IDE Support: Full type-hinting and IntelliSense-Support for seamless development.
  • Nested Support: Easily handle complex configuration structures.
  • TOML and JSON Interface: Load configuration from toml and/or json files.
  • CLI and Environment Interface: Load configuration data from CLI interface (--cfg_myint=1) and/or ENV varables (export CFG_MYINT=1).
  • Layered Configuration: Merges configuration data with a clear priority: env > cli > json > toml > payload > defaults
  • Immutability: Configuration data is readonly (default) after loading.
  • Self-Documenting: Generate help text from your configuration-schema definition.

Quick Start

Define your configuration schema/model by inheriting from ConfigModel. This will handle pydantic's parsing and validation while loading data from different sources, in this eyample directly from the payload:

from typedconf import ConfigModel

# define configuration schema
class AppConfig(ConfigModel):
    app_name: str # required field, no default
    port: int = 8080 # default value

# load configuration
conf = AppConfig.load(payload={'app_name':'app'})

print(f"Running {conf.app_name} on port {conf.port}")   # Running app on port 8080

Load configuration from TOML (or JSON)

Loading configuration data from source isn't a big deal. Most the time you will load data from a configurations file. Let's say from this toml-file:

# config.toml v1
app_name = "myapp"
port = 2000
from typedconf import ConfigModel

# define configuration schema
class AppConfig(ConfigModel):
    app_name: str
    port: int = 8080

# load configuration
conf = AppConfig.load(toml_files=['config.toml'])

print(f"Running {conf.app_name} on port {conf.port}")   # Running myapp on port 2000

Data Validation and nested configuration

ConfigModel is a pydantic BaseModel. So you can use the Field definitions to add descriptions, constraints, or default values. Nested configuration can be applied by nesting ConfigModelclasses.

from pydantic import Field
from typedconf import ConfigModel, ConfigError

# define configuration schema
class DatabaseConfig(ConfigModel):
    con: str = Field(..., description="DB connection-string, required field.")
    user: str = Field(..., description="DB username, required field.")
    pwd: str = Field(..., description="DB password, required field.")

class AppConfig(ConfigModel):
    app_name: str = Field(..., description="application name, required field.")
    port: int = Field(8080, ge=1000, le=9999, description="application listen on port. Between 1000 and 9999, defaullt=8080")
    db: DatabaseConfig = Field(default_factory=DatabaseConfig, description="database configuration")

# Load configuration
try:
    conf = AppConfig.load(toml_files=['config.toml'])
    print(f"Running {conf.app_name} on port {conf.port}. DB connected {conf.db.user} @ {conf.db.con}")
except ConfigError as e:
    print(e)

Loading from our "old" TOML-file will raise a ConfigError, because the stored data didn't reflect the new configuration-schema:

3 validation errors for DatabaseConfig 
con Field required [type=missing, input_value={}, input_type=dict] For further information visit https://errors.pydantic.dev/2.11/v/missing
user Field required [type=missing, input_value={}, input_type=dict] For further information visit https://errors.pydantic.dev/2.11/v/missing
pwd Field required [type=missing, input_value={}, input_type=dict] For further information visit https://errors.pydantic.dev/2.11/v/missing

TOML is perfect for nested configurations using tables and JSON requires nested objects to reflect the same structure 🌞. However, it's not the best idea to store sensitive or volatile data in a configuration-file. It is way better to handle this kind of data by cli-interface and/or through environment variables. Let's fix our TOML-file, while keeping the database password secret:

# config.toml v2
app_name = "toml-app"
port = 9090

[db]
con = "postgresql://localhost:5432/mydb"
user = "db_user_readonly"

Just remember: don't store any sensitive data in configuration-files!

ENV & CLI Interface

Cool, now we can inject the missing (or secret) data through the cli- and env-interface. Both interfaces are enabled per default.

# cli-interface
$ python app.py --cfg_db__pwd="secret"
Running toml-app on port 9090. DB connected db_user_readonly @ postgresql://localhost:5432/mydb

# env-interface
$ export CFG_DB__PWD="secret"
$ python app.py
Running toml-app on port 9090. DB connected db_user_readonly @ postgresql://localhost:5432/mydb

# mix them
$ export CFG_DB__USER="db_user_admin"
$ export CFG_DB__PWD="secret"
$ CFG_PORT=2525 python app.py --cfg_app_name="cli-app"
Running cli-app on port 2525. DB connected db_user_admin @ postgresql://localhost:5432/mydb

The CLI- and ENV interface follows this convention:

  • Case-sensitive: cli is lowercase, env is UPPERCASE
  • CLI uses only long format for the arguments like --key=val
  • Prefix: CLI arguments and ENV variables uses a prefix to avoid cross-situations in the shell. Defaults to cfg_. The prefix can be changed.
  • Nested configuration will be seperated by __ (two underscrores)
  • Examples:
    • cli-interface: --cfg_app_name or --cfg_db__user
    • env-inteface: CFG_APP_NAME or CFG_DB__USER

Priority Chain

TypedConf merges all data sources in a specific order. Higher-priority sources overwrite lower-priority ones:

  1. Environment Variables (Highest): Overrides all other sources - i.e. export CFG_DB__PWD="abc"
  2. CLI Arguments: Passed via command-line - i.e. --cfg_db__pwd='abc'
  3. JSON Files: Merged from the provided list in the order specified
  4. TOML Files: Merged from the provided list in the order specified
  5. Payload: A dictionary passed directly to the load method - i.e. .load(payload={"db":{"pwd":"abc"}})
  6. Defaults (Lowest): Default values defined in the ConfigModel class

Note: The system performs a deep merge, preserving nested structures when partial overrides are provided.

Utils

Exporting Configurations

Export your current configuration instance to JSON or TOML format.

# Export TOML string
print(conf.dumps_toml())

# Export JSON string
print(conf.dumps_json())

Note: Exporting to TOML requires python package tomli-w.

CLI Help included

TypedConf can include a --help argument to your application and generates a nice helptext for all field-names based on their types and descriptions. Let's step back to our nested configuration example and add some help for the user:

from pydantic import Field
from typedconf import ConfigModel, ConfigError

RFC3986_URI_REGEX = r'^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})(?::\d+)?([\/\w \.-]*)*\/?$'

# define configuration schema
class DatabaseConfig(ConfigModel):
    con: str = Field(..., pattern=RFC3986_URI_REGEX, description="DB connection-string, required field.")
    user: str = Field(..., description="DB username, required field.")
    pwd: str = Field(..., description="DB password, required field.")

class AppConfig(ConfigModel):
    app_name: str = Field(..., description="application name, required field.")
    port: int = Field(8080, gt=1000, lt=9999, description="application listen on port. Between 1000 and 9999, defaullt=8080")
    db: DatabaseConfig = Field(default_factory=DatabaseConfig, description="database configuration")

# need some help?
if AppConfig.user_needs_help():
    print(f"MYAPP\n\nAvailable CLI Parameter\n{AppConfig.get_cli_helptext()}")
    exit(0)

# Load configuration
try:
    conf = AppConfig.load(toml_files=['config.toml'])
    print(f"Running {conf.app_name} on port {conf.port}. DB connected {conf.db.user} @ {conf.db.con}")
except ConfigError as e:
    print(e)
$ python main.py --help
MYAPP

Available CLI Parameter
--cfg_app_name (AppConfig.app_name)
   type=str, default=None
   application name, required field.

--cfg_db__con (DatabaseConfig.con)
   type=str, default=None
   DB connection-string, required field.

--cfg_db__pwd (DatabaseConfig.pwd)
   type=str, default=None
   DB password, required field.

--cfg_db__user (DatabaseConfig.user)
   type=str, default=None
   DB username, required field.

--cfg_port (AppConfig.port)
   type=int, default=8080
   application listen on port. Between 1000 and 9999, defaullt=8080

Writeable Configuration

Set pydantic's frozen to False, if you need a writeable configuration.

from pydantic import Field
from typedconf import ConfigModel, ConfigError

# define configuration schema
class DatabaseConfig(ConfigModel):
    model_config = {'frozen': False}    # writeable BaseModel

    con: str = Field(..., description="DB connection-string, required field.")
    user: str = Field(..., description="DB username, required field.")
    pwd: str = Field(..., description="DB password, required field.")

class AppConfig(ConfigModel):
    model_config = {'frozen': False}    # writeable BaseModel

    app_name: str = Field(..., description="application name, required field.")
    port: int = Field(8080, ge=1000, le=9999, description="application listen on port. Between 1000 and 9999, defaullt=8080")
    db: DatabaseConfig = Field(default_factory=DatabaseConfig, description="database configuration")

# Load configuration
try:
    conf = AppConfig.load(toml_files=['config.toml'])
    print(f"Running {conf.app_name} on port {conf.port}. DB connected {conf.db.user} @ {conf.db.con}")
except ConfigError as e:
    print(e)

# write configuration (hint: this is instance-memory only!)
    conf.port = 6789

Note: ConfigModel sets the pydantic model_config to:

  • Set ConfigModel to readonly
  • Raise error, when loading unknown extra data
  • Validate default values when loading
  • validate when assigning a new value to a writeable ConfigModel
model_config = {
    "frozen": True,
    "extra": "forbid",
    "validate_default": True,
    "validate_assignment": True,
}

TODOs

  • override cli-seperator
  • use (prefix & ) cli-seperator for metadata and fullnames

Comparison with Other Configuration Approaches

Feature TypedConf dynaconf raw toml/json
Type Safety Yes (Pydantic-based) No No
IDE Support Excellent (Pydantic integration) Limited Limited
Nested Configurations Native support Native support Manual handling
Validation Built-in (Pydantic) Optional (schema validation) Manual
CLI Interface Built-in Built-in Manual parsing
Environment Variables Built-in Built-in Manual handling
TOML Support Yes Yes Yes
JSON Support Yes Yes Yes
Help Text Generation Yes Limited No
Immutability Default (configurable) Configurable Manual handling

License

This project is licensed under the GPL-3.0 License - see the LICENSE file for details.

Release files for typedconf 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for typedconf 0.1.0
File Size Uploaded
typedconf-0.1.0.tar.gz 21.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for typedconf 0.1.0
File Interpreter ABI Platform
typedconf-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 32.8 kB

Release files / typedconf-0.1.0.tar.gz

Download URL typedconf-0.1.0.tar.gz
Size 21.8 kB
Tags Source
SHA-256 checksum
How to use checksums
731707ea9bb159abe16a17c23b1255daa27076d687745dd72354a29edf9ab4f7
BLAKE2b-256 checksum
How to use checksums
6bc50c72ca36488504298cf60183c80b823f9a53ae64e17807d24d7f005204c1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release files / typedconf-0.1.0-py3-none-any.whl

Download URL typedconf-0.1.0-py3-none-any.whl
Size 11.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
65d9ba911c3b5707136b24c8e2b710024a84b1edb36de2b8e3121ef2b71d5013
BLAKE2b-256 checksum
How to use checksums
4a9774de8d09fe498849341ff5e65dbfb4f4acbbdbf71128116a6065700002fe
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.3

Release history Release notifications | RSS feed

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page