Use jsons for serialization

This commit is contained in:
Zoe Roux 2023-03-25 13:20:53 +09:00
parent c3b8595cd7
commit 31a349704b
11 changed files with 65 additions and 31 deletions

View File

@ -1,7 +1,7 @@
import asyncio
from datetime import datetime
import logging
from aiohttp import ClientSession
from datetime import datetime
from typing import Awaitable, Callable, Dict, Optional, Any, TypeVar
from ..provider import Provider
@ -175,7 +175,7 @@ class TheMovieDatabase(Provider):
f"/tv/{show_id}",
params={
"language": lng,
"append_to_response": "alternative_titles,videos,credits,keywords,images",
"append_to_response": "alternative_titles,videos,credits,keywords,images,external_ids",
},
)
logging.debug("TMDb responded: %s", show)
@ -183,7 +183,7 @@ class TheMovieDatabase(Provider):
ret = Show(
original_language=show["original_language"],
aliases=[x["title"] for x in show["alternative_titles"]["titles"]],
aliases=[x["title"] for x in show["alternative_titles"]["results"]],
start_air=datetime.strptime(show["first_air_date"], "%Y-%m-%d").date(),
end_air=datetime.strptime(show["last_air_date"], "%Y-%m-%d").date(),
status=ShowStatus.FINISHED
@ -202,9 +202,10 @@ class TheMovieDatabase(Provider):
show["id"], f"https://www.themoviedb.org/tv/{show['id']}"
),
"imdb": MetadataID(
show["imdb_id"],
f"https://www.imdb.com/title/{show['imdb_id']}",
show["external_ids"]["imdb_id"],
f"https://www.imdb.com/title/{show['external_ids']['imdb_id']}",
),
"tvdb": MetadataID(show["external_ids"]["tvdb_id"], link=None),
},
seasons=[
self.to_season(x, language=lng, show_id=show["id"])
@ -215,7 +216,7 @@ class TheMovieDatabase(Provider):
translation = ShowTranslation(
name=show["name"],
tagline=show["tagline"],
keywords=list(map(lambda x: x["name"], show["keywords"]["keywords"])),
keywords=list(map(lambda x: x["name"], show["keywords"]["results"])),
overview=show["overview"],
posters=self.get_image(show["images"]["posters"]),
logos=self.get_image(show["images"]["logos"]),
@ -257,8 +258,8 @@ class TheMovieDatabase(Provider):
) -> Season:
return Season(
season_number=season["season_number"],
start_date=datetime.strptime(season["air_date"], "%Y-%m-%d").date(),
end_date=None,
start_air=datetime.strptime(season["air_date"], "%Y-%m-%d").date(),
end_air=None,
external_id={
self.name: MetadataID(
season["id"],
@ -269,10 +270,10 @@ class TheMovieDatabase(Provider):
language: SeasonTranslation(
name=season["name"],
overview=season["overview"],
poster=[
posters=[
f"https://image.tmdb.org/t/p/original{season['poster_path']}"
]
if "poster_path" in season
if season["poster_path"] is not None
else [],
thumbnails=[],
)
@ -295,7 +296,9 @@ class TheMovieDatabase(Provider):
# TODO: Handle absolute episodes
if not season or not episode_nbr:
raise NotImplementedError("Absolute order episodes not implemented for the movie database")
raise NotImplementedError(
"Absolute order episodes not implemented for the movie database"
)
async def for_language(lng: str) -> Episode:
episode = await self.get(
@ -327,7 +330,7 @@ class TheMovieDatabase(Provider):
external_id={
self.name: MetadataID(
episode["id"],
f"https://www.themoviedb.org/movie/{episode['id']}",
f"https://www.themoviedb.org/tv/{show_id}/season/{episode['season_number']}/episode/{episode['episode_number']}",
),
},
)

View File

@ -28,7 +28,7 @@ class Episode:
season_number: Optional[int]
episode_number: Optional[int]
absolute_number: Optional[int]
release_date: Optional[date]
release_date: Optional[date | int]
thumbnail: Optional[str]
external_id: dict[str, MetadataID]
@ -43,5 +43,4 @@ class Episode:
return {
**asdict(self),
**asdict(self.translations[default_language]),
"release_date": format_date(self.release_date),
}

View File

@ -1,7 +1,8 @@
from dataclasses import dataclass
from typing import Optional
@dataclass
class MetadataID:
id: str
link: str
link: Optional[str]

View File

@ -56,7 +56,7 @@ class Movie:
),
"logo": next(iter(self.translations[default_language].logos), None),
"trailer": next(iter(self.translations[default_language].trailers), None),
"studio": next(iter(x.to_kyoo() for x in self.studios), None),
"studio": next(iter(self.studios), None),
"release_date": None,
"startAir": format_date(self.release_date),
"title": self.translations[default_language].name,

View File

@ -1,7 +1,9 @@
import os
from datetime import date
from dataclasses import dataclass, field
from dataclasses import dataclass, field, asdict
from typing import Optional
from ..utils import format_date
from .metadataid import MetadataID
@ -9,15 +11,27 @@ from .metadataid import MetadataID
class SeasonTranslation:
name: Optional[str]
overview: Optional[str]
poster: list[str]
posters: list[str]
thumbnails: list[str]
@dataclass
class Season:
season_number: int
start_date: Optional[date | int]
end_date: Optional[date | int]
start_air: Optional[date | int]
end_air: Optional[date | int]
external_id: dict[str, MetadataID]
translations: dict[str, SeasonTranslation] = field(default_factory=dict)
def to_kyoo(self):
# For now, the API of kyoo only support one language so we remove the others.
default_language = os.environ["LIBRARY_LANGUAGES"].split(",")[0]
return {
**asdict(self),
**asdict(self.translations[default_language]),
"poster": next(iter(self.translations[default_language].posters), None),
"thumbnail": next(
iter(self.translations[default_language].thumbnails), None
),
}

View File

@ -10,6 +10,7 @@ from .season import Season
from .metadataid import MetadataID
from ..utils import format_date
class Status(str, Enum):
UNKNOWN = "unknown"
FINISHED = "finished"
@ -58,9 +59,8 @@ class Show:
),
"logo": next(iter(self.translations[default_language].logos), None),
"trailer": next(iter(self.translations[default_language].trailers), None),
"studio": next(iter(x.to_kyoo() for x in self.studios), None),
"startAir": format_date(self.start_air),
"endAir": format_date(self.end_air),
"studio": next(iter(self.studios), None),
"title": self.translations[default_language].name,
"genres": [x.to_kyoo() for x in self.genres],
"seasons": [x.to_kyoo() for x in self.seasons],
}

View File

@ -8,6 +8,3 @@ class Studio:
name: str
logos: list[str] = field(default_factory=list)
external_id: dict[str, MetadataID] = field(default_factory=dict)
def to_kyoo(self):
return asdict(self)

View File

@ -1,8 +1,9 @@
from datetime import date
def format_date(date: date | int | None) -> str | None:
if date is None:
return None
if isinstance(date, int):
return f"{date}-01-01T00:00:00Z"
return f"{date}-01-01"
return date.isoformat()

View File

@ -1,3 +1,4 @@
guessit
aiohttp
jsons
black-with-tabs

View File

@ -5,7 +5,11 @@ async def main():
import os
import logging
import sys
import jsons
from datetime import date
from typing import Optional
from aiohttp import ClientSession
from providers.utils import format_date
path = os.environ.get("LIBRARY_ROOT")
if not path:
@ -28,7 +32,12 @@ async def main():
if len(sys.argv) > 1 and sys.argv[1] == "-vv":
logging.basicConfig(level=logging.DEBUG)
async with ClientSession() as client:
jsons.set_serializer(lambda x, **_: format_date(x), Optional[date | int])
async with ClientSession(
json_serialize=lambda *args, **kwargs: jsons.dumps(
*args, key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE, **kwargs
),
) as client:
await Scanner(client, languages=languages.split(","), api_key=api_key).scan(
path
)

View File

@ -1,13 +1,14 @@
from functools import wraps
import json
import os
import asyncio
import logging
import jsons
from aiohttp import ClientSession
from pathlib import Path
from guessit import guessit
from providers.provider import Provider
from providers.types.episode import PartialShow
from providers.types.episode import Episode, PartialShow
from providers.types.show import Show
def log_errors(f):
@ -81,7 +82,15 @@ class Scanner:
async def post(self, path: str, *, data: object) -> str:
url = os.environ.get("KYOO_URL", "http://back:5000")
print(json.dumps(data, indent=4))
logging.info(
"Sending %s: %s",
path,
jsons.dumps(
data,
key_transformer=jsons.KEY_TRANSFORMER_CAMELCASE,
jdkwargs={"indent": 4},
),
)
async with self._client.post(
f"{url}/{path}", json=data, headers={"X-API-Key": self._api_key}
) as r: