⬆️ upgrade Pydantic to V2

This commit is contained in:
omg-xtao 2024-11-30 22:32:07 +08:00 committed by GitHub
parent 4362f515cb
commit 5319fc116d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 688 additions and 578 deletions

View File

@ -1,7 +1,6 @@
import datetime import datetime
from typing import Dict, List from typing import Dict, List
from pytz import timezone
from simnet.models.genshin.chronicle.abyss import SpiralAbyss from simnet.models.genshin.chronicle.abyss import SpiralAbyss
from simnet.models.genshin.chronicle.img_theater import ImgTheaterData from simnet.models.genshin.chronicle.img_theater import ImgTheaterData
from simnet.models.genshin.diary import Diary from simnet.models.genshin.diary import Diary
@ -29,14 +28,6 @@ __all__ = (
"HistoryDataImgTheaterServices", "HistoryDataImgTheaterServices",
) )
TZ = timezone("Asia/Shanghai")
def json_encoder(value):
if isinstance(value, datetime.datetime):
return value.astimezone(TZ).strftime("%Y-%m-%d %H:%M:%S")
return value
class HistoryDataAbyssServices(BaseService, HistoryDataBaseServices): class HistoryDataAbyssServices(BaseService, HistoryDataBaseServices):
DATA_TYPE = HistoryDataTypeEnum.ABYSS.value DATA_TYPE = HistoryDataTypeEnum.ABYSS.value
@ -49,7 +40,7 @@ class HistoryDataAbyssServices(BaseService, HistoryDataBaseServices):
@staticmethod @staticmethod
def create(user_id: int, abyss_data: SpiralAbyss, character_data: Dict[int, int]): def create(user_id: int, abyss_data: SpiralAbyss, character_data: Dict[int, int]):
data = HistoryDataAbyss(abyss_data=abyss_data, character_data=character_data) data = HistoryDataAbyss(abyss_data=abyss_data, character_data=character_data)
json_data = data.json(by_alias=True, encoder=json_encoder) json_data = data.model_dump_json(by_alias=True)
return HistoryData( return HistoryData(
user_id=user_id, user_id=user_id,
data_id=abyss_data.season, data_id=abyss_data.season,
@ -65,7 +56,7 @@ class HistoryDataLedgerServices(BaseService, HistoryDataBaseServices):
@staticmethod @staticmethod
def create(user_id: int, diary_data: Diary): def create(user_id: int, diary_data: Diary):
data = HistoryDataLedger(diary_data=diary_data) data = HistoryDataLedger(diary_data=diary_data)
json_data = data.json(by_alias=True, encoder=json_encoder) json_data = data.model_dump_json(by_alias=True)
return HistoryData( return HistoryData(
user_id=user_id, user_id=user_id,
data_id=diary_data.data_id, data_id=diary_data.data_id,
@ -86,7 +77,7 @@ class HistoryDataImgTheaterServices(BaseService, HistoryDataBaseServices):
@staticmethod @staticmethod
def create(user_id: int, abyss_data: ImgTheaterData, character_data: Dict[int, int]): def create(user_id: int, abyss_data: ImgTheaterData, character_data: Dict[int, int]):
data = HistoryDataImgTheater(abyss_data=abyss_data, character_data=character_data) data = HistoryDataImgTheater(abyss_data=abyss_data, character_data=character_data)
json_data = data.json(by_alias=True, encoder=json_encoder) json_data = data.model_dump_json(by_alias=True)
return HistoryData( return HistoryData(
user_id=user_id, user_id=user_id,
data_id=abyss_data.schedule.id, data_id=abyss_data.schedule.id,

View File

@ -50,7 +50,7 @@ class WeaponEntry(BaseEntry):
class WeaponsEntry(BaseModel): class WeaponsEntry(BaseModel):
data: Optional[List[WeaponEntry]] data: Optional[List[WeaponEntry]] = None
class StrategyEntry(BaseEntry): class StrategyEntry(BaseEntry):
@ -69,4 +69,4 @@ class StrategyEntry(BaseEntry):
class StrategyEntryList(BaseModel): class StrategyEntryList(BaseModel):
data: Optional[List[StrategyEntry]] data: Optional[List[StrategyEntry]] = None

@ -1 +1 @@
Subproject commit 112b2e92d8492df17dbae23024fa805e3510a56e Subproject commit bf5b153001defd150d4dcc17b9baf06a3769adf0

View File

@ -1,6 +1,6 @@
from typing import List, Optional, Any from typing import List, Optional, Any
from pydantic import BaseModel, validator from pydantic import field_validator, BaseModel
__all__ = ("Member", "TeamRate", "FullTeamRate", "TeamRateResult") __all__ = ("Member", "TeamRate", "FullTeamRate", "TeamRateResult")
@ -14,9 +14,10 @@ class Member(BaseModel):
class TeamRate(BaseModel): class TeamRate(BaseModel):
rate: float rate: float
formation: List[Member] formation: List[Member]
owner_num: Optional[int] owner_num: Optional[int] = None
@validator("rate", pre=True) @field_validator("rate", mode="before")
@classmethod
def str2float(cls, v): # pylint: disable=R0201 def str2float(cls, v): # pylint: disable=R0201
return float(v.replace("%", "")) / 100.0 if isinstance(v, str) else v return float(v.replace("%", "")) / 100.0 if isinstance(v, str) else v
@ -24,8 +25,8 @@ class TeamRate(BaseModel):
class FullTeamRate(BaseModel): class FullTeamRate(BaseModel):
up: TeamRate up: TeamRate
down: TeamRate down: TeamRate
owner_num: Optional[int] owner_num: Optional[int] = None
nice: Optional[float] nice: Optional[float] = None
@property @property
def rate(self) -> float: def rate(self) -> float:

View File

@ -2,7 +2,8 @@ from datetime import datetime
from enum import Enum from enum import Enum
from typing import Dict, List, Any, Optional from typing import Dict, List, Any, Optional
from pydantic import BaseModel, Field from pydantic import Field
from simnet.models.base import APIModel as BaseModel
class AkashaSubStat(str, Enum): class AkashaSubStat(str, Enum):
@ -38,7 +39,7 @@ class AkashaRankCal(BaseModel):
class AkashaRank(BaseModel): class AkashaRank(BaseModel):
_id: str _id: str
characterId: int characterId: int
uid = int uid: int
constellation: int constellation: int
icon: str icon: str
@ -90,7 +91,7 @@ class AkashaLeaderboardArtifactSet(BaseModel):
class AkashaLeaderboardOwner(BaseModel): class AkashaLeaderboardOwner(BaseModel):
nickname: str nickname: str
adventureRank: float adventureRank: float
profilePicture: Any profilePicture: Any = None
nameCard: str nameCard: str
patreon: Dict[str, Any] patreon: Dict[str, Any]
region: str region: str
@ -121,7 +122,7 @@ class AkashaLeaderboardStats(BaseModel):
healingBonus: AkashaLeaderboardStatsValue healingBonus: AkashaLeaderboardStatsValue
critRate: AkashaLeaderboardStatsValue critRate: AkashaLeaderboardStatsValue
critDamage: AkashaLeaderboardStatsValue critDamage: AkashaLeaderboardStatsValue
electroDamageBonus: Optional[AkashaLeaderboardStatsValue] electroDamageBonus: Optional[AkashaLeaderboardStatsValue] = None
class AkashaLeaderboardWeaponInfo(BaseModel): class AkashaLeaderboardWeaponInfo(BaseModel):

View File

@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from pydantic import BaseModel, validator from pydantic import field_validator, BaseModel
__all__ = ("GachaInfo",) __all__ = ("GachaInfo",)
@ -12,6 +12,7 @@ class GachaInfo(BaseModel):
gacha_name: str gacha_name: str
gacha_type: int gacha_type: int
@validator("begin_time", "end_time", pre=True, allow_reuse=True) @field_validator("begin_time", "end_time", mode="before")
@classmethod
def validate_time(cls, v): def validate_time(cls, v):
return datetime.strptime(v, "%Y-%m-%d %H:%M:%S") return datetime.strptime(v, "%Y-%m-%d %H:%M:%S")

View File

@ -2,7 +2,7 @@ import datetime
from enum import Enum from enum import Enum
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
from pydantic import BaseModel, validator from pydantic import field_validator, BaseModel
from metadata.shortname import not_real_roles, roleToId, weaponToId from metadata.shortname import not_real_roles, roleToId, weaponToId
from modules.gacha_log.const import UIGF_VERSION from modules.gacha_log.const import UIGF_VERSION
@ -42,26 +42,30 @@ class GachaItem(BaseModel):
rank_type: str rank_type: str
time: datetime.datetime time: datetime.datetime
@validator("name") @field_validator("name")
@classmethod
def name_validator(cls, v): def name_validator(cls, v):
if item_id := (roleToId(v) or weaponToId(v)): if item_id := (roleToId(v) or weaponToId(v)):
if item_id not in not_real_roles: if item_id not in not_real_roles:
return v return v
raise ValueError(f"Invalid name {v}") raise ValueError(f"Invalid name {v}")
@validator("gacha_type") @field_validator("gacha_type")
@classmethod
def check_gacha_type(cls, v): def check_gacha_type(cls, v):
if v not in {"100", "200", "301", "302", "400", "500"}: if v not in {"100", "200", "301", "302", "400", "500"}:
raise ValueError(f"gacha_type must be 200, 301, 302, 400, 500, invalid value: {v}") raise ValueError(f"gacha_type must be 200, 301, 302, 400, 500, invalid value: {v}")
return v return v
@validator("item_type") @field_validator("item_type")
@classmethod
def check_item_type(cls, item): def check_item_type(cls, item):
if item not in {"角色", "武器"}: if item not in {"角色", "武器"}:
raise ValueError(f"error item type {item}") raise ValueError(f"error item type {item}")
return item return item
@validator("rank_type") @field_validator("rank_type")
@classmethod
def check_rank_type(cls, rank): def check_rank_type(cls, rank):
if rank not in {"5", "4", "3"}: if rank not in {"5", "4", "3"}:
raise ValueError(f"error rank type {rank}") raise ValueError(f"error rank type {rank}")

View File

@ -7,7 +7,7 @@ from httpx import HTTPError
from telegram import InlineKeyboardMarkup, InlineKeyboardButton from telegram import InlineKeyboardMarkup, InlineKeyboardButton
from telegram.helpers import create_deep_linked_url from telegram.helpers import create_deep_linked_url
from gram_core.basemodel import Settings from gram_core.basemodel import Settings, SettingsConfigDict
from modules.gacha_log.error import GachaLogWebNotConfigError, GachaLogWebUploadError, GachaLogNotFound from modules.gacha_log.error import GachaLogWebNotConfigError, GachaLogWebUploadError, GachaLogNotFound
@ -17,8 +17,7 @@ class GachaLogWebConfig(Settings):
url: Optional[str] = "" url: Optional[str] = ""
token: Optional[str] = "" token: Optional[str] = ""
class Config(Settings.Config): model_config = SettingsConfigDict(env_prefix="gacha_log_web_")
env_prefix = "gacha_log_web_"
gacha_log_web_config = GachaLogWebConfig() gacha_log_web_config = GachaLogWebConfig()

View File

@ -1,23 +1,11 @@
import datetime import datetime
from typing import Any, List from typing import Any, List
from pydantic import BaseModel, BaseConfig from pydantic import BaseModel
from simnet.models.genshin.transaction import BaseTransaction from simnet.models.genshin.transaction import BaseTransaction
try:
import ujson as jsonlib
except ImportError:
import json as jsonlib
class _ModelConfig(BaseConfig):
json_dumps = jsonlib.dumps
json_loads = jsonlib.loads
class BaseInfo(BaseModel): class BaseInfo(BaseModel):
Config = _ModelConfig
uid: str = "0" uid: str = "0"
lang: str = "zh-cn" lang: str = "zh-cn"
export_time: str = "" export_time: str = ""
@ -35,6 +23,5 @@ class BaseInfo(BaseModel):
class PayLog(BaseModel): class PayLog(BaseModel):
Config = _ModelConfig
info: BaseInfo info: BaseInfo
list: List[BaseTransaction] list: List[BaseTransaction]

View File

@ -9,7 +9,6 @@ from typing import AsyncIterator, ClassVar, List, Optional, Tuple, Union
import anyio import anyio
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from httpx import URL, AsyncClient, HTTPError, Response from httpx import URL, AsyncClient, HTTPError, Response
from pydantic import BaseConfig as PydanticBaseConfig
from pydantic import BaseModel as PydanticBaseModel from pydantic import BaseModel as PydanticBaseModel
from utils.log import logger from utils.log import logger
@ -29,14 +28,9 @@ class Model(PydanticBaseModel):
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
# 让每次new的时候都解析 # 让每次new的时候都解析
cls.update_forward_refs() cls.model_rebuild()
return super(Model, cls).__new__(cls) # pylint: disable=E1120 return super(Model, cls).__new__(cls) # pylint: disable=E1120
class Config(PydanticBaseConfig):
# 使用 ujson 作为解析库
json_dumps = jsonlib.dumps
json_loads = jsonlib.loads
class WikiModel(Model): class WikiModel(Model):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences

View File

@ -57,7 +57,7 @@ class CharacterIcon(Model):
icon: str icon: str
side: str side: str
gacha: str gacha: str
splash: Optional[str] splash: Optional[str] = None
class Character(WikiModel): class Character(WikiModel):
@ -83,7 +83,7 @@ class Character(WikiModel):
association: Association association: Association
weapon_type: WeaponType weapon_type: WeaponType
element: Element element: Element
birth: Optional[Birth] birth: Optional[Birth] = None
constellation: str constellation: str
cn_cv: str cn_cv: str
jp_cv: str jp_cv: str

View File

@ -34,7 +34,7 @@ class WeaponAffix(Model):
class WeaponState(Model): class WeaponState(Model):
level: str level: str
ATK: float ATK: float
bonus: Optional[str] bonus: Optional[str] = None
class WeaponIcon(Model): class WeaponIcon(Model):
@ -58,11 +58,11 @@ class Weapon(WikiModel):
weapon_type: WeaponType weapon_type: WeaponType
attack: float attack: float
attribute: Optional[WeaponAttribute] attribute: Optional[WeaponAttribute] = None
affix: Optional[WeaponAffix] affix: Optional[WeaponAffix] = None
description: str description: str
ascension: List[str] ascension: List[str]
story: Optional[str] story: Optional[str] = None
stats: List[WeaponState] stats: List[WeaponState]

View File

@ -14,8 +14,8 @@ class GenshinBannerType(Enum):
class GachaBanner(BaseModel): class GachaBanner(BaseModel):
weight4 = ((1, 510), (8, 510), (10, 10000)) weight4: tuple[tuple[int, int]] = ((1, 510), (8, 510), (10, 10000))
weight5 = ((1, 60), (73, 60), (90, 10000)) weight5: tuple[tuple[int, int]] = ((1, 60), (73, 60), (90, 10000))
fallback_items3: List[int] = [ fallback_items3: List[int] = [
11301, 11301,
11302, 11302,

View File

@ -25,7 +25,7 @@ from telegram.helpers import escape_markdown
from core.config import config from core.config import config
from core.plugin import Plugin, conversation, handler from core.plugin import Plugin, conversation, handler
from gram_core.basemodel import Settings from gram_core.basemodel import Settings, SettingsConfigDict
from gram_core.dependence.redisdb import RedisDB from gram_core.dependence.redisdb import RedisDB
from modules.apihelper.client.components.hoyolab import Hoyolab from modules.apihelper.client.components.hoyolab import Hoyolab
from modules.apihelper.client.components.hyperion import Hyperion, HyperionBase from modules.apihelper.client.components.hyperion import Hyperion, HyperionBase
@ -55,8 +55,7 @@ class PostConfig(Settings):
chat_id: Optional[int] = 0 chat_id: Optional[int] = 0
class Config(Settings.Config): model_config = SettingsConfigDict(env_prefix="post_")
env_prefix = "post_"
CHECK_POST, SEND_POST, CHECK_COMMAND, GTE_DELETE_PHOTO = range(10900, 10904) CHECK_POST, SEND_POST, CHECK_COMMAND, GTE_DELETE_PHOTO = range(10900, 10904)

View File

@ -11,7 +11,7 @@ from utils.log import logger
class WebAppData(BaseModel): class WebAppData(BaseModel):
path: str path: str
data: Optional[dict] data: Optional[dict] = None
code: int code: int
message: str message: str

View File

@ -3,12 +3,10 @@
import asyncio import asyncio
import math import math
import re import re
from datetime import datetime
from functools import lru_cache, partial from functools import lru_cache, partial
from typing import Any, Coroutine, List, Optional, Tuple, Union, Dict from typing import Any, Coroutine, List, Optional, Tuple, Union, Dict
from arkowrapper import ArkoWrapper from arkowrapper import ArkoWrapper
from pytz import timezone
from simnet import GenshinClient from simnet import GenshinClient
from simnet.models.genshin.chronicle.abyss import SpiralAbyss from simnet.models.genshin.chronicle.abyss import SpiralAbyss
from telegram import Message, Update, InlineKeyboardButton, InlineKeyboardMarkup from telegram import Message, Update, InlineKeyboardButton, InlineKeyboardMarkup
@ -36,8 +34,6 @@ try:
except ImportError: except ImportError:
import json as jsonlib import json as jsonlib
TZ = timezone("Asia/Shanghai")
get_args_pattern = re.compile(r"\d+") get_args_pattern = re.compile(r"\d+")
@ -206,11 +202,6 @@ class AbyssPlugin(Plugin):
bytes格式的图片 bytes格式的图片
""" """
def json_encoder(value):
if isinstance(value, datetime):
return value.astimezone(TZ).strftime("%Y-%m-%d %H:%M:%S")
return value
if not abyss_data.unlocked: if not abyss_data.unlocked:
raise AbyssUnlocked raise AbyssUnlocked
if not abyss_data.ranks.most_kills: if not abyss_data.ranks.most_kills:
@ -220,13 +211,13 @@ class AbyssPlugin(Plugin):
if (total or (floor > 0)) and len(abyss_data.floors[0].chambers[0].battles) == 0: if (total or (floor > 0)) and len(abyss_data.floors[0].chambers[0].battles) == 0:
raise AbyssNotFoundError raise AbyssNotFoundError
start_time = abyss_data.start_time.astimezone(TZ) start_time = abyss_data.start_time
time = start_time.strftime("%Y年%m月") + ("" if start_time.day <= 15 else "") time = start_time.strftime("%Y年%m月") + ("" if start_time.day <= 15 else "")
stars = [i.stars for i in filter(lambda x: x.floor > 8, abyss_data.floors)] stars = [i.stars for i in filter(lambda x: x.floor > 8, abyss_data.floors)]
total_stars = f"{sum(stars)} ({'-'.join(map(str, stars))})" total_stars = f"{sum(stars)} ({'-'.join(map(str, stars))})"
render_data = {} render_data = {}
result = abyss_data.json(encoder=json_encoder) result = abyss_data.model_dump_json()
render_data["time"] = time render_data["time"] = time
render_data["stars"] = total_stars render_data["stars"] = total_stars
@ -340,7 +331,7 @@ class AbyssPlugin(Plugin):
@staticmethod @staticmethod
def get_season_data_name(data: "HistoryDataAbyss"): def get_season_data_name(data: "HistoryDataAbyss"):
start_time = data.abyss_data.start_time.astimezone(TZ) start_time = data.abyss_data.start_time
time = start_time.strftime("%Y.%m ")[2:] + ("" if start_time.day <= 15 else "") time = start_time.strftime("%Y.%m ")[2:] + ("" if start_time.day <= 15 else "")
honor = "" honor = ""
if data.abyss_data.total_stars == 36: if data.abyss_data.total_stars == 36:

View File

@ -16,7 +16,7 @@ import bs4
import pydantic import pydantic
from arkowrapper import ArkoWrapper from arkowrapper import ArkoWrapper
from httpx import AsyncClient, HTTPError, TimeoutException from httpx import AsyncClient, HTTPError, TimeoutException
from pydantic import BaseModel from pydantic import BaseModel, RootModel
from simnet.errors import BadRequest as SimnetBadRequest from simnet.errors import BadRequest as SimnetBadRequest
from simnet.errors import InvalidCookies from simnet.errors import InvalidCookies
from simnet.models.genshin.chronicle.characters import Character from simnet.models.genshin.chronicle.characters import Character
@ -118,16 +118,66 @@ def get_material_serial_name(names: Iterable[str]) -> str:
return result return result
class MaterialsData(BaseModel): class AreaDailyMaterialsData(BaseModel):
__root__: Optional[List[Dict[str, "AreaDailyMaterialsData"]]] = None """
AreaDailyMaterialsData 储存某一天某个国家所有可以刷的突破素材以及可以突破的角色和武器
对应 /daily_material 命令返回的图中一个国家横向这一整条的信息
"""
avatar_materials: List[str] = []
"""
avatar_materials 是当日该国所有可以刷的精通和炼武素材的 ID 列表
举个例子稻妻周三可以刷天光系列材料
不用蒙德璃月举例是因为它们每天的角色武器太多了等稻妻多了再换
那么 avatar_materials 将会包括
- 104326 天光的教导
- 104327 天光的指引
- 104328 天光的哲学
"""
avatar: List[str] = []
"""
avatar 是排除旅行者后该国当日可以突破天赋的角色 ID 列表
举个例子稻妻周三可以刷天光系列精通素材
需要用到天光系列的角色有
- 10000052 雷电将军
- 10000053 早柚
- 10000055 五郎
- 10000058 八重神子
"""
weapon_materials: List[str] = []
"""
weapon_materials 是当日该国所有可以刷的炼武素材的 ID 列表
举个例子稻妻周三可以刷今昔剧画系列材料
那么 weapon_materials 将会包括
- 114033 今昔剧画之恶尉
- 114034 今昔剧画之虎啮
- 114035 今昔剧画之一角
- 114036 今昔剧画之鬼人
"""
weapon: List[str] = []
"""
weapon 是该国当日可以突破天赋的武器 ID 列表
举个例子稻妻周三可以刷今昔剧画系列炼武素材
需要用到今昔剧画系列的武器有
- 11416 笼钓瓶一心
- 13414 喜多院十文字
- 13415 渔获
- 13416 断浪长鳍
- 13509 薙草之稻光
- 14509 神乐之真意
"""
class MaterialsData(RootModel):
root: Optional[List[Dict[str, "AreaDailyMaterialsData"]]] = None
def weekday(self, weekday: int) -> Dict[str, "AreaDailyMaterialsData"]: def weekday(self, weekday: int) -> Dict[str, "AreaDailyMaterialsData"]:
if self.__root__ is None: if self.root is None:
return {} return {}
return self.__root__[weekday] return self.root[weekday]
def is_empty(self) -> bool: def is_empty(self) -> bool:
return self.__root__ is None return self.root is None
class DailyMaterial(Plugin): class DailyMaterial(Plugin):
@ -681,7 +731,7 @@ def _parse_honey_impact_source(source: bytes) -> MaterialsData:
ascendable_items = everyday_materials[weekday][current_country] ascendable_items = everyday_materials[weekday][current_country]
ascendable_items = ascendable_items.weapon if item_is_weapon else ascendable_items.avatar ascendable_items = ascendable_items.weapon if item_is_weapon else ascendable_items.avatar
ascendable_items.append(item_id) ascendable_items.append(item_id)
return MaterialsData(__root__=everyday_materials) return MaterialsData.model_validate(everyday_materials)
class FragileGenshinClient: class FragileGenshinClient:
@ -716,7 +766,7 @@ class AreaData(BaseModel):
name: str # 区域名 name: str # 区域名
material_name: str # 区域的材料系列名 material_name: str # 区域的材料系列名
materials: List[ItemData] = [] # 区域材料 materials: List[ItemData] = [] # 区域材料
items: Iterable[ItemData] = [] # 可培养的角色或武器 items: List[ItemData] = [] # 可培养的角色或武器
class RenderData(BaseModel): class RenderData(BaseModel):
@ -735,56 +785,3 @@ class UserOwned(BaseModel):
"""角色 ID 到角色对象的映射""" """角色 ID 到角色对象的映射"""
weapon: Dict[str, List[ItemData]] = {} weapon: Dict[str, List[ItemData]] = {}
"""用户同时可以拥有多把同名武器,因此是 ID 到 List 的映射""" """用户同时可以拥有多把同名武器,因此是 ID 到 List 的映射"""
class AreaDailyMaterialsData(BaseModel):
"""
AreaDailyMaterialsData 储存某一天某个国家所有可以刷的突破素材以及可以突破的角色和武器
对应 /daily_material 命令返回的图中一个国家横向这一整条的信息
"""
avatar_materials: List[str] = []
"""
avatar_materials 是当日该国所有可以刷的精通和炼武素材的 ID 列表
举个例子稻妻周三可以刷天光系列材料
不用蒙德璃月举例是因为它们每天的角色武器太多了等稻妻多了再换
那么 avatar_materials 将会包括
- 104326 天光的教导
- 104327 天光的指引
- 104328 天光的哲学
"""
avatar: List[str] = []
"""
avatar 是排除旅行者后该国当日可以突破天赋的角色 ID 列表
举个例子稻妻周三可以刷天光系列精通素材
需要用到天光系列的角色有
- 10000052 雷电将军
- 10000053 早柚
- 10000055 五郎
- 10000058 八重神子
"""
weapon_materials: List[str] = []
"""
weapon_materials 是当日该国所有可以刷的炼武素材的 ID 列表
举个例子稻妻周三可以刷今昔剧画系列材料
那么 weapon_materials 将会包括
- 114033 今昔剧画之恶尉
- 114034 今昔剧画之虎啮
- 114035 今昔剧画之一角
- 114036 今昔剧画之鬼人
"""
weapon: List[str] = []
"""
weapon 是该国当日可以突破天赋的武器 ID 列表
举个例子稻妻周三可以刷今昔剧画系列炼武素材
需要用到今昔剧画系列的武器有
- 11416 笼钓瓶一心
- 13414 喜多院十文字
- 13415 渔获
- 13416 断浪长鳍
- 13509 薙草之稻光
- 14509 神乐之真意
"""
MaterialsData.update_forward_refs()

View File

@ -2,7 +2,7 @@ from decimal import Decimal
from enum import Enum from enum import Enum
from typing import Optional, List, NewType from typing import Optional, List, NewType
from pydantic import BaseModel, Field, validator from pydantic import field_validator, BaseModel, Field, ValidationInfo
# TODO: 考虑自动生成Enum # TODO: 考虑自动生成Enum
Character = NewType("Character", str) Character = NewType("Character", str)
@ -72,15 +72,17 @@ class WeaponInfo(BaseModel):
refinement: int = 0 refinement: int = 0
ascension: int = 0 ascension: int = 0
@validator("max_level") @field_validator("max_level")
def validate_max_level(cls, v, values): @classmethod
def validate_max_level(cls, v, info: ValidationInfo):
if v == 0: if v == 0:
return values["level"] return info.data["level"]
if v < values["level"]: if v < info.data["level"]:
raise ValueError("max_level must be greater than or equal to level") raise ValueError("max_level must be greater than or equal to level")
return v return v
@validator("refinement") @field_validator("refinement")
@classmethod
def validate_refinement(cls, v): def validate_refinement(cls, v):
if v < 0 or v > 5: if v < 0 or v > 5:
raise ValueError("refinement must be between 1 and 5") raise ValueError("refinement must be between 1 and 5")
@ -96,19 +98,22 @@ class Artifact(BaseModel):
main_attribute: ArtifactAttribute main_attribute: ArtifactAttribute
sub_attributes: List[ArtifactAttribute] = [] sub_attributes: List[ArtifactAttribute] = []
@validator("level") @field_validator("level")
@classmethod
def validate_level(cls, v): def validate_level(cls, v):
if v < 0 or v > 20: if v < 0 or v > 20:
raise ValueError("level must be between 0 and 20") raise ValueError("level must be between 0 and 20")
return v return v
@validator("rarity") @field_validator("rarity")
@classmethod
def validate_rarity(cls, v): def validate_rarity(cls, v):
if v < 0 or v > 5: if v < 0 or v > 5:
raise ValueError("rarity must be between 0 and 5") raise ValueError("rarity must be between 0 and 5")
return v return v
@validator("sub_attributes") @field_validator("sub_attributes")
@classmethod
def validate_sub_attributes(cls, v): def validate_sub_attributes(cls, v):
if len(v) > 4: if len(v) > 4:
raise ValueError("sub_attributes must not be greater than 4") raise ValueError("sub_attributes must not be greater than 4")
@ -183,15 +188,17 @@ class CharacterInfo(BaseModel):
rarity: int = 0 rarity: int = 0
stats: CharacterStats = CharacterStats() stats: CharacterStats = CharacterStats()
@validator("max_level") @field_validator("max_level")
def validate_max_level(cls, v, values): @classmethod
def validate_max_level(cls, v, info: ValidationInfo):
if v == 0: if v == 0:
return values["level"] return info.data["level"]
if v < values["level"]: if v < info.data["level"]:
raise ValueError("max_level must be greater than or equal to level") raise ValueError("max_level must be greater than or equal to level")
return v return v
@validator("skills") @field_validator("skills")
@classmethod
def validate_skills(cls, v): def validate_skills(cls, v):
if len(v) > 3: if len(v) > 3:
raise ValueError("skills must not be greater than 3") raise ValueError("skills must not be greater than 3")

View File

@ -3,7 +3,7 @@ from typing import Any, NewType, List, Optional, Tuple, Dict
from gcsim_pypi.aliases import ARTIFACT_ALIASES, CHARACTER_ALIASES, WEAPON_ALIASES from gcsim_pypi.aliases import ARTIFACT_ALIASES, CHARACTER_ALIASES, WEAPON_ALIASES
from gcsim_pypi.availability import AVAILABLE_ARTIFACTS, AVAILABLE_CHARACTERS, AVAILABLE_WEAPONS from gcsim_pypi.availability import AVAILABLE_ARTIFACTS, AVAILABLE_CHARACTERS, AVAILABLE_WEAPONS
from pydantic import BaseModel, validator from pydantic import field_validator, BaseModel
GCSimCharacter = NewType("GCSimCharacter", str) GCSimCharacter = NewType("GCSimCharacter", str)
GCSimWeapon = NewType("GCSimWeapon", str) GCSimWeapon = NewType("GCSimWeapon", str)
@ -17,7 +17,8 @@ class GCSimWeaponInfo(BaseModel):
max_level: int = 20 max_level: int = 20
params: List[str] = [] params: List[str] = []
@validator("weapon") @field_validator("weapon")
@classmethod
def validate_weapon(cls, v): def validate_weapon(cls, v):
if v not in AVAILABLE_WEAPONS or v not in WEAPON_ALIASES: if v not in AVAILABLE_WEAPONS or v not in WEAPON_ALIASES:
raise ValueError(f"Not supported weapon: {v}") raise ValueError(f"Not supported weapon: {v}")
@ -29,7 +30,8 @@ class GCSimSetInfo(BaseModel):
count: int = 2 count: int = 2
params: List[str] = [] params: List[str] = []
@validator("set") @field_validator("set")
@classmethod
def validate_set(cls, v): def validate_set(cls, v):
if v not in AVAILABLE_ARTIFACTS or v not in ARTIFACT_ALIASES: if v not in AVAILABLE_ARTIFACTS or v not in ARTIFACT_ALIASES:
raise ValueError(f"Not supported set: {v}") raise ValueError(f"Not supported set: {v}")
@ -70,7 +72,8 @@ class GCSimCharacterInfo(BaseModel):
stats: GCSimCharacterStats = GCSimCharacterStats() stats: GCSimCharacterStats = GCSimCharacterStats()
params: List[str] = [] params: List[str] = []
@validator("character") @field_validator("character")
@classmethod
def validate_character(cls, v): def validate_character(cls, v):
if v not in AVAILABLE_CHARACTERS or v not in CHARACTER_ALIASES: if v not in AVAILABLE_CHARACTERS or v not in CHARACTER_ALIASES:
raise ValueError(f"Not supported character: {v}") raise ValueError(f"Not supported character: {v}")

View File

@ -2,7 +2,7 @@ import base64
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING, List, Optional, Union from typing import TYPE_CHECKING, List, Optional, Union
from pydantic import BaseModel, validator from pydantic import field_validator, BaseModel
from simnet import Region from simnet import Region
from simnet.errors import BadRequest as SimnetBadRequest, InvalidCookies, TimedOut as SimnetTimedOut from simnet.errors import BadRequest as SimnetBadRequest, InvalidCookies, TimedOut as SimnetTimedOut
from sqlalchemy.orm.exc import StaleDataError from sqlalchemy.orm.exc import StaleDataError
@ -30,7 +30,8 @@ class TaskDataBase(BaseModel):
class ResinData(TaskDataBase): class ResinData(TaskDataBase):
notice_num: Optional[int] = 140 notice_num: Optional[int] = 140
@validator("notice_num") @field_validator("notice_num")
@classmethod
def notice_num_validator(cls, v): def notice_num_validator(cls, v):
if v < 60 or v > 200: if v < 60 or v > 200:
raise ValueError("树脂提醒数值必须在 60 ~ 200 之间") raise ValueError("树脂提醒数值必须在 60 ~ 200 之间")
@ -40,7 +41,8 @@ class ResinData(TaskDataBase):
class RealmData(TaskDataBase): class RealmData(TaskDataBase):
notice_num: Optional[int] = 2000 notice_num: Optional[int] = 2000
@validator("notice_num") @field_validator("notice_num")
@classmethod
def notice_num_validator(cls, v): def notice_num_validator(cls, v):
if v < 100 or v > 2400: if v < 100 or v > 2400:
raise ValueError("洞天宝钱提醒数值必须在 100 ~ 2400 之间") raise ValueError("洞天宝钱提醒数值必须在 100 ~ 2400 之间")
@ -54,7 +56,8 @@ class ExpeditionData(TaskDataBase):
class DailyData(TaskDataBase): class DailyData(TaskDataBase):
notice_hour: Optional[int] = 22 notice_hour: Optional[int] = 22
@validator("notice_hour") @field_validator("notice_hour")
@classmethod
def notice_hour_validator(cls, v): def notice_hour_validator(cls, v):
if v < 0 or v > 23: if v < 0 or v > 23:
raise ValueError("每日任务提醒时间必须在 0 ~ 23 之间") raise ValueError("每日任务提醒时间必须在 0 ~ 23 之间")
@ -65,10 +68,10 @@ class WebAppData(BaseModel):
user_id: int user_id: int
player_id: int player_id: int
resin: Optional[ResinData] resin: Optional[ResinData] = None
realm: Optional[RealmData] realm: Optional[RealmData] = None
expedition: Optional[ExpeditionData] expedition: Optional[ExpeditionData] = None
daily: Optional[DailyData] daily: Optional[DailyData] = None
class DailyNoteTaskUser: class DailyNoteTaskUser:

View File

@ -13,7 +13,7 @@ authors = [
{name = "SiHuaN"}, {name = "SiHuaN"},
] ]
dependencies = [ dependencies = [
"httpx<1.0.0,>=0.25.0", "httpx<1.0.0,>=0.28.0",
"ujson<6.0.0,>=5.9.0", "ujson<6.0.0,>=5.9.0",
"Jinja2<4.0.0,>=3.1.2", "Jinja2<4.0.0,>=3.1.2",
"python-telegram-bot[ext,rate-limiter]<22.0,>=21.7", "python-telegram-bot[ext,rate-limiter]<22.0,>=21.7",
@ -43,9 +43,11 @@ dependencies = [
"playwright==1.48.0", "playwright==1.48.0",
"aiosqlite<1.0.0,>=0.20.0", "aiosqlite<1.0.0,>=0.20.0",
"simnet @ git+https://github.com/PaiGramTeam/SIMNet", "simnet @ git+https://github.com/PaiGramTeam/SIMNet",
"gcsim-pypi<3.0.0,>=2.23.0", "gcsim-pypi<3.0.0,>=2.29.0",
"psutil<7.0.0,>=6.0.0", "psutil<7.0.0,>=6.0.0",
"influxdb-client[async,ciso]>=1.43.0", "influxdb-client[async,ciso]>=1.48.0",
"pydantic>=2.0.0,<3.0.0",
"pydantic-settings>=2.6.1",
] ]
requires-python = "<4.0,>=3.9" requires-python = "<4.0,>=3.9"
readme = "README.md" readme = "README.md"

View File

@ -3,16 +3,17 @@
aiocsv==1.3.2 aiocsv==1.3.2
aiofiles==24.1.0 aiofiles==24.1.0
aiohappyeyeballs==2.4.3 aiohappyeyeballs==2.4.3
aiohttp==3.10.10 aiohttp==3.11.8
aiolimiter==1.1.0 aiolimiter==1.1.0
aiosignal==1.3.1 aiosignal==1.3.1
aiosqlite==0.20.0 aiosqlite==0.20.0
alembic==1.14.0 alembic==1.14.0
annotated-types==0.7.0
anyio==4.6.2.post1 anyio==4.6.2.post1
apscheduler==3.10.4 apscheduler==3.10.4
arko-wrapper==0.3.0 arko-wrapper==0.3.0
async-lru==2.0.4 async-lru==2.0.4
async-timeout==4.0.3 ; python_full_version < '3.11.3' async-timeout==5.0.1 ; python_full_version < '3.11.3'
asyncmy==0.2.9 asyncmy==0.2.9
attrs==24.2.0 attrs==24.2.0
beautifulsoup4==4.12.3 beautifulsoup4==4.12.3
@ -25,23 +26,23 @@ click==8.1.7
colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows' colorama==0.4.6 ; sys_platform == 'win32' or platform_system == 'Windows'
colorlog==6.9.0 colorlog==6.9.0
cryptography==43.0.3 cryptography==43.0.3
enkanetwork-py @ git+https://github.com/PaiGramTeam/EnkaNetwork.py@0889dc2de8f216a0bcbe983bcc4ed71cd7917d6a enkanetwork-py @ git+https://github.com/PaiGramTeam/EnkaNetwork.py@28ca9bc889589699b543782a82c17584b33e537d
et-xmlfile==2.0.0 et-xmlfile==2.0.0
exceptiongroup==1.2.2 ; python_full_version < '3.11' exceptiongroup==1.2.2 ; python_full_version < '3.11'
fakeredis==2.26.1 fakeredis==2.26.1
fastapi==0.115.4 fastapi==0.115.5
flaky==3.8.1 flaky==3.8.1
frozenlist==1.5.0 frozenlist==1.5.0
gcsim-pypi==2.25.6 gcsim-pypi==2.29.2
gitdb==4.0.11 gitdb==4.0.11
gitpython==3.1.43 gitpython==3.1.43
greenlet==3.1.1 greenlet==3.1.1
h11==0.14.0 h11==0.14.0
httpcore==1.0.6 httpcore==1.0.7
httptools==0.6.4 httptools==0.6.4
httpx==0.27.2 httpx==0.28.0
idna==3.10 idna==3.10
influxdb-client==1.47.0 influxdb-client==1.48.0
iniconfig==2.0.0 iniconfig==2.0.0
jinja2==3.1.4 jinja2==3.1.4
lxml==5.3.0 lxml==5.3.0
@ -62,7 +63,9 @@ propcache==0.2.0
psutil==6.1.0 psutil==6.1.0
pyaes==1.6.1 pyaes==1.6.1
pycparser==2.22 ; platform_python_implementation != 'PyPy' pycparser==2.22 ; platform_python_implementation != 'PyPy'
pydantic==1.10.19 pydantic==2.10.2
pydantic-core==2.27.1
pydantic-settings==2.6.1
pyee==12.0.0 pyee==12.0.0
pygments==2.18.0 pygments==2.18.0
pyrogram==2.0.106 pyrogram==2.0.106
@ -79,9 +82,9 @@ rapidfuzz==3.10.1
reactivex==4.0.4 reactivex==4.0.4
redis==5.2.0 redis==5.2.0
rich==13.9.4 rich==13.9.4
sentry-sdk==2.18.0 sentry-sdk==2.19.0
setuptools==75.3.0 setuptools==75.6.0
simnet @ git+https://github.com/PaiGramTeam/SIMNet@745000612682f7b346d92b21f4d60502b92f477c simnet @ git+https://github.com/PaiGramTeam/SIMNet@d7756addb558356adc65e7e14dc86e0a3cb5d8bd
six==1.16.0 six==1.16.0
smmap==5.0.1 smmap==5.0.1
sniffio==1.3.1 sniffio==1.3.1
@ -89,18 +92,18 @@ sortedcontainers==2.4.0
soupsieve==2.6 soupsieve==2.6
sqlalchemy==2.0.36 sqlalchemy==2.0.36
sqlmodel==0.0.22 sqlmodel==0.0.22
starlette==0.41.2 starlette==0.41.3
tgcrypto==1.2.5 tgcrypto==1.2.5
thefuzz==0.22.1 thefuzz==0.22.1
tomli==2.0.2 ; python_full_version < '3.11' tomli==2.2.1 ; python_full_version < '3.11'
tornado==6.4.1 tornado==6.4.2
typing-extensions==4.12.2 typing-extensions==4.12.2
tzdata==2024.2 ; platform_system == 'Windows' tzdata==2024.2 ; platform_system == 'Windows'
tzlocal==5.2 tzlocal==5.2
ujson==5.10.0 ujson==5.10.0
urllib3==2.2.3 urllib3==2.2.3
uvicorn==0.32.0 uvicorn==0.32.1
uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32' uvloop==0.21.0 ; platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'
watchfiles==0.24.0 watchfiles==1.0.0
websockets==13.1 websockets==14.1
yarl==1.17.1 yarl==1.18.0

View File

@ -1,8 +1,8 @@
from multiprocessing import RLock as Lock from multiprocessing import RLock as Lock
from pathlib import Path from pathlib import Path
from typing import List, Literal, Optional, Union from typing import List, Literal, Optional, Union, ClassVar
from pydantic import BaseSettings from pydantic_settings import BaseSettings
from utils.const import PROJECT_ROOT from utils.const import PROJECT_ROOT
@ -10,13 +10,13 @@ __all__ = ("LoggerConfig",)
class LoggerConfig(BaseSettings): class LoggerConfig(BaseSettings):
_lock = Lock() _lock: ClassVar[Lock] = Lock()
_instance: Optional["LoggerConfig"] = None _instance: ClassVar[Optional["LoggerConfig"]] = None
def __new__(cls, *args, **kwargs) -> "LoggerConfig": def __new__(cls, *args, **kwargs) -> "LoggerConfig":
with cls._lock: with cls._lock:
if cls._instance is None: if cls._instance is None:
cls.update_forward_refs() cls.model_rebuild()
result = super(LoggerConfig, cls).__new__(cls) # pylint: disable=E1120 result = super(LoggerConfig, cls).__new__(cls) # pylint: disable=E1120
result.__init__(*args, **kwargs) result.__init__(*args, **kwargs)
cls._instance = result cls._instance = result

897
uv.lock

File diff suppressed because it is too large Load Diff