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.