mrkeyoor.com_
Sun 20 Sept 02:39 UTC
PyPIUtilsupdated 19 Sept 2026

python-dotenv review

python-dotenv 1.2.3 reads shell-like key and value lines from a `.env` file, then either places them in `os.environ` or returns them as a dictionary. It is a local configuration adapter for programs already designed around environment variables, with extra support for streams, upward file discovery, file editing, IPython, and a command wrapper. It does not validate types or protect secrets at rest. Version 1.2.3 strips a UTF-8 BOM before parsing the first key, preserves backslashes through `set_key()`, caches an empty parsed file, and gives `dotenv run` a clean missing-command error. Our pure-Python package shipped `py.typed` and imported as `dotenv`, not `python_dotenv`.

Verdict

python-dotenv 1.2.3 installed in 0.3 seconds as one 1 MB typed package with no audit findings in our sandbox, so its runtime cost is tiny for local environment loading. Install it as a development adapter, keep paths and precedence explicit, and use a real secret store for production credentials.

We installed it

Lab card: what happened when we installed python-dotenvScreenshot of python-dotenv documentation
Install✓ · 0.3s1 package on disk · 1 MB
Importimport dotenv in 0.25s · pure Python · py.typed · requires Python >=3.10
Known vulns0(pip-audit)

Answers from our run

Does python-dotenv install cleanly?

Yes. In a fresh container with an empty cache, pip install python-dotenv finished in 0.3s, leaving 1 package and 1 MB on disk. pip-audit reported no known vulnerabilities.

What does python-dotenv need to run?

Python >=3.10, and nothing compiled: it is pure Python. In our run import dotenv succeeded in 0.25s, and the package ships py.typed for type checkers.

python-dotenv or pydantic-settings: which should you use?

pydantic-settings: Use it for typed and validated settings models that can include dotenv as one source. python-dotenv 1.2.3 installed in 0.3 seconds as one 1 MB typed package with no audit findings in our sandbox, so its runtime cost is tiny for local environment loading.

When should you not use python-dotenv?

Settings require types, mandatory fields, nested structures, and useful validation errors. pydantic-settings owns those concerns.

API stability5/5The core choices remain `load_dotenv()` for process mutation, `dotenv_values()` for a mapping, `find_dotenv()` for discovery, and small key-editing helpers. Version 1.2.3 changes BOM, escaping, caching, and CLI error behavior without reshaping those calls. Precedence is still controlled by `override`, though version 1.2.2 changed symlink handling for editors, so code that intentionally modifies a linked file must now request that behavior explicitly.
Docs4/5The official site explains the file grammar, braced expansion, multiline values, streams, IPython commands, CLI installation, precedence, and the `PYTHON_DOTENV_DISABLED` switch with runnable examples. It also distinguishes a valueless key from an empty assignment. The weak point is cross-tool behavior: the README accurately says the format is not formally specified, leaving teams to test files shared with shells, Compose, or another dotenv parser.
Maintenance5/5Version 1.2.3 shipped on August 16, 2026, and GitHub records a later push on August 23. The unarchived repository has 8,854 stars and currently counts 107 open issues plus pull requests. The latest patch addresses a first-key BOM failure, Windows path escaping, empty-file caching, and a CLI error path. Version 1.2.2 also included Windows CI and explicit symlink plus file-mode behavior, showing active attention to platform details.
Ecosystem5/5The supplied snapshot reports 192,589,603 weekly PyPI downloads. The dotenv convention is common in Python web servers, notebooks, CLIs, containers, and framework launchers, and python-dotenv supports both library and command-line entry points. Familiarity can create duplicate loading, however, when a framework already accepts an env-file flag and application startup calls `load_dotenv()` again under a different path or precedence rule.

Use it if

  • The application already consumes `os.environ`, and local developers need to supply the same variables from one ignored file.
  • Several dotenv files must be parsed into mappings and combined under a precedence order your code can show explicitly.
  • Configuration text arrives through an in-memory stream or FIFO and should be parsed without creating a temporary regular file.
  • A local command needs a selected dotenv file applied to its child process through the optional CLI.
Skip it if

Setup reality

We installed python-dotenv 1.2.3 in a fresh unprivileged Python 3.12 Bookworm sandbox. pip took 0.3 seconds, and one installed package used 1 MB. import dotenv completed in 0.25 seconds. The distribution is pure Python, includes py.typed, declares one direct dependency, requires Python 3.10+, and uses the BSD-3-Clause license. pip-audit found zero known vulnerabilities in our resolved environment.

Load the file before importing modules that read variables at module scope. load_dotenv() preserves an existing process value unless override=True is set. Its automatic finder walks upward, which makes the chosen file depend on script location and runtime context. Services and tests are easier to reason about when they construct a path from a known application root.

Every result is a string, None, or absence. KEY and KEY= are different: dictionary parsing maps the first form to None and the second to an empty string, while environment loading ignores a valueless key. Expansion recognizes ${DOMAIN}, not bare $DOMAIN. Version 1.2.3 now removes a leading UTF-8 BOM and keeps backslashes intact through set_key(), fixing two Windows-heavy failure modes.

Use dotenv_values() when parsing should not mutate global process state. Merge mappings in visible order and put os.environ last if deployed values must win. Editing functions rewrite a shared file and need external coordination when several processes can write. The dotenv command comes from the cli extra. On a host that already injects variables, omit file loading or set PYTHON_DOTENV_DISABLED=1 when code outside your control calls it.

Patterns

Load variables from the application directory load-fixed-path

import os
from pathlib import Path
from dotenv import load_dotenv

root = Path(__file__).resolve().parent
load_dotenv(root / ".env")
database_url = os.environ["DATABASE_URL"]

Call this before imports that read environment values. A path anchored to source avoids selection changes when the process starts elsewhere.

Keep host-injected values ahead of file values preserve-process-environment

from dotenv import load_dotenv

load_dotenv(".env", override=False)

`False` is the default. Any name already present in `os.environ` remains untouched.

Apply a controlled test file with higher priority override-process-environment

from dotenv import load_dotenv

load_dotenv(".env.test", override=True)

This changes process-global state. Restore affected keys in test cleanup when other cases share the same interpreter.

Read a file without touching `os.environ` parse-to-dictionary

from dotenv import dotenv_values

values = dotenv_values(".env")
api_url = values.get("API_URL")

A line containing only a name can map to `None`; `NAME=` maps to an empty string. Decide whether either form is valid configuration.

Put host variables after file defaults layer-configuration

import os
from dotenv import dotenv_values

config = {
    **dotenv_values(".env.shared"),
    **dotenv_values(".env.local"),
    **os.environ,
}

Dictionary order defines precedence here. With `os.environ` last, deployed variables replace both shared and local file entries.

Read dotenv content held in memory parse-memory-stream

from io import StringIO
from dotenv import dotenv_values

config = dotenv_values(stream=StringIO("MODE=worker\nPORT=9000\n"))

Stream parsing avoids a temporary file. The caller still owns transport security, logging policy, and lifetime of the plaintext content.

Interpolate a previously defined value expand-file-variable

# .env
DOMAIN=example.com
API_URL=https://${DOMAIN}/api

Braces are required. python-dotenv leaves `$DOMAIN` unchanged, while `${DOMAIN}` participates in its documented expansion order.

Find a file relative to process startup search-from-current-directory

from dotenv import find_dotenv, load_dotenv

path = find_dotenv(usecwd=True)
if path:
    load_dotenv(path)

`usecwd=True` makes the search depend on the current directory. It suits an interactive CLI but is risky for services launched from varying paths.

Start a child process with a selected file run-child-command

python -m pip install "python-dotenv[cli]"
dotenv -f .env.local run -- python app.py

The `dotenv` executable is installed by the `cli` extra. Review its override flags before placing this command in shared automation.

Block loading in a deployed process disable-dotenv-read

export PYTHON_DOTENV_DISABLED=1
python app.py

This switch makes `load_dotenv()` ignore files and streams, including calls made by third-party packages that cannot be edited.

Alternatives

PackageRegistryPick it when
pydantic-settingsPyPIUse it for typed and validated settings models that can include dotenv as one source.
environsPyPIUse it for environment parsing and casting without building Pydantic models.
dynaconfPyPIUse it for named environments, layered file formats, and external secret backends.

More utils guides

lru-cache · type-fest · ajv · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.