From 8f23c5b9645e480cb1019c2010248904567f7f80 Mon Sep 17 00:00:00 2001 From: Nattsu39 Date: Thu, 11 Dec 2025 07:37:54 +0000 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E6=88=90=E5=B0=B1=E5=8F=8A=E7=9B=B8=E5=85=B3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seerapi/_client.pyi | 58 +++++++++++++++++++++++++++++++++++++++++++ seerapi/_model_map.py | 12 ++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/seerapi/_client.pyi b/seerapi/_client.pyi index 3556266..4e8cae6 100644 --- a/seerapi/_client.pyi +++ b/seerapi/_client.pyi @@ -28,6 +28,24 @@ class SeerAPI: async def __aexit__(self, exc_type, exc_val, exc_tb) -> None: ... async def aclose(self) -> None: ... @overload + async def get( + self, resource_name: Literal['achievement'], id: int + ) -> M.Achievement: ... + @overload + async def get( + self, resource_name: Literal['achievement_branch'], id: int + ) -> M.AchievementBranch: ... + @overload + async def get( + self, resource_name: Literal['achievement_category'], id: int + ) -> M.AchievementCategory: ... + @overload + async def get( + self, resource_name: Literal['achievement_type'], id: int + ) -> M.AchievementType: ... + @overload + async def get(self, resource_name: Literal['title'], id: int) -> M.Title: ... + @overload async def get( self, resource_name: Literal['battle_effect'], id: int ) -> M.BattleEffect: ... @@ -200,6 +218,26 @@ class SeerAPI: self, resource_name: ResourceRef[T_ModelInstance] ) -> T_ModelInstance: ... @overload + async def paginated_list( + self, resource_name: Literal['achievement'], page_info: PageInfo + ) -> PagedResponse[M.Achievement]: ... + @overload + async def paginated_list( + self, resource_name: Literal['achievement_branch'], page_info: PageInfo + ) -> PagedResponse[M.AchievementBranch]: ... + @overload + async def paginated_list( + self, resource_name: Literal['achievement_category'], page_info: PageInfo + ) -> PagedResponse[M.AchievementCategory]: ... + @overload + async def paginated_list( + self, resource_name: Literal['achievement_type'], page_info: PageInfo + ) -> PagedResponse[M.AchievementType]: ... + @overload + async def paginated_list( + self, resource_name: Literal['title'], page_info: PageInfo + ) -> PagedResponse[M.Title]: ... + @overload async def paginated_list( self, resource_name: Literal['battle_effect'], page_info: PageInfo ) -> PagedResponse[M.BattleEffect]: ... @@ -388,6 +426,26 @@ class SeerAPI: self, resource_name: type[T_ModelInstance], page_info: PageInfo ) -> PagedResponse[T_ModelInstance]: ... @overload + async def list( + self, resource_name: Literal['achievement'] + ) -> AsyncGenerator[M.Achievement, None]: ... + @overload + async def list( + self, resource_name: Literal['achievement_branch'] + ) -> AsyncGenerator[M.AchievementBranch, None]: ... + @overload + async def list( + self, resource_name: Literal['achievement_category'] + ) -> AsyncGenerator[M.AchievementCategory, None]: ... + @overload + async def list( + self, resource_name: Literal['achievement_type'] + ) -> AsyncGenerator[M.AchievementType, None]: ... + @overload + async def list( + self, resource_name: Literal['title'] + ) -> AsyncGenerator[M.Title, None]: ... + @overload async def list( self, resource_name: Literal['battle_effect'] ) -> AsyncGenerator[M.BattleEffect, None]: ... diff --git a/seerapi/_model_map.py b/seerapi/_model_map.py index 542f422..fe7b692 100644 --- a/seerapi/_model_map.py +++ b/seerapi/_model_map.py @@ -1,10 +1,15 @@ from typing import Literal, TypeAlias, TypeVar import seerapi_models as M -from seerapi_models.common import BaseResModel +from seerapi_models.build_model import BaseResModel # 所有可用的模型路径名称 ModelName: TypeAlias = Literal[ + 'achievement', + 'achievement_branch', + 'achievement_category', + 'achievement_type', + 'title', 'battle_effect', 'battle_effect_type', 'pet_effect', @@ -59,6 +64,11 @@ T_ModelInstance = TypeVar('T_ModelInstance', bound=ModelInstance) MODEL_MAP: dict[ModelName, ModelType] = { + 'achievement': M.Achievement, + 'achievement_branch': M.AchievementBranch, + 'achievement_category': M.AchievementCategory, + 'achievement_type': M.AchievementType, + 'title': M.Title, 'battle_effect': M.BattleEffect, 'battle_effect_type': M.BattleEffectCategory, 'pet_effect': M.PetEffect, From b570427d3f41c5677c20f11e406a1fa81ff44ea9 Mon Sep 17 00:00:00 2001 From: Nattsu39 Date: Fri, 12 Dec 2025 19:50:04 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E9=80=9A=E8=BF=87=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E8=8E=B7=E5=8F=96=E8=B5=84=E6=BA=90=20(#1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 更新 seerapi-models 依赖版本至 1.2.2 * feat: 添加通过名称获取资源的方法,并更新类型提示以支持命名模型实例 --- pyproject.toml | 2 +- seerapi/_client.py | 31 ++++++++- seerapi/_client.pyi | 156 +++++++++++++++++++++++++++++++++++++++++- seerapi/_model_map.py | 65 +++++++++++++++--- 4 files changed, 237 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f95e119..e7bac04 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ requires-python = ">=3.10" dependencies = [ "hishel[httpx]>=1.1.5", "httpx>=0.28.1", - "seerapi-models>=1.0.2", + "seerapi-models>=1.2.2,<1.3.0", "typing-extensions>=4.15.0", ] diff --git a/seerapi/_client.py b/seerapi/_client.py index c38055b..c52e09c 100644 --- a/seerapi/_client.py +++ b/seerapi/_client.py @@ -5,14 +5,23 @@ from hishel.httpx import AsyncCacheClient from httpx import URL from httpx._urls import QueryParams -from seerapi_models.common import ResourceRef - -from seerapi._model_map import MODEL_MAP, ModelName, T_ModelInstance +from seerapi_models.common import NamedData, ResourceRef + +from seerapi._model_map import ( + MODEL_MAP, + ModelName, + NamedModelName, + T_ModelInstance, + T_NamedModelInstance, +) from seerapi._models import PagedResponse, PageInfo ResourceArg: TypeAlias = ( ModelName | type[T_ModelInstance] | ResourceRef[T_ModelInstance] ) +NamedResourceArg: TypeAlias = ( + NamedModelName | type[T_NamedModelInstance] | ResourceRef[T_NamedModelInstance] +) def _parse_url_params(url: str) -> QueryParams: @@ -145,3 +154,19 @@ async def create_generator(page_info: PageInfo): page_info = paged_response.next return create_generator(PageInfo(offset=0, limit=10)) + + async def get_by_name( + self, resource_name: NamedResourceArg[T_NamedModelInstance], name: str + ) -> NamedData[T_NamedModelInstance]: + res_name = self._get_resource_name(resource_name) + model_type = MODEL_MAP[res_name] + response = await self._client.get(f'/{res_name}/{name}') + response.raise_for_status() + return NamedData.model_validate( + { + 'data': { + id: model_type.model_validate(item) + for id, item in response.json()['data'].items() + } + } + ) diff --git a/seerapi/_client.pyi b/seerapi/_client.pyi index 4e8cae6..9c7f1c9 100644 --- a/seerapi/_client.pyi +++ b/seerapi/_client.pyi @@ -5,9 +5,9 @@ from typing_extensions import Self from hishel.httpx import AsyncCacheClient from httpx import URL import seerapi_models as M -from seerapi_models.common import ResourceRef +from seerapi_models.common import NamedData, ResourceRef -from seerapi._model_map import T_ModelInstance +from seerapi._model_map import T_ModelInstance, T_NamedModelInstance from seerapi._models import PagedResponse, PageInfo class SeerAPI: @@ -633,3 +633,155 @@ class SeerAPI: async def list( self, resource_name: type[T_ModelInstance] ) -> AsyncGenerator[T_ModelInstance, None]: ... + @overload + async def get_by_name( + self, resource_name: Literal['battle_effect'], name: str + ) -> NamedData[M.BattleEffect]: ... + @overload + async def get_by_name( + self, resource_name: Literal['battle_effect_type'], name: str + ) -> NamedData[M.BattleEffectCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_effect'], name: str + ) -> NamedData[M.PetEffect]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_effect_group'], name: str + ) -> NamedData[M.PetEffectGroup]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_variation'], name: str + ) -> NamedData[M.VariationEffect]: ... + @overload + async def get_by_name( + self, resource_name: Literal['energy_bead'], name: str + ) -> NamedData[M.EnergyBead]: ... + @overload + async def get_by_name( + self, resource_name: Literal['equip'], name: str + ) -> NamedData[M.Equip]: ... + @overload + async def get_by_name( + self, resource_name: Literal['suit'], name: str + ) -> NamedData[M.Suit]: ... + @overload + async def get_by_name( + self, resource_name: Literal['equip_type'], name: str + ) -> NamedData[M.EquipType]: ... + @overload + async def get_by_name( + self, resource_name: Literal['soulmark_tag'], name: str + ) -> NamedData[M.SoulmarkTagCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['element_type'], name: str + ) -> NamedData[M.ElementType]: ... + @overload + async def get_by_name( + self, resource_name: Literal['element_type_combination'], name: str + ) -> NamedData[M.TypeCombination]: ... + @overload + async def get_by_name( + self, resource_name: Literal['item'], name: str + ) -> NamedData[M.Item]: ... + @overload + async def get_by_name( + self, resource_name: Literal['item_category'], name: str + ) -> NamedData[M.ItemCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['gem'], name: str + ) -> NamedData[M.Gem]: ... + @overload + async def get_by_name( + self, resource_name: Literal['gem_category'], name: str + ) -> NamedData[M.GemCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_activation_item'], name: str + ) -> NamedData[M.SkillActivationItem]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_stone'], name: str + ) -> NamedData[M.SkillStone]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_stone_category'], name: str + ) -> NamedData[M.SkillStoneCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['mintmark'], name: str + ) -> NamedData[M.Mintmark]: ... + @overload + async def get_by_name( + self, resource_name: Literal['ability_mintmark'], name: str + ) -> NamedData[M.AbilityMintmark]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_mintmark'], name: str + ) -> NamedData[M.SkillMintmark]: ... + @overload + async def get_by_name( + self, resource_name: Literal['universal_mintmark'], name: str + ) -> NamedData[M.UniversalMintmark]: ... + @overload + async def get_by_name( + self, resource_name: Literal['mintmark_class'], name: str + ) -> NamedData[M.MintmarkClassCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['mintmark_type'], name: str + ) -> NamedData[M.MintmarkTypeCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet'], name: str + ) -> NamedData[M.Pet]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_gender'], name: str + ) -> NamedData[M.PetGenderCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_vipbuff'], name: str + ) -> NamedData[M.PetVipBuffCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_mount_type'], name: str + ) -> NamedData[M.PetMountTypeCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_skin'], name: str + ) -> NamedData[M.PetSkin]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_archive_story_book'], name: str + ) -> NamedData[M.PetArchiveStoryBook]: ... + @overload + async def get_by_name( + self, resource_name: Literal['pet_encyclopedia_entry'], name: str + ) -> NamedData[M.PetEncyclopediaEntry]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill'], name: str + ) -> NamedData[M.Skill]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_hide_effect'], name: str + ) -> NamedData[M.SkillHideEffect]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_category'], name: str + ) -> NamedData[M.SkillCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['skill_effect_type_tag'], name: str + ) -> NamedData[M.SkillEffectTypeTag]: ... + @overload + async def get_by_name( + self, resource_name: type[T_NamedModelInstance], name: str + ) -> NamedData[T_NamedModelInstance]: ... + @overload + async def get_by_name( + self, resource_name: ResourceRef[T_NamedModelInstance], name: str + ) -> NamedData[T_NamedModelInstance]: ... diff --git a/seerapi/_model_map.py b/seerapi/_model_map.py index fe7b692..0944cbd 100644 --- a/seerapi/_model_map.py +++ b/seerapi/_model_map.py @@ -3,8 +3,7 @@ import seerapi_models as M from seerapi_models.build_model import BaseResModel -# 所有可用的模型路径名称 -ModelName: TypeAlias = Literal[ +NamedModelName: TypeAlias = Literal[ 'achievement', 'achievement_branch', 'achievement_category', @@ -19,8 +18,6 @@ 'equip', 'suit', 'equip_type', - 'equip_effective_occasion', - 'soulmark', 'soulmark_tag', 'element_type', 'element_type_combination', @@ -28,7 +25,6 @@ 'item_category', 'gem', 'gem_category', - 'gem_generation_category', 'skill_activation_item', 'skill_stone', 'skill_stone_category', @@ -38,30 +34,77 @@ 'universal_mintmark', 'mintmark_class', 'mintmark_type', - 'mintmark_rarity', 'pet', - 'pet_class', 'pet_gender', 'pet_vipbuff', 'pet_mount_type', 'pet_skin', - 'pet_skin_category', - 'pet_archive_story_entry', 'pet_archive_story_book', 'pet_encyclopedia_entry', 'skill', - 'skill_effect_type', - 'skill_effect_param', 'skill_hide_effect', 'skill_category', 'skill_effect_type_tag', +] + +# 所有可用的模型路径名称 +ModelName: TypeAlias = Literal[ + NamedModelName, + 'equip_effective_occasion', + 'soulmark', + 'gem_generation_category', + 'mintmark_rarity', + 'pet_class', + 'pet_skin_category', + 'pet_archive_story_entry', + 'skill_effect_type', + 'skill_effect_param', 'eid_effect', ] ModelInstance: TypeAlias = BaseResModel +NamedModelInstance: TypeAlias = ( + M.BattleEffect + | M.BattleEffectCategory + | M.PetEffect + | M.PetEffectGroup + | M.VariationEffect + | M.EnergyBead + | M.Equip + | M.Suit + | M.EquipType + | M.SoulmarkTagCategory + | M.ElementType + | M.TypeCombination + | M.Item + | M.ItemCategory + | M.Gem + | M.GemCategory + | M.SkillActivationItem + | M.SkillStone + | M.SkillStoneCategory + | M.Mintmark + | M.AbilityMintmark + | M.SkillMintmark + | M.UniversalMintmark + | M.MintmarkClassCategory + | M.MintmarkTypeCategory + | M.Pet + | M.PetGenderCategory + | M.PetVipBuffCategory + | M.PetMountTypeCategory + | M.PetSkin + | M.PetArchiveStoryBook + | M.PetEncyclopediaEntry + | M.Skill + | M.SkillHideEffect + | M.SkillCategory + | M.SkillEffectTypeTag +) ModelType: TypeAlias = type[ModelInstance] T_ModelInstance = TypeVar('T_ModelInstance', bound=ModelInstance) +T_NamedModelInstance = TypeVar('T_NamedModelInstance', bound=NamedModelInstance) MODEL_MAP: dict[ModelName, ModelType] = { 'achievement': M.Achievement, From db861150174c5deccf9656bf97940a0874cae6a3 Mon Sep 17 00:00:00 2001 From: Nattsu39 Date: Thu, 11 Dec 2025 07:37:54 +0000 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=AF=B9?= =?UTF-8?q?=E6=88=90=E5=B0=B1=E5=8F=8A=E7=9B=B8=E5=85=B3=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- seerapi/_client.pyi | 20 ++++++++++++++++++++ seerapi/_model_map.py | 7 ++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/seerapi/_client.pyi b/seerapi/_client.pyi index 9c7f1c9..7f68768 100644 --- a/seerapi/_client.pyi +++ b/seerapi/_client.pyi @@ -634,6 +634,26 @@ class SeerAPI: self, resource_name: type[T_ModelInstance] ) -> AsyncGenerator[T_ModelInstance, None]: ... @overload + async def get_by_name( + self, resource_name: Literal['achievement'], name: str + ) -> NamedData[M.Achievement]: ... + @overload + async def get_by_name( + self, resource_name: Literal['achievement_branch'], name: str + ) -> NamedData[M.AchievementBranch]: ... + @overload + async def get_by_name( + self, resource_name: Literal['achievement_category'], name: str + ) -> NamedData[M.AchievementCategory]: ... + @overload + async def get_by_name( + self, resource_name: Literal['achievement_type'], name: str + ) -> NamedData[M.AchievementType]: ... + @overload + async def get_by_name( + self, resource_name: Literal['title'], name: str + ) -> NamedData[M.Title]: ... + @overload async def get_by_name( self, resource_name: Literal['battle_effect'], name: str ) -> NamedData[M.BattleEffect]: ... diff --git a/seerapi/_model_map.py b/seerapi/_model_map.py index 0944cbd..f33e793 100644 --- a/seerapi/_model_map.py +++ b/seerapi/_model_map.py @@ -64,7 +64,12 @@ ModelInstance: TypeAlias = BaseResModel NamedModelInstance: TypeAlias = ( - M.BattleEffect + M.Achievement + | M.AchievementBranch + | M.AchievementCategory + | M.AchievementType + | M.Title + | M.BattleEffect | M.BattleEffectCategory | M.PetEffect | M.PetEffectGroup