From 58605ee0c3afe9f11514b70615f5b788bb816555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Paduszyn=CC=81ski?= Date: Fri, 3 Nov 2023 07:43:52 +0100 Subject: [PATCH 1/6] =?UTF-8?q?=E2=9C=A8=20Add=20`use=5Fbackup`=20to=20the?= =?UTF-8?q?=20`fetch=5Fguide`=20function=20signature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gitmojis/core.py | 9 ++++++--- tests/test_core.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/gitmojis/core.py b/src/gitmojis/core.py index b3b2291..aa4c041 100644 --- a/src/gitmojis/core.py +++ b/src/gitmojis/core.py @@ -7,7 +7,7 @@ from .model import Gitmoji, Guide -def fetch_guide() -> Guide: +def fetch_guide(*, use_backup: bool = False) -> Guide: """Fetch the Gitmoji guide from the official Gitmoji API. This function sends a GET request to the Gitmoji API to retrieve the current state @@ -30,8 +30,11 @@ def fetch_guide() -> Guide: if (gitmojis_json := response.json().get(defaults.GITMOJI_API_KEY)) is None: raise ResponseJsonError except requests.RequestException: - with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: - gitmojis_json = json.load(f) + if use_backup: + with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: + gitmojis_json = json.load(f) + else: + raise guide = Guide(gitmojis=[Gitmoji(**gitmoji_json) for gitmoji_json in gitmojis_json]) diff --git a/tests/test_core.py b/tests/test_core.py index c4a21ce..b6f4dd2 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -31,13 +31,20 @@ def test_fetch_guide_raises_error_if_gitmoji_api_key_not_in_response_json(mocker fetch_guide() -def test_fetch_guide_fall_back_to_backup_data_if_request_error(mocker): +def test_fetch_guide_fall_back_to_backup_data_if_request_error_and_using_backup(mocker): mocker.patch("pathlib.Path.open", mocker.mock_open(read_data="[]")) mocker.patch("requests.get", side_effect=requests.RequestException) json_load = mocker.patch("json.load") - guide = fetch_guide() + guide = fetch_guide(use_backup=True) assert json_load.called assert guide == Guide(gitmojis=[]) + + +def test_fetch_guide_raises_error_if_request_error_and_not_using_backup(mocker): + mocker.patch("requests.get", side_effect=requests.RequestException) + + with pytest.raises(requests.RequestException): + fetch_guide(use_backup=False) From 5634831e72504f1364e0caf9769bf1f9eaf87a9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Paduszyn=CC=81ski?= Date: Fri, 3 Nov 2023 07:54:34 +0100 Subject: [PATCH 2/6] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20a=20custom=20exc?= =?UTF-8?q?eption=20if=20error=20is=20raised=20and=20using=20backup=20not?= =?UTF-8?q?=20allowed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gitmojis/core.py | 6 +++--- tests/test_core.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/gitmojis/core.py b/src/gitmojis/core.py index aa4c041..71dce09 100644 --- a/src/gitmojis/core.py +++ b/src/gitmojis/core.py @@ -3,7 +3,7 @@ import requests from . import defaults -from .exceptions import ResponseJsonError +from .exceptions import ApiError, ResponseJsonError from .model import Gitmoji, Guide @@ -29,12 +29,12 @@ def fetch_guide(*, use_backup: bool = False) -> Guide: if (gitmojis_json := response.json().get(defaults.GITMOJI_API_KEY)) is None: raise ResponseJsonError - except requests.RequestException: + except requests.RequestException as exc_info: if use_backup: with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: gitmojis_json = json.load(f) else: - raise + raise ApiError from exc_info guide = Guide(gitmojis=[Gitmoji(**gitmoji_json) for gitmoji_json in gitmojis_json]) diff --git a/tests/test_core.py b/tests/test_core.py index b6f4dd2..5c291aa 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,7 +3,7 @@ from gitmojis import defaults from gitmojis.core import fetch_guide -from gitmojis.exceptions import ResponseJsonError +from gitmojis.exceptions import ApiError, ResponseJsonError from gitmojis.model import Guide @@ -46,5 +46,6 @@ def test_fetch_guide_fall_back_to_backup_data_if_request_error_and_using_backup( def test_fetch_guide_raises_error_if_request_error_and_not_using_backup(mocker): mocker.patch("requests.get", side_effect=requests.RequestException) - with pytest.raises(requests.RequestException): + with pytest.raises(ApiError) as exc_info: fetch_guide(use_backup=False) + assert isinstance(exc_info.value.__cause__, requests.RequestException) From 54fa0c8c8e7b43bd2d529ab21b5a6c8be42088c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Paduszyn=CC=81ski?= Date: Fri, 3 Nov 2023 07:55:47 +0100 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=92=AC=20Rename=20`ApiError`=20to=20`?= =?UTF-8?q?ApiRequestError`=20and=20add=20a=20default=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gitmojis/core.py | 4 ++-- src/gitmojis/exceptions.py | 6 +++--- tests/test_core.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gitmojis/core.py b/src/gitmojis/core.py index 71dce09..7a20487 100644 --- a/src/gitmojis/core.py +++ b/src/gitmojis/core.py @@ -3,7 +3,7 @@ import requests from . import defaults -from .exceptions import ApiError, ResponseJsonError +from .exceptions import ApiRequestError, ResponseJsonError from .model import Gitmoji, Guide @@ -34,7 +34,7 @@ def fetch_guide(*, use_backup: bool = False) -> Guide: with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: gitmojis_json = json.load(f) else: - raise ApiError from exc_info + raise ApiRequestError from exc_info guide = Guide(gitmojis=[Gitmoji(**gitmoji_json) for gitmoji_json in gitmojis_json]) diff --git a/src/gitmojis/exceptions.py b/src/gitmojis/exceptions.py index 75f4ad3..22971dc 100644 --- a/src/gitmojis/exceptions.py +++ b/src/gitmojis/exceptions.py @@ -5,9 +5,9 @@ def __init__(self, message: str | None = None) -> None: super().__init__(message or getattr(self.__class__, "message", "")) -class ApiError(GitmojisException): - pass +class ApiRequestError(GitmojisException): + message = "there was an issue when requesting the data from the API" -class ResponseJsonError(ApiError): +class ResponseJsonError(ApiRequestError): message = "unsupported format of the JSON data returned by the API" diff --git a/tests/test_core.py b/tests/test_core.py index 5c291aa..bda9a4e 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -3,7 +3,7 @@ from gitmojis import defaults from gitmojis.core import fetch_guide -from gitmojis.exceptions import ApiError, ResponseJsonError +from gitmojis.exceptions import ApiRequestError, ResponseJsonError from gitmojis.model import Guide @@ -46,6 +46,6 @@ def test_fetch_guide_fall_back_to_backup_data_if_request_error_and_using_backup( def test_fetch_guide_raises_error_if_request_error_and_not_using_backup(mocker): mocker.patch("requests.get", side_effect=requests.RequestException) - with pytest.raises(ApiError) as exc_info: + with pytest.raises(ApiRequestError) as exc_info: fetch_guide(use_backup=False) assert isinstance(exc_info.value.__cause__, requests.RequestException) From f84cdb7988645ab7f896f57b1b621e03b2b0eb5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Paduszyn=CC=81ski?= Date: Fri, 3 Nov 2023 08:14:15 +0100 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=93=9D=20Update=20docstring=20of=20`f?= =?UTF-8?q?etch=5Fguide`=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gitmojis/core.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/gitmojis/core.py b/src/gitmojis/core.py index 7a20487..4d265ba 100644 --- a/src/gitmojis/core.py +++ b/src/gitmojis/core.py @@ -13,14 +13,20 @@ def fetch_guide(*, use_backup: bool = False) -> Guide: This function sends a GET request to the Gitmoji API to retrieve the current state of the Gitmoji guide. If the request is successful and contains the expected JSON data, a `Guide` object is returned. If the expected JSON data is not present, a - `ResponseJsonError` is raised. In case of an HTTP error during the request (e.g., - connection error, timeout), the function falls back to loading a local copy of the - Gitmoji guide. + `ResponseJsonError` is raised. In case of an HTTP error during the request + (e.g., connection error, timeout), and if `use_backup` is set to `True`, the function + falls back to loading a local copy of the Gitmoji guide. Otherwise, it raises an + `ApiRequestError`. + + Args: + use_backup: A flag indicating whether to use a local backup in case of a request + failure. Defaults to `False`. Returns: A `Guide` object representing the current state of the Gitmoji API. Raises: + ApiRequestError: If the API request fails and `use_backup` is `False`. ResponseJsonError: If the API response doesn't contain the expected JSON data or if there is an error loading the local backup of the Gitmoji guide. """ From 6cb3c5c42d2a30cf773c42f25722b91dd2c770ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Paduszyn=CC=81ski?= Date: Fri, 3 Nov 2023 11:33:55 +0100 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8=20Add=20`--use-backup`=20to=20the?= =?UTF-8?q?=20`sync`=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gitmojis/cli/__init__.py | 9 +++++++-- tests/test_cli.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/gitmojis/cli/__init__.py b/src/gitmojis/cli/__init__.py index 792bf95..037d678 100644 --- a/src/gitmojis/cli/__init__.py +++ b/src/gitmojis/cli/__init__.py @@ -18,15 +18,20 @@ def get_commands() -> list[click.Command]: name="gitmojis", commands=get_commands(), ) +@click.option( + "--use-backup", + is_flag=True, + help="Use the backup to fetch data if the API request fails.", +) @click.version_option( package_name="python-gitmojis", prog_name="gitmojis", ) @click.pass_context -def gitmojis_cli(context: click.Context) -> None: +def gitmojis_cli(context: click.Context, use_backup: bool) -> None: """Command-line interface for managing the official Gitmoji guide.""" # Initialize the context object context.ensure_object(dict) # Pass the current state of the Gitmoji guide to the group context - context.obj["guide"] = fetch_guide() + context.obj["guide"] = fetch_guide(use_backup=use_backup) diff --git a/tests/test_cli.py b/tests/test_cli.py index f91d47c..c00fc99 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -48,6 +48,21 @@ def command(context): assert result.exit_code == 0 +def test_gitmojis_cli_passes_use_backup_option_to_fetch_guide(mocker, cli_runner): + fetch_guide = mocker.patch("gitmojis.cli.fetch_guide", return_value=Guide()) + + @click.command() + @click.pass_context + def command(context): + pass + + gitmojis_cli.add_command(command) + + cli_runner.invoke(gitmojis_cli, ["--use-backup", "command"]) + + assert fetch_guide.call_args.kwargs == {"use_backup": True} + + def test_sync_command_dumps_api_data_to_backup_file(tmp_path, mocker, cli_runner): # Mock the backup file as empty file gitmoji_api_path = tmp_path / "gitmojis.json" From 2afba9bbcf6491949ff65befab691e10356654c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Paduszy=C5=84ski?= <92403542+paduszyk@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:47:49 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=A7=90=20Apply=20suggestions=20from?= =?UTF-8?q?=20code=20self-review=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gitmojis/exceptions.py | 2 +- tests/test_core.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gitmojis/exceptions.py b/src/gitmojis/exceptions.py index 22971dc..2954406 100644 --- a/src/gitmojis/exceptions.py +++ b/src/gitmojis/exceptions.py @@ -6,7 +6,7 @@ def __init__(self, message: str | None = None) -> None: class ApiRequestError(GitmojisException): - message = "there was an issue when requesting the data from the API" + message = "request to get the Gitmoji data from the API failed" class ResponseJsonError(ApiRequestError): diff --git a/tests/test_core.py b/tests/test_core.py index bda9a4e..47a6bd1 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -31,7 +31,7 @@ def test_fetch_guide_raises_error_if_gitmoji_api_key_not_in_response_json(mocker fetch_guide() -def test_fetch_guide_fall_back_to_backup_data_if_request_error_and_using_backup(mocker): +def test_fetch_guide_falls_back_to_backup_data_if_request_error_and_using_backup(mocker): # fmt: skip mocker.patch("pathlib.Path.open", mocker.mock_open(read_data="[]")) mocker.patch("requests.get", side_effect=requests.RequestException)