diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f93e67d..e895882 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,6 +10,12 @@ repos: files: (tests) args: - --pytest-test-first + - id: pretty-format-json + args: + - --autofix + - --indent=2 + - --no-ensure-ascii + - --no-sort-keys - id: trailing-whitespace - repo: https://github.com/psf/black rev: '23.10.1' diff --git a/pyproject.toml b/pyproject.toml index 37c885f..c97273c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,9 @@ test = [ include = ["gitmojis*"] where = ["src"] +[tool.setuptools.package-data] +"gitmojis" = ["assets*"] + # Black # https://black.readthedocs.io/en/stable/usage_and_configuration/ diff --git a/src/gitmojis/assets/gitmojis.json b/src/gitmojis/assets/gitmojis.json new file mode 100644 index 0000000..605faad --- /dev/null +++ b/src/gitmojis/assets/gitmojis.json @@ -0,0 +1,586 @@ +[ + { + "emoji": "๐ŸŽจ", + "entity": "🎨", + "code": ":art:", + "description": "Improve structure / format of the code.", + "name": "art", + "semver": null + }, + { + "emoji": "โšก๏ธ", + "entity": "⚡", + "code": ":zap:", + "description": "Improve performance.", + "name": "zap", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”ฅ", + "entity": "🔥", + "code": ":fire:", + "description": "Remove code or files.", + "name": "fire", + "semver": null + }, + { + "emoji": "๐Ÿ›", + "entity": "🐛", + "code": ":bug:", + "description": "Fix a bug.", + "name": "bug", + "semver": "patch" + }, + { + "emoji": "๐Ÿš‘๏ธ", + "entity": "🚑", + "code": ":ambulance:", + "description": "Critical hotfix.", + "name": "ambulance", + "semver": "patch" + }, + { + "emoji": "โœจ", + "entity": "✨", + "code": ":sparkles:", + "description": "Introduce new features.", + "name": "sparkles", + "semver": "minor" + }, + { + "emoji": "๐Ÿ“", + "entity": "📝", + "code": ":memo:", + "description": "Add or update documentation.", + "name": "memo", + "semver": null + }, + { + "emoji": "๐Ÿš€", + "entity": "🚀", + "code": ":rocket:", + "description": "Deploy stuff.", + "name": "rocket", + "semver": null + }, + { + "emoji": "๐Ÿ’„", + "entity": "&#ff99cc;", + "code": ":lipstick:", + "description": "Add or update the UI and style files.", + "name": "lipstick", + "semver": "patch" + }, + { + "emoji": "๐ŸŽ‰", + "entity": "🎉", + "code": ":tada:", + "description": "Begin a project.", + "name": "tada", + "semver": null + }, + { + "emoji": "โœ…", + "entity": "✅", + "code": ":white_check_mark:", + "description": "Add, update, or pass tests.", + "name": "white-check-mark", + "semver": null + }, + { + "emoji": "๐Ÿ”’๏ธ", + "entity": "🔒", + "code": ":lock:", + "description": "Fix security or privacy issues.", + "name": "lock", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”", + "entity": "🔐", + "code": ":closed_lock_with_key:", + "description": "Add or update secrets.", + "name": "closed-lock-with-key", + "semver": null + }, + { + "emoji": "๐Ÿ”–", + "entity": "🔖", + "code": ":bookmark:", + "description": "Release / Version tags.", + "name": "bookmark", + "semver": null + }, + { + "emoji": "๐Ÿšจ", + "entity": "🚨", + "code": ":rotating_light:", + "description": "Fix compiler / linter warnings.", + "name": "rotating-light", + "semver": null + }, + { + "emoji": "๐Ÿšง", + "entity": "🚧", + "code": ":construction:", + "description": "Work in progress.", + "name": "construction", + "semver": null + }, + { + "emoji": "๐Ÿ’š", + "entity": "💚", + "code": ":green_heart:", + "description": "Fix CI Build.", + "name": "green-heart", + "semver": null + }, + { + "emoji": "โฌ‡๏ธ", + "entity": "โฌ‡๏ธ", + "code": ":arrow_down:", + "description": "Downgrade dependencies.", + "name": "arrow-down", + "semver": "patch" + }, + { + "emoji": "โฌ†๏ธ", + "entity": "โฌ†๏ธ", + "code": ":arrow_up:", + "description": "Upgrade dependencies.", + "name": "arrow-up", + "semver": "patch" + }, + { + "emoji": "๐Ÿ“Œ", + "entity": "📌", + "code": ":pushpin:", + "description": "Pin dependencies to specific versions.", + "name": "pushpin", + "semver": "patch" + }, + { + "emoji": "๐Ÿ‘ท", + "entity": "👷", + "code": ":construction_worker:", + "description": "Add or update CI build system.", + "name": "construction-worker", + "semver": null + }, + { + "emoji": "๐Ÿ“ˆ", + "entity": "📈", + "code": ":chart_with_upwards_trend:", + "description": "Add or update analytics or track code.", + "name": "chart-with-upwards-trend", + "semver": "patch" + }, + { + "emoji": "โ™ป๏ธ", + "entity": "♻", + "code": ":recycle:", + "description": "Refactor code.", + "name": "recycle", + "semver": null + }, + { + "emoji": "โž•", + "entity": "➕", + "code": ":heavy_plus_sign:", + "description": "Add a dependency.", + "name": "heavy-plus-sign", + "semver": "patch" + }, + { + "emoji": "โž–", + "entity": "➖", + "code": ":heavy_minus_sign:", + "description": "Remove a dependency.", + "name": "heavy-minus-sign", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”ง", + "entity": "🔧", + "code": ":wrench:", + "description": "Add or update configuration files.", + "name": "wrench", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”จ", + "entity": "🔨", + "code": ":hammer:", + "description": "Add or update development scripts.", + "name": "hammer", + "semver": null + }, + { + "emoji": "๐ŸŒ", + "entity": "🌐", + "code": ":globe_with_meridians:", + "description": "Internationalization and localization.", + "name": "globe-with-meridians", + "semver": "patch" + }, + { + "emoji": "โœ๏ธ", + "entity": "", + "code": ":pencil2:", + "description": "Fix typos.", + "name": "pencil2", + "semver": "patch" + }, + { + "emoji": "๐Ÿ’ฉ", + "entity": "", + "code": ":poop:", + "description": "Write bad code that needs to be improved.", + "name": "poop", + "semver": null + }, + { + "emoji": "โช๏ธ", + "entity": "⏪", + "code": ":rewind:", + "description": "Revert changes.", + "name": "rewind", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”€", + "entity": "🔀", + "code": ":twisted_rightwards_arrows:", + "description": "Merge branches.", + "name": "twisted-rightwards-arrows", + "semver": null + }, + { + "emoji": "๐Ÿ“ฆ๏ธ", + "entity": "F4E6;", + "code": ":package:", + "description": "Add or update compiled files or packages.", + "name": "package", + "semver": "patch" + }, + { + "emoji": "๐Ÿ‘ฝ๏ธ", + "entity": "F47D;", + "code": ":alien:", + "description": "Update code due to external API changes.", + "name": "alien", + "semver": "patch" + }, + { + "emoji": "๐Ÿšš", + "entity": "F69A;", + "code": ":truck:", + "description": "Move or rename resources (e.g.: files, paths, routes).", + "name": "truck", + "semver": null + }, + { + "emoji": "๐Ÿ“„", + "entity": "F4C4;", + "code": ":page_facing_up:", + "description": "Add or update license.", + "name": "page-facing-up", + "semver": null + }, + { + "emoji": "๐Ÿ’ฅ", + "entity": "💥", + "code": ":boom:", + "description": "Introduce breaking changes.", + "name": "boom", + "semver": "major" + }, + { + "emoji": "๐Ÿฑ", + "entity": "F371", + "code": ":bento:", + "description": "Add or update assets.", + "name": "bento", + "semver": "patch" + }, + { + "emoji": "โ™ฟ๏ธ", + "entity": "♿", + "code": ":wheelchair:", + "description": "Improve accessibility.", + "name": "wheelchair", + "semver": "patch" + }, + { + "emoji": "๐Ÿ’ก", + "entity": "💡", + "code": ":bulb:", + "description": "Add or update comments in source code.", + "name": "bulb", + "semver": null + }, + { + "emoji": "๐Ÿป", + "entity": "🍻", + "code": ":beers:", + "description": "Write code drunkenly.", + "name": "beers", + "semver": null + }, + { + "emoji": "๐Ÿ’ฌ", + "entity": "💬", + "code": ":speech_balloon:", + "description": "Add or update text and literals.", + "name": "speech-balloon", + "semver": "patch" + }, + { + "emoji": "๐Ÿ—ƒ๏ธ", + "entity": "🗃", + "code": ":card_file_box:", + "description": "Perform database related changes.", + "name": "card-file-box", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”Š", + "entity": "🔊", + "code": ":loud_sound:", + "description": "Add or update logs.", + "name": "loud-sound", + "semver": null + }, + { + "emoji": "๐Ÿ”‡", + "entity": "🔇", + "code": ":mute:", + "description": "Remove logs.", + "name": "mute", + "semver": null + }, + { + "emoji": "๐Ÿ‘ฅ", + "entity": "👥", + "code": ":busts_in_silhouette:", + "description": "Add or update contributor(s).", + "name": "busts-in-silhouette", + "semver": null + }, + { + "emoji": "๐Ÿšธ", + "entity": "🚸", + "code": ":children_crossing:", + "description": "Improve user experience / usability.", + "name": "children-crossing", + "semver": "patch" + }, + { + "emoji": "๐Ÿ—๏ธ", + "entity": "f3d7;", + "code": ":building_construction:", + "description": "Make architectural changes.", + "name": "building-construction", + "semver": null + }, + { + "emoji": "๐Ÿ“ฑ", + "entity": "📱", + "code": ":iphone:", + "description": "Work on responsive design.", + "name": "iphone", + "semver": "patch" + }, + { + "emoji": "๐Ÿคก", + "entity": "🤡", + "code": ":clown_face:", + "description": "Mock things.", + "name": "clown-face", + "semver": null + }, + { + "emoji": "๐Ÿฅš", + "entity": "🥚", + "code": ":egg:", + "description": "Add or update an easter egg.", + "name": "egg", + "semver": "patch" + }, + { + "emoji": "๐Ÿ™ˆ", + "entity": "bdfe7;", + "code": ":see_no_evil:", + "description": "Add or update a .gitignore file.", + "name": "see-no-evil", + "semver": null + }, + { + "emoji": "๐Ÿ“ธ", + "entity": "📸", + "code": ":camera_flash:", + "description": "Add or update snapshots.", + "name": "camera-flash", + "semver": null + }, + { + "emoji": "โš—๏ธ", + "entity": "⚗", + "code": ":alembic:", + "description": "Perform experiments.", + "name": "alembic", + "semver": "patch" + }, + { + "emoji": "๐Ÿ”๏ธ", + "entity": "🔍", + "code": ":mag:", + "description": "Improve SEO.", + "name": "mag", + "semver": "patch" + }, + { + "emoji": "๐Ÿท๏ธ", + "entity": "🏷", + "code": ":label:", + "description": "Add or update types.", + "name": "label", + "semver": "patch" + }, + { + "emoji": "๐ŸŒฑ", + "entity": "🌱", + "code": ":seedling:", + "description": "Add or update seed files.", + "name": "seedling", + "semver": null + }, + { + "emoji": "๐Ÿšฉ", + "entity": "🚩", + "code": ":triangular_flag_on_post:", + "description": "Add, update, or remove feature flags.", + "name": "triangular-flag-on-post", + "semver": "patch" + }, + { + "emoji": "๐Ÿฅ…", + "entity": "🥅", + "code": ":goal_net:", + "description": "Catch errors.", + "name": "goal-net", + "semver": "patch" + }, + { + "emoji": "๐Ÿ’ซ", + "entity": "💫", + "code": ":dizzy:", + "description": "Add or update animations and transitions.", + "name": "dizzy", + "semver": "patch" + }, + { + "emoji": "๐Ÿ—‘๏ธ", + "entity": "🗑", + "code": ":wastebasket:", + "description": "Deprecate code that needs to be cleaned up.", + "name": "wastebasket", + "semver": "patch" + }, + { + "emoji": "๐Ÿ›‚", + "entity": "🛂", + "code": ":passport_control:", + "description": "Work on code related to authorization, roles and permissions.", + "name": "passport-control", + "semver": "patch" + }, + { + "emoji": "๐Ÿฉน", + "entity": "🩹", + "code": ":adhesive_bandage:", + "description": "Simple fix for a non-critical issue.", + "name": "adhesive-bandage", + "semver": "patch" + }, + { + "emoji": "๐Ÿง", + "entity": "🧐", + "code": ":monocle_face:", + "description": "Data exploration/inspection.", + "name": "monocle-face", + "semver": null + }, + { + "emoji": "โšฐ๏ธ", + "entity": "⚰", + "code": ":coffin:", + "description": "Remove dead code.", + "name": "coffin", + "semver": null + }, + { + "emoji": "๐Ÿงช", + "entity": "🧪", + "code": ":test_tube:", + "description": "Add a failing test.", + "name": "test-tube", + "semver": null + }, + { + "emoji": "๐Ÿ‘”", + "entity": "👔", + "code": ":necktie:", + "description": "Add or update business logic.", + "name": "necktie", + "semver": "patch" + }, + { + "emoji": "๐Ÿฉบ", + "entity": "🩺", + "code": ":stethoscope:", + "description": "Add or update healthcheck.", + "name": "stethoscope", + "semver": null + }, + { + "emoji": "๐Ÿงฑ", + "entity": "🧱", + "code": ":bricks:", + "description": "Infrastructure related changes.", + "name": "bricks", + "semver": null + }, + { + "emoji": "๐Ÿง‘โ€๐Ÿ’ป", + "entity": "🧑‍💻", + "code": ":technologist:", + "description": "Improve developer experience.", + "name": "technologist", + "semver": null + }, + { + "emoji": "๐Ÿ’ธ", + "entity": "💸", + "code": ":money_with_wings:", + "description": "Add sponsorships or money related infrastructure.", + "name": "money-with-wings", + "semver": null + }, + { + "emoji": "๐Ÿงต", + "entity": "🧵", + "code": ":thread:", + "description": "Add or update code related to multithreading or concurrency.", + "name": "thread", + "semver": null + }, + { + "emoji": "๐Ÿฆบ", + "entity": "🦺", + "code": ":safety_vest:", + "description": "Add or update code related to validation.", + "name": "safety-vest", + "semver": null + } +] diff --git a/src/gitmojis/core.py b/src/gitmojis/core.py index 5bec8b6..b3b2291 100644 --- a/src/gitmojis/core.py +++ b/src/gitmojis/core.py @@ -1,3 +1,5 @@ +import json + import requests from . import defaults @@ -9,18 +11,27 @@ 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. + 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. 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. + 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. """ - response = requests.get(defaults.GITMOJI_API_URL) - - if (gitmojis_json := response.json().get(defaults.GITMOJI_API_KEY)) is None: - raise ResponseJsonError + try: + (response := requests.get(defaults.GITMOJI_API_URL)).raise_for_status() + + 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) guide = Guide(gitmojis=[Gitmoji(**gitmoji_json) for gitmoji_json in gitmojis_json]) diff --git a/src/gitmojis/defaults.py b/src/gitmojis/defaults.py index e735cab..f4ef149 100644 --- a/src/gitmojis/defaults.py +++ b/src/gitmojis/defaults.py @@ -1,5 +1,10 @@ +from pathlib import Path from typing import Final +import gitmojis + GITMOJI_API_URL: Final = "https://gitmoji.dev/api/gitmojis" GITMOJI_API_KEY: Final = "gitmojis" + +GITMOJI_API_PATH: Final = Path(gitmojis.__file__).parent / "assets" / "gitmojis.json" diff --git a/tests/test_core.py b/tests/test_core.py index cf28e9b..c4a21ce 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -29,3 +29,15 @@ def test_fetch_guide_raises_error_if_gitmoji_api_key_not_in_response_json(mocker with pytest.raises(ResponseJsonError): fetch_guide() + + +def test_fetch_guide_fall_back_to_backup_data_if_request_error(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() + + assert json_load.called + assert guide == Guide(gitmojis=[]) diff --git a/tests/test_defaults.py b/tests/test_defaults.py new file mode 100644 index 0000000..321a68e --- /dev/null +++ b/tests/test_defaults.py @@ -0,0 +1,32 @@ +import json + +import pytest + +from gitmojis import defaults +from gitmojis.model import Gitmoji + + +def test_gitmoji_api_path_file_is_json_decodable(): + try: + with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: + json.load(f) + except json.JSONDecodeError: + pytest.fail() + + +def test_gitmoji_api_path_file_data_is_list_of_dicts(): + with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: + gitmojis_json = json.load(f) + + assert isinstance(gitmojis_json, list) + assert all(isinstance(gitmoji_json, dict) for gitmoji_json in gitmojis_json) + + +def test_gitmoji_api_path_file_data_can_create_gitmoji_objects(): + with defaults.GITMOJI_API_PATH.open(encoding="UTF-8") as f: + gitmojis_json = json.load(f) + + for gitmoji_json in gitmojis_json: + gitmoji = Gitmoji(**gitmoji_json) + + assert isinstance(gitmoji, Gitmoji)