diff --git a/README.md b/README.md index acd260c..3913f88 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Logo

- Simple read-only Anilist API wrapper + Simple Anilist API wrapper to fetch data about it's media

@@ -17,7 +17,7 @@ [![PyPI - Version](https://img.shields.io/pypi/v/pyanilist?link=https%3A%2F%2Fpypi.org%2Fproject%2Fpyanilist%2F)](https://pypi.org/project/pyanilist/) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pyanilist) ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/Ravencentric/pyanilist/release.yml) -![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ravencentric/pyanilist/test.yml?label=tests&link=https%3A%2F%2Fgithub.com%2FRavencentric%2Fpyanilist%2Factions%2Fworkflows%2Ftest.yml) +![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/ravencentric/pyanilist/test.yml?label=tests) ![License](https://img.shields.io/github/license/Ravencentric/pyanilist) ![Checked with mypy](https://www.mypy-lang.org/static/mypy_badge.svg) ![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json) @@ -33,9 +33,9 @@ ## About -- Supports both sync and async. -- Read-only API wrapper. -- Only supports querying the `Media` type. +- Supports both sync and async. +- Provides easy access to almost every field present in Anilist's `Media` type. +- Only supports querying the `Media` type ## Installation diff --git a/docs/api-reference/exceptions.md b/docs/api-reference/exceptions.md index 65b9a6c..851ad95 100644 --- a/docs/api-reference/exceptions.md +++ b/docs/api-reference/exceptions.md @@ -1,5 +1,5 @@ !!! note - This module simply re-exports exceptions from + PyAnilist simply re-exports exceptions from [`httpx`](https://www.python-httpx.org/exceptions/) and [`pydantic`](https://docs.pydantic.dev/latest/api/pydantic_core/#pydantic_core.ValidationError) for convenience. diff --git a/docs/api-reference/types.md b/docs/api-reference/types.md index 809f877..1118f8f 100644 --- a/docs/api-reference/types.md +++ b/docs/api-reference/types.md @@ -1,5 +1,5 @@ !!! note - Additionally, this module also offers the convenience of re-exporting some of the Pydantic types used within this library: + Additionally, PyAnilist also uses and exports the following [pydantic](https://docs.pydantic.dev/latest/) types for convenience: - [HttpUrl](https://docs.pydantic.dev/latest/api/networks/#pydantic.networks.HttpUrl) @@ -10,4 +10,6 @@ ::: pyanilist._types.AnilistID ::: pyanilist._types.AnilistTitle ::: pyanilist._types.AnilistYear -::: pyanilist._types.YearsActive \ No newline at end of file +::: pyanilist._types.YearsActive +::: pyanilist._types.HTTPXAsyncClientKwargs +::: pyanilist._types.HTTPXClientKwargs diff --git a/docs/index.md b/docs/index.md index 6df01d2..d70e9ad 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ Logo

- Simple read-only Anilist API wrapper + Simple Anilist API wrapper to fetch data about it's media

@@ -13,8 +13,8 @@

PyPI - Version PyPI - Python Version -GitHub Workflow Status -GitHub Workflow Status +GitHub Workflow Status +GitHub Workflow Status License Checked with mypy Ruff @@ -23,9 +23,9 @@ ## About -- Supports both sync and async. -- Read-only API wrapper. -- Only supports querying the `Media` type. +- Supports both sync and async. +- Provides easy access to almost every field present in Anilist's `Media` type. +- Only supports querying the `Media` type ## Installation diff --git a/poetry.lock b/poetry.lock index d49268d..b52974c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -278,13 +278,13 @@ dev = ["flake8", "markdown", "twine", "wheel"] [[package]] name = "griffe" -version = "0.42.0" +version = "0.42.1" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." optional = false python-versions = ">=3.8" files = [ - {file = "griffe-0.42.0-py3-none-any.whl", hash = "sha256:384df6b802a60f70e65fdb7e83f5b27e2da869a12eac85b25b55250012dbc263"}, - {file = "griffe-0.42.0.tar.gz", hash = "sha256:fb83ee602701ffdf99c9a6bf5f0a5a3bd877364b3bffb2c451dc8fbd9645b0cf"}, + {file = "griffe-0.42.1-py3-none-any.whl", hash = "sha256:7e805e35617601355edcac0d3511cedc1ed0cb1f7645e2d336ae4b05bbae7b3b"}, + {file = "griffe-0.42.1.tar.gz", hash = "sha256:57046131384043ed078692b85d86b76568a686266cc036b9b56b704466f803ce"}, ] [package.dependencies] @@ -373,13 +373,13 @@ files = [ [[package]] name = "importlib-metadata" -version = "7.0.2" +version = "7.1.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-7.0.2-py3-none-any.whl", hash = "sha256:f4bc4c0c070c490abf4ce96d715f68e95923320370efb66143df00199bb6c100"}, - {file = "importlib_metadata-7.0.2.tar.gz", hash = "sha256:198f568f3230878cb1b44fbd7975f87906c22336dba2e4a7f05278c281fbd792"}, + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, ] [package.dependencies] @@ -388,7 +388,7 @@ zipp = ">=0.5" [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] [[package]] name = "iniconfig" @@ -994,13 +994,13 @@ testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygm [[package]] name = "pytest-asyncio" -version = "0.23.5.post1" +version = "0.23.6" description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-asyncio-0.23.5.post1.tar.gz", hash = "sha256:b9a8806bea78c21276bc34321bbf234ba1b2ea5b30d9f0ce0f2dea45e4685813"}, - {file = "pytest_asyncio-0.23.5.post1-py3-none-any.whl", hash = "sha256:30f54d27774e79ac409778889880242b0403d09cabd65b727ce90fe92dd5d80e"}, + {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"}, + {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"}, ] [package.dependencies] @@ -1223,28 +1223,28 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" -version = "0.3.3" +version = "0.3.4" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:973a0e388b7bc2e9148c7f9be8b8c6ae7471b9be37e1cc732f8f44a6f6d7720d"}, - {file = "ruff-0.3.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfa60d23269d6e2031129b053fdb4e5a7b0637fc6c9c0586737b962b2f834493"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1eca7ff7a47043cf6ce5c7f45f603b09121a7cc047447744b029d1b719278eb5"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7d3f6762217c1da954de24b4a1a70515630d29f71e268ec5000afe81377642d"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b24c19e8598916d9c6f5a5437671f55ee93c212a2c4c569605dc3842b6820386"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:5a6cbf216b69c7090f0fe4669501a27326c34e119068c1494f35aaf4cc683778"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:352e95ead6964974b234e16ba8a66dad102ec7bf8ac064a23f95371d8b198aab"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d6ab88c81c4040a817aa432484e838aaddf8bfd7ca70e4e615482757acb64f8"}, - {file = "ruff-0.3.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79bca3a03a759cc773fca69e0bdeac8abd1c13c31b798d5bb3c9da4a03144a9f"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:2700a804d5336bcffe063fd789ca2c7b02b552d2e323a336700abb8ae9e6a3f8"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd66469f1a18fdb9d32e22b79f486223052ddf057dc56dea0caaf1a47bdfaf4e"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:45817af234605525cdf6317005923bf532514e1ea3d9270acf61ca2440691376"}, - {file = "ruff-0.3.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0da458989ce0159555ef224d5b7c24d3d2e4bf4c300b85467b08c3261c6bc6a8"}, - {file = "ruff-0.3.3-py3-none-win32.whl", hash = "sha256:f2831ec6a580a97f1ea82ea1eda0401c3cdf512cf2045fa3c85e8ef109e87de0"}, - {file = "ruff-0.3.3-py3-none-win_amd64.whl", hash = "sha256:be90bcae57c24d9f9d023b12d627e958eb55f595428bafcb7fec0791ad25ddfc"}, - {file = "ruff-0.3.3-py3-none-win_arm64.whl", hash = "sha256:0171aab5fecdc54383993389710a3d1227f2da124d76a2784a7098e818f92d61"}, - {file = "ruff-0.3.3.tar.gz", hash = "sha256:38671be06f57a2f8aba957d9f701ea889aa5736be806f18c0cd03d6ff0cbca8d"}, + {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:60c870a7d46efcbc8385d27ec07fe534ac32f3b251e4fc44b3cbfd9e09609ef4"}, + {file = "ruff-0.3.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6fc14fa742e1d8f24910e1fff0bd5e26d395b0e0e04cc1b15c7c5e5fe5b4af91"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3ee7880f653cc03749a3bfea720cf2a192e4f884925b0cf7eecce82f0ce5854"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cf133dd744f2470b347f602452a88e70dadfbe0fcfb5fd46e093d55da65f82f7"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f3860057590e810c7ffea75669bdc6927bfd91e29b4baa9258fd48b540a4365"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:986f2377f7cf12efac1f515fc1a5b753c000ed1e0a6de96747cdf2da20a1b369"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c4fd98e85869603e65f554fdc5cddf0712e352fe6e61d29d5a6fe087ec82b76c"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64abeed785dad51801b423fa51840b1764b35d6c461ea8caef9cf9e5e5ab34d9"}, + {file = "ruff-0.3.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df52972138318bc7546d92348a1ee58449bc3f9eaf0db278906eb511889c4b50"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:98e98300056445ba2cc27d0b325fd044dc17fcc38e4e4d2c7711585bd0a958ed"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:519cf6a0ebed244dce1dc8aecd3dc99add7a2ee15bb68cf19588bb5bf58e0488"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb0acfb921030d00070539c038cd24bb1df73a2981e9f55942514af8b17be94e"}, + {file = "ruff-0.3.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cf187a7e7098233d0d0c71175375c5162f880126c4c716fa28a8ac418dcf3378"}, + {file = "ruff-0.3.4-py3-none-win32.whl", hash = "sha256:af27ac187c0a331e8ef91d84bf1c3c6a5dea97e912a7560ac0cef25c526a4102"}, + {file = "ruff-0.3.4-py3-none-win_amd64.whl", hash = "sha256:de0d5069b165e5a32b3c6ffbb81c350b1e3d3483347196ffdf86dc0ef9e37dd6"}, + {file = "ruff-0.3.4-py3-none-win_arm64.whl", hash = "sha256:6810563cc08ad0096b57c717bd78aeac888a1bfd38654d9113cb3dc4d3f74232"}, + {file = "ruff-0.3.4.tar.gz", hash = "sha256:f0f4484c6541a99862b693e13a151435a279b271cff20e37101116a21e2a1ad1"}, ] [[package]] @@ -1444,4 +1444,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = ">=3.9" -content-hash = "f6dd92f77cb7569be7989111541daf796446055f4c1cb7173ba233dd6501a316" +content-hash = "f5138cbf88977bea07513a6b87f707a3ffb7014fed12711f18b2639a9e495006" diff --git a/pyproject.toml b/pyproject.toml index d4eca6e..4a39195 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [tool.poetry] name = "pyanilist" version = "0.3.0" -description = "Simple read-only Anilist API wrapper" +description = "Simple Anilist API wrapper to fetch data about it's media" authors = ["Raventric "] license = "Unlicense" readme = "README.md" @@ -30,7 +30,7 @@ httpx = ">=0.27.0" boltons = ">=23.1.1" tenacity = ">=8.2.3" strenum = { version = ">=0.4.15", python = "<3.11" } -importlib-metadata = { version = ">=7.0.2", python = "<3.10" } +importlib-metadata = { version = ">=7.1.0", python = "<3.10" } eval-type-backport = { version = ">=0.1.3", python = "<3.10" } [tool.ruff] diff --git a/src/pyanilist/_clients/_async.py b/src/pyanilist/_clients/_async.py index 595aa06..4f2974c 100644 --- a/src/pyanilist/_clients/_async.py +++ b/src/pyanilist/_clients/_async.py @@ -9,13 +9,13 @@ from .._enums import MediaFormat, MediaSeason, MediaStatus, MediaType from .._models import Media from .._query import query_string -from .._types import AnilistID, AnilistTitle, AnilistYear +from .._types import AnilistID, AnilistTitle, AnilistYear, HTTPXAsyncClientKwargs from .._utils import flatten, remove_null_fields class AsyncAnilist: def __init__( - self, api_url: str = "https://graphql.anilist.co", retries: int = 5, **httpx_async_client_kwargs: Any + self, api_url: str = "https://graphql.anilist.co", retries: int = 5, **kwargs: HTTPXAsyncClientKwargs ) -> None: """ Async Anilist API client. @@ -26,13 +26,13 @@ def __init__( The URL of the Anilist API. Default is "https://graphql.anilist.co". retries : int, optional Number of times to retry a failed request before raising an error. Default is 5. - httpx_async_client_kwargs : Any, optional - Keyword arguments to pass to the internal [httpx.AsyncClient()](https://www.python-httpx.org/api/#asyncclient) + kwargs : HTTPXAsyncClientKwargs, optional + Keyword arguments to pass to the underlying [httpx.AsyncClient()](https://www.python-httpx.org/api/#asyncclient) used to make the POST request. """ self.api_url = api_url self.retries = retries - self.httpx_async_client_kwargs = httpx_async_client_kwargs + self.kwargs = kwargs async def _post_request( self, @@ -98,7 +98,7 @@ async def _post_request( reraise=True, ): with attempt: - async with httpx.AsyncClient(**self.httpx_async_client_kwargs) as client: + async with httpx.AsyncClient(**self.kwargs) as client: response = await client.post(self.api_url, json=payload) response.raise_for_status() @@ -194,9 +194,8 @@ async def search( ------ ValidationError Invalid input - pyanilist._exceptions.* - Any exception from the pyanilist._exceptions module may be raised - in case of errors encountered during the POST request. + HTTPStatusError + Anilist returned a non 2xx response. Returns ------- @@ -231,9 +230,8 @@ async def get(self, id: AnilistID) -> Media: ------ ValidationError Invalid input - pyanilist._exceptions.* - Any exception from the pyanilist._exceptions module may be raised - in case of errors encountered during the POST request. + HTTPStatusError + Anilist returned a non 2xx response. Returns ------- diff --git a/src/pyanilist/_clients/_sync.py b/src/pyanilist/_clients/_sync.py index 34d5f8c..02c5cd3 100644 --- a/src/pyanilist/_clients/_sync.py +++ b/src/pyanilist/_clients/_sync.py @@ -9,13 +9,13 @@ from .._enums import MediaFormat, MediaSeason, MediaStatus, MediaType from .._models import Media from .._query import query_string -from .._types import AnilistID, AnilistTitle, AnilistYear +from .._types import AnilistID, AnilistTitle, AnilistYear, HTTPXClientKwargs from .._utils import flatten, remove_null_fields class Anilist: def __init__( - self, api_url: str = "https://graphql.anilist.co", retries: int = 5, **httpx_client_kwargs: Any + self, api_url: str = "https://graphql.anilist.co", retries: int = 5, **kwargs: HTTPXClientKwargs ) -> None: """ Anilist API client. @@ -26,13 +26,13 @@ def __init__( The URL of the Anilist API. Default is "https://graphql.anilist.co". retries : int, optional Number of times to retry a failed request before raising an error. Default is 5. - httpx_client_kwargs : Any, optional - Keyword arguments to pass to the internal [httpx.Client()](https://www.python-httpx.org/api/#client) + kwargs : HTTPXClientKwargs, optional + Keyword arguments to pass to the underlying [httpx.Client()](https://www.python-httpx.org/api/#client) used to make the POST request. """ self.api_url = api_url self.retries = retries - self.httpx_client_kwargs = httpx_client_kwargs + self.kwargs = kwargs def _post_request( self, @@ -98,7 +98,7 @@ def _post_request( reraise=True, ): with attempt: - with httpx.Client(**self.httpx_client_kwargs) as client: + with httpx.Client(**self.kwargs) as client: response = client.post(self.api_url, json=payload).raise_for_status() return response @@ -193,9 +193,8 @@ def search( ------ ValidationError Invalid input - pyanilist._exceptions.* - Any exception from the pyanilist._exceptions module may be raised - in case of errors encountered during the POST request. + HTTPStatusError + Anilist returned a non 2xx response. Returns ------- @@ -230,9 +229,8 @@ def get(self, id: AnilistID) -> Media: ------ ValidationError Invalid input - pyanilist._exceptions.* - Any exception from the pyanilist._exceptions module may be raised - in case of errors encountered during the POST request. + HTTPStatusError + Anilist returned a non 2xx response. Returns ------- diff --git a/src/pyanilist/_models.py b/src/pyanilist/_models.py index 8049e35..9417d82 100644 --- a/src/pyanilist/_models.py +++ b/src/pyanilist/_models.py @@ -484,7 +484,7 @@ class Relation(ParentModel): trailer: MediaTrailer = MediaTrailer() """Media trailer or advertisement""" - updated_at: datetime + updated_at: datetime = datetime.min """When the media's data was last updated""" cover_image: MediaCoverImage = MediaCoverImage() @@ -608,7 +608,7 @@ class Media(ParentModel): trailer: MediaTrailer = MediaTrailer() """Media trailer or advertisement""" - updated_at: datetime + updated_at: datetime = datetime.min """When the media's data was last updated""" cover_image: MediaCoverImage = MediaCoverImage() diff --git a/src/pyanilist/_types.py b/src/pyanilist/_types.py index c506dbd..2999e19 100644 --- a/src/pyanilist/_types.py +++ b/src/pyanilist/_types.py @@ -7,12 +7,12 @@ from __future__ import annotations -from typing import Annotated +from typing import Annotated, Any from pydantic import Field, HttpUrl from pydantic_extra_types.color import Color from pydantic_extra_types.country import CountryAlpha2 as CountryCode -from typing_extensions import NamedTuple +from typing_extensions import NamedTuple, TypeAlias class YearsActive(NamedTuple): @@ -29,6 +29,11 @@ class YearsActive(NamedTuple): AnilistYear = Annotated[int, Field(ge=1000, description="Release Year")] AnilistTitle = Annotated[str, Field(min_length=1, description="Title of the media")] +HTTPXClientKwargs: TypeAlias = Any +"""Simple TypeAlias to refer to `httpx.Client()` kwargs""" + +HTTPXAsyncClientKwargs: TypeAlias = Any +"""Simple TypeAlias to refer to `httpx.AsyncClient()` kwargs""" __all__ = [ "AnilistID",