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_nameor--cfg_db__user - env-inteface:
CFG_APP_NAMEorCFG_DB__USER
- cli-interface:
Priority Chain
TypedConf merges all data sources in a specific order. Higher-priority sources overwrite lower-priority ones:
- Environment Variables (Highest): Overrides all other sources - i.e.
export CFG_DB__PWD="abc" - CLI Arguments: Passed via command-line - i.e.
--cfg_db__pwd='abc' - JSON Files: Merged from the provided list in the order specified
- TOML Files: Merged from the provided list in the order specified
- Payload: A dictionary passed directly to the load method - i.e.
.load(payload={"db":{"pwd":"abc"}}) - Defaults (Lowest): Default values defined in the
ConfigModelclass
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)
| File | Size | Uploaded | |
|---|---|---|---|
| typedconf-0.1.0.tar.gz | 21.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|