Skip to content

Commit

Permalink
Improve error message for get and list commands
Browse files Browse the repository at this point in the history
The error message would previously be confusing. For example, `dotenv -f
. list` would print:

    Error: Invalid value: Path "." does not exist.

Instead, we now print:

    Error opening env file: [Errno 21] Is a directory: '.'

I used this opportunity to slightly refactor the I/O code (e.g. fewer
system calls and possible race conditions) for those two subcommands
(`get` and `list`).
  • Loading branch information
bbc2 committed Nov 25, 2022
1 parent 025f762 commit 7dc2492
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 20 deletions.
48 changes: 31 additions & 17 deletions src/dotenv/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import os
import shlex
import sys
from contextlib import contextmanager
from subprocess import Popen
from typing import Any, Dict, List
from typing import Any, Dict, IO, Iterator, List

try:
import click
Expand All @@ -12,7 +13,7 @@
'Run pip install "python-dotenv[cli]" to fix this.')
sys.exit(1)

from .main import dotenv_values, get_key, set_key, unset_key
from .main import dotenv_values, set_key, unset_key
from .version import __version__


Expand All @@ -33,6 +34,22 @@ def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None:
ctx.obj = {'QUOTE': quote, 'EXPORT': export, 'FILE': file}


@contextmanager
def stream_file(path: os.PathLike) -> Iterator[IO[str]]:
"""
Open a file and yield the corresponding (decoded) stream.
Exits with error code 2 if the file cannot be opened.
"""

try:
with open(path) as stream:
yield stream
except OSError as exc:
print(f"Error opening env file: {exc}", file=sys.stderr)
exit(2)


@cli.command()
@click.pass_context
@click.option('--format', default='simple',
Expand All @@ -42,18 +59,16 @@ def cli(ctx: click.Context, file: Any, quote: Any, export: Any) -> None:
def list(ctx: click.Context, format: bool) -> None:
"""Display all the stored key/value."""
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
f'Path "{file}" does not exist.',
ctx=ctx
)
dotenv_as_dict = dotenv_values(file)

with stream_file(file) as stream:
values = dotenv_values(stream=stream)

if format == 'json':
click.echo(json.dumps(dotenv_as_dict, indent=2, sort_keys=True))
click.echo(json.dumps(values, indent=2, sort_keys=True))
else:
prefix = 'export ' if format == 'export' else ''
for k in sorted(dotenv_as_dict):
v = dotenv_as_dict[k]
for k in sorted(values):
v = values[k]
if v is not None:
if format in ('export', 'shell'):
v = shlex.quote(v)
Expand Down Expand Up @@ -82,12 +97,11 @@ def set(ctx: click.Context, key: Any, value: Any) -> None:
def get(ctx: click.Context, key: Any) -> None:
"""Retrieve the value for the given key."""
file = ctx.obj['FILE']
if not os.path.isfile(file):
raise click.BadParameter(
f'Path "{file}" does not exist.',
ctx=ctx
)
stored_value = get_key(file, key)

with stream_file(file) as stream:
values = dotenv_values(stream=stream)

stored_value = values.get(key)
if stored_value:
click.echo(stored_value)
else:
Expand Down
20 changes: 17 additions & 3 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ def test_list_non_existent_file(cli):
result = cli.invoke(dotenv_cli, ['--file', 'nx_file', 'list'])

assert result.exit_code == 2, result.output
assert "does not exist" in result.output
assert "Error opening env file" in result.output


def test_list_not_a_file(cli):
result = cli.invoke(dotenv_cli, ['--file', '.', 'list'])

assert result.exit_code == 2, result.output
assert "Error opening env file" in result.output


def test_list_no_file(cli):
Expand All @@ -64,11 +71,18 @@ def test_get_non_existent_value(cli, dotenv_file):
assert (result.exit_code, result.output) == (1, "")


def test_get_no_file(cli):
def test_get_non_existent_file(cli):
result = cli.invoke(dotenv_cli, ['--file', 'nx_file', 'get', 'a'])

assert result.exit_code == 2
assert "does not exist" in result.output
assert "Error opening env file" in result.output


def test_get_not_a_file(cli):
result = cli.invoke(dotenv_cli, ['--file', '.', 'get', 'a'])

assert result.exit_code == 2
assert "Error opening env file" in result.output


def test_unset_existing_value(cli, dotenv_file):
Expand Down

0 comments on commit 7dc2492

Please sign in to comment.