xn-model 0.11.4__py3-none-any.whl → 1.0.1__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 +45 -150
- x_model/pydantic.py +9 -9
- {xn_model-0.11.4.dist-info → xn_model-1.0.1.dist-info}/METADATA +8 -7
- xn_model-1.0.1.dist-info/RECORD +9 -0
- {xn_model-0.11.4.dist-info → xn_model-1.0.1.dist-info}/WHEEL +1 -1
- xn_model-0.11.4.dist-info/RECORD +0 -9
- {xn_model-0.11.4.dist-info → xn_model-1.0.1.dist-info}/top_level.txt +0 -0
x_model/models.py
CHANGED
|
@@ -1,175 +1,70 @@
|
|
|
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
2
|
|
|
11
|
-
from
|
|
12
|
-
from
|
|
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
|
|
13
7
|
|
|
8
|
+
from x_model import HTTPException, FailReason
|
|
14
9
|
|
|
15
|
-
class BaseModel(TortoiseModel):
|
|
16
|
-
# todo: resolve ownable + add only own list method
|
|
17
|
-
# todo: refact: clean old garbage
|
|
18
|
-
id: int = fields.IntField(True)
|
|
19
10
|
|
|
20
|
-
|
|
21
|
-
|
|
11
|
+
class DatetimeSecField(DatetimeField):
|
|
12
|
+
class _db_postgres:
|
|
13
|
+
SQL_TYPE = "TIMESTAMPTZ(0)"
|
|
22
14
|
|
|
23
|
-
def repr(self, sep: str = " ") -> str:
|
|
24
|
-
return sep.join(getattr(self, name_fragment) for name_fragment in self._name)
|
|
25
15
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
34
|
-
@classmethod
|
|
35
|
-
def _page_query(cls, sorts: tuple[str], limit: int = 1000, offset: int = 0, q: str = None, **filters) -> QuerySet:
|
|
36
|
-
query = cls.filter(**filters).order_by(*sorts).limit(limit).offset(offset)
|
|
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
|
|
16
|
+
class TsTrait:
|
|
17
|
+
created_at: datetime | None = DatetimeSecField(auto_now_add=True)
|
|
18
|
+
updated_at: datetime | None = DatetimeSecField(auto_now=True)
|
|
80
19
|
|
|
81
20
|
|
|
82
21
|
class Model(BaseModel):
|
|
83
|
-
|
|
84
|
-
_pydIn: type[PydanticModel] = None
|
|
85
|
-
_pydListItem: type[PydanticModel] = None
|
|
22
|
+
id: int = IntField(True)
|
|
86
23
|
|
|
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
|
|
24
|
+
_name: tuple[str] = ("name",)
|
|
25
|
+
_sorts: tuple[str] = ("-id",)
|
|
104
26
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
cls._pyd = cls._pyd or pydantic_model_creator(cls, name=cls.__name__)
|
|
108
|
-
return cls._pyd
|
|
27
|
+
def repr(self) -> str:
|
|
28
|
+
return " ".join(getattr(self, name_fragment) for name_fragment in self._name)
|
|
109
29
|
|
|
110
30
|
@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
|
|
31
|
+
def _pyd(cls, suffix: str, **kwargs) -> type[PydanticModel]:
|
|
32
|
+
return pydantic_model_creator(cls, name=cls.__name__ + suffix, **kwargs)
|
|
127
33
|
|
|
128
34
|
@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
|
|
35
|
+
def pyd(cls):
|
|
36
|
+
return cls._pyd("Root")
|
|
135
37
|
|
|
136
38
|
@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
|
-
)
|
|
39
|
+
def pyd_in(cls):
|
|
40
|
+
return cls._pyd("In", exclude_readonly=True)
|
|
145
41
|
|
|
146
42
|
# # # CRUD Methods # # #
|
|
147
43
|
@classmethod
|
|
148
|
-
async def
|
|
149
|
-
|
|
150
|
-
|
|
44
|
+
async def get_one(cls, id_: int, **filters) -> PydanticModel:
|
|
45
|
+
if obj := await cls.get_or_none(id=id_, **filters):
|
|
46
|
+
return await cls.pyd().from_tortoise_orm(obj)
|
|
47
|
+
raise HTTPException(reason=FailReason.path, status_=404, parent=f"{cls.__name__}#{id_} not found")
|
|
151
48
|
|
|
152
49
|
@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)
|
|
50
|
+
async def get_or_create_by_name(cls, name: str, attr_name: str = None, def_dict: dict = None) -> "Model":
|
|
51
|
+
attr_name = attr_name or list(cls._name)[0]
|
|
52
|
+
if not (obj := await cls.get_or_none(**{attr_name: name})):
|
|
53
|
+
next_id = (await cls.all().order_by("-id").first()).id + 1
|
|
54
|
+
obj = await cls.create(id=next_id, **{attr_name: name}, **(def_dict or {}))
|
|
55
|
+
return obj
|
|
171
56
|
|
|
57
|
+
class PydanticMeta:
|
|
58
|
+
model_config = ConfigDict(use_enum_values=True)
|
|
59
|
+
# include: tuple[str, ...] = ()
|
|
60
|
+
# exclude: tuple[str, ...] = ("Meta",)
|
|
61
|
+
# computed: tuple[str, ...] = ()
|
|
62
|
+
# backward_relations: bool = True
|
|
63
|
+
max_recursion: int = 1 # default: 3
|
|
64
|
+
# allow_cycles: bool = False
|
|
65
|
+
# exclude_raw_fields: bool = True
|
|
66
|
+
# sort_alphabetically: bool = False
|
|
67
|
+
# model_config: ConfigDict | None = None
|
|
172
68
|
|
|
173
|
-
class
|
|
174
|
-
|
|
175
|
-
updated_at: datetime | None = DatetimeSecField(auto_now=True)
|
|
69
|
+
class Meta:
|
|
70
|
+
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):
|
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: xn-model
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Base model for xn-api
|
|
5
5
|
Author-email: Mike Artemiev <mixartemev@gmail.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/XyncNet/x-model
|
|
8
8
|
Project-URL: Repository, https://github.com/XyncNet/x-model
|
|
9
9
|
Keywords: tortoise,model,crud,generator,api,admin
|
|
10
|
-
Requires-Python: >=3.
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
11
|
Description-Content-Type: text/markdown
|
|
12
12
|
Requires-Dist: tortoise-orm[accel,asyncpg]
|
|
13
|
+
Requires-Dist: fastapi
|
|
13
14
|
Provides-Extra: dev
|
|
14
|
-
Requires-Dist: pytest
|
|
15
|
-
Requires-Dist: python-dotenv
|
|
16
|
-
Requires-Dist: build
|
|
17
|
-
Requires-Dist: twine
|
|
15
|
+
Requires-Dist: pytest; extra == "dev"
|
|
16
|
+
Requires-Dist: python-dotenv; extra == "dev"
|
|
17
|
+
Requires-Dist: build; extra == "dev"
|
|
18
|
+
Requires-Dist: twine; extra == "dev"
|
|
18
19
|
|
|
19
20
|
## INSTALL
|
|
20
21
|
```bash
|
|
@@ -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=3ygo54HJTpHiD65Rgdzy5BNWJjtZiyypGiRlbDQ3oRc,2367
|
|
5
|
+
x_model/pydantic.py,sha256=fe8yOCVlnlBcT64bhNodxegnA6f4YdJ0WcXDIs6Ug20,608
|
|
6
|
+
xn_model-1.0.1.dist-info/METADATA,sha256=pk-jykzVjmVx3n6NnpYYvJyLXAyh8ohliVndnsijKeU,990
|
|
7
|
+
xn_model-1.0.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
8
|
+
xn_model-1.0.1.dist-info/top_level.txt,sha256=QCYyfv5AA_8jPPtCpShkBXzQRUCGuuW7Ro0mqysDE8E,8
|
|
9
|
+
xn_model-1.0.1.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
|