📋 Product Owner view
Feature inventory, spec alignment, behavioural gaps · Product Owner, Product Manager
⚡ Agent trace · 13 steps · $0.0510 · click to expand
search_claims(query='feature capability user', category='any') →
search_claims(query='api endpoint route', category='any') →
search_claims(query='spec readme documentation', category='any') →
search_claims(query='user', category='any') →
search_claims(query='feature', category='any') →
search_claims(query='a', category='any') →
search_claims(query='b', category='any') →
search_claims(query='c', category='any') →
search_claims(query='d', category='any') →
read_source_file(file_path='src/dotenv/__init__.py', start_line=1) →
read_source_file(file_path='src/dotenv/cli.py', start_line=1) →
read_source_file(file_path='src/dotenv/main.py', start_line=1) →
read_source_file(file_path='src/dotenv/main.py', start_line=61) →
read_source_file(file_path='src/dotenv/cli.py', start_line=62) →
read_source_file(file_path='src/dotenv/main.py', start_line=122) →
read_source_file(file_path='src/dotenv/cli.py', start_line=122) →
read_source_file(file_path='src/dotenv/main.py', start_line=182) →
read_source_file(file_path='src/dotenv/cli.py', start_line=182) →
read_source_file(file_path='src/dotenv/main.py', start_line=242) →
read_source_file(file_path='src/dotenv/cli.py', start_line=220) →
read_source_file(file_path='src/dotenv/main.py', start_line=302) →
read_source_file(file_path='src/dotenv/parser.py', start_line=1) →
read_source_file(file_path='src/dotenv/main.py', start_line=362) →
read_source_file(file_path='src/dotenv/parser.py', start_line=61) →
read_source_file(file_path='src/dotenv/variables.py', start_line=1) →
read_source_file(file_path='src/dotenv/main.py', start_line=422) →
read_source_file(file_path='src/dotenv/parser.py', start_line=121) →
read_source_file(file_path='src/dotenv/variables.py', start_line=61)
Product View: python-dotenv (v1.2.2)
Feature Inventory
| Feature | Status | Confidence | Notes |
|---|---|---|---|
| Load .env into environment variables | Implemented | High | Core load_dotenv() function at main.py:383-430. Returns True if at least one variable was set. |
| Read .env as dictionary (no env side effects) | Implemented | High | dotenv_values() at main.py:433-467. Returns parsed key-value dict without modifying os.environ. |
| CLI: List all values | Implemented | High | dotenv list command at cli.py:93-109. Supports 4 output formats: simple, json, shell, export. |
| CLI: Get a single value | Implemented | High | dotenv get <key> at cli.py:137-148. Exits with code 1 if key not found. |
| CLI: Set a key/value | Implemented | High | dotenv set <key> <value> at cli.py:116-131. Creates file if it doesn't exist. |
| CLI: Unset a key | Implemented | High | dotenv unset <key> at cli.py:154-167. Removes key from file. |
| CLI: Run command with .env | Implemented | High | dotenv run <command> at cli.py:184-201. Replaces current process (Unix) or spawns subprocess (Windows). |
| Variable interpolation | Implemented | High | ${VAR} and ${VAR:-default} syntax supported. main.py:289-311 and variables.py:70-86. |
| IPython/Jupyter magic | Implemented | High | %dotenv line magic at ipython.py:34-45. Supports --override and --verbose flags. |
| Auto-discovery of .env file | Implemented | High | find_dotenv() walks up directory tree from caller's file or CWD. main.py:332-380. |
| Kill switch (disable loading) | Implemented | High | PYTHON_DOTENV_DISABLED env var. main.py:22-29. |
| Quoting modes for set_key | Implemented | High | always, auto, never modes. main.py:211-216. |
| Symlink safety | Implemented | High | set_key and unset_key don't follow symlinks by default. main.py:207-209, main.py:261-263. |
| Export prefix support | Implemented | High | Parser handles export KEY=value lines. parser.py:21,153. CLI has --export flag. |
| FIFO (named pipe) support | Implemented | High | _is_file_or_fifo() at main.py:470-482 — reads from named pipes, not just regular files. |
| CLI helper for Fabric tasks | Implemented | High | get_cli_string() at __init__.py:12-39. Builds shell command strings. |
| Version info | Implemented | High | __version__ = "1.2.2" at version.py:1. Exposed via --version flag. |
What the Code Does That the Spec Doesn't Mention
-
FIFO/named pipe support is undocumented. The
_is_file_or_fifo()function (main.py:470-482) allows reading .env content from named pipes, not just regular files. This is a deliberate design choice (the function is used in_get_streamandfind_dotenv) but is not mentioned in any docstring or README-level documentation. This means users could pipe environment variables through a FIFO, but there's no public API surface or documentation for this capability. -
The
dotenv_values()function always usesoverride=Trueinternally (main.py:460-467), even though theload_dotenv()function defaults tooverride=False. This means reading values as a dict always resolves variable references with environment variables taking precedence over .env values, while loading into the environment does the opposite by default. This asymmetry is not documented and could surprise users who expect consistent behavior between the two APIs. -
The CLI
getcommand has a subtle bug with empty values. Atcli.py:144-148, the code checksif stored_value:— this means a key that exists but has an empty string value (KEY=in .env) will be treated as "not found" and exit with code 1. The Python API (DotEnv.get()atmain.py:112-122) correctly returnsNoneonly for missing keys, but the CLI treats empty strings as missing. This is a behavioral inconsistency between the API and CLI. -
The
set_keyfunction always returnsTrueon success (main.py:245), but the return type annotation saysTuple[Optional[bool], str, str]. The first element is alwaysTrue— it never returnsFalse. The "failure" case is actually an exception being raised. This means the CLI'sset_valuecommand (cli.py:127-131) will only hit thesys.exit(1)path if an exception occurs, not from a return value check. -
The
rewritecontext manager has a silent file mode preservation behavior (main.py:150-155, 181-182). When modifying .env files, it preserves the original file's Unix permissions (mode bits) on the temporary file before replacing. This is a thoughtful touch for production environments but is not documented anywhere. -
The
enumerate_env()CLI helper silently returnsNonewhen CWD doesn't exist (cli.py:30-33). This is an edge case (process has no working directory) that would cause the CLI to fail with a confusing error rather than a helpful message.
Spec Gaps
-
No README or spec file exists in the codebase. There are 283 claims across 19 files, but no documentation file (README.md, SPEC.md, etc.) was found. The only "documentation" is the docstrings in the code itself. This means there is no external spec to compare against — the code is the spec.
-
The
get_cli_string()function is exported in__all__(__init__.py:42-50) but has no tests and no usage within the codebase. It appears to be a legacy helper for Fabric task integration that may be untested. -
The
load_ipython_extension()function is defined twice — once in__init__.py:6-9(which delegates toipython.py) and once inipython.py:48-50. The__init__.pyversion re-exports theipython.pyversion, which is fine, but the duplication could confuse maintainers. -
The
DotEnvclass'sget()method returnsNonefor missing keys (main.py:122), but the docstring says nothing about this behavior. Theget_key()wrapper function (main.py:125-135) documents this correctly, but the class method itself is undocumented.
Open Questions for Engineering
-
CLI
getcommand treats empty values as missing. Is this intentional? If a user hasMY_KEY=in their .env, shoulddotenv get MY_KEYreturn an empty string or exit with code 1? The Python API returns the empty string; the CLI exits with error. This needs a decision before the next release. -
What is the intended audience for FIFO/named pipe support? The
_is_file_or_fifo()function is used in both_get_streamandfind_dotenv. Is this a feature we want to document and support, or is it an implementation detail that could be simplified? If it's intentional, we should add documentation and possibly a test. -
The
dotenv_values()function hardcodesoverride=Truewhileload_dotenv()defaults tooverride=False. This means variable interpolation behaves differently depending on which API you use. Shoulddotenv_values()exposeoverrideas a parameter? Currently it doesn't (main.py:433-467). -
Is the
get_cli_string()function still needed? It's exported in__all__but has no tests and appears to be a legacy Fabric integration helper. Can we deprecate it, or does it have active users? -
The
set_keyreturn type is misleading — it always returnsTrueas the first tuple element. Should the return type be simplified to justTuple[str, str](key, value) since the boolean is alwaysTrue? This would be a breaking change for anyone pattern-matching on the return value. -
Windows
runcommand behavior differs from Unix. On Unix,os.execvpereplaces the current process entirely. On Windows,subprocess.Popenspawns a child process and waits. This means on Windows, thedotenv runcommand returns control to the shell after the command finishes, while on Unix it does not. Is this documented anywhere for users?