📋 Product Owner view

Feature inventory, spec alignment, behavioural gaps  ·  Product Owner, Product Manager

Generated 3 days ago
⚡ 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

  1. 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_stream and find_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.

  2. The dotenv_values() function always uses override=True internally (main.py:460-467), even though the load_dotenv() function defaults to override=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.

  3. The CLI get command has a subtle bug with empty values. At cli.py:144-148, the code checks if 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() at main.py:112-122) correctly returns None only for missing keys, but the CLI treats empty strings as missing. This is a behavioral inconsistency between the API and CLI.

  4. The set_key function always returns True on success (main.py:245), but the return type annotation says Tuple[Optional[bool], str, str]. The first element is always True — it never returns False. The "failure" case is actually an exception being raised. This means the CLI's set_value command (cli.py:127-131) will only hit the sys.exit(1) path if an exception occurs, not from a return value check.

  5. The rewrite context 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.

  6. The enumerate_env() CLI helper silently returns None when 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

  1. 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.

  2. 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.

  3. The load_ipython_extension() function is defined twice — once in __init__.py:6-9 (which delegates to ipython.py) and once in ipython.py:48-50. The __init__.py version re-exports the ipython.py version, which is fine, but the duplication could confuse maintainers.

  4. The DotEnv class's get() method returns None for missing keys (main.py:122), but the docstring says nothing about this behavior. The get_key() wrapper function (main.py:125-135) documents this correctly, but the class method itself is undocumented.


Open Questions for Engineering

  1. CLI get command treats empty values as missing. Is this intentional? If a user has MY_KEY= in their .env, should dotenv get MY_KEY return 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.

  2. What is the intended audience for FIFO/named pipe support? The _is_file_or_fifo() function is used in both _get_stream and find_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.

  3. The dotenv_values() function hardcodes override=True while load_dotenv() defaults to override=False. This means variable interpolation behaves differently depending on which API you use. Should dotenv_values() expose override as a parameter? Currently it doesn't (main.py:433-467).

  4. 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?

  5. The set_key return type is misleading — it always returns True as the first tuple element. Should the return type be simplified to just Tuple[str, str] (key, value) since the boolean is always True? This would be a breaking change for anyone pattern-matching on the return value.

  6. Windows run command behavior differs from Unix. On Unix, os.execvpe replaces the current process entirely. On Windows, subprocess.Popen spawns a child process and waits. This means on Windows, the dotenv run command returns control to the shell after the command finishes, while on Unix it does not. Is this documented anywhere for users?