Convert env values into custom types¶
When the built-in type set isn't enough — most commonly for enums or
types like Decimal and pathlib.Path — pass a callable as
convert_using. The callable receives the raw str from the environment
and must return the typed value.
import enum
from decimal import Decimal
from env_proxy import EnvConfig, Field
class Level(enum.Enum):
LOW = "low"
HIGH = "high"
class AppConfig(EnvConfig):
env_prefix: str = "APP"
level: Level = Field(convert_using=Level, default=Level.LOW)
amount: Decimal = Field(convert_using=Decimal)
Behaviour¶
- The converter is called only when the env value is present. If the
env var is missing and a
defaultis provided, the default is returned as-is — supply a default of the target type (e.g.default=Level.LOW, notdefault="low"). - If the converter raises, the exception is wrapped in
EnvValueError. The original exception is preserved on__cause__. - Passing both
convert_usingandtype_hintemits aUserWarningand ignorestype_hint. - The annotation on the field is informational (used by static type
checkers);
convert_usingis the source of truth for runtime conversion.
Type label in exports and errors¶
For the type label shown in exported .env files and EnvValueError
messages, the resolution order is:
- Explicit
type_name=if given. - The field annotation, if it's a simple type (
int,Level, …). convert_using.__name__, unless it would be"<lambda>".- Fallback:
"custom".
So field: Level = Field(convert_using=Level) renders as (Level) in
.env exports, and:
renders as (Doubled).
Common patterns¶
from pathlib import Path
import json
class AppConfig(EnvConfig):
env_prefix: str = "APP"
# Path
data_dir: Path = Field(convert_using=Path)
# Enum
log_level: LogLevel = Field(convert_using=LogLevel)
# Comma-separated list of ints
port_ranges: list[int] = Field(
convert_using=lambda s: [int(p) for p in s.split(",")],
type_name="list[int]",
)
# Tagged JSON
metadata: dict[str, str] = Field(convert_using=json.loads, type_name="json")