seerapi-models 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,406 @@
1
+ import inspect
2
+ from typing import (
3
+ TYPE_CHECKING,
4
+ ClassVar,
5
+ Generic,
6
+ TypeVar,
7
+ cast,
8
+ overload,
9
+ )
10
+ from typing_extensions import Self
11
+
12
+ from pydantic import (
13
+ ConfigDict,
14
+ computed_field,
15
+ )
16
+ from sqlalchemy.orm import column_property, declared_attr
17
+ from sqlmodel import JSON, Column, Computed, Field, Integer, Relationship
18
+
19
+ from ._utils import move_to_last
20
+ from .build_model import (
21
+ BaseGeneralModel,
22
+ BaseResModel,
23
+ BaseResModelWithOptionalId,
24
+ ConvertToORM,
25
+ )
26
+
27
+ if TYPE_CHECKING:
28
+ from .effect import PetEffectORM, VariationEffectORM
29
+ from .items import (
30
+ EnergyBeadORM,
31
+ EquipBonusORM,
32
+ GemORM,
33
+ SkillStoneEffectORM,
34
+ SuitBonusORM,
35
+ )
36
+ from .pet import SoulmarkORM
37
+ from .skill import SkillEffectType, SkillEffectTypeORM, SkillORM
38
+
39
+
40
+ TResModel = TypeVar('TResModel', bound=BaseResModel)
41
+ _TResModelArg = TypeVar('_TResModelArg', bound=BaseResModel)
42
+
43
+
44
+ class ResourceRef(BaseGeneralModel, Generic[TResModel]):
45
+ """API资源类"""
46
+
47
+ base_data_url: ClassVar[str] = ''
48
+
49
+ id: int = Field(description='资源ID')
50
+ resource_name: str = Field(description='资源类型名称', exclude=True)
51
+ path: str = Field(default='', description='资源路径', exclude=True)
52
+
53
+ @computed_field(description='资源URL')
54
+ @property
55
+ def url(self) -> str:
56
+ path_parts: list[str] = [
57
+ self.base_data_url,
58
+ self.resource_name,
59
+ str(self.id),
60
+ self.path,
61
+ ]
62
+ return '/'.join(path_parts)
63
+
64
+ @classmethod
65
+ def schema_path(cls) -> str:
66
+ return 'common/resource_ref/'
67
+
68
+ @overload
69
+ @classmethod
70
+ def from_model(
71
+ cls,
72
+ model: _TResModelArg,
73
+ *,
74
+ resource_name: str | None = None,
75
+ ) -> 'ResourceRef[_TResModelArg]': ...
76
+
77
+ @overload
78
+ @classmethod
79
+ def from_model(
80
+ cls,
81
+ model: type[_TResModelArg],
82
+ *,
83
+ id: int,
84
+ resource_name: str | None = None,
85
+ ) -> 'ResourceRef[_TResModelArg]': ...
86
+
87
+ @classmethod
88
+ def from_model(
89
+ cls,
90
+ model: type[_TResModelArg] | _TResModelArg,
91
+ *,
92
+ id: int | None = None,
93
+ resource_name: str | None = None,
94
+ ) -> 'ResourceRef[_TResModelArg]':
95
+ if not inspect.isclass(model):
96
+ id = model.id
97
+ if id is None:
98
+ raise ValueError('id is required')
99
+
100
+ resource_name = resource_name or model.resource_name()
101
+ obj = cls(id=id, resource_name=resource_name)
102
+ return cast(ResourceRef[_TResModelArg], obj)
103
+
104
+
105
+ class NamedResourceRef(ResourceRef[TResModel]):
106
+ name: str | None = Field(default=None, description='资源名称')
107
+
108
+ @classmethod
109
+ def schema_path(cls) -> str:
110
+ return 'common/named_resource_ref/'
111
+
112
+
113
+ class ApiResourceList(BaseGeneralModel, Generic[TResModel]):
114
+ """API资源列表,兼容RFC 5988的Link标准"""
115
+
116
+ count: int = Field(description='资源数量')
117
+ next: str | None = Field(default=None, description='下一页URL')
118
+ previous: str | None = Field(default=None, description='上一页URL')
119
+ first: str | None = Field(default=None, description='第一页URL')
120
+ last: str | None = Field(default=None, description='最后一页URL')
121
+ results: list[NamedResourceRef[TResModel]] = Field(description='资源列表')
122
+
123
+ @classmethod
124
+ def schema_path(cls) -> str:
125
+ return 'common/api_resource_list/'
126
+
127
+
128
+ class EidEffect(BaseResModel, BaseGeneralModel, ConvertToORM['EidEffectORM']):
129
+ # info: str | None = Field(
130
+ # default=None,
131
+ # description='效果描述,当效果描述为空时,该字段为null',
132
+ # )
133
+ args_num: int = Field(
134
+ description='效果需要的参数数量,该值是从使用该效果的资源中推测得出的'
135
+ )
136
+
137
+ @classmethod
138
+ def schema_path(cls) -> str:
139
+ return 'common/eid_effect/'
140
+
141
+ @classmethod
142
+ def resource_name(cls) -> str:
143
+ return 'eid_effect'
144
+
145
+ @classmethod
146
+ def get_orm_model(cls) -> type['EidEffectORM']:
147
+ return EidEffectORM
148
+
149
+ def to_orm(self) -> 'EidEffectORM':
150
+ return EidEffectORM(id=self.id, args_num=self.args_num)
151
+
152
+
153
+ class EidEffectORM(EidEffect, table=True):
154
+ in_use: list['EidEffectInUseORM'] = Relationship(back_populates='effect')
155
+
156
+
157
+ class EidEffectInUseBase(BaseResModelWithOptionalId):
158
+ effect_args: list[int] | None = Field(
159
+ default=None,
160
+ description='效果参数,当效果参数为空时,该字段为null',
161
+ sa_type=JSON,
162
+ )
163
+
164
+ @classmethod
165
+ def resource_name(cls) -> str:
166
+ return 'eid_effect_in_use'
167
+
168
+
169
+ class EidEffectInUse(
170
+ EidEffectInUseBase, BaseGeneralModel, ConvertToORM['EidEffectInUseORM']
171
+ ):
172
+ effect: 'ResourceRef[EidEffect]' = Field(description='效果引用')
173
+
174
+ @classmethod
175
+ def schema_path(cls) -> str:
176
+ return 'common/eid_effect_in_use/'
177
+
178
+ @classmethod
179
+ def get_orm_model(cls) -> type['EidEffectInUseORM']:
180
+ return EidEffectInUseORM
181
+
182
+ def to_orm(self) -> 'EidEffectInUseORM':
183
+ return EidEffectInUseORM(
184
+ id=self.id,
185
+ eid=self.effect.id,
186
+ effect_args=self.effect_args,
187
+ )
188
+
189
+
190
+ class EidEffectInUseORM(EidEffectInUseBase, table=True):
191
+ eid: int = Field(foreign_key='eid_effect.id')
192
+ effect: 'EidEffectORM' = Relationship(back_populates='in_use')
193
+ # 特性,魂印,装备效果,etc...
194
+ energy_bead: 'EnergyBeadORM' = Relationship(back_populates='effect_in_use')
195
+ soulmark: 'SoulmarkORM' = Relationship(back_populates='effect_in_use')
196
+ pet_effect: 'PetEffectORM' = Relationship(back_populates='effect_in_use')
197
+ variation_effect: 'VariationEffectORM' = Relationship(
198
+ back_populates='effect_in_use'
199
+ )
200
+ equip_bonus: 'EquipBonusORM' = Relationship(back_populates='effect_in_use')
201
+ suit_bonus: 'SuitBonusORM' = Relationship(back_populates='effect_in_use')
202
+
203
+
204
+ class SixAttributesBase(BaseResModelWithOptionalId, BaseGeneralModel):
205
+ """六维属性类"""
206
+
207
+ atk: int = Field(description='攻击')
208
+ def_: int = Field(
209
+ sa_type=Integer,
210
+ sa_column_kwargs={'name': 'def', 'nullable': False},
211
+ description='防御',
212
+ schema_extra={'serialization_alias': 'def'},
213
+ )
214
+ sp_atk: int = Field(description='特攻')
215
+ sp_def: int = Field(description='特防')
216
+ spd: int = Field(description='速度')
217
+ hp: int = Field(description='体力')
218
+
219
+ percent: bool = Field(
220
+ default=False,
221
+ description='该对象描述的是否是百分比加成,如果为true,属性值为省略百分比(%)符号的加成',
222
+ )
223
+
224
+ @classmethod
225
+ def schema_path(cls) -> str:
226
+ return 'common/six_attributes/'
227
+
228
+ @classmethod
229
+ def from_string(
230
+ cls, value: str, *, hp_first: bool = False, percent: bool = False
231
+ ) -> Self:
232
+ """从字符串创建六维属性对象"""
233
+ attributes = value.split(' ')
234
+ if len(attributes) < 6:
235
+ raise ValueError('无效的属性字符串')
236
+ attributes = [int(attribute) for attribute in attributes[0:6]]
237
+ return cls.from_list(attributes, hp_first=hp_first, percent=percent)
238
+
239
+ @classmethod
240
+ def from_list(
241
+ cls, attributes: list[int], *, hp_first: bool = False, percent: bool = False
242
+ ) -> Self:
243
+ if len(attributes) < 6:
244
+ raise ValueError('无效的属性列表')
245
+ if hp_first:
246
+ move_to_last(attributes, 0)
247
+ return cls(
248
+ atk=attributes[0],
249
+ def_=attributes[1],
250
+ sp_atk=attributes[2],
251
+ sp_def=attributes[3],
252
+ spd=attributes[4],
253
+ hp=attributes[5],
254
+ percent=percent,
255
+ )
256
+
257
+ def __add__(self, other) -> Self:
258
+ """两个六维属性相加"""
259
+ cls = type(self)
260
+ if not isinstance(other, cls):
261
+ return self
262
+ return cls(
263
+ atk=self.atk + other.atk,
264
+ def_=self.def_ + other.def_,
265
+ sp_atk=self.sp_atk + other.sp_atk,
266
+ sp_def=self.sp_def + other.sp_def,
267
+ spd=self.spd + other.spd,
268
+ hp=self.hp + other.hp,
269
+ )
270
+
271
+ def __sub__(self, other) -> Self:
272
+ """两个六维属性相减"""
273
+ cls = type(self)
274
+ if not isinstance(other, cls):
275
+ return self
276
+ return cls(
277
+ atk=self.atk - other.atk,
278
+ def_=self.def_ - other.def_,
279
+ sp_atk=self.sp_atk - other.sp_atk,
280
+ sp_def=self.sp_def - other.sp_def,
281
+ spd=self.spd - other.spd,
282
+ hp=self.hp - other.hp,
283
+ )
284
+
285
+ @classmethod
286
+ def resource_name(cls) -> str:
287
+ return 'six_attributes'
288
+
289
+
290
+ class SixAttributes(
291
+ SixAttributesBase, BaseGeneralModel, ConvertToORM['SixAttributesORM']
292
+ ):
293
+ @computed_field
294
+ @property
295
+ def total(self) -> int:
296
+ """总属性值"""
297
+ return self.atk + self.def_ + self.sp_atk + self.sp_def + self.spd + self.hp
298
+
299
+ @classmethod
300
+ def get_orm_model(cls) -> type['SixAttributesORM']:
301
+ return SixAttributesORM
302
+
303
+ def to_orm(self) -> 'SixAttributesORM':
304
+ return SixAttributesORM(
305
+ atk=self.atk,
306
+ def_=self.def_,
307
+ sp_atk=self.sp_atk,
308
+ sp_def=self.sp_def,
309
+ spd=self.spd,
310
+ hp=self.hp,
311
+ percent=self.percent,
312
+ )
313
+
314
+
315
+ class SixAttributesORM(SixAttributesBase):
316
+ model_config = ConfigDict(ignored_types=(declared_attr,)) # type: ignore
317
+
318
+ @declared_attr
319
+ def total(self): # type: ignore
320
+ return column_property(
321
+ Column(
322
+ Integer,
323
+ Computed('atk + def + sp_atk + sp_def + spd + hp'),
324
+ nullable=False,
325
+ )
326
+ )
327
+
328
+
329
+ class SkillEffectInUseBase(BaseResModelWithOptionalId):
330
+ """描述一条“使用中的”技能效果"""
331
+
332
+ info: str = Field(description='技能效果描述')
333
+ args: list[int | float] | list[int] | list[float] = Field(
334
+ description='技能效果参数列表',
335
+ sa_type=JSON,
336
+ )
337
+
338
+ @classmethod
339
+ def resource_name(cls) -> str:
340
+ return 'skill_effect_in_use'
341
+
342
+
343
+ class SkillEffectInUse(
344
+ SkillEffectInUseBase, BaseGeneralModel, ConvertToORM['SkillEffectInUseORM']
345
+ ):
346
+ effect: 'ResourceRef[SkillEffectType]'
347
+
348
+ @classmethod
349
+ def schema_path(cls) -> str:
350
+ return 'common/skill_effect_in_use/'
351
+
352
+ @classmethod
353
+ def get_orm_model(cls) -> type['SkillEffectInUseORM']:
354
+ return SkillEffectInUseORM
355
+
356
+ def to_orm(self) -> 'SkillEffectInUseORM':
357
+ return SkillEffectInUseORM(
358
+ effect_id=self.effect.id,
359
+ args=self.args,
360
+ info=self.info,
361
+ )
362
+
363
+
364
+ class SkillEffectInUseORM(SkillEffectInUseBase, table=True):
365
+ effect_id: int = Field(foreign_key='skill_effect_type.id')
366
+ effect: 'SkillEffectTypeORM' = Relationship(back_populates='in_use')
367
+ skill: list['SkillORM'] = Relationship(
368
+ sa_relationship_kwargs={
369
+ 'secondary': 'skilleffectlink',
370
+ }
371
+ )
372
+ friend_skill: list['SkillORM'] = Relationship(
373
+ sa_relationship_kwargs={
374
+ 'secondary': 'skillfriendskilleffectlink',
375
+ }
376
+ )
377
+ gem: list['GemORM'] = Relationship(
378
+ back_populates='skill_effect_in_use',
379
+ sa_relationship_kwargs={
380
+ 'secondary': 'gemeffectlink',
381
+ },
382
+ )
383
+ skill_stone_effect: list['SkillStoneEffectORM'] = Relationship(
384
+ back_populates='effect',
385
+ sa_relationship_kwargs={
386
+ 'secondary': 'skillstoneeffectlink',
387
+ },
388
+ )
389
+
390
+
391
+ __all__ = [
392
+ 'ApiResourceList',
393
+ 'EidEffect',
394
+ 'EidEffectInUse',
395
+ 'EidEffectInUseBase',
396
+ 'EidEffectInUseORM',
397
+ 'EidEffectORM',
398
+ 'NamedResourceRef',
399
+ 'ResourceRef',
400
+ 'SixAttributes',
401
+ 'SixAttributesBase',
402
+ 'SixAttributesORM',
403
+ 'SkillEffectInUse',
404
+ 'SkillEffectInUseBase',
405
+ 'SkillEffectInUseORM',
406
+ ]
@@ -0,0 +1,119 @@
1
+ from sqlmodel import Field, Relationship
2
+
3
+ from seerapi_models.build_model import BaseCategoryModel, BaseResModel, ConvertToORM
4
+ from seerapi_models.common import (
5
+ EidEffectInUse,
6
+ EidEffectInUseORM,
7
+ ResourceRef,
8
+ )
9
+
10
+
11
+ class EffectSeDataBase(BaseResModel):
12
+ name: str = Field(description='名称')
13
+ desc: str = Field(description='描述')
14
+
15
+
16
+ class EffectSeData(EffectSeDataBase):
17
+ effect: EidEffectInUse = Field(description='效果')
18
+
19
+
20
+ class EffectSeDataORM(EffectSeDataBase):
21
+ effect_in_use_id: int | None = Field(
22
+ default=None, foreign_key='eid_effect_in_use.id'
23
+ )
24
+
25
+
26
+ class VariationEffectBase(BaseResModel):
27
+ @classmethod
28
+ def resource_name(cls) -> str:
29
+ return 'pet_variation'
30
+
31
+
32
+ class VariationEffect(
33
+ VariationEffectBase, EffectSeData, ConvertToORM['VariationEffectORM']
34
+ ):
35
+ """特质效果"""
36
+
37
+ @classmethod
38
+ def get_orm_model(cls) -> type['VariationEffectORM']:
39
+ return VariationEffectORM
40
+
41
+ def to_orm(self) -> 'VariationEffectORM':
42
+ return VariationEffectORM(
43
+ id=self.id,
44
+ name=self.name,
45
+ desc=self.desc,
46
+ effect_in_use=self.effect.to_orm(),
47
+ )
48
+
49
+
50
+ class VariationEffectORM(VariationEffectBase, EffectSeDataORM, table=True):
51
+ effect_in_use: 'EidEffectInUseORM' = Relationship(
52
+ back_populates='variation_effect',
53
+ )
54
+
55
+
56
+ class PetEffectBase(EffectSeDataBase):
57
+ star_level: int = Field(description='特性星级')
58
+
59
+ @classmethod
60
+ def resource_name(cls) -> str:
61
+ return 'pet_effect'
62
+
63
+
64
+ class PetEffect(PetEffectBase, EffectSeData, ConvertToORM['PetEffectORM']):
65
+ effect_group: ResourceRef['PetEffectGroup'] = Field(
66
+ description='特性组资源引用,同特性的不同星级属于同一组'
67
+ )
68
+
69
+ @classmethod
70
+ def get_orm_model(cls) -> type['PetEffectORM']:
71
+ return PetEffectORM
72
+
73
+ def to_orm(self) -> 'PetEffectORM':
74
+ return PetEffectORM(
75
+ id=self.id,
76
+ name=self.name,
77
+ desc=self.desc,
78
+ effect_in_use=self.effect.to_orm(),
79
+ star_level=self.star_level,
80
+ effect_group_id=self.effect_group.id,
81
+ )
82
+
83
+
84
+ class PetEffectORM(PetEffectBase, EffectSeDataORM, table=True):
85
+ effect_in_use: 'EidEffectInUseORM' = Relationship(
86
+ back_populates='pet_effect',
87
+ )
88
+ effect_group_id: int = Field(foreign_key='pet_effect_group.id')
89
+ effect_group: 'PetEffectGroupORM' = Relationship(back_populates='effect')
90
+
91
+
92
+ class PetEffectGroupBase(BaseCategoryModel):
93
+ name: str = Field(description='名称')
94
+
95
+ @classmethod
96
+ def resource_name(cls) -> str:
97
+ return 'pet_effect_group'
98
+
99
+
100
+ class PetEffectGroup(PetEffectGroupBase, ConvertToORM['PetEffectGroupORM']):
101
+ effect: list[ResourceRef[PetEffect]] = Field(
102
+ default_factory=list, description='特性列表'
103
+ )
104
+
105
+ @classmethod
106
+ def get_orm_model(cls) -> type['PetEffectGroupORM']:
107
+ return PetEffectGroupORM
108
+
109
+ def to_orm(self) -> 'PetEffectGroupORM':
110
+ return PetEffectGroupORM(
111
+ id=self.id,
112
+ name=self.name,
113
+ )
114
+
115
+
116
+ class PetEffectGroupORM(PetEffectGroupBase, table=True):
117
+ effect: list['PetEffectORM'] = Relationship(
118
+ back_populates='effect_group',
119
+ )
@@ -0,0 +1,118 @@
1
+ from typing import TYPE_CHECKING
2
+
3
+ from pydantic import computed_field
4
+ from sqlmodel import Boolean, Column, Computed, Field, Relationship
5
+
6
+ from seerapi_models.build_model import BaseResModel, ConvertToORM
7
+ from seerapi_models.common import ResourceRef
8
+
9
+ if TYPE_CHECKING:
10
+ from .items.skill_stone import SkillStoneCategoryORM
11
+ from .pet import PetORM
12
+ from .skill import SkillORM
13
+
14
+
15
+ class ElementType(BaseResModel, ConvertToORM['ElementTypeORM']):
16
+ name: str = Field(description='属性中文名')
17
+ name_en: str = Field(description='属性英文名')
18
+
19
+ @classmethod
20
+ def resource_name(cls) -> str:
21
+ return 'element_type'
22
+
23
+ @classmethod
24
+ def get_orm_model(cls) -> type['ElementTypeORM']:
25
+ return ElementTypeORM
26
+
27
+ def to_orm(self) -> 'ElementTypeORM':
28
+ return ElementTypeORM(
29
+ id=self.id,
30
+ name=self.name,
31
+ name_en=self.name_en,
32
+ )
33
+
34
+
35
+ class ElementTypeORM(ElementType, table=True):
36
+ primary_combination: list['TypeCombinationORM'] = Relationship(
37
+ back_populates='primary',
38
+ sa_relationship_kwargs={
39
+ 'primaryjoin': 'ElementTypeORM.id == TypeCombinationORM.primary_id',
40
+ 'viewonly': True,
41
+ },
42
+ )
43
+ secondary_combination: list['TypeCombinationORM'] = Relationship(
44
+ back_populates='secondary',
45
+ sa_relationship_kwargs={
46
+ 'primaryjoin': 'ElementTypeORM.id == TypeCombinationORM.secondary_id',
47
+ 'viewonly': True,
48
+ },
49
+ )
50
+
51
+
52
+ class TypeCombinationBase(BaseResModel):
53
+ name: str = Field(description='组合类型中文名')
54
+ name_en: str = Field(description='组合类型英文名')
55
+
56
+ @classmethod
57
+ def resource_name(cls) -> str:
58
+ return 'element_type_combination'
59
+
60
+
61
+ class TypeCombination(TypeCombinationBase, ConvertToORM['TypeCombinationORM']):
62
+ primary: ResourceRef[ElementType] = Field(description='主属性')
63
+ secondary: ResourceRef[ElementType] | None = Field(
64
+ default=None, description='副属性'
65
+ )
66
+
67
+ @computed_field(description='是否是双属性')
68
+ @property
69
+ def is_double(self) -> bool:
70
+ return self.secondary is not None
71
+
72
+ @classmethod
73
+ def get_orm_model(cls) -> type['TypeCombinationORM']:
74
+ return TypeCombinationORM
75
+
76
+ def to_orm(self) -> 'TypeCombinationORM':
77
+ return TypeCombinationORM(
78
+ id=self.id,
79
+ name=self.name,
80
+ name_en=self.name_en,
81
+ primary_id=self.primary.id,
82
+ secondary_id=self.secondary.id if self.secondary else None,
83
+ )
84
+
85
+
86
+ class TypeCombinationORM(TypeCombinationBase, table=True):
87
+ primary_id: int = Field(foreign_key='element_type.id')
88
+ primary: 'ElementTypeORM' = Relationship(
89
+ back_populates='primary_combination',
90
+ sa_relationship_kwargs={
91
+ 'primaryjoin': 'TypeCombinationORM.primary_id == ElementTypeORM.id',
92
+ },
93
+ )
94
+ secondary_id: int | None = Field(default=None, foreign_key='element_type.id')
95
+ secondary: ElementTypeORM | None = Relationship(
96
+ back_populates='secondary_combination',
97
+ sa_relationship_kwargs={
98
+ 'primaryjoin': 'TypeCombinationORM.secondary_id == ElementTypeORM.id',
99
+ },
100
+ )
101
+ if not TYPE_CHECKING:
102
+ is_double: bool = Field(
103
+ sa_column=Column(
104
+ Boolean,
105
+ Computed('secondary_id IS NOT NULL', persisted=True),
106
+ nullable=False,
107
+ )
108
+ )
109
+
110
+ skill: list['SkillORM'] = Relationship(
111
+ back_populates='type',
112
+ )
113
+ pet: list['PetORM'] = Relationship(
114
+ back_populates='type',
115
+ )
116
+ skill_stone_category: list['SkillStoneCategoryORM'] = Relationship(
117
+ back_populates='type',
118
+ )