summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Wilk <colin.wilk@tum.de>2023-10-09 11:52:05 +0200
committerColin Wilk <colin.wilk@tum.de>2023-10-09 12:08:43 +0200
commitc61d6fc80d7c20f580ad111db59352b8eae7b7da (patch)
tree752979c86371d311b0d82043c8376eb56c3ed1ee
parent1da7f1638babdfe4173d0e87ff3b45b32e0c9123 (diff)
downloadszuruboorupy-c61d6fc80d7c20f580ad111db59352b8eae7b7da.tar.gz
szuruboorupy-c61d6fc80d7c20f580ad111db59352b8eae7b7da.zip
Rename booru-sync to szuruboorupy
Initially the project was intended as a script repository containing scripts for managing my szurubooru instance. Since most of my work was actually writing an API client, I decided to rename this repository to an API client and do the script repository later on separately. Signed-off-by: Colin Wilk <colin.wilk@tum.de>
-rw-r--r--poetry.lock139
-rw-r--r--pyproject.toml10
-rw-r--r--requirements.txt17
-rw-r--r--szuruboorupy/__init__.py10
-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.py341
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