Loading¶
Environment variable and .env file loading logic.
loading ¶
Environment variable and .env file reading, plus load-parameter resolution.
LoadParams
dataclass
¶
LoadParams(
*,
env: str,
override: bool,
env_dir: Path,
read_dotfiles: bool,
read_environ: bool = True,
load_local: bool,
)
Resolved load settings for one load() / reload() / cached() call.
Every behavior knob follows the same three-tier model: an explicit
(non-None) argument wins, then the well-known environment variable,
then the documented default. Instances record the resolved values —
booleans are never None, and env_dir is the resolved base
directory (explicit argument > DOTENV_DIR > cwd at load time) — so a
bare reload() repeats exactly what the previous load did, even across
cwd changes.
Construction is keyword-only: a future knob cannot silently break positional constructions, because there are none.
Attributes:
| Name | Type | Description |
|---|---|---|
env |
str
|
Environment name selecting the |
override |
bool
|
Whether the merged dotfile layer beats the process
environment (default |
env_dir |
Path
|
Resolved base directory for |
read_dotfiles |
bool
|
Whether |
read_environ |
bool
|
Whether the process environment is read as a value
source at all (default |
load_local |
bool
|
Whether |
DotenvLayer
dataclass
¶
The merged .env cascade for one load — read, never injected.
Attributes:
| Name | Type | Description |
|---|---|---|
values |
dict[str, str]
|
Merged key/value pairs; a later (more specific) file wins,
and bare keys ( |
base_dir |
Path
|
The directory the cascade was read from. |
files |
tuple[Path, ...]
|
The files that existed and were read, in cascade order. |
Note
repr() is masked by design: it shows key names and counts, never
values — merged values may be secrets, including process-env values
pulled in via ${VAR} interpolation. Access values directly when
you need them.
__repr__ ¶
Key names and counts only — merged values may be secrets.
Source code in dotenvmodel/loading.py
resolve_env_name ¶
Resolve the environment name: explicit argument > ENV env var > "dev".
When to use
- Called by
resolve_load_params()andread_env_files()— call directly only if you need the same resolution without loading
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
str | None
|
Environment name, or None to read |
required |
Returns:
| Type | Description |
|---|---|
str
|
The resolved environment name. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the resolved name is empty or contains characters
other than alphanumerics, hyphens, and underscores — the guard
that prevents path traversal via |
Source code in dotenvmodel/loading.py
resolve_env_dir ¶
Resolve the .env base directory: explicit argument > DOTENV_DIR > cwd.
The result is always absolute: a relative argument or DOTENV_DIR
value is joined onto the current working directory. The join is
lexical — no resolve(), no symlink following, no .. normalization
— so the recorded directory is exactly the path as spelled from the
cwd at load time. Recording the absolute path is what keeps a bare
reload() cwd-stable and cached()'s warm-path comparison from
misjudging a relative spelling of the same directory.
No existence check happens here. read_env_files() raises
FileNotFoundError when it is about to read from a missing directory,
or NotADirectoryError when the path exists but is not a directory;
a read_dotfiles=False load must not raise for either.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env_dir
|
Path | str | None
|
Explicit base directory — a |
required |
Returns:
| Type | Description |
|---|---|
Path
|
The resolved base directory — always absolute. |
Source code in dotenvmodel/loading.py
resolve_bool ¶
Resolve a boolean knob: explicit argument > env var > default.
When to use
- Called by
resolve_load_params()for every boolean parameter
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
bool | None
|
Explicit argument; anything but None wins immediately. |
required |
env_var
|
str
|
Environment variable consulted when |
required |
default
|
bool
|
Value used when the env var is unset — or unparseable. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
The resolved boolean. |
Note
A stray env var never raises: values are stripped, then parsed
case-insensitively against true/1/yes/on and false/0/no/off.
A whitespace-only value is treated as unset (returning default
silently — the same policy DOTENV_DIR applies to an empty value);
anything else logs a warning naming the variable and value, then
falls back to default.
Source code in dotenvmodel/loading.py
resolve_load_params ¶
resolve_load_params(
env: str | None = None,
*,
override: bool | None = None,
env_dir: Path | str | None = None,
read_dotfiles: bool | None = None,
read_environ: bool | None = None,
load_local: bool | None = None,
) -> LoadParams
Resolve every load() behavior knob into a LoadParams record.
Each knob follows the tier model documented on LoadParams — explicit
argument > environment variable > default:
| Knob | Argument | Env var | Default |
|---|---|---|---|
| Environment name | env |
ENV |
"dev" |
| Base directory | env_dir |
DOTENV_DIR |
cwd |
| Dotfiles beat process env | override |
DOTENV_OVERRIDE |
False |
| Read dotfiles at all | read_dotfiles |
DOTENV_READ_DOTFILES |
True |
| Read the process environment | read_environ |
DOTENV_READ_ENVIRON |
True |
Include .local files |
load_local |
DOTENV_LOAD_LOCAL |
False iff resolved env is "test" (case-insensitive) |
The load_local default skips .env.local / .env.{env}.local when
the resolved environment is "test" (case-insensitive). This extends
the Next.js / dotenv-flow rule — which skips only .env.local in test
and still loads .env.{env}.local — to all .local files, because a
gitignored .env.test.local must not decide test outcomes either;
.env.{env} itself is still read in every environment.
read_environ=False excludes os.environ as a value source only:
the knob channel itself (ENV, DOTENV_DIR, DOTENV_OVERRIDE,
DOTENV_READ_DOTFILES, DOTENV_READ_ENVIRON, DOTENV_LOAD_LOCAL)
still resolves from the process environment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
str | None
|
Environment name, or None for the |
None
|
override
|
bool | None
|
Whether dotfiles beat the process env, or None for the tier. |
None
|
env_dir
|
Path | str | None
|
Base directory, or None for the tier. |
None
|
read_dotfiles
|
bool | None
|
Whether to read dotfiles at all, or None for the tier. |
None
|
read_environ
|
bool | None
|
Whether to read the process environment as a value source, or None for the tier. |
None
|
load_local
|
bool | None
|
Whether to include |
None
|
Returns:
| Type | Description |
|---|---|
LoadParams
|
The fully resolved |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the resolved environment name is invalid (see
|
See Also
DotEnvConfig.load: Consumes these params.LoadParams: The record type.
Source code in dotenvmodel/loading.py
interpolate_value ¶
Resolve the ${VAR} / ${VAR:-default} references in one string.
The single interpolation entry point: .env file values (via
read_env_files) and literal string field defaults (via
DotEnvConfig.load) both resolve through this function, so every
template-bearing value shares one reference syntax and one semantics
table. base supplies the names in lookup order (e.g. merged dotfile
values over os.environ); a name absent from it resolves to the
:- default when one is given, else "" — a present-but-empty
value beats the :- default, plain dict.get semantics. Nothing
else is a reference: a bare $, $VAR shorthand, and an unclosed
${ stay literal.
When to use
- Resolving a template-bearing string against the same base
load()resolves string field defaults against (the merged dotfile values overos.environ)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The string to resolve. |
required |
base
|
Mapping[str, str]
|
Names to resolve references against, in lookup order. |
required |
Returns:
| Type | Description |
|---|---|
str
|
The resolved string — text itself, the same object, when it |
str
|
contains no |
str
|
path. |
Example
Source code in dotenvmodel/loading.py
read_env_files ¶
read_env_files(
env: str | None = None,
*,
env_dir: Path | str | None = None,
load_local: bool | None = None,
read_environ: bool | None = None,
) -> DotenvLayer
Read the cascading .env files and merge them — purely, without touching os.environ.
The process environment is never written; the merged values are
returned instead. Files are probed in Node.js-style cascade order and merged
with later (more specific) files winning, regardless of any override
policy — the override policy is applied later, once, against the whole
merged layer (see DotEnvConfig.load()).
Interpolation happens once, after the merge, and progressively in
merged-key order: a ${VAR} reference in a file value sees the keys
defined earlier in the merged cascade — with their already-resolved
values — over the process environment, so a later file can build on an
earlier file's value, while a forward or self reference (to a key defined
later in the merged order, or after it in the same file) sees only
os.environ. With read_environ=False the process environment is
excluded from this base too: references resolve against the merged
dotfile values alone, so a variable defined only in the process
environment resolves to "" (or the :- default) just like an
unresolved one. A reference to a variable defined only in the process
environment still resolves in the default mode, and interpolation is
independent of the override knob, which only governs per-field
precedence afterwards. python-dotenv 1.2.3's semantics apply,
replicated locally (${VAR} / ${VAR:-default}; no $VAR shorthand;
an unresolved reference becomes ""; the :- default applies only
when the name is absent from the base — a present-but-empty value wins
over it). Bare keys (KEY with no =) are left unset, matching
load_dotenv(), which skips them.
Probing order
.env(base configuration).env.local(local base overrides — only whenload_local).env.{env}(environment-specific).env.{env}.local(local environment overrides — only whenload_local)
When to use
- Called automatically by
DotEnvConfig.load()— you rarely call this directly - Call directly if you need the merged dotfile layer without creating a config
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
env
|
str | None
|
Environment name (e.g., "dev", "prod", "test"). If None, reads from
the |
None
|
env_dir
|
Path | str | None
|
Custom base directory for .env files — a |
None
|
load_local
|
bool | None
|
Whether to include |
None
|
read_environ
|
bool | None
|
Whether the process environment participates in the
interpolation base. If None (the default), resolves through
|
None
|
Returns:
| Type | Description |
|---|---|
DotenvLayer
|
A |
DotenvLayer
|
the files that were read |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
FileNotFoundError
|
If the resolved base directory doesn't exist |
NotADirectoryError
|
If the resolved base directory exists but is not
a directory — typically |
Example
# Merge the .env cascade for the dev environment
layer = read_env_files(env="dev")
# Custom directory, skipping .local files
from pathlib import Path
layer = read_env_files(env="prod", env_dir=Path("/app/config"), load_local=False)
# Interpolate against the dotfiles only, never the process environment
layer = read_env_files(env="prod", read_environ=False)
See Also
DotEnvConfig.load: Loads config, applying its override policy against this layer.get_env_var: Get a single env var by field name.
Source code in dotenvmodel/loading.py
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 | |
get_env_var ¶
Get environment variable value by field name or alias.
Reads os.environ only — dotfile values are not consulted; use
read_env_files() for the file layer.
When to use
- Use directly if you need to check a config env var without loading the full config
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field_name
|
str
|
Name of the field (converted to UPPER_CASE for env var lookup) |
required |
alias
|
str | None
|
Optional alias that overrides the field name for env var lookup.
When provided, |
None
|
prefix
|
str | None
|
Optional class-level prefix to prepend to the env var name.
Not applied when |
None
|
Returns:
| Type | Description |
|---|---|
str | None
|
Environment variable value as string, or None if not set |
See Also
get_env_var_name: Get just the name, not the value.
Source code in dotenvmodel/loading.py
get_env_var_name ¶
Get the environment variable name for a field.
When to use
- For generating documentation or .env.example files
- For error messages that reference the env var name
- Called internally during config loading
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
field_name
|
str
|
Name of the field (converted to UPPER_CASE for env var lookup) |
required |
alias
|
str | None
|
Optional alias that overrides the field name. When provided,
|
None
|
prefix
|
str | None
|
Optional class-level prefix to prepend. Not applied when
|
None
|
Returns:
| Type | Description |
|---|---|
str
|
The environment variable name string |
Example
See Also
get_env_var: Get the value, not just the name.