Source code for animedex.models.art

"""
Image / art-post models for the tagging upstreams.

:class:`ArtPost` is the cross-source projection for Danbooru,
Waifu.im, and NekosBest. The shapes are remarkably similar: every
upstream returns an image URL plus a tag list, and most expose
some content-rating axis (Danbooru's four-letter ``g/s/q/e`` is the
most expressive; Waifu.im collapses to a boolean ``is_nsfw``;
NekosBest is SFW-only).

Consistent with the project's Human Agency Principle, the model
**does not** filter results by rating; it simply preserves whichever
rating the upstream reports so a downstream filter step is possible
without a second HTTP call.
"""

from __future__ import annotations

from typing import List, Literal, Optional

from animedex.models.common import AnimedexModel, SourceTag


#: Danbooru's four-letter content rating. Backends that use a less
#: granular vocabulary normalise to this set.
ArtRating = Literal["g", "s", "q", "e"]


[docs] class ArtPost(AnimedexModel): """A single image record from an art-tagging upstream. :ivar id: Canonical ``"<source>:<id>"`` identifier. :vartype id: str :ivar url: Full-resolution image URL. :vartype url: str :ivar preview_url: Thumbnail URL when one is exposed. :vartype preview_url: str or None :ivar rating: Content rating, when reported. Normalised to :data:`ArtRating`. :vartype rating: str or None :ivar tags: Tag list (free-form; vocabulary varies per upstream). :vartype tags: list of str :ivar score: Upstream's popularity / vote count. :vartype score: int or None :ivar artist: Attributed artist when reported. :vartype artist: str or None :ivar source_url: External URL where the image was originally sourced (e.g. the artist's gallery page). :vartype source_url: str or None :ivar width: Image width in pixels. :vartype width: int or None :ivar height: Image height in pixels. :vartype height: int or None :ivar source: Provenance tag. :vartype source: SourceTag """ id: str url: str preview_url: Optional[str] = None rating: Optional[ArtRating] = None tags: List[str] = [] score: Optional[int] = None artist: Optional[str] = None source_url: Optional[str] = None width: Optional[int] = None height: Optional[int] = None source: SourceTag
[docs] def selftest() -> bool: """Smoke-test the art model. :return: ``True`` on success; raises on schema errors. :rtype: bool """ from datetime import datetime, timezone src = SourceTag(backend="_selftest", fetched_at=datetime.now(timezone.utc)) p = ArtPost( id="_selftest:1", url="https://x.invalid/x.jpg", preview_url="https://x.invalid/x_thumb.jpg", rating="g", tags=["x"], score=1, artist="x", source_url="https://x.invalid", width=1, height=1, source=src, ) ArtPost.model_validate_json(p.model_dump_json()) return True