diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91e3af9..f93e67d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,6 +20,8 @@ repos: hooks: - id: mypy exclude: '^(?:(?!src).)*$' + additional_dependencies: + - types-requests - repo: https://github.com/astral-sh/ruff-pre-commit rev: 'v0.1.3' hooks: diff --git a/pyproject.toml b/pyproject.toml index 7dd16d2..37c885f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,9 @@ build-backend = "setuptools.build_meta" name = "python-gitmojis" version = "0.0.0" requires-python = ">= 3.10" -dependencies = [] +dependencies = [ + "requests", +] [project.optional-dependencies] dev = [ @@ -20,6 +22,7 @@ lint = [ "black", "mypy", "ruff", + "types-requests", ] test = [ "pytest", diff --git a/src/gitmojis/__init__.py b/src/gitmojis/__init__.py index faf89ac..55e7de1 100644 --- a/src/gitmojis/__init__.py +++ b/src/gitmojis/__init__.py @@ -1,6 +1,8 @@ +from .core import fetch_guide from .model import Gitmoji, Guide __all__ = [ "Gitmoji", "Guide", + "fetch_guide", ] diff --git a/src/gitmojis/core.py b/src/gitmojis/core.py new file mode 100644 index 0000000..5bec8b6 --- /dev/null +++ b/src/gitmojis/core.py @@ -0,0 +1,27 @@ +import requests + +from . import defaults +from .exceptions import ResponseJsonError +from .model import Gitmoji, Guide + + +def fetch_guide() -> 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 + of the Gitmoji guide. + + Returns: + A `Guide` object representing the current state of the Gitmoji API. + + Raises: + ResponseJsonError: If the API response doesn't contain the expected JSON data. + """ + response = requests.get(defaults.GITMOJI_API_URL) + + if (gitmojis_json := response.json().get(defaults.GITMOJI_API_KEY)) is None: + raise ResponseJsonError + + guide = Guide(gitmojis=[Gitmoji(**gitmoji_json) for gitmoji_json in gitmojis_json]) + + return guide diff --git a/src/gitmojis/defaults.py b/src/gitmojis/defaults.py new file mode 100644 index 0000000..e735cab --- /dev/null +++ b/src/gitmojis/defaults.py @@ -0,0 +1,5 @@ +from typing import Final + +GITMOJI_API_URL: Final = "https://gitmoji.dev/api/gitmojis" + +GITMOJI_API_KEY: Final = "gitmojis" diff --git a/src/gitmojis/exceptions.py b/src/gitmojis/exceptions.py new file mode 100644 index 0000000..75f4ad3 --- /dev/null +++ b/src/gitmojis/exceptions.py @@ -0,0 +1,13 @@ +class GitmojisException(Exception): + message: str + + def __init__(self, message: str | None = None) -> None: + super().__init__(message or getattr(self.__class__, "message", "")) + + +class ApiError(GitmojisException): + pass + + +class ResponseJsonError(ApiError): + message = "unsupported format of the JSON data returned by the API" diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..cf28e9b --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,31 @@ +import pytest +import requests + +from gitmojis import defaults +from gitmojis.core import fetch_guide +from gitmojis.exceptions import ResponseJsonError +from gitmojis.model import Guide + + +@pytest.fixture() +def response(mocker): + return mocker.Mock(spec_set=requests.Response) + + +def test_fetch_guide_creates_guide_from_api_response_json(mocker, response): + response.json.return_value = {defaults.GITMOJI_API_KEY: []} + + mocker.patch("requests.get", return_value=response) + + guide = fetch_guide() + + assert isinstance(guide, Guide) + + +def test_fetch_guide_raises_error_if_gitmoji_api_key_not_in_response_json(mocker, response): # fmt: skip + response.json.return_value = {} # `GITMOJI_API_KEY` not in the response's JSON + + mocker.patch("requests.get", return_value=response) + + with pytest.raises(ResponseJsonError): + fetch_guide()