Skip to content

Commit

Permalink
feat(cli): add --outfile option for saving results to disk (#166)
Browse files Browse the repository at this point in the history
Fixes #120
  • Loading branch information
afuetterer committed Jul 6, 2024
1 parent 2396cf5 commit 1e689e8
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 5 deletions.
49 changes: 44 additions & 5 deletions src/re3data/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

import logging
import sys
import typing
from typing import Annotated, Optional
from pathlib import Path
from typing import Annotated, Any, Optional

from rich.console import Console

Expand Down Expand Up @@ -54,9 +54,27 @@ def _version_callback(show_version: bool) -> None:
raise typer.Exit(code=0)


def _write_to_file(response: Any, outfile: Path | None) -> None:
"""Write the given response to the specified file.
Args:
response: The response to be written.
outfile: The path to the output file. If None, no file will be written.
Returns:
None
"""
if outfile and outfile.exists():
overwrite = typer.confirm(f"{outfile} already exists. Do you want to overwrite it?")
if not overwrite:
return
if outfile:
outfile.write_text(str(response))


@app.callback(context_settings=CONTEXT_SETTINGS)
def callback(
version: typing.Annotated[
version: Annotated[
bool,
typer.Option(
"--version",
Expand All @@ -73,25 +91,46 @@ def callback(
@repositories_app.command("list")
def list_repositories(
query: Annotated[
Optional[str], # noqa: UP007
Optional[str], # noqa: UP007 - typer does not support "str | None"
typer.Option(
help="A query to filter the results. If provided, only repositories matching the query will be returned."
),
] = None,
return_type: ReturnType = ReturnType.DATACLASS,
count: bool = False,
outfile: Annotated[
Optional[Path], # noqa: UP007 - typer does not support "Path | None"
typer.Option(
file_okay=True,
dir_okay=False,
writable=True,
),
] = None,
) -> None:
"""List the metadata of all repositories in the re3data API."""
response = re3data.repositories.list(query, return_type, count)
_write_to_file(response, outfile)
console.print(response)


@repositories_app.command("get")
def get_repository(repository_id: str, return_type: ReturnType = ReturnType.DATACLASS) -> None:
def get_repository(
repository_id: str,
return_type: ReturnType = ReturnType.DATACLASS,
outfile: Annotated[
Optional[Path], # noqa: UP007 - typer does not support "Path | None"
typer.Option(
file_okay=True,
dir_okay=False,
writable=True,
),
] = None,
) -> None:
"""Get the metadata of a specific repository."""
try:
response = re3data.repositories.get(repository_id, return_type)
console.print(response)
_write_to_file(response, outfile)
except RepositoryNotFoundError as error:
print_error(str(error))
raise typer.Exit(code=1) from error
43 changes: 43 additions & 0 deletions tests/integration/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from re3data._cli import app

if TYPE_CHECKING:
from pathlib import Path

from respx import Route

runner = CliRunner()
Expand Down Expand Up @@ -128,6 +130,47 @@ def test_repository_list_query_returns_empty_list(mock_repository_list_query_emp
assert result.exit_code == 0


def test_repository_list_with_valid_outfile(
mock_repository_list_route: Route, repository_list_xml: str, tmp_path: Path
) -> None:
outfile = tmp_path / "list.xml"
result = runner.invoke(app, ["repository", "list", "--return-type", "xml", "--outfile", str(outfile)])
assert result.exit_code == 0
file_content = outfile.read_text()
assert repository_list_xml in file_content


def test_repository_list_with_dir_as_outfile(mock_repository_list_route: Route, tmp_path: Path) -> None:
outfile = tmp_path
result = runner.invoke(app, ["repository", "list", "--outfile", str(outfile)])
assert result.exit_code == 2
assert "Error" in result.output
assert "Invalid value for '--outfile':" in result.output


def test_repository_list_with_existing_outfile_overwrite_yes(
mock_repository_list_route: Route, repository_list_xml: str, tmp_path: Path
) -> None:
outfile = tmp_path / "list.xml"
outfile.write_text("some-content")
result = runner.invoke(app, ["repository", "list", "--return-type", "xml", "--outfile", str(outfile)], input="y")
assert result.exit_code == 0
file_content = outfile.read_text()
assert repository_list_xml in file_content


def test_repository_list_with_existing_outfile_overwrite_no(
mock_repository_list_route: Route, repository_list_xml: str, tmp_path: Path
) -> None:
outfile = tmp_path / "list.xml"
outfile.write_text("some-content")
result = runner.invoke(app, ["repository", "list", "--return-type", "xml", "--outfile", str(outfile)], input="N")
assert result.exit_code == 0
file_content = outfile.read_text()
assert "some-content" in file_content
assert repository_list_xml not in file_content


def test_repository_get_without_repository_id(mock_repository_list_route: Route) -> None:
result = runner.invoke(app, ["repository", "get"])
assert result.exit_code == 2
Expand Down

0 comments on commit 1e689e8

Please sign in to comment.