Skip to content

Commit

Permalink
fix: workaround pydantic bug
Browse files Browse the repository at this point in the history
  • Loading branch information
Ravencentric committed Jul 5, 2024
1 parent 9e88100 commit e8bf608
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 54 deletions.
7 changes: 5 additions & 2 deletions src/pyanilist/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
MediaRankType,
MediaRelation,
MediaSeason,
MediaSort,
MediaSource,
MediaStatus,
MediaType,
Expand All @@ -36,10 +37,10 @@
StaffName,
Studio,
)
from ._types import YearsActive
from ._types import FuzzyDateInt, YearsActive
from ._version import Version, _get_version

__version__ = _get_version()
__version__ = _get_version()
__version_tuple__ = Version(*map(int, __version__.split(".")))

__all__ = [
Expand All @@ -53,11 +54,13 @@
"MediaRankType",
"MediaRelation",
"MediaSeason",
"MediaSort",
"MediaSource",
"MediaStatus",
"MediaType",
# Types
"YearsActive",
"FuzzyDateInt",
# Models
"AiringSchedule",
"Character",
Expand Down
7 changes: 3 additions & 4 deletions src/pyanilist/_clients/_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@

from httpx import AsyncClient, HTTPError, Response
from pydantic import PositiveInt, validate_call
from pydantic_extra_types.country import CountryAlpha2 as CountryCode
from stamina import retry_context

from .._enums import MediaFormat, MediaSeason, MediaSort, MediaSource, MediaStatus, MediaType
from .._models import Media
from .._parser import post_process_response
from .._query import query_string
from .._types import FuzzyDateInt
from .._utils import query_variables_constructor
from .._utils import to_anilist_case


class AsyncAniList:
Expand Down Expand Up @@ -60,7 +59,7 @@ async def _post_request(self, **kwargs: Any) -> Response:

payload = {
"query": query_string,
"variables": query_variables_constructor(kwargs),
"variables": {to_anilist_case(key): value for key, value in kwargs.items() if value is not None},
}

async for attempt in retry_context(on=HTTPError, attempts=self.retries):
Expand Down Expand Up @@ -99,7 +98,7 @@ async def get(
average_score: int | None = None,
popularity: int | None = None,
source: MediaSource | None = None,
country_of_origin: CountryCode | str | None = None,
country_of_origin: str | None = None,
is_licensed: bool | None = None,
id_not: int | None = None,
id_in: Iterable[int] | None = None,
Expand Down
10 changes: 4 additions & 6 deletions src/pyanilist/_clients/_sync.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
from __future__ import annotations

from collections.abc import Iterable
from typing import Any

from httpx import Client, HTTPError, Response
from pydantic import PositiveInt, validate_call
from pydantic_extra_types.country import CountryAlpha2 as CountryCode
from stamina import retry_context

from .._enums import MediaFormat, MediaSeason, MediaSort, MediaSource, MediaStatus, MediaType
from .._models import Media
from .._parser import post_process_response
from .._query import query_string
from .._types import FuzzyDateInt
from .._utils import query_variables_constructor
from .._types import FuzzyDateInt, Iterable
from .._utils import to_anilist_case


class AniList:
Expand Down Expand Up @@ -60,7 +58,7 @@ def _post_request(self, **kwargs: Any) -> Response:

payload = {
"query": query_string,
"variables": query_variables_constructor(kwargs),
"variables": {to_anilist_case(key): value for key, value in kwargs.items() if value is not None},
}

for attempt in retry_context(on=HTTPError, attempts=self.retries):
Expand Down Expand Up @@ -98,7 +96,7 @@ def get(
average_score: int | None = None,
popularity: int | None = None,
source: MediaSource | None = None,
country_of_origin: CountryCode | str | None = None,
country_of_origin: str | None = None,
is_licensed: bool | None = None,
id_not: int | None = None,
id_in: Iterable[int] | None = None,
Expand Down
20 changes: 7 additions & 13 deletions src/pyanilist/_types.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
"""
Aside from providing it's own types, this module also re-exports the following from pydantic for convenience:
- [HttpUrl](https://docs.pydantic.dev/latest/api/networks/#pydantic.networks.HttpUrl)
- [Color](https://docs.pydantic.dev/latest/api/pydantic_extra_types_color/#pydantic_extra_types.color.Color)
- [CountryAlpha2 as CountryCoude](https://docs.pydantic.dev/latest/api/pydantic_extra_types_country/#pydantic_extra_types.country.CountryAlpha2)
"""

from __future__ import annotations

from typing import Annotated

from pydantic import Field
from typing_extensions import NamedTuple
from typing_extensions import NamedTuple, TypeAlias, TypeVar, Union

# A simpler Iterable type instead of collections.abc.Iterable
# to stop pydantic from converting them to ValidatorIterator
# https://github.com/pydantic/pydantic/issues/9541
T = TypeVar("T")
Iterable: TypeAlias = Union[set[T], tuple[T, ...], list[T]]


class YearsActive(NamedTuple):
Expand All @@ -31,8 +30,3 @@ class YearsActive(NamedTuple):
description="8 digit long date integer (YYYYMMDD). Unknown dates represented by 0. E.g. 2016: 20160000, May 1976: 19760500",
),
]


__all__ = [
"YearsActive",
]
25 changes: 13 additions & 12 deletions src/pyanilist/_utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import re
from collections.abc import Iterable
from typing import Any

import nh3
Expand Down Expand Up @@ -102,13 +101,23 @@ def remove_null_fields(dictionary: dict[str, Any]) -> dict[str, Any]:
return remap(dictionary, lambda path, key, value: value not in [None, {}, []]) # type: ignore


def query_variables_constructor(vars: dict[str, Any]) -> dict[str, Any]:
def to_anilist_case(var: str) -> str:
"""
Anilist doesn't stick to a single casing.
Most of it is camelCase but then there's some made up stuff in there too.
So can do nothing but create a mapping from snake_case to anilistCase
Parameters
----------
var : str
A snake_case variable.
Returns
-------
str
Same thing but in anilist's case.
"""
casing = {
casemap = {
"id": "id",
"id_mal": "idMal",
"start_date": "startDate",
Expand Down Expand Up @@ -179,12 +188,4 @@ def query_variables_constructor(vars: dict[str, Any]) -> dict[str, Any]:
"sort": "sort",
}

query_vars = {}

for key, value in vars.items():
if value is not None:
if isinstance(value, Iterable) and not isinstance(value, str):
value = set(value)
query_vars[casing[key]] = value

return query_vars
return casemap[var]
4 changes: 2 additions & 2 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ def test_fuzzy_date() -> None:
assert FuzzyDate(year=2023, month=1, day=4).iso_format() == "2023-01-04"
assert FuzzyDate(year=2023, month=1).iso_format() == "2023-01"
assert FuzzyDate(year=2023).iso_format() == "2023"
assert FuzzyDate().iso_format() == ""

assert FuzzyDate(year=2023, month=1, day=4).as_int() == 20230104
assert FuzzyDate(year=2023, month=1).as_int() == 20230100
assert FuzzyDate(year=2023).as_int() == 20230000

assert FuzzyDate().iso_format() == ""
assert FuzzyDate().as_int() == 10000000
88 changes: 73 additions & 15 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
from pyanilist import MediaSource
from pyanilist._utils import (
flatten,
markdown_formatter,
query_variables_constructor,
remove_null_fields,
sanitize_description,
text_formatter,
to_anilist_case,
)

from .mock_descriptions import BloomIntoYouAnthologyDescriptions
Expand Down Expand Up @@ -116,18 +115,77 @@ def test_formatters() -> None:


def test_query_variables_constructor() -> None:
kwargs = {
"id": 123456,
"is_licensed": True,
"search": "Attack on Titan",
"id_not_in": [1, 2, 3, 4, 5, 5, 5],
"source_in": [MediaSource.ANIME, MediaSource.ANIME, MediaSource.MANGA],
}

assert query_variables_constructor(kwargs) == {
"id": 123456,
"isLicensed": True,
"search": "Attack on Titan",
"id_not_in": {1, 2, 3, 4, 5},
"source_in": {MediaSource.ANIME, MediaSource.MANGA},
casemap = {
"id": "id",
"id_mal": "idMal",
"start_date": "startDate",
"end_date": "endDate",
"season": "season",
"season_year": "seasonYear",
"type": "type",
"format": "format",
"status": "status",
"episodes": "episodes",
"chapters": "chapters",
"duration": "duration",
"volumes": "volumes",
"is_adult": "isAdult",
"genre": "genre",
"tag": "tag",
"minimum_tag_rank": "minimumTagRank",
"tag_category": "tagCategory",
"licensed_by": "licensedBy",
"licensed_by_id": "licensedById",
"average_score": "averageScore",
"popularity": "popularity",
"source": "source",
"country_of_origin": "countryOfOrigin",
"is_licensed": "isLicensed",
"search": "search",
"id_not": "id_not",
"id_in": "id_in",
"id_not_in": "id_not_in",
"id_mal_not": "idMal_not",
"id_mal_in": "idMal_in",
"id_mal_not_in": "idMal_not_in",
"start_date_greater": "startDate_greater",
"start_date_lesser": "startDate_lesser",
"start_date_like": "startDate_like",
"end_date_greater": "endDate_greater",
"end_date_lesser": "endDate_lesser",
"end_date_like": "endDate_like",
"format_in": "format_in",
"format_not": "format_not",
"format_not_in": "format_not_in",
"status_in": "status_in",
"status_not": "status_not",
"status_not_in": "status_not_in",
"episodes_greater": "episodes_greater",
"episodes_lesser": "episodes_lesser",
"duration_greater": "duration_greater",
"duration_lesser": "duration_lesser",
"chapters_greater": "chapters_greater",
"chapters_lesser": "chapters_lesser",
"volumes_greater": "volumes_greater",
"volumes_lesser": "volumes_lesser",
"genre_in": "genre_in",
"genre_not_in": "genre_not_in",
"tag_in": "tag_in",
"tag_not_in": "tag_not_in",
"tag_category_in": "tagCategory_in",
"tag_category_not_in": "tagCategory_not_in",
"licensed_by_in": "licensedBy_in",
"licensed_by_id_in": "licensedById_in",
"average_score_not": "averageScore_not",
"average_score_greater": "averageScore_greater",
"average_score_lesser": "averageScore_lesser",
"popularity_not": "popularity_not",
"popularity_greater": "popularity_greater",
"popularity_lesser": "popularity_lesser",
"source_in": "source_in",
"sort": "sort",
}

for key, value in casemap.items():
assert to_anilist_case(key) == value

0 comments on commit e8bf608

Please sign in to comment.