diff options
| -rw-r--r-- | poetry.lock | 139 | ||||
| -rw-r--r-- | pyproject.toml | 10 | ||||
| -rw-r--r-- | requirements.txt | 17 | ||||
| -rw-r--r-- | szuruboorupy/__init__.py | 10 | ||||
| -rw-r--r-- | szuruboorupy/api.py (renamed from src/lib/szurubooru.py) | 347 | ||||
| -rw-r--r-- | szuruboorupy/dataclasses.py (renamed from src/lib/dataclasses.py) | 5 | ||||
| -rw-r--r-- | szuruboorupy/exceptions.py | 341 |
7 files changed, 426 insertions, 443 deletions
diff --git a/poetry.lock b/poetry.lock index 6921e85..3b73725 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,30 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.0.dev0 and should not be changed by hand. - -[[package]] -name = "about-time" -version = "4.2.1" -description = "Easily measure timing and throughput of code blocks, with beautiful human friendly representations." -optional = false -python-versions = ">=3.7, <4" -files = [ - {file = "about-time-4.2.1.tar.gz", hash = "sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece"}, - {file = "about_time-4.2.1-py3-none-any.whl", hash = "sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341"}, -] - -[[package]] -name = "alive-progress" -version = "3.1.4" -description = "A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!" -optional = false -python-versions = ">=3.7, <4" -files = [ - {file = "alive-progress-3.1.4.tar.gz", hash = "sha256:74a95d8d0d42bc99d3a3725dbd06ebb852245f1b64e301a7c375b92b22663f7b"}, - {file = "alive_progress-3.1.4-py3-none-any.whl", hash = "sha256:c80ad87ce9c1054b01135a87fae69ecebbfc2107497ae87cbe6aec7e534903db"}, -] - -[package.dependencies] -about-time = "4.2.1" -grapheme = "0.6.0" +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "annotated-types" @@ -166,30 +140,6 @@ files = [ ] [[package]] -name = "colorama" -version = "0.4.6" -description = "Cross-platform colored terminal text." -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "grapheme" -version = "0.6.0" -description = "Unicode grapheme helpers" -optional = false -python-versions = "*" -files = [ - {file = "grapheme-0.6.0.tar.gz", hash = "sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca"}, -] - -[package.extras] -test = ["pytest", "sphinx", "sphinx-autobuild", "twine", "wheel"] - -[[package]] name = "idna" version = "3.4" description = "Internationalized Domain Names in Applications (IDNA)" @@ -339,6 +289,52 @@ files = [ ] [[package]] +name = "mypy" +version = "1.5.1" +description = "Optional static typing for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "mypy-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f33592ddf9655a4894aef22d134de7393e95fcbdc2d15c1ab65828eee5c66c70"}, + {file = "mypy-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:258b22210a4a258ccd077426c7a181d789d1121aca6db73a83f79372f5569ae0"}, + {file = "mypy-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9ec1f695f0c25986e6f7f8778e5ce61659063268836a38c951200c57479cc12"}, + {file = "mypy-1.5.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:abed92d9c8f08643c7d831300b739562b0a6c9fcb028d211134fc9ab20ccad5d"}, + {file = "mypy-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a156e6390944c265eb56afa67c74c0636f10283429171018446b732f1a05af25"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ac9c21bfe7bc9f7f1b6fae441746e6a106e48fc9de530dea29e8cd37a2c0cc4"}, + {file = "mypy-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51cb1323064b1099e177098cb939eab2da42fea5d818d40113957ec954fc85f4"}, + {file = "mypy-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:596fae69f2bfcb7305808c75c00f81fe2829b6236eadda536f00610ac5ec2243"}, + {file = "mypy-1.5.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:32cb59609b0534f0bd67faebb6e022fe534bdb0e2ecab4290d683d248be1b275"}, + {file = "mypy-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:159aa9acb16086b79bbb0016145034a1a05360626046a929f84579ce1666b315"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6b0e77db9ff4fda74de7df13f30016a0a663928d669c9f2c057048ba44f09bb"}, + {file = "mypy-1.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26f71b535dfc158a71264e6dc805a9f8d2e60b67215ca0bfa26e2e1aa4d4d373"}, + {file = "mypy-1.5.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fc3a600f749b1008cc75e02b6fb3d4db8dbcca2d733030fe7a3b3502902f161"}, + {file = "mypy-1.5.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:26fb32e4d4afa205b24bf645eddfbb36a1e17e995c5c99d6d00edb24b693406a"}, + {file = "mypy-1.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:82cb6193de9bbb3844bab4c7cf80e6227d5225cc7625b068a06d005d861ad5f1"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4a465ea2ca12804d5b34bb056be3a29dc47aea5973b892d0417c6a10a40b2d65"}, + {file = "mypy-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9fece120dbb041771a63eb95e4896791386fe287fefb2837258925b8326d6160"}, + {file = "mypy-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d28ddc3e3dfeab553e743e532fb95b4e6afad51d4706dd22f28e1e5e664828d2"}, + {file = "mypy-1.5.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:57b10c56016adce71fba6bc6e9fd45d8083f74361f629390c556738565af8eeb"}, + {file = "mypy-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:ff0cedc84184115202475bbb46dd99f8dcb87fe24d5d0ddfc0fe6b8575c88d2f"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8f772942d372c8cbac575be99f9cc9d9fb3bd95c8bc2de6c01411e2c84ebca8a"}, + {file = "mypy-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5d627124700b92b6bbaa99f27cbe615c8ea7b3402960f6372ea7d65faf376c14"}, + {file = "mypy-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361da43c4f5a96173220eb53340ace68cda81845cd88218f8862dfb0adc8cddb"}, + {file = "mypy-1.5.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:330857f9507c24de5c5724235e66858f8364a0693894342485e543f5b07c8693"}, + {file = "mypy-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:c543214ffdd422623e9fedd0869166c2f16affe4ba37463975043ef7d2ea8770"}, + {file = "mypy-1.5.1-py3-none-any.whl", hash = "sha256:f757063a83970d67c444f6e01d9550a7402322af3557ce7630d3c957386fa8f5"}, + {file = "mypy-1.5.1.tar.gz", hash = "sha256:b031b9601f1060bf1281feab89697324726ba0c0bae9d7cd7ab4b690940f0b92"}, +] + +[package.dependencies] +mypy-extensions = ">=1.0.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +install-types = ["pip"] +reports = ["lxml"] + +[[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." @@ -397,20 +393,6 @@ files = [ test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "pytest (>=4.5.0)", "pytest-cov (>=2.7.1)", "pytest-runner (>=5.1)", "pytest-virtualenv (>=1.7.0)", "virtualenv (>=15.0.3)"] [[package]] -name = "pybooru" -version = "4.2.2" -description = "Pybooru is a Python package to access to the API of Danbooru/Moebooru based sites." -optional = false -python-versions = "*" -files = [ - {file = "Pybooru-4.2.2-py2.py3-none-any.whl", hash = "sha256:a855cfa9dbb6d641d81d7bbeb378977345edc466e11b0c32346e9209f2ae4d3b"}, - {file = "Pybooru-4.2.2.tar.gz", hash = "sha256:c3e31bb718753b8ee678fc7b87a9f6a9cf18747066f84efe245d3b25ecb2882f"}, -] - -[package.dependencies] -requests = "*" - -[[package]] name = "pycnite" version = "2023.10.5" description = "Python bytecode utilities" @@ -604,20 +586,6 @@ files = [ diagrams = ["jinja2", "railroad-diagrams"] [[package]] -name = "python-dotenv" -version = "1.0.0" -description = "Read key-value pairs from a .env file and set them as environment variables" -optional = false -python-versions = ">=3.8" -files = [ - {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"}, - {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"}, -] - -[package.extras] -cli = ["click (>=5.0)"] - -[[package]] name = "pytype" version = "2023.10.5" description = "Python type inferencer" @@ -756,6 +724,17 @@ files = [ ] [[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" @@ -801,4 +780,4 @@ zstd = ["zstandard (>=0.18.0)"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "0569366cc46e30e59708e5aba3b17fe878b3f37b1aaa68900c88f08ed04da98b" +content-hash = "b3493b47ffa295b1352cb8ddb9cb69853296b68c4a46e6d9836928033c4182b1" diff --git a/pyproject.toml b/pyproject.toml index 8af13f3..06aa9db 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,20 +1,18 @@ [tool.poetry] -name = "booru-sync" +name = "szuruboorupy" version = "0.1.0" -description = "Python scripts and library for syncing content between Danbooru and Szurubooru" +description = "An API client written in Python for szurubooru based sites" authors = ["Colin Wilk <colin.wilk@tum.de>"] readme = "README.md" [tool.poetry.dependencies] python = "^3.10" -pybooru = "^4.2.2" -alive-progress = "^3.1.4" -colorama = "^0.4.6" -python-dotenv = "^1.0.0" pydantic = "^2.4.2" +requests = "^2.31.0" [tool.poetry.group.dev.dependencies] pytype = "^2023.9.27" +mypy = "^1.5.1" pydocstyle = "^6.3.0" [build-system] diff --git a/requirements.txt b/requirements.txt index 3529f21..a829784 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,3 @@ -about-time==4.2.1 ; python_version >= "3.10" and python_version < "4" \ - --hash=sha256:6a538862d33ce67d997429d14998310e1dbfda6cb7d9bbfbf799c4709847fece \ - --hash=sha256:8bbf4c75fe13cbd3d72f49a03b02c5c7dca32169b6d49117c257e7eb3eaee341 -alive-progress==3.1.4 ; python_version >= "3.10" and python_version < "4" \ - --hash=sha256:74a95d8d0d42bc99d3a3725dbd06ebb852245f1b64e301a7c375b92b22663f7b \ - --hash=sha256:c80ad87ce9c1054b01135a87fae69ecebbfc2107497ae87cbe6aec7e534903db annotated-types==0.6.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43 \ --hash=sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d @@ -101,17 +95,9 @@ charset-normalizer==3.3.0 ; python_version >= "3.10" and python_version < "4.0" --hash=sha256:f5969baeaea61c97efa706b9b107dcba02784b1601c74ac84f2a532ea079403e \ --hash=sha256:f8888e31e3a85943743f8fc15e71536bda1c81d5aa36d014a3c0c44481d7db6e \ --hash=sha256:fc52b79d83a3fe3a360902d3f5d79073a993597d48114c29485e9431092905d8 -colorama==0.4.6 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \ - --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 -grapheme==0.6.0 ; python_version >= "3.10" and python_version < "4" \ - --hash=sha256:44c2b9f21bbe77cfb05835fec230bd435954275267fea1858013b102f8603cca idna==3.4 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2 -pybooru==4.2.2 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:a855cfa9dbb6d641d81d7bbeb378977345edc466e11b0c32346e9209f2ae4d3b \ - --hash=sha256:c3e31bb718753b8ee678fc7b87a9f6a9cf18747066f84efe245d3b25ecb2882f pydantic-core==2.10.1 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:042462d8d6ba707fd3ce9649e7bf268633a41018d6a998fb5fbacb7e928a183e \ --hash=sha256:0523aeb76e03f753b58be33b26540880bac5aa54422e4462404c432230543f33 \ @@ -222,9 +208,6 @@ pydantic-core==2.10.1 ; python_version >= "3.10" and python_version < "4.0" \ pydantic==2.4.2 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:94f336138093a5d7f426aac732dcfe7ab4eb4da243c88f891d65deb4a2556ee7 \ --hash=sha256:bc3ddf669d234f4220e6e1c4d96b061abe0998185a8d7855c0126782b7abc8c1 -python-dotenv==1.0.0 ; python_version >= "3.10" and python_version < "4.0" \ - --hash=sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba \ - --hash=sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a requests==2.31.0 ; python_version >= "3.10" and python_version < "4.0" \ --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 diff --git a/szuruboorupy/__init__.py b/szuruboorupy/__init__.py new file mode 100644 index 0000000..44165b1 --- /dev/null +++ b/szuruboorupy/__init__.py @@ -0,0 +1,10 @@ +""" +szuruboorupy + +szuruboorupy is an API client written in Python for szurubooru based sites. + +szuruboorupy modules: + api -- Main module doing the requests to szurubooru + dataclasses -- Contains all szurubooru resources as python dataclasses + exceptions -- Manages and builds szurubooru API exceptions +""" diff --git a/src/lib/szurubooru.py b/szuruboorupy/api.py index 2484691..3d8380c 100644 --- a/src/lib/szurubooru.py +++ b/szuruboorupy/api.py @@ -1,353 +1,24 @@ """ -Szurubooru Library -~~~~~~~~~~~~~~~~~~ +Szurubooru API +~~~~~~~~~~~~~~ -Szurubooru Library is used for communicating with a Szurubooru instance through python. +Szurubooru API is used for communicating with a Szurubooru instance through python. :copyright (c) 2023 Colin Wilk. :license: MIT, see LICENSE for more details. """ -from lib.dataclasses import Tag -import requests as r -from typing import List +import functools import json -from urllib.parse import quote import math -import functools - - -class SzurubooruException(Exception): - """General Error returned from the Szurubooru REST API. - - The Szurubooru API returns errors together with an http code in a form like this: - .. code-block:: JSON - { - "name": "Name of the error, e.g. 'PostNotFoundError'", - "title": "Generic title of error message, e.g. 'Not found'", - "description": "Detailed description of what went wrong, e.g. - 'User `rr-` not found." - } - - We map this json response to the SzurubooruException class or more specific children - such as MissingRequiredFileError with the fields / args that are provided from the - json response. - - Args: - name: Name of the exception as defined by the Szurubooru REST API - The name is also used in more specific Exceptions such as - MissingRequiredFileError(SzurubooruException). - title: Generic, human readable title of the error message. - description: More detailed description of what went wrong. Often includes - input from the user that caused the error. - """ - - name: str - title: str - description: str - - def __init__(self, name: str, title: str, description: str) -> None: - self.name = name - self.title = title - self.description = description - super().__init__(description) - - @staticmethod - def response_is_exception(res: r.Response) -> bool: - """Check if the Szurubooru REST API response is an error or not. - - Args: - res (r.Response): the Response of the request from the requests module. - - Returns: - bool: True if response is an error and False if it is not. - """ - return res.status_code != 200 - - @staticmethod - def map_exception(res: r.Response) -> Exception: - """Generate an exception for an error response from the Szurubooru REST API. - - Args: - res (r.Response): the Response from the Szurubooru REST API that should be - mapped to a Python Exception. - - Raises: - LookupError: Thrown when the Szurubooru REST API sent an error type (name) - that could not be mapped to one of the SzurubooruException - Python Exceptions. This can happen when there was an unexpected - e.g. an Internal Server Error in the Szurubooru REST API. - - Returns: - Exception: Returns a more specific exception. - """ - content: dict[str, str] = json.loads(res.content) - name: str = content["name"] - title: str = content["title"] - description: str = content["description"] - - match name: - case "MissingRequiredFileError": - return MissingRequiredFileError(name, title, description) - case "MissingRequiredParameterError": - return MissingRequiredParameterError(name, title, description) - case "InvalidParameterError": - return InvalidParameterError(name, title, description) - case "IntegrityError": - return IntegrityError(name, title, description) - case "SearchError": - return SearchError(name, title, description) - case "AuthError": - return AuthError(name, title, description) - case "PostNotFoundError": - return PostNotFoundError(name, title, description) - case "PostAlreadyFeaturedError": - return PostAlreadyFeaturedError(name, title, description) - case "PostAlreadyUploadedError": - return PostAlreadyUploadedError(name, title, description) - case "InvalidPostIdError": - return InvalidPostIdError(name, title, description) - case "InvalidPostSafetyError": - return InvalidPostSafetyError(name, title, description) - case "InvalidPostSourceError": - return InvalidPostSourceError(name, title, description) - case "InvalidPostContentError": - return InvalidPostContentError(name, title, description) - case "InvalidPostRelationError": - return InvalidPostRelationError(name, title, description) - case "InvalidPostNoteError": - return InvalidPostNoteError(name, title, description) - case "InvalidPostFlagError": - return InvalidPostFlagError(name, title, description) - case "InvalidFavoriteTargetError": - return InvalidFavoriteTargetError(name, title, description) - case "InvalidCommentIdError": - return InvalidCommentIdError(name, title, description) - case "CommentNotFoundError": - return CommentNotFoundError(name, title, description) - case "EmptyCommentTextError": - return EmptyCommentTextError(name, title, description) - case "InvalidScoreTargetError": - return InvalidScoreTargetError(name, title, description) - case "InvalidScoreValueError": - return InvalidScoreValueError(name, title, description) - case "TagCategoryNotFoundError": - return TagCategoryNotFoundError(name, title, description) - case "TagCategoryAlreadyExistsError": - return TagCategoryAlreadyExistsError(name, title, description) - case "TagCategoryIsInUseError": - return TagCategoryIsInUseError(name, title, description) - case "InvalidTagCategoryNameError": - return InvalidTagCategoryNameError(name, title, description) - case "InvalidTagCategoryColorError": - return InvalidTagCategoryColorError(name, title, description) - case "TagNotFoundError": - return TagNotFoundError(name, title, description) - case "TagAlreadyExistsError": - return TagAlreadyExistsError(name, title, description) - case "TagIsInUseError": - return TagIsInUseError(name, title, description) - case "InvalidTagNameError": - return InvalidTagNameError(name, title, description) - case "InvalidTagRelationError": - return InvalidTagRelationError(name, title, description) - case "InvalidTagCategoryError": - return InvalidTagCategoryError(name, title, description) - case "InvalidTagDescriptionError": - return InvalidTagDescriptionError(name, title, description) - case "UserNotFoundError": - return UserNotFoundError(name, title, description) - case "UserAlreadyExistsError": - return UserAlreadyExistsError(name, title, description) - case "InvalidUserNameError": - return InvalidUserNameError(name, title, description) - case "InvalidEmailError": - return InvalidEmailError(name, title, description) - case "InvalidPasswordError": - return InvalidPasswordError(name, title, description) - case "InvalidRankError": - return InvalidRankError(name, title, description) - case "InvalidAvatarError": - return InvalidAvatarError(name, title, description) - case "ProcessingError": - return ProcessingError(name, title, description) - case "ValidationError": - return ValidationError(name, title, description) - case _: - raise LookupError(f'Unknown SzurubooruException: "{name}"') - - -class MissingRequiredFileError(SzurubooruException): # noqa: D101 - pass - - -class MissingRequiredParameterError(SzurubooruException): # noqa: D101 - pass - - -class InvalidParameterError(SzurubooruException): # noqa: D101 - pass - - -class IntegrityError(SzurubooruException): # noqa: D101 - pass - - -class SearchError(SzurubooruException): # noqa: D101 - pass - - -class AuthError(SzurubooruException): # noqa: D101 - pass - - -class PostNotFoundError(SzurubooruException): # noqa: D101 - pass - - -class PostAlreadyFeaturedError(SzurubooruException): # noqa: D101 - pass - - -class PostAlreadyUploadedError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostIdError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostSafetyError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostSourceError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostContentError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostRelationError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostNoteError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPostFlagError(SzurubooruException): # noqa: D101 - pass - - -class InvalidFavoriteTargetError(SzurubooruException): # noqa: D101 - pass - - -class InvalidCommentIdError(SzurubooruException): # noqa: D101 - pass - - -class CommentNotFoundError(SzurubooruException): # noqa: D101 - pass - - -class EmptyCommentTextError(SzurubooruException): # noqa: D101 - pass - - -class InvalidScoreTargetError(SzurubooruException): # noqa: D101 - pass - - -class InvalidScoreValueError(SzurubooruException): # noqa: D101 - pass - - -class TagCategoryNotFoundError(SzurubooruException): # noqa: D101 - pass - - -class TagCategoryAlreadyExistsError(SzurubooruException): # noqa: D101 - pass - - -class TagCategoryIsInUseError(SzurubooruException): # noqa: D101 - pass - - -class InvalidTagCategoryNameError(SzurubooruException): # noqa: D101 - pass - - -class InvalidTagCategoryColorError(SzurubooruException): # noqa: D101 - pass - - -class TagNotFoundError(SzurubooruException): # noqa: D101 - pass - - -class TagAlreadyExistsError(SzurubooruException): # noqa: D101 - pass - - -class TagIsInUseError(SzurubooruException): # noqa: D101 - pass - - -class InvalidTagNameError(SzurubooruException): # noqa: D101 - pass - - -class InvalidTagRelationError(SzurubooruException): # noqa: D101 - pass - - -class InvalidTagCategoryError(SzurubooruException): # noqa: D101 - pass - - -class InvalidTagDescriptionError(SzurubooruException): # noqa: D101 - pass - - -class UserNotFoundError(SzurubooruException): # noqa: D101 - pass - - -class UserAlreadyExistsError(SzurubooruException): # noqa: D101 - pass - - -class InvalidUserNameError(SzurubooruException): # noqa: D101 - pass - - -class InvalidEmailError(SzurubooruException): # noqa: D101 - pass - - -class InvalidPasswordError(SzurubooruException): # noqa: D101 - pass - - -class InvalidRankError(SzurubooruException): # noqa: D101 - pass - - -class InvalidAvatarError(SzurubooruException): # noqa: D101 - pass - - -class ProcessingError(SzurubooruException): # noqa: D101 - pass +from typing import List +from urllib.parse import quote +import requests as r -class ValidationError(SzurubooruException): # noqa: D101 - pass +from szuruboorupy.dataclasses import Tag +from szuruboorupy.exceptions import SzurubooruException, TagNotFoundError class Szurubooru: diff --git a/src/lib/dataclasses.py b/szuruboorupy/dataclasses.py index 6313fca..350a876 100644 --- a/src/lib/dataclasses.py +++ b/szuruboorupy/dataclasses.py @@ -1,12 +1,13 @@ """ -Module for collection of dataclasses that map Szurubooru objects to python classes +Module for collection of dataclasses that map Szurubooru r. """ -from pydantic.dataclasses import dataclass from dataclasses import field from typing import List, Optional +from pydantic.dataclasses import dataclass + @dataclass class Tag: diff --git a/szuruboorupy/exceptions.py b/szuruboorupy/exceptions.py new file mode 100644 index 0000000..bde9c9a --- /dev/null +++ b/szuruboorupy/exceptions.py @@ -0,0 +1,341 @@ +"""Possible Exceptions that are returned by the Szurubooru REST API + +We define the general Exception `SzurubooruException` and possible child exceptions +defined in this module as well as the Szurubooru API docs: +https://github.com/rr-/szurubooru/blob/master/doc/API.md#error-handling +""" + +import requests as r +import json + + +class SzurubooruException(Exception): + """General Error returned from the Szurubooru REST API. + + The Szurubooru API returns errors together with an http code in a form like this: + .. code-block:: JSON + { + "name": "Name of the error, e.g. 'PostNotFoundError'", + "title": "Generic title of error message, e.g. 'Not found'", + "description": "Detailed description of what went wrong, e.g. + 'User `rr-` not found." + } + + We map this json response to the SzurubooruException class or more specific children + such as MissingRequiredFileError with the fields / args that are provided from the + json response. + + Args: + name: Name of the exception as defined by the Szurubooru REST API + The name is also used in more specific Exceptions such as + MissingRequiredFileError(SzurubooruException). + title: Generic, human readable title of the error message. + description: More detailed description of what went wrong. Often includes + input from the user that caused the error. + """ + + name: str + title: str + description: str + + def __init__(self, name: str, title: str, description: str) -> None: + self.name = name + self.title = title + self.description = description + super().__init__(description) + + @staticmethod + def response_is_exception(res: r.Response) -> bool: + """Check if the Szurubooru REST API response is an error or not. + + Args: + res (r.Response): the Response of the request from the requests module. + + Returns: + bool: True if response is an error and False if it is not. + """ + return res.status_code != 200 + + @staticmethod + def map_exception(res: r.Response) -> Exception: + """Generate an exception for an error response from the Szurubooru REST API. + + Args: + res (r.Response): the Response from the Szurubooru REST API that should be + mapped to a Python Exception. + + Raises: + LookupError: Thrown when the Szurubooru REST API sent an error type (name) + that could not be mapped to one of the SzurubooruException + Python Exceptions. This can happen when there was an unexpected + e.g. an Internal Server Error in the Szurubooru REST API. + + Returns: + Exception: Returns a more specific exception. + """ + content: dict[str, str] = json.loads(res.content) + name: str = content["name"] + title: str = content["title"] + description: str = content["description"] + + match name: + case "MissingRequiredFileError": + return MissingRequiredFileError(name, title, description) + case "MissingRequiredParameterError": + return MissingRequiredParameterError(name, title, description) + case "InvalidParameterError": + return InvalidParameterError(name, title, description) + case "IntegrityError": + return IntegrityError(name, title, description) + case "SearchError": + return SearchError(name, title, description) + case "AuthError": + return AuthError(name, title, description) + case "PostNotFoundError": + return PostNotFoundError(name, title, description) + case "PostAlreadyFeaturedError": + return PostAlreadyFeaturedError(name, title, description) + case "PostAlreadyUploadedError": + return PostAlreadyUploadedError(name, title, description) + case "InvalidPostIdError": + return InvalidPostIdError(name, title, description) + case "InvalidPostSafetyError": + return InvalidPostSafetyError(name, title, description) + case "InvalidPostSourceError": + return InvalidPostSourceError(name, title, description) + case "InvalidPostContentError": + return InvalidPostContentError(name, title, description) + case "InvalidPostRelationError": + return InvalidPostRelationError(name, title, description) + case "InvalidPostNoteError": + return InvalidPostNoteError(name, title, description) + case "InvalidPostFlagError": + return InvalidPostFlagError(name, title, description) + case "InvalidFavoriteTargetError": + return InvalidFavoriteTargetError(name, title, description) + case "InvalidCommentIdError": + return InvalidCommentIdError(name, title, description) + case "CommentNotFoundError": + return CommentNotFoundError(name, title, description) + case "EmptyCommentTextError": + return EmptyCommentTextError(name, title, description) + case "InvalidScoreTargetError": + return InvalidScoreTargetError(name, title, description) + case "InvalidScoreValueError": + return InvalidScoreValueError(name, title, description) + case "TagCategoryNotFoundError": + return TagCategoryNotFoundError(name, title, description) + case "TagCategoryAlreadyExistsError": + return TagCategoryAlreadyExistsError(name, title, description) + case "TagCategoryIsInUseError": + return TagCategoryIsInUseError(name, title, description) + case "InvalidTagCategoryNameError": + return InvalidTagCategoryNameError(name, title, description) + case "InvalidTagCategoryColorError": + return InvalidTagCategoryColorError(name, title, description) + case "TagNotFoundError": + return TagNotFoundError(name, title, description) + case "TagAlreadyExistsError": + return TagAlreadyExistsError(name, title, description) + case "TagIsInUseError": + return TagIsInUseError(name, title, description) + case "InvalidTagNameError": + return InvalidTagNameError(name, title, description) + case "InvalidTagRelationError": + return InvalidTagRelationError(name, title, description) + case "InvalidTagCategoryError": + return InvalidTagCategoryError(name, title, description) + case "InvalidTagDescriptionError": + return InvalidTagDescriptionError(name, title, description) + case "UserNotFoundError": + return UserNotFoundError(name, title, description) + case "UserAlreadyExistsError": + return UserAlreadyExistsError(name, title, description) + case "InvalidUserNameError": + return InvalidUserNameError(name, title, description) + case "InvalidEmailError": + return InvalidEmailError(name, title, description) + case "InvalidPasswordError": + return InvalidPasswordError(name, title, description) + case "InvalidRankError": + return InvalidRankError(name, title, description) + case "InvalidAvatarError": + return InvalidAvatarError(name, title, description) + case "ProcessingError": + return ProcessingError(name, title, description) + case "ValidationError": + return ValidationError(name, title, description) + case _: + raise LookupError(f'Unknown SzurubooruException: "{name}"') + + +class MissingRequiredFileError(SzurubooruException): # noqa: D101 + pass + + +class MissingRequiredParameterError(SzurubooruException): # noqa: D101 + pass + + +class InvalidParameterError(SzurubooruException): # noqa: D101 + pass + + +class IntegrityError(SzurubooruException): # noqa: D101 + pass + + +class SearchError(SzurubooruException): # noqa: D101 + pass + + +class AuthError(SzurubooruException): # noqa: D101 + pass + + +class PostNotFoundError(SzurubooruException): # noqa: D101 + pass + + +class PostAlreadyFeaturedError(SzurubooruException): # noqa: D101 + pass + + +class PostAlreadyUploadedError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostIdError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostSafetyError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostSourceError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostContentError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostRelationError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostNoteError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPostFlagError(SzurubooruException): # noqa: D101 + pass + + +class InvalidFavoriteTargetError(SzurubooruException): # noqa: D101 + pass + + +class InvalidCommentIdError(SzurubooruException): # noqa: D101 + pass + + +class CommentNotFoundError(SzurubooruException): # noqa: D101 + pass + + +class EmptyCommentTextError(SzurubooruException): # noqa: D101 + pass + + +class InvalidScoreTargetError(SzurubooruException): # noqa: D101 + pass + + +class InvalidScoreValueError(SzurubooruException): # noqa: D101 + pass + + +class TagCategoryNotFoundError(SzurubooruException): # noqa: D101 + pass + + +class TagCategoryAlreadyExistsError(SzurubooruException): # noqa: D101 + pass + + +class TagCategoryIsInUseError(SzurubooruException): # noqa: D101 + pass + + +class InvalidTagCategoryNameError(SzurubooruException): # noqa: D101 + pass + + +class InvalidTagCategoryColorError(SzurubooruException): # noqa: D101 + pass + + +class TagNotFoundError(SzurubooruException): # noqa: D101 + pass + + +class TagAlreadyExistsError(SzurubooruException): # noqa: D101 + pass + + +class TagIsInUseError(SzurubooruException): # noqa: D101 + pass + + +class InvalidTagNameError(SzurubooruException): # noqa: D101 + pass + + +class InvalidTagRelationError(SzurubooruException): # noqa: D101 + pass + + +class InvalidTagCategoryError(SzurubooruException): # noqa: D101 + pass + + +class InvalidTagDescriptionError(SzurubooruException): # noqa: D101 + pass + + +class UserNotFoundError(SzurubooruException): # noqa: D101 + pass + + +class UserAlreadyExistsError(SzurubooruException): # noqa: D101 + pass + + +class InvalidUserNameError(SzurubooruException): # noqa: D101 + pass + + +class InvalidEmailError(SzurubooruException): # noqa: D101 + pass + + +class InvalidPasswordError(SzurubooruException): # noqa: D101 + pass + + +class InvalidRankError(SzurubooruException): # noqa: D101 + pass + + +class InvalidAvatarError(SzurubooruException): # noqa: D101 + pass + + +class ProcessingError(SzurubooruException): # noqa: D101 + pass + + +class ValidationError(SzurubooruException): # noqa: D101 + pass |