everysk-lib 1.10.2__cp312-cp312-win_amd64.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.
- everysk/__init__.py +30 -0
- everysk/_version.py +683 -0
- everysk/api/__init__.py +61 -0
- everysk/api/api_requestor.py +167 -0
- everysk/api/api_resources/__init__.py +23 -0
- everysk/api/api_resources/api_resource.py +371 -0
- everysk/api/api_resources/calculation.py +779 -0
- everysk/api/api_resources/custom_index.py +42 -0
- everysk/api/api_resources/datastore.py +81 -0
- everysk/api/api_resources/file.py +42 -0
- everysk/api/api_resources/market_data.py +223 -0
- everysk/api/api_resources/parser.py +66 -0
- everysk/api/api_resources/portfolio.py +43 -0
- everysk/api/api_resources/private_security.py +42 -0
- everysk/api/api_resources/report.py +65 -0
- everysk/api/api_resources/report_template.py +39 -0
- everysk/api/api_resources/tests.py +115 -0
- everysk/api/api_resources/worker_execution.py +64 -0
- everysk/api/api_resources/workflow.py +65 -0
- everysk/api/api_resources/workflow_execution.py +93 -0
- everysk/api/api_resources/workspace.py +42 -0
- everysk/api/http_client.py +63 -0
- everysk/api/tests.py +32 -0
- everysk/api/utils.py +262 -0
- everysk/config.py +451 -0
- everysk/core/_tests/serialize/test_json.py +336 -0
- everysk/core/_tests/serialize/test_orjson.py +295 -0
- everysk/core/_tests/serialize/test_pickle.py +48 -0
- everysk/core/cloud_function/main.py +78 -0
- everysk/core/cloud_function/tests.py +86 -0
- everysk/core/compress.py +245 -0
- everysk/core/datetime/__init__.py +12 -0
- everysk/core/datetime/calendar.py +144 -0
- everysk/core/datetime/date.py +424 -0
- everysk/core/datetime/date_expression.py +299 -0
- everysk/core/datetime/date_mixin.py +1475 -0
- everysk/core/datetime/date_settings.py +30 -0
- everysk/core/datetime/datetime.py +713 -0
- everysk/core/exceptions.py +435 -0
- everysk/core/fields.py +1176 -0
- everysk/core/firestore.py +555 -0
- everysk/core/fixtures/_settings.py +29 -0
- everysk/core/fixtures/other/_settings.py +18 -0
- everysk/core/fixtures/user_agents.json +88 -0
- everysk/core/http.py +691 -0
- everysk/core/lists.py +92 -0
- everysk/core/log.py +709 -0
- everysk/core/number.py +37 -0
- everysk/core/object.py +1469 -0
- everysk/core/redis.py +1021 -0
- everysk/core/retry.py +51 -0
- everysk/core/serialize.py +674 -0
- everysk/core/sftp.py +414 -0
- everysk/core/signing.py +53 -0
- everysk/core/slack.py +127 -0
- everysk/core/string.py +199 -0
- everysk/core/tests.py +240 -0
- everysk/core/threads.py +199 -0
- everysk/core/undefined.py +70 -0
- everysk/core/unittests.py +73 -0
- everysk/core/workers.py +241 -0
- everysk/sdk/__init__.py +23 -0
- everysk/sdk/base.py +98 -0
- everysk/sdk/brutils/cnpj.py +391 -0
- everysk/sdk/brutils/cnpj_pd.py +129 -0
- everysk/sdk/engines/__init__.py +26 -0
- everysk/sdk/engines/cache.py +185 -0
- everysk/sdk/engines/compliance.py +37 -0
- everysk/sdk/engines/cryptography.py +69 -0
- everysk/sdk/engines/expression.cp312-win_amd64.pyd +0 -0
- everysk/sdk/engines/expression.pyi +55 -0
- everysk/sdk/engines/helpers.cp312-win_amd64.pyd +0 -0
- everysk/sdk/engines/helpers.pyi +26 -0
- everysk/sdk/engines/lock.py +120 -0
- everysk/sdk/engines/market_data.py +244 -0
- everysk/sdk/engines/settings.py +19 -0
- everysk/sdk/entities/__init__.py +23 -0
- everysk/sdk/entities/base.py +784 -0
- everysk/sdk/entities/base_list.py +131 -0
- everysk/sdk/entities/custom_index/base.py +209 -0
- everysk/sdk/entities/custom_index/settings.py +29 -0
- everysk/sdk/entities/datastore/base.py +160 -0
- everysk/sdk/entities/datastore/settings.py +17 -0
- everysk/sdk/entities/fields.py +375 -0
- everysk/sdk/entities/file/base.py +215 -0
- everysk/sdk/entities/file/settings.py +63 -0
- everysk/sdk/entities/portfolio/base.py +248 -0
- everysk/sdk/entities/portfolio/securities.py +241 -0
- everysk/sdk/entities/portfolio/security.py +580 -0
- everysk/sdk/entities/portfolio/settings.py +97 -0
- everysk/sdk/entities/private_security/base.py +226 -0
- everysk/sdk/entities/private_security/settings.py +17 -0
- everysk/sdk/entities/query.py +603 -0
- everysk/sdk/entities/report/base.py +214 -0
- everysk/sdk/entities/report/settings.py +23 -0
- everysk/sdk/entities/script.py +310 -0
- everysk/sdk/entities/secrets/base.py +128 -0
- everysk/sdk/entities/secrets/script.py +119 -0
- everysk/sdk/entities/secrets/settings.py +17 -0
- everysk/sdk/entities/settings.py +48 -0
- everysk/sdk/entities/tags.py +174 -0
- everysk/sdk/entities/worker_execution/base.py +307 -0
- everysk/sdk/entities/worker_execution/settings.py +63 -0
- everysk/sdk/entities/workflow_execution/base.py +113 -0
- everysk/sdk/entities/workflow_execution/settings.py +32 -0
- everysk/sdk/entities/workspace/base.py +99 -0
- everysk/sdk/entities/workspace/settings.py +27 -0
- everysk/sdk/settings.py +67 -0
- everysk/sdk/tests.py +105 -0
- everysk/sdk/worker_base.py +47 -0
- everysk/server/__init__.py +9 -0
- everysk/server/applications.py +63 -0
- everysk/server/endpoints.py +516 -0
- everysk/server/example_api.py +69 -0
- everysk/server/middlewares.py +80 -0
- everysk/server/requests.py +62 -0
- everysk/server/responses.py +119 -0
- everysk/server/routing.py +64 -0
- everysk/server/settings.py +36 -0
- everysk/server/tests.py +36 -0
- everysk/settings.py +98 -0
- everysk/sql/__init__.py +9 -0
- everysk/sql/connection.py +232 -0
- everysk/sql/model.py +376 -0
- everysk/sql/query.py +417 -0
- everysk/sql/row_factory.py +63 -0
- everysk/sql/settings.py +49 -0
- everysk/sql/utils.py +129 -0
- everysk/tests.py +23 -0
- everysk/utils.py +81 -0
- everysk/version.py +15 -0
- everysk_lib-1.10.2.dist-info/.gitignore +5 -0
- everysk_lib-1.10.2.dist-info/METADATA +326 -0
- everysk_lib-1.10.2.dist-info/RECORD +137 -0
- everysk_lib-1.10.2.dist-info/WHEEL +5 -0
- everysk_lib-1.10.2.dist-info/licenses/LICENSE.txt +9 -0
- everysk_lib-1.10.2.dist-info/top_level.txt +2 -0
everysk/sql/model.py
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
###############################################################################
|
|
2
|
+
#
|
|
3
|
+
# (C) Copyright 2025 EVERYSK TECHNOLOGIES
|
|
4
|
+
#
|
|
5
|
+
# This is an unpublished work containing confidential and proprietary
|
|
6
|
+
# information of EVERYSK TECHNOLOGIES. Disclosure, use, or reproduction
|
|
7
|
+
# without authorization of EVERYSK TECHNOLOGIES is prohibited.
|
|
8
|
+
#
|
|
9
|
+
###############################################################################
|
|
10
|
+
import inspect
|
|
11
|
+
from copy import deepcopy
|
|
12
|
+
from types import GenericAlias, UnionType
|
|
13
|
+
from typing import Any, Generic, Literal, Self, TypeVar, Union, get_args, get_origin
|
|
14
|
+
|
|
15
|
+
from everysk.core.datetime import Date, DateTime
|
|
16
|
+
from everysk.sql.connection import execute
|
|
17
|
+
from everysk.sql.query import Query
|
|
18
|
+
|
|
19
|
+
FT = TypeVar('FT')
|
|
20
|
+
MT = TypeVar('MT', bound='BaseModel')
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseModelMetaClass(type):
|
|
24
|
+
# Method called when a class is created in the Python runtime
|
|
25
|
+
def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any]) -> MT:
|
|
26
|
+
# Attributes that are inside annotations -> var: str or var: str = 'value'
|
|
27
|
+
# Attributes that are not inside annotations -> var = 'value'
|
|
28
|
+
# We need to get both
|
|
29
|
+
for key in attrs['__annotations__'].keys() | attrs.keys():
|
|
30
|
+
# Discard internal attributes
|
|
31
|
+
if not key.startswith('__'):
|
|
32
|
+
default_value = attrs.get(key)
|
|
33
|
+
if not inspect.isroutine(default_value) and not isinstance(default_value, (property, BaseModelField)):
|
|
34
|
+
# If the attribute is not inside annotations (var = 'value'), we add it
|
|
35
|
+
if key not in attrs['__annotations__']:
|
|
36
|
+
attrs['__annotations__'][key] = type(default_value)
|
|
37
|
+
|
|
38
|
+
# Get the type from the annotations
|
|
39
|
+
field_type = attrs['__annotations__'].get(key)
|
|
40
|
+
# Change the attribute to a BaseModelField descriptor
|
|
41
|
+
attrs[key] = BaseModelField(default=default_value, field_type=field_type)
|
|
42
|
+
|
|
43
|
+
obj: BaseModel = super().__new__(cls, name, bases, attrs)
|
|
44
|
+
obj._generate_attributes() # noqa: SLF001
|
|
45
|
+
obj._generate_fields() # noqa: SLF001
|
|
46
|
+
obj._generate_query() # noqa: SLF001
|
|
47
|
+
return obj
|
|
48
|
+
|
|
49
|
+
def __setattr__(cls: 'BaseModel', name: str, value: Any) -> None:
|
|
50
|
+
# Internal attributes are set normally
|
|
51
|
+
# They are __attributes__, __fields__, __query__
|
|
52
|
+
if name.startswith('__'):
|
|
53
|
+
return super().__setattr__(name, value)
|
|
54
|
+
|
|
55
|
+
if name in cls.__attributes__:
|
|
56
|
+
# Get the type that was already defined to validate the value
|
|
57
|
+
field_type = cls.__attributes__[name].field_type
|
|
58
|
+
else:
|
|
59
|
+
# If the attribute is not defined, we add it
|
|
60
|
+
field_type = type(value)
|
|
61
|
+
cls.__annotations__[name] = field_type
|
|
62
|
+
cls.__attributes__[name] = field_type
|
|
63
|
+
if not name.startswith('_'):
|
|
64
|
+
cls.__fields__[name] = field_type
|
|
65
|
+
|
|
66
|
+
# Change the attribute to a BaseModelField descriptor
|
|
67
|
+
value = BaseModelField(default=value, field_type=field_type, field_name=name)
|
|
68
|
+
return super().__setattr__(name, value)
|
|
69
|
+
|
|
70
|
+
def __delattr__(cls: 'BaseModel', name: str) -> None:
|
|
71
|
+
if name in cls.__attributes__:
|
|
72
|
+
del cls.__attributes__[name]
|
|
73
|
+
|
|
74
|
+
if name in cls.__fields__:
|
|
75
|
+
del cls.__fields__[name]
|
|
76
|
+
|
|
77
|
+
if name in cls.__annotations__:
|
|
78
|
+
del cls.__annotations__[name]
|
|
79
|
+
|
|
80
|
+
return super().__delattr__(name)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class BaseModelField(Generic[FT]):
|
|
84
|
+
# https://docs.python.org/3.12/howto/descriptor.html
|
|
85
|
+
default: FT | None
|
|
86
|
+
field_name: str
|
|
87
|
+
field_type: FT
|
|
88
|
+
|
|
89
|
+
def __init__(self, default: FT | None = None, *, field_type: FT, field_name: str | None = None) -> None:
|
|
90
|
+
if isinstance(field_type, GenericAlias):
|
|
91
|
+
# We need to get the origin of the generic type
|
|
92
|
+
field_type = get_origin(field_type)
|
|
93
|
+
# TypeError: typing.Union cannot be used with isinstance()
|
|
94
|
+
# The correct class of Unions is types.UnionType
|
|
95
|
+
elif isinstance(field_type, UnionType):
|
|
96
|
+
# We need to find if there are GenericAlias inside the Union
|
|
97
|
+
types = []
|
|
98
|
+
for _type in get_args(field_type):
|
|
99
|
+
if isinstance(_type, GenericAlias):
|
|
100
|
+
_type = get_origin(_type)
|
|
101
|
+
# We discard NoneType from the Union because it's normally used as optional
|
|
102
|
+
# This check is here because None is always the last type in the Union
|
|
103
|
+
if _type is type(None):
|
|
104
|
+
continue
|
|
105
|
+
types.append(_type)
|
|
106
|
+
|
|
107
|
+
# If there is only one type, we use it directly
|
|
108
|
+
if len(types) == 1:
|
|
109
|
+
field_type = types[0]
|
|
110
|
+
else:
|
|
111
|
+
field_type = Union[tuple(types)] # noqa: UP007
|
|
112
|
+
|
|
113
|
+
self.field_type = field_type
|
|
114
|
+
self.field_name = field_name
|
|
115
|
+
# We always validate the default value
|
|
116
|
+
if default is not None:
|
|
117
|
+
self.validate(default)
|
|
118
|
+
|
|
119
|
+
self.default = default
|
|
120
|
+
|
|
121
|
+
def __delete__(self, obj: 'BaseModel') -> None:
|
|
122
|
+
if self.field_name in obj:
|
|
123
|
+
del obj[self.field_name]
|
|
124
|
+
|
|
125
|
+
def __get__(self, obj: 'BaseModel', cls: type) -> FT:
|
|
126
|
+
# First we get the value from the dict, if not present we get the value from the default
|
|
127
|
+
if obj is None:
|
|
128
|
+
return self.default
|
|
129
|
+
|
|
130
|
+
return obj.get(self.field_name, self.default)
|
|
131
|
+
|
|
132
|
+
def __set__(self, obj: 'BaseModel', value: FT) -> None:
|
|
133
|
+
value = self.clean_value(value)
|
|
134
|
+
|
|
135
|
+
if obj._validate_fields: # noqa: SLF001
|
|
136
|
+
self.validate(value)
|
|
137
|
+
|
|
138
|
+
obj[self.field_name] = value
|
|
139
|
+
|
|
140
|
+
def __set_name__(self, cls: type, name: str) -> None:
|
|
141
|
+
self.field_name = name
|
|
142
|
+
|
|
143
|
+
## Validation methods
|
|
144
|
+
def clean_value(self, value: object) -> object:
|
|
145
|
+
# The database value is datetime or date, we need to convert it to DateTime or Date
|
|
146
|
+
# Or we could store it as ISO format string and convert it back
|
|
147
|
+
if value:
|
|
148
|
+
# The result from the database could be a string, datetime or date
|
|
149
|
+
# We need to convert it to DateTime or Date
|
|
150
|
+
if self.field_type in (DateTime, Date):
|
|
151
|
+
if isinstance(value, str):
|
|
152
|
+
return self.field_type.fromisoformat(value)
|
|
153
|
+
|
|
154
|
+
return self.field_type.ensure(value)
|
|
155
|
+
|
|
156
|
+
# For sets and tuples, we save them as JSONB in the database that is a list
|
|
157
|
+
# when we get it back, we need to convert it to set or tuple
|
|
158
|
+
if self.field_type in (set, tuple):
|
|
159
|
+
return self.field_type(value)
|
|
160
|
+
|
|
161
|
+
return value
|
|
162
|
+
|
|
163
|
+
def _validate_instance(self, value: object) -> None:
|
|
164
|
+
if value and not isinstance(value, self.field_type):
|
|
165
|
+
name = self.field_name
|
|
166
|
+
type_name = self.field_type.__qualname__
|
|
167
|
+
received_name = type(value).__qualname__
|
|
168
|
+
msg = f'Field {name} must be of type {type_name}, got {received_name}.'
|
|
169
|
+
raise TypeError(msg)
|
|
170
|
+
|
|
171
|
+
def validate(self, value: object) -> None:
|
|
172
|
+
validate_func = getattr(self, f'_validate_{self.field_name}', self._validate_instance)
|
|
173
|
+
validate_func(value=value)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class BaseModel(dict, metaclass=BaseModelMetaClass):
|
|
177
|
+
# https://docs.python.org/3/reference/datamodel.html#slots
|
|
178
|
+
# These are class attributes that are not in the __dict__
|
|
179
|
+
__slots__ = ('__attributes__', '__dict__', '__fields__', '__query__')
|
|
180
|
+
|
|
181
|
+
## Private attributes
|
|
182
|
+
_dsn: str = None
|
|
183
|
+
_primary_key: str = None
|
|
184
|
+
_schema: str = None
|
|
185
|
+
_table_name: str = None
|
|
186
|
+
_validate_fields: bool = True
|
|
187
|
+
|
|
188
|
+
## Public attributes
|
|
189
|
+
# All public attributes are table fields and keys of the dict
|
|
190
|
+
created_on: DateTime = None
|
|
191
|
+
updated_on: DateTime = None
|
|
192
|
+
|
|
193
|
+
## Internal methods
|
|
194
|
+
def __init__(self, *, validate_fields: bool | None = None, **kwargs) -> None:
|
|
195
|
+
# If validate_fields is None, we use the class attribute
|
|
196
|
+
if validate_fields is not None:
|
|
197
|
+
self._validate_fields = validate_fields
|
|
198
|
+
|
|
199
|
+
if self._validate_fields:
|
|
200
|
+
for key, value in kwargs.items():
|
|
201
|
+
self._validate(key, value)
|
|
202
|
+
|
|
203
|
+
# Set created_on if not provided
|
|
204
|
+
if 'created_on' not in kwargs:
|
|
205
|
+
kwargs['created_on'] = DateTime.now()
|
|
206
|
+
|
|
207
|
+
# We need to set all fields to None to complete the dict keys
|
|
208
|
+
fields = self._get_field_values(init_values=kwargs)
|
|
209
|
+
super().__init__(**fields)
|
|
210
|
+
|
|
211
|
+
def __repr__(self) -> str:
|
|
212
|
+
return self.__str__()
|
|
213
|
+
|
|
214
|
+
def __setitem__(self, key: str, value: Any) -> None:
|
|
215
|
+
if self._validate_fields:
|
|
216
|
+
self._validate(key, value)
|
|
217
|
+
|
|
218
|
+
return super().__setitem__(key, value)
|
|
219
|
+
|
|
220
|
+
def __str__(self) -> str:
|
|
221
|
+
# Use the class name and primary key for string representation
|
|
222
|
+
class_name = type(self).__name__
|
|
223
|
+
if self._primary_key is not None:
|
|
224
|
+
pk = getattr(self, self._primary_key, None)
|
|
225
|
+
if pk is not None:
|
|
226
|
+
return f'{class_name}({self._primary_key}={pk})'
|
|
227
|
+
|
|
228
|
+
return f'{class_name}()'
|
|
229
|
+
|
|
230
|
+
## Private methods
|
|
231
|
+
def _get_field_values(self, *, init_values: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
232
|
+
if init_values is None:
|
|
233
|
+
init_values = {}
|
|
234
|
+
|
|
235
|
+
# Get only fields that are not already in init_values
|
|
236
|
+
cls = type(self)
|
|
237
|
+
fields = cls._get_fields() - init_values.keys()
|
|
238
|
+
for field in fields:
|
|
239
|
+
value = getattr(self, field, None)
|
|
240
|
+
init_values[field] = value
|
|
241
|
+
|
|
242
|
+
return init_values
|
|
243
|
+
|
|
244
|
+
def _validate(self, key: str, value: Any) -> None:
|
|
245
|
+
if key in self._get_attributes():
|
|
246
|
+
field = self._get_attributes()[key]
|
|
247
|
+
value = field.clean_value(value)
|
|
248
|
+
field.validate(value)
|
|
249
|
+
else:
|
|
250
|
+
msg = f'Field {key} is not defined in {type(self).__name__}.'
|
|
251
|
+
raise KeyError(msg)
|
|
252
|
+
|
|
253
|
+
## Class methods
|
|
254
|
+
@classmethod
|
|
255
|
+
def _execute(
|
|
256
|
+
cls,
|
|
257
|
+
query: str,
|
|
258
|
+
params: dict | None = None,
|
|
259
|
+
return_type: Literal['dict', 'list'] = 'list',
|
|
260
|
+
klass: type | None = None,
|
|
261
|
+
) -> Any:
|
|
262
|
+
kwargs = {'query': query, 'params': params, 'return_type': return_type, 'dsn': cls._dsn, 'cls': klass}
|
|
263
|
+
return execute(**kwargs)
|
|
264
|
+
|
|
265
|
+
@classmethod
|
|
266
|
+
def _generate_attributes(cls) -> None:
|
|
267
|
+
cls.__attributes__ = {}
|
|
268
|
+
# We need to get all attributes from the class and its bases
|
|
269
|
+
# We stop at BaseModel (inclusive)
|
|
270
|
+
for base in cls.mro()[:-2]:
|
|
271
|
+
# Annotations are all attributes that are defined in the class
|
|
272
|
+
# In the __dict__ is the correct Field descriptor for each attribute
|
|
273
|
+
if base.__attributes__:
|
|
274
|
+
cls.__attributes__.update(base.__attributes__)
|
|
275
|
+
# If the base has the attributes, we don't need to get the others
|
|
276
|
+
break
|
|
277
|
+
# If we don't have the attributes in the base, we make then
|
|
278
|
+
keys = base.__annotations__.keys()
|
|
279
|
+
cls.__attributes__.update({key: base.__dict__[key] for key in keys})
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
def _generate_fields(cls) -> None:
|
|
283
|
+
# Fields are all attributes except those starting with _
|
|
284
|
+
cls.__fields__ = {
|
|
285
|
+
key: field.field_type for key, field in cls._get_attributes().items() if not key.startswith('_')
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@classmethod
|
|
289
|
+
def _generate_query(cls) -> None:
|
|
290
|
+
cls.__query__ = Query(table_name=cls._table_name, primary_key=cls._primary_key, schema=cls._schema)
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def _get_attributes(cls) -> dict[str, BaseModelField]:
|
|
294
|
+
if cls.__attributes__ is None:
|
|
295
|
+
cls._generate_attributes()
|
|
296
|
+
|
|
297
|
+
return cls.__attributes__
|
|
298
|
+
|
|
299
|
+
@classmethod
|
|
300
|
+
def _get_fields(cls) -> dict[str, type]:
|
|
301
|
+
if cls.__fields__ is None:
|
|
302
|
+
cls._generate_fields()
|
|
303
|
+
|
|
304
|
+
return cls.__fields__
|
|
305
|
+
|
|
306
|
+
@classmethod
|
|
307
|
+
def _get_query(cls) -> Query:
|
|
308
|
+
if cls.__query__ is None:
|
|
309
|
+
cls._generate_query()
|
|
310
|
+
|
|
311
|
+
return cls.__query__
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def create_table(cls) -> None:
|
|
315
|
+
# First we create the table if it doesn't exist
|
|
316
|
+
sql = cls._get_query().parse_create_table(fields=cls._get_fields())
|
|
317
|
+
cls._execute(query=sql)
|
|
318
|
+
# Then we create the index on the primary key if it doesn't exist
|
|
319
|
+
sql = cls._get_query().parse_index(fields=cls._primary_key)
|
|
320
|
+
cls._execute(query=sql)
|
|
321
|
+
|
|
322
|
+
@classmethod
|
|
323
|
+
def loads(
|
|
324
|
+
cls,
|
|
325
|
+
fields: list | None = None,
|
|
326
|
+
order_by: str | None = None,
|
|
327
|
+
limit: int | None = None,
|
|
328
|
+
return_type: Literal['dict', 'list'] = 'list',
|
|
329
|
+
**conditions: dict,
|
|
330
|
+
) -> list[Self] | dict[str, Self]:
|
|
331
|
+
if fields is None:
|
|
332
|
+
fields = set(cls._get_fields().keys())
|
|
333
|
+
sql, params = cls._get_query().parse_select(
|
|
334
|
+
fields=fields, order_by=order_by, limit=limit, conditions=conditions
|
|
335
|
+
)
|
|
336
|
+
return cls._execute(query=sql, params=params, return_type=return_type, klass=cls)
|
|
337
|
+
|
|
338
|
+
## Instance methods
|
|
339
|
+
def delete(self) -> None:
|
|
340
|
+
cls = type(self)
|
|
341
|
+
sql = cls._get_query().parse_delete()
|
|
342
|
+
cls._execute(query=sql, params={'ids': [getattr(self, self._primary_key)]})
|
|
343
|
+
|
|
344
|
+
def load(self) -> Self:
|
|
345
|
+
cls = type(self)
|
|
346
|
+
conditions = {f'{self._primary_key}__eq': getattr(self, self._primary_key)}
|
|
347
|
+
sql, params = cls._get_query().parse_select(fields=cls._get_fields(), limit=1, conditions=conditions)
|
|
348
|
+
results = cls._execute(query=sql, params=params)
|
|
349
|
+
|
|
350
|
+
if not results:
|
|
351
|
+
msg = f'{cls.__name__} with {self._primary_key}={getattr(self, self._primary_key)} not found.'
|
|
352
|
+
raise ValueError(msg)
|
|
353
|
+
|
|
354
|
+
self.update(results[0])
|
|
355
|
+
return self
|
|
356
|
+
|
|
357
|
+
def save(self) -> Self:
|
|
358
|
+
self.updated_on = DateTime.now()
|
|
359
|
+
cls = type(self)
|
|
360
|
+
sql = cls._get_query().parse_insert_or_update(fields=cls._get_fields())
|
|
361
|
+
# We need to change some values so they can be used as params
|
|
362
|
+
# To avoid modifying the current instance, we create a copy of it
|
|
363
|
+
# The deepcopy creates a new instance of the same class so we need to change it
|
|
364
|
+
# to dict avoiding validation and other checks
|
|
365
|
+
params = dict(deepcopy(self))
|
|
366
|
+
params = cls._get_query().parse_insert_or_update_params(params=params)
|
|
367
|
+
cls._execute(query=sql, params=params)
|
|
368
|
+
return self
|
|
369
|
+
|
|
370
|
+
def update(self, *args, **kwargs) -> None:
|
|
371
|
+
dct = dict(*args, **kwargs)
|
|
372
|
+
if self._validate_fields:
|
|
373
|
+
for key, value in dct.items():
|
|
374
|
+
self._validate(key, value)
|
|
375
|
+
|
|
376
|
+
super().update(dct)
|