Env Magic¶
The Environment Wizard (or EnvWizard
) is a powerful Mixin class for effortlessly mapping environment variables and .env
files to strongly-typed Python dataclass fields.
It provides built-in type validation, automatic string-to-type conversion, and the ability to handle secret files, where the file name serves as the key and its content as the value.
Additionally, EnvWizard
supports type hinting and automatically applies the @dataclass
decorator to your subclasses.
Hint
These docs are inspired by and adapted from Pydantic’s Settings Management documentation.
Key Features¶
Auto Mapping: Seamlessly maps environment variables to dataclass fields, using field names or aliases.
Dotenv Support: Load environment variables from
.env
files or custom dotenv paths.Secret Files: Handle secret files where filenames act as keys and file contents as values.
Custom Configuration: Configure variable prefixing, logging, and error handling.
Type Parsing: Supports basic types (int, float, bool) and collections (list, dict, etc.).
Installation¶
Install via pip
:
$ pip install dataclass-wizard
For .env
file support, install the python-dotenv
dependency with the dotenv
extra:
$ pip install dataclass-wizard[dotenv]
Quick Start¶
Define your environment variables and map them using EnvWizard:
import os
from dataclass_wizard import EnvWizard
# Set environment variables
# or:
# export APP_NAME='...'
os.environ.update({
'APP_NAME': 'Env Wizard',
'MAX_CONNECTIONS': '10',
'DEBUG_MODE': 'true'
})
# Define the dataclass
class AppConfig(EnvWizard):
app_name: str
max_connections: int
debug_mode: bool
# Instantiate and use
config = AppConfig()
print(config.app_name) #> Env Wizard
print(config.debug_mode) #> True
assert config.max_connections == 10
# Override with keyword arguments
config = AppConfig(app_name='Dataclass Wizard Rocks!', debug_mode='false')
print(config.app_name) #> Dataclass Wizard Rocks!
assert config.debug_mode is False
Advanced Usage¶
Handling Missing Variables
If required variables are not set, EnvWizard raises a MissingVars exception. Provide defaults for optional fields:
class AppConfig(EnvWizard):
app_name: str
max_connections: int = 5
debug_mode: bool = False
Dotenv Support
Load environment variables from a .env
file by enabling Meta.env_file
:
class AppConfig(EnvWizard):
class _(EnvWizard.Meta):
env_file = True
app_name: str
max_connections: int
debug_mode: bool
Custom Field Mappings
Map environment variables to differently named fields using json_field
or Meta.field_to_env_var
:
class AppConfig(EnvWizard):
class _(EnvWizard.Meta):
field_to_env_var = {'max_conn': 'MAX_CONNECTIONS'}
app_name: str
max_conn: int
Prefixes
Use a static or dynamic prefix for environment variable keys:
class AppConfig(EnvWizard):
class _(EnvWizard.Meta):
env_prefix = 'APP_'
name: str = json_field('NAME')
debug: bool
# Prefix is applied dynamically
config = AppConfig(_env_prefix='CUSTOM_')
Configuration Options¶
The Meta
class provides additional configuration:
env_file
: Path to a dotenv file. Defaults to True for .env in the current directory.env_prefix
: A string prefix to prepend to all variable names.field_to_env_var
: Map fields to custom variable names.debug_enabled
: Enable debug logging.extra
: Handle unexpected fields. Options:ALLOW
,DENY
,IGNORE
.
Error Handling¶
MissingVars: Raised when required fields are missing.
ParseError: Raised for invalid values (e.g., converting abc to int).
ExtraData: Raised when extra fields are passed (default behavior).
Examples¶
Basic Example
import os
from dataclass_wizard import EnvWizard
os.environ['API_KEY'] = '12345'
class Config(EnvWizard):
api_key: str
config = Config()
print(config.api_key) # Output: 12345
Dotenv with Paths
from pathlib import Path
from dataclass_wizard import EnvWizard
class Config(EnvWizard):
class _(EnvWizard.Meta):
env_file = Path('/path/to/.env')
db_host: str
db_port: int
Complete Example
Here is a more complete example of using EnvWizard
to
load environment variables into a dataclass schema:
from os import environ
from datetime import datetime, time
from typing import NamedTuple
try:
from typing import TypedDict
except ImportError:
from typing_extensions import TypedDict
from dataclass_wizard import EnvWizard
# ideally these variables will be set in the environment, like so:
# $ export MY_FLOAT=1.23
environ.update(
myStr='Hello',
my_float='432.1',
# lists/dicts can also be specified in JSON format
MyTuple='[1, "2"]',
Keys='{ "k1": "false", "k2": "true" }',
# or in shorthand format...
MY_PENCIL='sharpened=Y, uses_left = 3',
My_Emails=' first_user@abc.com , second-user@xyz.org',
SOME_DT_VAL='1651077045', # 2022-04-27T12:30:45
)
class Pair(NamedTuple):
first: str
second: int
class Pencil(TypedDict):
sharpened: bool
uses_left: int
class MyClass(EnvWizard):
class _(EnvWizard.Meta):
field_to_env_var = {
'my_dt': 'SOME_DT_VAL',
}
my_str: str
my_float: float
my_tuple: Pair
keys: dict[str, bool]
my_pencil: Pencil
my_emails: list[str]
my_dt: datetime
my_time: time = time.min
c = MyClass()
print('Class Fields:')
print(c.dict())
# {'my_str': 'Hello', 'my_float': 432.1, ...}
print()
print('JSON:')
print(c.to_json(indent=2))
# {
# "my_str": "Hello",
# "my_float": 432.1,
# ...
assert c.my_pencil['uses_left'] == 3
assert c.my_dt.isoformat() == '2022-04-27T16:30:45+00:00'
This code highlights the ability to:
Load variables from the environment or
.env
files.Map fields to specific environment variable names using
field_to_env_var
.Support complex types such as
NamedTuple
,TypedDict
, and more.