From cac20dd72e94dad9afd7318a3b03698384615575 Mon Sep 17 00:00:00 2001 From: seriaati Date: Thu, 19 Sep 2024 11:50:37 +0800 Subject: [PATCH] Use DateTimeField --- genshin/models/genshin/chronicle/abyss.py | 16 ++----- .../models/genshin/chronicle/img_theater.py | 29 +++--------- genshin/models/genshin/daily.py | 10 +--- genshin/models/genshin/diary.py | 20 ++------ genshin/models/genshin/lineup.py | 5 +- genshin/models/genshin/teapot.py | 5 +- genshin/models/genshin/transaction.py | 5 +- genshin/models/model.py | 11 +++++ genshin/models/zzz/chronicle/challenge.py | 46 +++++++++---------- 9 files changed, 56 insertions(+), 91 deletions(-) diff --git a/genshin/models/genshin/chronicle/abyss.py b/genshin/models/genshin/chronicle/abyss.py index d81fd681..2aa1330a 100644 --- a/genshin/models/genshin/chronicle/abyss.py +++ b/genshin/models/genshin/chronicle/abyss.py @@ -1,11 +1,9 @@ -import datetime import typing import pydantic -from genshin.constants import CN_TIMEZONE from genshin.models.genshin import character -from genshin.models.model import Aliased, APIModel +from genshin.models.model import Aliased, APIModel, DateTimeField __all__ = [ "AbyssCharacter", @@ -50,7 +48,7 @@ class Battle(APIModel): """Battle in the spiral abyss.""" half: int = Aliased("index") - timestamp: datetime.datetime + timestamp: DateTimeField characters: typing.Sequence[AbyssCharacter] = Aliased("avatars") @@ -80,11 +78,11 @@ class SpiralAbyss(APIModel): unlocked: bool = Aliased("is_unlock") season: int = Aliased("schedule_id") - start_time: datetime.datetime - end_time: datetime.datetime + start_time: DateTimeField + end_time: DateTimeField total_battles: int = Aliased("total_battle_times") - total_wins: str = Aliased("total_win_times") + total_wins: int = Aliased("total_win_times") max_floor: str total_stars: int = Aliased("total_star") @@ -98,10 +96,6 @@ def __nest_ranks(cls, values: typing.Dict[str, typing.Any]) -> typing.Dict[str, values.setdefault("ranks", {}).update(values) return values - @pydantic.field_validator("start_time", "end_time", mode="before") - def __parse_timezones(cls, value: str) -> datetime.datetime: - return datetime.datetime.fromtimestamp(int(value), tz=CN_TIMEZONE) - class SpiralAbyssPair(APIModel): """Pair of both current and previous spiral abyss. diff --git a/genshin/models/genshin/chronicle/img_theater.py b/genshin/models/genshin/chronicle/img_theater.py index 575ba892..77675e18 100644 --- a/genshin/models/genshin/chronicle/img_theater.py +++ b/genshin/models/genshin/chronicle/img_theater.py @@ -4,9 +4,8 @@ import pydantic -from genshin.constants import CN_TIMEZONE from genshin.models.genshin import character -from genshin.models.model import Aliased, APIModel +from genshin.models.model import Aliased, APIModel, DateTimeField __all__ = ( "Act", @@ -68,19 +67,11 @@ class Act(APIModel): medal_obtained: bool = Aliased("is_get_medal") round_id: int finish_time: int # As timestamp - finish_datetime: datetime.datetime = Aliased("finish_date_time") + finish_datetime: DateTimeField = Aliased("finish_date_time") @pydantic.field_validator("finish_datetime", mode="before") def __parse_datetime(cls, value: typing.Mapping[str, typing.Any]) -> datetime.datetime: - return datetime.datetime( - year=value["year"], - month=value["month"], - day=value["day"], - hour=value["hour"], - minute=value["minute"], - second=value["second"], - tzinfo=CN_TIMEZONE, - ) + return datetime.datetime(**value) class TheaterStats(APIModel): @@ -109,20 +100,12 @@ class TheaterSchedule(APIModel): end_time: int # As timestamp schedule_type: int # Not sure what this is id: int = Aliased("schedule_id") - start_datetime: datetime.datetime = Aliased("start_date_time") - end_datetime: datetime.datetime = Aliased("end_date_time") + start_datetime: DateTimeField = Aliased("start_date_time") + end_datetime: DateTimeField = Aliased("end_date_time") @pydantic.field_validator("start_datetime", "end_datetime", mode="before") def __parse_datetime(cls, value: typing.Mapping[str, typing.Any]) -> datetime.datetime: - return datetime.datetime( - year=value["year"], - month=value["month"], - day=value["day"], - hour=value["hour"], - minute=value["minute"], - second=value["second"], - tzinfo=CN_TIMEZONE, - ) + return datetime.datetime(**value) class BattleStatCharacter(APIModel): diff --git a/genshin/models/genshin/daily.py b/genshin/models/genshin/daily.py index 31a2bd57..abfc3028 100644 --- a/genshin/models/genshin/daily.py +++ b/genshin/models/genshin/daily.py @@ -3,10 +3,8 @@ import datetime import typing -import pydantic - from genshin.constants import CN_TIMEZONE -from genshin.models.model import Aliased, APIModel, Unique +from genshin.models.model import Aliased, APIModel, DateTimeField, Unique __all__ = ["ClaimedDailyReward", "DailyReward", "DailyRewardInfo"] @@ -38,8 +36,4 @@ class ClaimedDailyReward(APIModel, Unique): name: str amount: int = Aliased("cnt") icon: str = Aliased("img") - time: datetime.datetime = Aliased("created_at") - - @pydantic.field_validator("time") - def __add_timezone(cls, value: datetime.datetime) -> datetime.datetime: - return value.replace(tzinfo=CN_TIMEZONE) + time: DateTimeField = Aliased("created_at") diff --git a/genshin/models/genshin/diary.py b/genshin/models/genshin/diary.py index 54a44a6c..960776c8 100644 --- a/genshin/models/genshin/diary.py +++ b/genshin/models/genshin/diary.py @@ -1,13 +1,9 @@ """Genshin diary models.""" -import datetime import enum import typing -import pydantic - -from genshin.constants import CN_TIMEZONE -from genshin.models.model import Aliased, APIModel +from genshin.models.model import Aliased, APIModel, DateTimeField __all__ = [ "BaseDiary", @@ -91,14 +87,9 @@ class DiaryAction(APIModel): action_id: int action: str - time: datetime.datetime + time: DateTimeField amount: int = Aliased("num") - @pydantic.field_validator("time") - @classmethod - def __add_timezone(cls, value: datetime.datetime) -> datetime.datetime: - return value.replace(tzinfo=CN_TIMEZONE) - class DiaryPage(BaseDiary): """Page of a diary.""" @@ -162,14 +153,9 @@ class StarRailDiaryAction(APIModel): action: str action_name: str - time: datetime.datetime + time: DateTimeField amount: int = Aliased("num") - @pydantic.field_validator("time") - @classmethod - def __add_timezone(cls, value: datetime.datetime) -> datetime.datetime: - return value.replace(tzinfo=CN_TIMEZONE) - class StarRailDiaryPage(BaseDiary): """Page of a diary.""" diff --git a/genshin/models/genshin/lineup.py b/genshin/models/genshin/lineup.py index 30521d69..ef6c0792 100644 --- a/genshin/models/genshin/lineup.py +++ b/genshin/models/genshin/lineup.py @@ -2,13 +2,12 @@ from __future__ import annotations -import datetime import typing import pydantic from genshin.models.genshin import character -from genshin.models.model import Aliased, APIModel, Unique +from genshin.models.model import Aliased, APIModel, DateTimeField, Unique __all__ = [ "Lineup", @@ -280,7 +279,7 @@ class LineupPreview(APIModel, Unique): likes: int = Aliased("like_cnt") comments: int = Aliased("comment_cnt") - created_at: datetime.datetime + created_at: DateTimeField original_lang: str = Aliased("trans_from") diff --git a/genshin/models/genshin/teapot.py b/genshin/models/genshin/teapot.py index 766d2ae1..6f175e0a 100644 --- a/genshin/models/genshin/teapot.py +++ b/genshin/models/genshin/teapot.py @@ -2,12 +2,11 @@ from __future__ import annotations -import datetime import typing import pydantic -from genshin.models.model import Aliased, APIModel, Unique +from genshin.models.model import Aliased, APIModel, DateTimeField, Unique __all__ = [ "TeapotReplica", @@ -53,7 +52,7 @@ class TeapotReplica(APIModel, Unique): title: str content: str images: typing.List[str] = Aliased("imgs") - created_at: datetime.datetime + created_at: DateTimeField stats: TeapotReplicaStats lang: str # type: ignore diff --git a/genshin/models/genshin/transaction.py b/genshin/models/genshin/transaction.py index a1adf771..92bb250f 100644 --- a/genshin/models/genshin/transaction.py +++ b/genshin/models/genshin/transaction.py @@ -1,10 +1,9 @@ """Genshin transaction models.""" -import datetime import enum import typing -from genshin.models.model import Aliased, APIModel, Unique +from genshin.models.model import Aliased, APIModel, DateTimeField, Unique __all__ = ["BaseTransaction", "ItemTransaction", "Transaction", "TransactionKind"] @@ -34,7 +33,7 @@ class BaseTransaction(APIModel, Unique): kind: TransactionKind id: int - time: datetime.datetime = Aliased("datetime") + time: DateTimeField = Aliased("datetime") amount: int = Aliased("add_num") reason: str = Aliased("reason") diff --git a/genshin/models/model.py b/genshin/models/model.py index 63672a7d..2f5b93c5 100644 --- a/genshin/models/model.py +++ b/genshin/models/model.py @@ -3,9 +3,13 @@ from __future__ import annotations import abc +import datetime import typing import pydantic +from typing_extensions import Annotated + +from genshin.constants import CN_TIMEZONE __all__ = ["APIModel", "Aliased", "Unique"] @@ -35,3 +39,10 @@ def Aliased( ) -> typing.Any: """Create an aliased field.""" return pydantic.Field(default, alias=alias, **kwargs) + + +def add_timezone(value: datetime.datetime) -> datetime.datetime: + return value.replace(tzinfo=CN_TIMEZONE) + + +DateTimeField = Annotated[datetime.datetime, pydantic.AfterValidator(add_timezone)] diff --git a/genshin/models/zzz/chronicle/challenge.py b/genshin/models/zzz/chronicle/challenge.py index d2164a83..9c47926e 100644 --- a/genshin/models/zzz/chronicle/challenge.py +++ b/genshin/models/zzz/chronicle/challenge.py @@ -3,8 +3,7 @@ import pydantic -from genshin.constants import CN_TIMEZONE -from genshin.models.model import Aliased, APIModel +from genshin.models.model import Aliased, APIModel, DateTimeField from genshin.models.zzz.character import ZZZElementType __all__ = ( @@ -57,9 +56,16 @@ class ShiyuDefenseMonster(APIModel): id: int name: str - weakness: ZZZElementType = Aliased("weak_element_type") + weakness: typing.Optional[ZZZElementType] = Aliased("weak_element_type") level: int + @pydantic.field_validator("weakness", mode="before") + @classmethod + def __parse_weakness(cls, value: int) -> typing.Optional[ZZZElementType]: + if value == 0: + return None + return ZZZElementType(value) + class ShiyuDefenseNode(APIModel): """Shiyu Defense node model.""" @@ -91,25 +97,23 @@ class ShiyuDefenseFloor(APIModel): buffs: typing.List[ShiyuDefenseBuff] node_1: ShiyuDefenseNode node_2: ShiyuDefenseNode - challenge_time: datetime.datetime = Aliased("floor_challenge_time") + challenge_time: DateTimeField = Aliased("floor_challenge_time") name: str = Aliased("zone_name") @pydantic.field_validator("challenge_time", mode="before") @classmethod - def __add_timezone( - cls, v: typing.Dict[typing.Literal["year", "month", "day", "hour", "minute", "second"], int] - ) -> datetime.datetime: - return datetime.datetime( - v["year"], v["month"], v["day"], v["hour"], v["minute"], v["second"], tzinfo=CN_TIMEZONE - ) + def __parse_datetime(cls, value: typing.Mapping[str, typing.Any]) -> typing.Optional[DateTimeField]: + if value: + return datetime.datetime(**value) + return None class ShiyuDefense(APIModel): """ZZZ Shiyu Defense model.""" schedule_id: int - begin_time: typing.Optional[datetime.datetime] = Aliased("hadal_begin_time") - end_time: typing.Optional[datetime.datetime] = Aliased("hadal_end_time") + begin_time: typing.Optional[DateTimeField] = Aliased("hadal_begin_time") + end_time: typing.Optional[DateTimeField] = Aliased("hadal_end_time") has_data: bool ratings: typing.Mapping[typing.Literal["S", "A", "B"], int] = Aliased("rating_list") floors: typing.List[ShiyuDefenseFloor] = Aliased("all_floor_detail") @@ -117,20 +121,16 @@ class ShiyuDefense(APIModel): """Fastest clear time this season in seconds.""" max_floor: int = Aliased("max_layer") + @pydantic.field_validator("begin_time", "end_time", mode="before") + @classmethod + def __parse_datetime(cls, value: typing.Mapping[str, typing.Any]) -> typing.Optional[DateTimeField]: + if value: + return datetime.datetime(**value) + return None + @pydantic.field_validator("ratings", mode="before") @classmethod def __convert_ratings( cls, v: typing.List[typing.Dict[typing.Literal["times", "rating"], typing.Any]] ) -> typing.Mapping[typing.Literal["S", "A", "B"], int]: return {d["rating"]: d["times"] for d in v} - - @pydantic.field_validator("begin_time", "end_time", mode="before") - @classmethod - def __add_timezone( - cls, v: typing.Optional[typing.Dict[typing.Literal["year", "month", "day", "hour", "minute", "second"], int]] - ) -> typing.Optional[datetime.datetime]: - if v is not None: - return datetime.datetime( - v["year"], v["month"], v["day"], v["hour"], v["minute"], v["second"], tzinfo=CN_TIMEZONE - ) - return None