xn-model 0.11.4__py3-none-any.whl → 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.
- x_model/models.py +40 -148
- x_model/pydantic.py +9 -9
- {xn_model-0.11.4.dist-info → xn_model-1.0.0.dist-info}/METADATA +1 -1
- xn_model-1.0.0.dist-info/RECORD +9 -0
- xn_model-0.11.4.dist-info/RECORD +0 -9
- {xn_model-0.11.4.dist-info → xn_model-1.0.0.dist-info}/WHEEL +0 -0
- {xn_model-0.11.4.dist-info → xn_model-1.0.0.dist-info}/top_level.txt +0 -0
x_model/models.py
CHANGED
|
@@ -1,147 +1,41 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from pydantic import create_model
|
|
3
|
-
from tortoise import Model as TortoiseModel
|
|
4
|
-
from tortoise.contrib.pydantic import pydantic_model_creator, PydanticModel
|
|
5
|
-
from tortoise import fields
|
|
6
|
-
from tortoise.exceptions import FieldError
|
|
7
|
-
from tortoise.models import MetaInfo
|
|
8
|
-
from tortoise.queryset import QuerySet
|
|
9
|
-
from x_model import HTTPException, FailReason
|
|
10
|
-
|
|
11
|
-
from x_model.field import DatetimeSecField
|
|
12
|
-
from x_model.pydantic import PydList
|
|
13
|
-
|
|
14
2
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
3
|
+
from pydantic import ConfigDict
|
|
4
|
+
from tortoise import Model as BaseModel
|
|
5
|
+
from tortoise.contrib.pydantic import pydantic_model_creator, PydanticModel
|
|
6
|
+
from tortoise.fields import DatetimeField, IntField
|
|
19
7
|
|
|
20
|
-
_name: tuple[str] = ("name",)
|
|
21
|
-
_sorts: tuple[str] = ("-id",)
|
|
22
8
|
|
|
23
|
-
|
|
24
|
-
|
|
9
|
+
class DatetimeSecField(DatetimeField):
|
|
10
|
+
class _db_postgres:
|
|
11
|
+
SQL_TYPE = "TIMESTAMPTZ(0)"
|
|
25
12
|
|
|
26
|
-
@classmethod
|
|
27
|
-
async def get_or_create_by_name(cls, name: str, attr_name: str = None, def_dict: dict = None) -> TortoiseModel:
|
|
28
|
-
attr_name = attr_name or list(cls._name)[0]
|
|
29
|
-
if not (obj := await cls.get_or_none(**{attr_name: name})):
|
|
30
|
-
next_id = (await cls.all().order_by("-id").first()).id + 1
|
|
31
|
-
obj = await cls.create(id=next_id, **{attr_name: name}, **(def_dict or {}))
|
|
32
|
-
return obj
|
|
33
13
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if q:
|
|
38
|
-
query = query.filter(**{f"{cls._name[0]}__icontains": q})
|
|
39
|
-
return query
|
|
40
|
-
|
|
41
|
-
@classmethod
|
|
42
|
-
async def upsert(cls, data: dict, oid=None):
|
|
43
|
-
meta: MetaInfo = cls._meta
|
|
44
|
-
|
|
45
|
-
# pop fields for relations from general data dict # todo: add backwards fields for save
|
|
46
|
-
m2ms = {k: data.pop(k) for k in meta.m2m_fields if k in data}
|
|
47
|
-
# bfks = {k: data.pop(k) for k in meta.backward_fk_fields if k in data}
|
|
48
|
-
# bo2os = {k: data.pop(k) for k in meta.backward_o2o_fields if k in data}
|
|
49
|
-
|
|
50
|
-
# save general model
|
|
51
|
-
# if pk := meta.pk_attr in data.keys():
|
|
52
|
-
# unq = {pk: data.pop(pk)}
|
|
53
|
-
# else:
|
|
54
|
-
# unq = {key: data.pop(key) for key, ft in meta.fields_map.items() if ft.unique and key in data.keys()}
|
|
55
|
-
# # unq = meta.unique_together
|
|
56
|
-
# obj, is_created = await cls.update_or_create(data, **unq)
|
|
57
|
-
obj = (await cls.update_or_create(data, id=oid))[0] if oid else await cls.create(**data)
|
|
58
|
-
|
|
59
|
-
# save relations
|
|
60
|
-
for k, ids in m2ms.items():
|
|
61
|
-
if ids:
|
|
62
|
-
m2m_rel: fields.ManyToManyRelation = getattr(obj, k)
|
|
63
|
-
items = [await m2m_rel.remote_model[i] for i in ids]
|
|
64
|
-
await m2m_rel.clear() # for updating, not just adding
|
|
65
|
-
await m2m_rel.add(*items)
|
|
66
|
-
# for k, ids in bfks.items():
|
|
67
|
-
# bfk_rel: ReverseRelation = getattr(obj, k)
|
|
68
|
-
# items = [await bfk_rel.remote_model[i] for i in ids]
|
|
69
|
-
# [await item.update_from_dict({bfk_rel.relation_field: obj.pk}).save() for item in items]
|
|
70
|
-
# for k, oid in bo2os.items():
|
|
71
|
-
# bo2o_rel: QuerySet = getattr(obj, k)
|
|
72
|
-
# item = await bo2o_rel.model[oid]
|
|
73
|
-
# await item.update_from_dict({obj._meta.db_table: obj}).save()
|
|
74
|
-
|
|
75
|
-
await obj.fetch_related(*cls._meta.fetch_fields)
|
|
76
|
-
return obj
|
|
77
|
-
|
|
78
|
-
class Meta:
|
|
79
|
-
abstract = True
|
|
14
|
+
class TsTrait:
|
|
15
|
+
created_at: datetime | None = DatetimeSecField(auto_now_add=True)
|
|
16
|
+
updated_at: datetime | None = DatetimeSecField(auto_now=True)
|
|
80
17
|
|
|
81
18
|
|
|
82
19
|
class Model(BaseModel):
|
|
83
|
-
|
|
84
|
-
_pydIn: type[PydanticModel] = None
|
|
85
|
-
_pydListItem: type[PydanticModel] = None
|
|
20
|
+
id: int = IntField(True)
|
|
86
21
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# exclude: tuple[str, ...] = ()
|
|
90
|
-
# computed: tuple[str, ...] = ()
|
|
91
|
-
exclude_raw_fields = False # default: True
|
|
92
|
-
max_recursion: int = 1 # default: 3
|
|
93
|
-
|
|
94
|
-
class PydanticMetaIn:
|
|
95
|
-
max_recursion: int = 0 # default: 3
|
|
96
|
-
backward_relations: bool = False # no need to disable when max_recursion=0 # default: True
|
|
97
|
-
exclude_raw_fields: bool = False # default: True
|
|
98
|
-
|
|
99
|
-
class PydanticMetaListItem:
|
|
100
|
-
max_recursion: int = 0 # default: 3
|
|
101
|
-
backward_relations: bool = False # default: True
|
|
102
|
-
exclude_raw_fields = False # default: True
|
|
103
|
-
sort_alphabetically: bool = True # default: False
|
|
22
|
+
_name: tuple[str] = ("name",)
|
|
23
|
+
_sorts: tuple[str] = ("-id",)
|
|
104
24
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
cls._pyd = cls._pyd or pydantic_model_creator(cls, name=cls.__name__)
|
|
108
|
-
return cls._pyd
|
|
25
|
+
def repr(self) -> str:
|
|
26
|
+
return " ".join(getattr(self, name_fragment) for name_fragment in self._name)
|
|
109
27
|
|
|
110
28
|
@classmethod
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
opts = tuple(k for k, v in cls._meta.fields_map.items() if not v.required)
|
|
114
|
-
cls._pydIn = pydantic_model_creator(
|
|
115
|
-
cls,
|
|
116
|
-
name=cls.__name__ + "In",
|
|
117
|
-
meta_override=cls.PydanticMetaIn,
|
|
118
|
-
optional=opts,
|
|
119
|
-
exclude_readonly=True,
|
|
120
|
-
exclude=("created_at", "updated_at"),
|
|
121
|
-
)
|
|
122
|
-
if m2ms := cls._meta.m2m_fields: # hack for direct inserting m2m values
|
|
123
|
-
cls._pydIn = create_model(
|
|
124
|
-
cls._pydIn.__name__, __base__=cls._pydIn, **{m2m: (list[int] | None, None) for m2m in m2ms}
|
|
125
|
-
)
|
|
126
|
-
return cls._pydIn
|
|
29
|
+
def _pyd(cls, suffix: str, **kwargs) -> type[PydanticModel]:
|
|
30
|
+
return pydantic_model_creator(cls, name=cls.__name__ + suffix, **kwargs)
|
|
127
31
|
|
|
128
32
|
@classmethod
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
cls._pydListItem = pydantic_model_creator(
|
|
132
|
-
cls, name=cls.__name__ + "ListItem", meta_override=cls.PydanticMetaListItem
|
|
133
|
-
)
|
|
134
|
-
return cls._pydListItem
|
|
33
|
+
def pyd(cls):
|
|
34
|
+
return cls._pyd("Root")
|
|
135
35
|
|
|
136
36
|
@classmethod
|
|
137
|
-
def
|
|
138
|
-
return
|
|
139
|
-
cls.__name__ + "List",
|
|
140
|
-
data=(list[cls.pyd_list_item()], []),
|
|
141
|
-
total=(int, 0),
|
|
142
|
-
filtered=(int | None, None),
|
|
143
|
-
__base__=PydList[cls.pyd_list_item()],
|
|
144
|
-
)
|
|
37
|
+
def pyd_in(cls):
|
|
38
|
+
return cls._pyd("In", exclude_readonly=True)
|
|
145
39
|
|
|
146
40
|
# # # CRUD Methods # # #
|
|
147
41
|
@classmethod
|
|
@@ -150,26 +44,24 @@ class Model(BaseModel):
|
|
|
150
44
|
return await cls.pyd().from_queryset_single(q)
|
|
151
45
|
|
|
152
46
|
@classmethod
|
|
153
|
-
async def
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
except FieldError as e:
|
|
160
|
-
raise HTTPException(FailReason.body, e)
|
|
161
|
-
if limit - (li := len(data)):
|
|
162
|
-
filtered = total = li + offset
|
|
163
|
-
else:
|
|
164
|
-
total = await cls.all().count()
|
|
165
|
-
filtered_query = cls.filter(**filters)
|
|
166
|
-
if q:
|
|
167
|
-
filtered_query = filtered_query.filter(**{f"{cls._name}__icontains": q})
|
|
168
|
-
filtered = await filtered_query.count()
|
|
169
|
-
pyds = cls.pyds_list()
|
|
170
|
-
return pyds(data=data, total=total, filtered=filtered)
|
|
47
|
+
async def get_or_create_by_name(cls, name: str, attr_name: str = None, def_dict: dict = None) -> "Model":
|
|
48
|
+
attr_name = attr_name or list(cls._name)[0]
|
|
49
|
+
if not (obj := await cls.get_or_none(**{attr_name: name})):
|
|
50
|
+
next_id = (await cls.all().order_by("-id").first()).id + 1
|
|
51
|
+
obj = await cls.create(id=next_id, **{attr_name: name}, **(def_dict or {}))
|
|
52
|
+
return obj
|
|
171
53
|
|
|
54
|
+
class PydanticMeta:
|
|
55
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
56
|
+
# include: tuple[str, ...] = ()
|
|
57
|
+
# exclude: tuple[str, ...] = ("Meta",)
|
|
58
|
+
# computed: tuple[str, ...] = ()
|
|
59
|
+
# backward_relations: bool = True
|
|
60
|
+
max_recursion: int = 1 # default: 3
|
|
61
|
+
# allow_cycles: bool = False
|
|
62
|
+
# exclude_raw_fields: bool = True
|
|
63
|
+
# sort_alphabetically: bool = False
|
|
64
|
+
# model_config: ConfigDict | None = None
|
|
172
65
|
|
|
173
|
-
class
|
|
174
|
-
|
|
175
|
-
updated_at: datetime | None = DatetimeSecField(auto_now=True)
|
|
66
|
+
class Meta:
|
|
67
|
+
abstract = True
|
x_model/pydantic.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
from typing import TypeVar, Generic
|
|
2
|
-
from pydantic import BaseModel, ConfigDict
|
|
1
|
+
# from typing import TypeVar, Generic
|
|
2
|
+
from pydantic import BaseModel # , ConfigDict
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
RootModelType = TypeVar("RootModelType")
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class PydList(BaseModel, Generic[RootModelType]):
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
# RootModelType = TypeVar("RootModelType")
|
|
6
|
+
#
|
|
7
|
+
#
|
|
8
|
+
# class PydList(BaseModel, Generic[RootModelType]):
|
|
9
|
+
# model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
10
|
+
# data: list[RootModelType]
|
|
11
|
+
# total: int
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class Names(BaseModel):
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
x_model/__init__.py,sha256=leq1K2Lq0zlTY2s5sdhDiGEyfNZPySCOJi0xB5xUSG0,1231
|
|
2
|
+
x_model/field.py,sha256=S461M94ryQG7yu8lreXtWnZo3YdCP97xhbcCJ3BzXsY,2751
|
|
3
|
+
x_model/func.py,sha256=E7jDoHJGaFpKvxbHnT_lyBxUZeMo-GRd5gv9dLw7B9s,289
|
|
4
|
+
x_model/models.py,sha256=wrH9WLrXsj521d9_YYgdZVracwuR8bGlMJmW4qykTHA,2189
|
|
5
|
+
x_model/pydantic.py,sha256=fe8yOCVlnlBcT64bhNodxegnA6f4YdJ0WcXDIs6Ug20,608
|
|
6
|
+
xn_model-1.0.0.dist-info/METADATA,sha256=pJMOvyxmYm9E3uplCGpVD-TyDHgKe4uCr_2jfo8pOLM,971
|
|
7
|
+
xn_model-1.0.0.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
8
|
+
xn_model-1.0.0.dist-info/top_level.txt,sha256=QCYyfv5AA_8jPPtCpShkBXzQRUCGuuW7Ro0mqysDE8E,8
|
|
9
|
+
xn_model-1.0.0.dist-info/RECORD,,
|
xn_model-0.11.4.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
x_model/__init__.py,sha256=leq1K2Lq0zlTY2s5sdhDiGEyfNZPySCOJi0xB5xUSG0,1231
|
|
2
|
-
x_model/field.py,sha256=S461M94ryQG7yu8lreXtWnZo3YdCP97xhbcCJ3BzXsY,2751
|
|
3
|
-
x_model/func.py,sha256=E7jDoHJGaFpKvxbHnT_lyBxUZeMo-GRd5gv9dLw7B9s,289
|
|
4
|
-
x_model/models.py,sha256=V1lrDJH-DPnGznhxCkNKFI58igzO2KTVCQNX2MDKzuM,7048
|
|
5
|
-
x_model/pydantic.py,sha256=OjNCp4ZFGvUIxZSbDCmXISl9mOjI_UnWi5JMVntSqjM,590
|
|
6
|
-
xn_model-0.11.4.dist-info/METADATA,sha256=1Njxvb3fKUa8Vrjec4Mdy_2Oh7tjpIk8DR2WJO34SjY,972
|
|
7
|
-
xn_model-0.11.4.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
8
|
-
xn_model-0.11.4.dist-info/top_level.txt,sha256=QCYyfv5AA_8jPPtCpShkBXzQRUCGuuW7Ro0mqysDE8E,8
|
|
9
|
-
xn_model-0.11.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|