clear-skies 1.21.0__py3-none-any.whl → 1.21.2__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.
Potentially problematic release.
This version of clear-skies might be problematic. Click here for more details.
- {clear_skies-1.21.0.dist-info → clear_skies-1.21.2.dist-info}/METADATA +2 -1
- {clear_skies-1.21.0.dist-info → clear_skies-1.21.2.dist-info}/RECORD +9 -9
- clearskies/column_types/category_tree.py +79 -1
- clearskies/column_types/datetime.py +1 -0
- clearskies/di/standard_dependencies.py +2 -1
- clearskies/model.py +38 -33
- clearskies/models.py +43 -43
- {clear_skies-1.21.0.dist-info → clear_skies-1.21.2.dist-info}/LICENSE +0 -0
- {clear_skies-1.21.0.dist-info → clear_skies-1.21.2.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: clear-skies
|
|
3
|
-
Version: 1.21.
|
|
3
|
+
Version: 1.21.2
|
|
4
4
|
Summary: A framework for building backends in the cloud
|
|
5
5
|
Home-page: https://github.com/cmancone/clearskies
|
|
6
6
|
License: MIT
|
|
@@ -25,6 +25,7 @@ Requires-Dist: jose (>=1.0.0,<2.0.0) ; extra == "jose"
|
|
|
25
25
|
Requires-Dist: jwcrypto (>=1.5.6,<2.0.0) ; extra == "jwcrypto"
|
|
26
26
|
Requires-Dist: pymysql (>=1.1.0,<2.0.0) ; extra == "mysql"
|
|
27
27
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
28
|
+
Requires-Dist: typing-extensions (>=4.12.0,<5.0.0) ; python_version >= "3.10" and python_version < "3.11"
|
|
28
29
|
Project-URL: Repository, https://github.com/cmancone/clearskies
|
|
29
30
|
Description-Content-Type: text/markdown
|
|
30
31
|
|
|
@@ -59,7 +59,7 @@ clearskies/column_types/__init__.py,sha256=wofhLfyW00I6tb6o9DMsMx7j9hlbbqefhDzWf
|
|
|
59
59
|
clearskies/column_types/audit.py,sha256=2YcrZVVElpOUdmxYTQ_6CshL1HVou6fz65dOOs_b8Gw,9659
|
|
60
60
|
clearskies/column_types/belongs_to.py,sha256=tH1tbTOfjifSNuVjO-KbMF7GiUIoLfcDItrrS3TGGM8,11044
|
|
61
61
|
clearskies/column_types/boolean.py,sha256=1yyM1CUfgD84pPE65c1OP1Qjf_J0Z45hjPrDR51AUkQ,1878
|
|
62
|
-
clearskies/column_types/category_tree.py,sha256=
|
|
62
|
+
clearskies/column_types/category_tree.py,sha256=5Wt3W2c2CvqiURqlFBORMvK0TK6qAhgzhx3wP8WfJSc,13167
|
|
63
63
|
clearskies/column_types/column.py,sha256=ftuDFswjk-KE9Frxo1rhgkjr4sjSjnUc5ZtfNrnGLIc,15530
|
|
64
64
|
clearskies/column_types/created.py,sha256=LIWSzPJ9rbHuk1u53pNvKtVCOG9y9XCn-mEEsSi97Zc,649
|
|
65
65
|
clearskies/column_types/created_by_authorization_data.py,sha256=--1w1TOSo2CMwrpn6Y_iorl2RTqLgG8MbR8k27qreew,1108
|
|
@@ -68,7 +68,7 @@ clearskies/column_types/created_by_ip.py,sha256=wwCUoEwHEVGN89x4xP7NJ6QR85Aum6v3
|
|
|
68
68
|
clearskies/column_types/created_by_routing_data.py,sha256=EhVorRaGV2OhEb0YSPwPmrsK2NQycYgGEd4ab8-qI2I,569
|
|
69
69
|
clearskies/column_types/created_by_user_agent.py,sha256=sSYDRrqSjsCwcYlhF_s9NO-iDww3PaH6aO2ATp_SKGQ,419
|
|
70
70
|
clearskies/column_types/created_micro.py,sha256=Y_YADPg-TYPfWCFuGbs1b7NeM1Ixi_oBP8OQfJgTI7s,670
|
|
71
|
-
clearskies/column_types/datetime.py,sha256=
|
|
71
|
+
clearskies/column_types/datetime.py,sha256=xtuZpUC9fA16i1oO80kPIx--8RDPuei9RdsDDqensbw,4340
|
|
72
72
|
clearskies/column_types/datetime_micro.py,sha256=ewQSniCc2MmNIyX2XNuNcCIwh5Fpf1HcvpLfzB8lz8g,382
|
|
73
73
|
clearskies/column_types/email.py,sha256=qq0Yo_C3KxUqT68q2HWXocBBR4xwMqjxcIdgZRv218U,584
|
|
74
74
|
clearskies/column_types/float.py,sha256=j8jJeBueSOusPtAFCWgLHYBncfLnqT1U7bh1zcAkYiA,1332
|
|
@@ -120,7 +120,7 @@ clearskies/di/__init__.py,sha256=T7SgQNny2XAZQPeFkdmp1XxxmEVxtnpcRiGK8YflkwU,304
|
|
|
120
120
|
clearskies/di/additional_config.py,sha256=jdoS_HWC0MAabori3WwLRAG1i5YKZmQfQ1o0hCoxsPs,526
|
|
121
121
|
clearskies/di/additional_config_auto_import.py,sha256=m57IODPbnCAus9iDu3mDp42u4H87oPZvjAlBGoS8uRQ,111
|
|
122
122
|
clearskies/di/di.py,sha256=g0U0PI73eNp0mkGH3KUN1fmqNic5eEUK-_IB8hQh-Kg,15511
|
|
123
|
-
clearskies/di/standard_dependencies.py,sha256=
|
|
123
|
+
clearskies/di/standard_dependencies.py,sha256=NW8tNZ2Punwq70XaH_e9IhmQ7ggsFFvum8ymwinoD7U,4522
|
|
124
124
|
clearskies/di/test_module/__init__.py,sha256=7YHQF7JHP0FdI7GdEGANSZ_t1EISQYhUNm1wqOg0NKw,88
|
|
125
125
|
clearskies/di/test_module/another_module/__init__.py,sha256=8SRmHPDepLKGWTUSc1ucDF6U8mJPsNDsBDmBQCpzPWo,35
|
|
126
126
|
clearskies/di/test_module/module_class.py,sha256=I_-wnMuHfbsvti-7d2Z4bXnr6deo__uvww9nds9qrlE,46
|
|
@@ -181,8 +181,8 @@ clearskies/input_requirements/unique.py,sha256=gpbm9uoXcy8WCHsuWqAotwockbjDfJOWi
|
|
|
181
181
|
clearskies/mocks/__init__.py,sha256=T68OUB9gGCX0WoisGzsY3Bt2cCFX7ILHKPqi6XKTJM0,113
|
|
182
182
|
clearskies/mocks/input_output.py,sha256=2wD5GbUyVSkXcBg1GTZ-Oz9VzcYxNHfTlmZAODW-7CI,3898
|
|
183
183
|
clearskies/mocks/models.py,sha256=DCzsnMddBvPoBA8JwwbSOhzY7enQWrosgeYD4gx2deI,5124
|
|
184
|
-
clearskies/model.py,sha256=
|
|
185
|
-
clearskies/models.py,sha256=
|
|
184
|
+
clearskies/model.py,sha256=N5el03awXEfTBfhqjS3Yuc6ozMjATs7cMLs_IoZNuaE,13511
|
|
185
|
+
clearskies/models.py,sha256=1H5Vohv1U4avN5_YHqULzc7ynZDRytmZ56x775xWTIo,13038
|
|
186
186
|
clearskies/secrets/__init__.py,sha256=ctTmA_etV9G_5U21APWENI1HvThrBS4DidGWRtEDHQs,1053
|
|
187
187
|
clearskies/secrets/additional_configs/__init__.py,sha256=cFCrbtKF5nuR061S2y1iKZp349x-y8Srdwe3VZbfSFU,1119
|
|
188
188
|
clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py,sha256=fOt2eOrVtQXhnK05XuSfWw9GoX6klxXLisJcFT0ycAE,2562
|
|
@@ -205,7 +205,7 @@ clearskies/tests/simple_api/models/__init__.py,sha256=nUA0W6fgXw_Bxa9CudkaDkC80t
|
|
|
205
205
|
clearskies/tests/simple_api/models/status.py,sha256=PEhPbaQh5qdUNHp8O0gz91LOLENAEBtqSaHxUPXchaM,699
|
|
206
206
|
clearskies/tests/simple_api/models/user.py,sha256=5_P4Tp1tTdX7PkMJ__epPM5MA7JAeVYGas69vcWloLc,819
|
|
207
207
|
clearskies/tests/simple_api/users_api.py,sha256=KYXCgEofDxHeRdQK67txN5oYUPvxxmB8JTku7L-apk4,2344
|
|
208
|
-
clear_skies-1.21.
|
|
209
|
-
clear_skies-1.21.
|
|
210
|
-
clear_skies-1.21.
|
|
211
|
-
clear_skies-1.21.
|
|
208
|
+
clear_skies-1.21.2.dist-info/LICENSE,sha256=3Ehd0g3YOpCj8sqj0Xjq5qbOtjjgk9qzhhD9YjRQgOA,1053
|
|
209
|
+
clear_skies-1.21.2.dist-info/METADATA,sha256=lvnx3rHgpMbUzIpeFsfGbjnLa98Bx4mHMMbHmRXfUkk,1817
|
|
210
|
+
clear_skies-1.21.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
|
211
|
+
clear_skies-1.21.2.dist-info/RECORD,,
|
|
@@ -119,6 +119,10 @@ class CategoryTree(BelongsTo):
|
|
|
119
119
|
"tree_level_column_name",
|
|
120
120
|
"max_iterations",
|
|
121
121
|
"parent_models_class",
|
|
122
|
+
"children_column_name",
|
|
123
|
+
"descendents_column_name",
|
|
124
|
+
"ancestors_column_name",
|
|
125
|
+
"load_relatives_strategy",
|
|
122
126
|
]
|
|
123
127
|
|
|
124
128
|
def __init__(self, di):
|
|
@@ -137,6 +141,11 @@ class CategoryTree(BelongsTo):
|
|
|
137
141
|
configuration["tree_models_class"],
|
|
138
142
|
config_name="tree_models_class",
|
|
139
143
|
)
|
|
144
|
+
load_relatives_strategy = configuration.get("load_relatives_strategy", None)
|
|
145
|
+
if load_relatives_strategy and load_relatives_strategy not in ["join", "where_in", "individual"]:
|
|
146
|
+
raise ValueError(
|
|
147
|
+
f"Configuration error for category_tree column '{self.name} in model class '{self.model_class.__name__}': load_relatives_strategy must be one of ['join', 'where_in', or 'individual']"
|
|
148
|
+
)
|
|
140
149
|
|
|
141
150
|
def _finalize_configuration(self, configuration):
|
|
142
151
|
return {
|
|
@@ -152,6 +161,10 @@ class CategoryTree(BelongsTo):
|
|
|
152
161
|
"tree_is_parent_column_name": configuration.get("tree_is_parent_column_name", "is_parent"),
|
|
153
162
|
"tree_level_column_name": configuration.get("tree_level_column_name", "level"),
|
|
154
163
|
"max_iterations": configuration.get("max_iterations", 100),
|
|
164
|
+
"children_column_name": configuration.get("children_column_name", "children"),
|
|
165
|
+
"descendents_column_name": configuration.get("descendents_column_name", "descendents"),
|
|
166
|
+
"ancestors_column_name": configuration.get("ancestors_column_name", "ancestors"),
|
|
167
|
+
"load_relatives_strategy": configuration.get("load_relatives_strategy", "join"),
|
|
155
168
|
},
|
|
156
169
|
}
|
|
157
170
|
|
|
@@ -167,7 +180,7 @@ class CategoryTree(BelongsTo):
|
|
|
167
180
|
return data
|
|
168
181
|
|
|
169
182
|
def force_tree_update(self, model):
|
|
170
|
-
self.update_tree_table(model, model.
|
|
183
|
+
self.update_tree_table(model, model.get(self.id_column_name), model.__getattr__(self.name))
|
|
171
184
|
|
|
172
185
|
def update_tree_table(self, model, child_id, direct_parent_id):
|
|
173
186
|
tree_models = self.tree_models
|
|
@@ -224,3 +237,68 @@ class CategoryTree(BelongsTo):
|
|
|
224
237
|
+ "You may have accidentally created a circular cateogry tree. If not, and your category tree "
|
|
225
238
|
+ "really _is_ that deep, then adjust the 'max_iterations' configuration for this column accordingly. "
|
|
226
239
|
)
|
|
240
|
+
|
|
241
|
+
def can_provide(self, column_name):
|
|
242
|
+
return column_name in [
|
|
243
|
+
self.config("model_column_name"),
|
|
244
|
+
self.config("children_column_name"),
|
|
245
|
+
self.config("descendents_column_name"),
|
|
246
|
+
self.config("ancestors_column_name"),
|
|
247
|
+
]
|
|
248
|
+
|
|
249
|
+
def provide(self, data, column_name):
|
|
250
|
+
if column_name == self.config("model_column_name"):
|
|
251
|
+
return super().provide(data, column_name)
|
|
252
|
+
|
|
253
|
+
if column_name == self.config("children_column_name"):
|
|
254
|
+
return self.relatives(data)
|
|
255
|
+
|
|
256
|
+
if column_name == self.config("descendents_column_name"):
|
|
257
|
+
return self.relatives(data, include_all=True)
|
|
258
|
+
|
|
259
|
+
if column_name == self.config("ancestors_column_name"):
|
|
260
|
+
return self.relatives(data, find_parents=True)
|
|
261
|
+
|
|
262
|
+
def relatives(self, data, include_all=False, find_parents=False):
|
|
263
|
+
id_column_name = self.model_class.id_column_name
|
|
264
|
+
model_id = data[self.model_class.id_column_name]
|
|
265
|
+
model_table_name = self.model_class.table_name()
|
|
266
|
+
tree_table_name = self.config("tree_models_class").table_name()
|
|
267
|
+
parent_id_column_name = self.config("tree_parent_id_column_name")
|
|
268
|
+
child_id_column_name = self.config("tree_child_id_column_name")
|
|
269
|
+
is_parent_column_name = self.config("is_parent_column_name")
|
|
270
|
+
level_column_name = self.config("tree_level_column_name")
|
|
271
|
+
|
|
272
|
+
if find_parents:
|
|
273
|
+
join_on = parent_id_column_name
|
|
274
|
+
search_on = child_id_column_name
|
|
275
|
+
else:
|
|
276
|
+
join_on = child_id_column_name
|
|
277
|
+
search_on = parent_id_column_name
|
|
278
|
+
|
|
279
|
+
# if we can join then use a join.
|
|
280
|
+
if self.config("load_relatives_strategy") == "join":
|
|
281
|
+
relatives = self.parent_models.join(
|
|
282
|
+
f"{tree_table_name} as tree on tree.{join_on}={model_table_name}.{id_column_name}"
|
|
283
|
+
)
|
|
284
|
+
relatives = relatives.where(f"tree.{search_on}={model_id}")
|
|
285
|
+
if not include_all:
|
|
286
|
+
relatives = relatives.where(f"tree.{is_parent_column_name}=1")
|
|
287
|
+
if find_parents:
|
|
288
|
+
relatives = relatives.sort_by(level_column_name, "asc")
|
|
289
|
+
return relatives
|
|
290
|
+
|
|
291
|
+
# joins only work for SQL-like backends. Otherwise, we have to pull out our list of ids
|
|
292
|
+
branches = self.tree_models.where(f"{search_on}={model_id}")
|
|
293
|
+
if not include_all:
|
|
294
|
+
branches = branches.where(f"tree.{is_parent_column_name}=1")
|
|
295
|
+
if find_parents:
|
|
296
|
+
branches = branches.sort_by(level_column_name, "asc")
|
|
297
|
+
ids = [str(branch.get(child_id_column_name)) for branch in branches]
|
|
298
|
+
|
|
299
|
+
# Can we search with a WHERE IN() clause? If the backend supports it, it is probably faster
|
|
300
|
+
if self.config("load_relatives_strategy") == "where_in":
|
|
301
|
+
return self.parent_models.where(f"{id_column_name} IN ('" + "','".join(ids) + "')")
|
|
302
|
+
|
|
303
|
+
# otherwise we have to load each model individually which is SLOW....
|
|
304
|
+
return [self.parent_models.find(f"{id_column_name}={id}") for id in ids]
|
clearskies/model.py
CHANGED
|
@@ -5,6 +5,11 @@ from .functional import string
|
|
|
5
5
|
import re
|
|
6
6
|
from .models import Models
|
|
7
7
|
|
|
8
|
+
try:
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
except ModuleNotFoundError:
|
|
11
|
+
from typing import Self
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
class Model(Models):
|
|
10
15
|
_configured_columns = None
|
|
@@ -14,14 +19,14 @@ class Model(Models):
|
|
|
14
19
|
_transformed = None
|
|
15
20
|
id_column_name = "id"
|
|
16
21
|
|
|
17
|
-
def __init__(self, backend, columns):
|
|
22
|
+
def __init__(self: Self, backend, columns):
|
|
18
23
|
super().__init__(backend, columns)
|
|
19
24
|
self._transformed = {}
|
|
20
25
|
self._data = {}
|
|
21
26
|
self._previous_data = None
|
|
22
27
|
self._touched_columns = None
|
|
23
28
|
|
|
24
|
-
def model_class(self):
|
|
29
|
+
def model_class(self: Self) -> type[Self]:
|
|
25
30
|
"""
|
|
26
31
|
Return the model class that this models object will find/return instances of
|
|
27
32
|
|
|
@@ -30,7 +35,7 @@ class Model(Models):
|
|
|
30
35
|
return self.__class__
|
|
31
36
|
|
|
32
37
|
@classmethod
|
|
33
|
-
def table_name(cls):
|
|
38
|
+
def table_name(cls: type[Self]) -> str:
|
|
34
39
|
"""Return the name of the table that the model uses for data storage"""
|
|
35
40
|
singular = string.camel_case_to_snake_case(cls.__name__)
|
|
36
41
|
if singular[-1] == "y":
|
|
@@ -40,16 +45,16 @@ class Model(Models):
|
|
|
40
45
|
return f"{singular}s"
|
|
41
46
|
|
|
42
47
|
@abstractmethod
|
|
43
|
-
def columns_configuration(self):
|
|
48
|
+
def columns_configuration(self: Self):
|
|
44
49
|
"""Returns an ordered dictionary with the configuration for the columns"""
|
|
45
50
|
pass
|
|
46
51
|
|
|
47
|
-
def all_columns(self):
|
|
52
|
+
def all_columns(self: Self):
|
|
48
53
|
default = OrderedDict([(self.id_column_name, {"class": UUID})])
|
|
49
54
|
default.update(self.columns_configuration())
|
|
50
55
|
return default
|
|
51
56
|
|
|
52
|
-
def columns(self, overrides=None):
|
|
57
|
+
def columns(self: Self, overrides=None):
|
|
53
58
|
# no caching if we have overrides
|
|
54
59
|
if overrides is not None:
|
|
55
60
|
return self._columns.configure(self.all_columns(), self.__class__, overrides=overrides)
|
|
@@ -58,13 +63,13 @@ class Model(Models):
|
|
|
58
63
|
self._configured_columns = self._columns.configure(self.all_columns(), self.__class__)
|
|
59
64
|
return self._configured_columns
|
|
60
65
|
|
|
61
|
-
def supports_n_plus_one(self):
|
|
66
|
+
def supports_n_plus_one(self: Self):
|
|
62
67
|
return self._backend.supports_n_plus_one
|
|
63
68
|
|
|
64
|
-
def __getitem__(self, column_name):
|
|
69
|
+
def __getitem__(self: Self, column_name):
|
|
65
70
|
return self.__getattr__(column_name)
|
|
66
71
|
|
|
67
|
-
def __getattr__(self, column_name):
|
|
72
|
+
def __getattr__(self: Self, column_name):
|
|
68
73
|
# this should be adjusted to only return None for empty records if the column name corresponds
|
|
69
74
|
# to an actual column in the table.
|
|
70
75
|
if not self.exists:
|
|
@@ -72,13 +77,13 @@ class Model(Models):
|
|
|
72
77
|
|
|
73
78
|
return self.get_transformed_from_data(column_name, self._data)
|
|
74
79
|
|
|
75
|
-
def get(self, column_name, silent=False):
|
|
80
|
+
def get(self: Self, column_name, silent=False):
|
|
76
81
|
if not self.exists:
|
|
77
82
|
return None
|
|
78
83
|
|
|
79
84
|
return self.get_transformed_from_data(column_name, self._data, silent=silent)
|
|
80
85
|
|
|
81
|
-
def get_transformed_from_data(self, column_name, data, cache=True, check_providers=True, silent=False):
|
|
86
|
+
def get_transformed_from_data(self: Self, column_name, data, cache=True, check_providers=True, silent=False):
|
|
82
87
|
if cache and column_name in self._transformed:
|
|
83
88
|
return self._transformed[column_name]
|
|
84
89
|
|
|
@@ -111,18 +116,18 @@ class Model(Models):
|
|
|
111
116
|
return value
|
|
112
117
|
|
|
113
118
|
@property
|
|
114
|
-
def exists(self):
|
|
119
|
+
def exists(self: Self) -> bool:
|
|
115
120
|
return True if (self.id_column_name in self._data and self._data[self.id_column_name]) else False
|
|
116
121
|
|
|
117
122
|
@property
|
|
118
|
-
def data(self):
|
|
123
|
+
def data(self: Self):
|
|
119
124
|
return self._data
|
|
120
125
|
|
|
121
126
|
@data.setter
|
|
122
|
-
def data(self, data):
|
|
127
|
+
def data(self: Self, data) -> None:
|
|
123
128
|
self._data = {} if data is None else data
|
|
124
129
|
|
|
125
|
-
def save(self, data, columns=None):
|
|
130
|
+
def save(self: Self, data, columns=None) -> bool:
|
|
126
131
|
"""
|
|
127
132
|
Save data to the database and update the model!
|
|
128
133
|
|
|
@@ -168,7 +173,7 @@ class Model(Models):
|
|
|
168
173
|
|
|
169
174
|
return True
|
|
170
175
|
|
|
171
|
-
def is_changing(self, key, data):
|
|
176
|
+
def is_changing(self: Self, key, data) -> bool:
|
|
172
177
|
"""
|
|
173
178
|
Returns True/False to denote if the given column is being modified by the active save operation
|
|
174
179
|
|
|
@@ -184,7 +189,7 @@ class Model(Models):
|
|
|
184
189
|
|
|
185
190
|
return self.__getattr__(key) != data[key]
|
|
186
191
|
|
|
187
|
-
def latest(self, key, data):
|
|
192
|
+
def latest(self: Self, key, data):
|
|
188
193
|
"""
|
|
189
194
|
Returns the 'latest' value for a column during the save operation
|
|
190
195
|
|
|
@@ -198,7 +203,7 @@ class Model(Models):
|
|
|
198
203
|
return data[key]
|
|
199
204
|
return self.__getattr__(key)
|
|
200
205
|
|
|
201
|
-
def was_changed(self, key):
|
|
206
|
+
def was_changed(self: Self, key) -> bool:
|
|
202
207
|
"""Returns True/False to denote if a column was changed in the last save"""
|
|
203
208
|
if self._previous_data is None:
|
|
204
209
|
raise ValueError("was_changed was called before a save was finished - you must save something first")
|
|
@@ -221,10 +226,10 @@ class Model(Models):
|
|
|
221
226
|
return old_value != new_value
|
|
222
227
|
return not columns[key].values_match(old_value, new_value)
|
|
223
228
|
|
|
224
|
-
def previous_value(self, key):
|
|
229
|
+
def previous_value(self: Self, key):
|
|
225
230
|
return self.get_transformed_from_data(key, self._previous_data, cache=False, check_providers=False, silent=True)
|
|
226
231
|
|
|
227
|
-
def delete(self, except_if_not_exists=True):
|
|
232
|
+
def delete(self: Self, except_if_not_exists=True) -> bool:
|
|
228
233
|
if not self.exists:
|
|
229
234
|
if except_if_not_exists:
|
|
230
235
|
raise ValueError("Cannot delete model that already exists")
|
|
@@ -240,7 +245,7 @@ class Model(Models):
|
|
|
240
245
|
self.post_delete()
|
|
241
246
|
return True
|
|
242
247
|
|
|
243
|
-
def columns_pre_save(self, data, columns):
|
|
248
|
+
def columns_pre_save(self: Self, data, columns):
|
|
244
249
|
"""Uses the column information present in the model to make any necessary changes before saving"""
|
|
245
250
|
for column in columns.values():
|
|
246
251
|
data = column.pre_save(data, self)
|
|
@@ -258,7 +263,7 @@ class Model(Models):
|
|
|
258
263
|
"""
|
|
259
264
|
return data
|
|
260
265
|
|
|
261
|
-
def columns_to_backend(self, data, columns):
|
|
266
|
+
def columns_to_backend(self: Self, data, columns):
|
|
262
267
|
backend_data = {**data}
|
|
263
268
|
temporary_data = {}
|
|
264
269
|
for column in columns.values():
|
|
@@ -275,10 +280,10 @@ class Model(Models):
|
|
|
275
280
|
|
|
276
281
|
return [backend_data, temporary_data]
|
|
277
282
|
|
|
278
|
-
def to_backend(self, data, columns):
|
|
283
|
+
def to_backend(self: Self, data, columns):
|
|
279
284
|
return data
|
|
280
285
|
|
|
281
|
-
def columns_post_save(self, data, id, columns):
|
|
286
|
+
def columns_post_save(self: Self, data, id, columns):
|
|
282
287
|
"""Uses the column information present in the model to make additional changes as needed after saving"""
|
|
283
288
|
for column in columns.values():
|
|
284
289
|
data = column.post_save(data, self, id)
|
|
@@ -288,12 +293,12 @@ class Model(Models):
|
|
|
288
293
|
)
|
|
289
294
|
return data
|
|
290
295
|
|
|
291
|
-
def columns_save_finished(self, columns):
|
|
296
|
+
def columns_save_finished(self: Self, columns):
|
|
292
297
|
"""Calls the save_finished method on all of our columns"""
|
|
293
298
|
for column in columns.values():
|
|
294
299
|
column.save_finished(self)
|
|
295
300
|
|
|
296
|
-
def post_save(self, data, id):
|
|
301
|
+
def post_save(self: Self, data, id):
|
|
297
302
|
"""
|
|
298
303
|
A hook to extend so you can provide additional pre-save logic as needed
|
|
299
304
|
|
|
@@ -302,7 +307,7 @@ class Model(Models):
|
|
|
302
307
|
"""
|
|
303
308
|
pass
|
|
304
309
|
|
|
305
|
-
def pre_save(self, data):
|
|
310
|
+
def pre_save(self: Self, data):
|
|
306
311
|
"""
|
|
307
312
|
A hook to extend so you can provide additional pre-save logic as needed
|
|
308
313
|
|
|
@@ -310,7 +315,7 @@ class Model(Models):
|
|
|
310
315
|
"""
|
|
311
316
|
return data
|
|
312
317
|
|
|
313
|
-
def save_finished(self):
|
|
318
|
+
def save_finished(self: Self):
|
|
314
319
|
"""
|
|
315
320
|
A hook to extend so you can provide additional logic after a save operation has fully completed
|
|
316
321
|
|
|
@@ -320,29 +325,29 @@ class Model(Models):
|
|
|
320
325
|
"""
|
|
321
326
|
pass
|
|
322
327
|
|
|
323
|
-
def columns_pre_delete(self, columns):
|
|
328
|
+
def columns_pre_delete(self: Self, columns):
|
|
324
329
|
"""Uses the column information present in the model to make any necessary changes before deleting"""
|
|
325
330
|
for column in columns.values():
|
|
326
331
|
column.pre_delete(self)
|
|
327
332
|
|
|
328
|
-
def pre_delete(self):
|
|
333
|
+
def pre_delete(self: Self):
|
|
329
334
|
"""
|
|
330
335
|
A hook to extend so you can provide additional pre-delete logic as needed
|
|
331
336
|
"""
|
|
332
337
|
pass
|
|
333
338
|
|
|
334
|
-
def columns_post_delete(self, columns):
|
|
339
|
+
def columns_post_delete(self: Self, columns):
|
|
335
340
|
"""Uses the column information present in the model to make any necessary changes after deleting"""
|
|
336
341
|
for column in columns.values():
|
|
337
342
|
column.post_delete(self)
|
|
338
343
|
|
|
339
|
-
def post_delete(self):
|
|
344
|
+
def post_delete(self: Self):
|
|
340
345
|
"""
|
|
341
346
|
A hook to extend so you can provide additional post-delete logic as needed
|
|
342
347
|
"""
|
|
343
348
|
pass
|
|
344
349
|
|
|
345
|
-
def where_for_request(self, models, routing_data, authorization_data, input_output, overrides=None):
|
|
350
|
+
def where_for_request(self: Self, models, routing_data, authorization_data, input_output, overrides=None):
|
|
346
351
|
"""
|
|
347
352
|
A hook to automatically apply filtering whenever the model makes an appearance in a get/update/list/search handler.
|
|
348
353
|
"""
|
clearskies/models.py
CHANGED
|
@@ -47,30 +47,30 @@ class Models(ABC, ConditionParser):
|
|
|
47
47
|
self.query_select_all = True
|
|
48
48
|
|
|
49
49
|
@abstractmethod
|
|
50
|
-
def model_class(self):
|
|
50
|
+
def model_class(self: Self) -> type[Self]:
|
|
51
51
|
"""Return the model class that this models object will find/return instances of"""
|
|
52
52
|
pass
|
|
53
53
|
|
|
54
|
-
def clone(self) -> Self:
|
|
54
|
+
def clone(self: Self) -> Self:
|
|
55
55
|
clone = self.blank()
|
|
56
56
|
clone.query_configuration = self.query_configuration
|
|
57
57
|
return clone
|
|
58
58
|
|
|
59
|
-
def blank(self) -> Self:
|
|
59
|
+
def blank(self: Self) -> Self:
|
|
60
60
|
return self._build_model()
|
|
61
61
|
|
|
62
|
-
def get_table_name(self) -> str:
|
|
62
|
+
def get_table_name(self: Self) -> str:
|
|
63
63
|
if self._table_name is None:
|
|
64
64
|
self._table_name = self.model_class().table_name()
|
|
65
65
|
return self._table_name
|
|
66
66
|
|
|
67
|
-
def get_id_column_name(self) -> str:
|
|
67
|
+
def get_id_column_name(self: Self) -> str:
|
|
68
68
|
if self._id_column_name is None:
|
|
69
69
|
self._id_column_name = self.empty_model().id_column_name
|
|
70
70
|
return self._id_column_name
|
|
71
71
|
|
|
72
72
|
@property
|
|
73
|
-
def query_configuration(self) -> Dict[str, Any]:
|
|
73
|
+
def query_configuration(self: Self) -> Dict[str, Any]:
|
|
74
74
|
return {
|
|
75
75
|
"wheres": [*self.query_wheres],
|
|
76
76
|
"sorts": [*self.query_sorts],
|
|
@@ -85,7 +85,7 @@ class Models(ABC, ConditionParser):
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
@query_configuration.setter
|
|
88
|
-
def query_configuration(self, configuration: Dict[str, Any]):
|
|
88
|
+
def query_configuration(self: Self, configuration: Dict[str, Any]):
|
|
89
89
|
self.query_wheres = configuration["wheres"]
|
|
90
90
|
self.query_sorts = configuration["sorts"]
|
|
91
91
|
self.query_group_by_column = configuration["group_by_column"]
|
|
@@ -97,34 +97,34 @@ class Models(ABC, ConditionParser):
|
|
|
97
97
|
self._model_columns = configuration["model_columns"]
|
|
98
98
|
|
|
99
99
|
@property
|
|
100
|
-
def model_columns(self):
|
|
100
|
+
def model_columns(self: Self):
|
|
101
101
|
if self._model_columns is None:
|
|
102
102
|
self._model_columns = self.empty_model().columns()
|
|
103
103
|
return self._model_columns
|
|
104
104
|
|
|
105
|
-
def select(self, selects) -> Self:
|
|
105
|
+
def select(self: Self, selects) -> Self:
|
|
106
106
|
return self.clone().select_in_place(selects)
|
|
107
107
|
|
|
108
|
-
def select_in_place(self, selects) -> Self:
|
|
108
|
+
def select_in_place(self: Self, selects) -> Self:
|
|
109
109
|
self.query_selects.append(selects)
|
|
110
110
|
self.must_rexecute = True
|
|
111
111
|
self._next_page_data = None
|
|
112
112
|
return self
|
|
113
113
|
|
|
114
|
-
def select_all(self, select_all=True) -> Self:
|
|
114
|
+
def select_all(self: Self, select_all=True) -> Self:
|
|
115
115
|
return self.clone().select_all_in_place(select_all=select_all)
|
|
116
116
|
|
|
117
|
-
def select_all_in_place(self, select_all=True) -> Self:
|
|
117
|
+
def select_all_in_place(self: Self, select_all=True) -> Self:
|
|
118
118
|
self.query_select_all = select_all
|
|
119
119
|
self.must_rexecute = True
|
|
120
120
|
self._next_page_data = None
|
|
121
121
|
return self
|
|
122
122
|
|
|
123
|
-
def where(self, where: str) -> Self:
|
|
123
|
+
def where(self: Self, where: str) -> Self:
|
|
124
124
|
"""Adds the given condition to the query and returns a new Models object"""
|
|
125
125
|
return self.clone().where_in_place(where)
|
|
126
126
|
|
|
127
|
-
def where_in_place(self, where: str) -> Self:
|
|
127
|
+
def where_in_place(self: Self, where: str) -> Self:
|
|
128
128
|
"""Adds the given condition to the query for the current Models object"""
|
|
129
129
|
condition = self.parse_condition(where)
|
|
130
130
|
self._validate_column(condition["column"], "filter", table=condition["table"])
|
|
@@ -134,17 +134,17 @@ class Models(ABC, ConditionParser):
|
|
|
134
134
|
self.must_recount = True
|
|
135
135
|
return self
|
|
136
136
|
|
|
137
|
-
def join(self, join: str) -> Self:
|
|
137
|
+
def join(self: Self, join: str) -> Self:
|
|
138
138
|
return self.clone().join_in_place(join)
|
|
139
139
|
|
|
140
|
-
def join_in_place(self, join: str) -> Self:
|
|
140
|
+
def join_in_place(self: Self, join: str) -> Self:
|
|
141
141
|
self.query_joins.append(self.parse_join(join))
|
|
142
142
|
self.must_rexecute = True
|
|
143
143
|
self._next_page_data = None
|
|
144
144
|
self.must_recount = True
|
|
145
145
|
return self
|
|
146
146
|
|
|
147
|
-
def is_joined(self, table_name, alias=None):
|
|
147
|
+
def is_joined(self: Self, table_name, alias=None):
|
|
148
148
|
for join in self.query_joins:
|
|
149
149
|
if join["table"] != table_name:
|
|
150
150
|
continue
|
|
@@ -155,10 +155,10 @@ class Models(ABC, ConditionParser):
|
|
|
155
155
|
return join["alias"] if join["alias"] else join["table"]
|
|
156
156
|
return False
|
|
157
157
|
|
|
158
|
-
def group_by(self, group_column: str) -> Self:
|
|
158
|
+
def group_by(self: Self, group_column: str) -> Self:
|
|
159
159
|
return self.clone().group_by_in_place(group_column)
|
|
160
160
|
|
|
161
|
-
def group_by_in_place(self, group_column: str) -> Self:
|
|
161
|
+
def group_by_in_place(self: Self, group_column: str) -> Self:
|
|
162
162
|
self._validate_column(group_column, "group")
|
|
163
163
|
self.query_group_by_column = group_column
|
|
164
164
|
self.must_rexecute = True
|
|
@@ -167,7 +167,7 @@ class Models(ABC, ConditionParser):
|
|
|
167
167
|
return self
|
|
168
168
|
|
|
169
169
|
def sort_by(
|
|
170
|
-
self,
|
|
170
|
+
self: Self,
|
|
171
171
|
primary_column,
|
|
172
172
|
primary_direction,
|
|
173
173
|
primary_table=None,
|
|
@@ -185,7 +185,7 @@ class Models(ABC, ConditionParser):
|
|
|
185
185
|
)
|
|
186
186
|
|
|
187
187
|
def sort_by_in_place(
|
|
188
|
-
self,
|
|
188
|
+
self: Self,
|
|
189
189
|
primary_column,
|
|
190
190
|
primary_direction,
|
|
191
191
|
primary_table=None,
|
|
@@ -205,7 +205,7 @@ class Models(ABC, ConditionParser):
|
|
|
205
205
|
self._next_page_data = None
|
|
206
206
|
return self
|
|
207
207
|
|
|
208
|
-
def _normalize_and_validate_sort(self, sort):
|
|
208
|
+
def _normalize_and_validate_sort(self: Self, sort):
|
|
209
209
|
if "column" not in sort or not sort["column"]:
|
|
210
210
|
raise ValueError("Missing 'column' for sort")
|
|
211
211
|
if "direction" not in sort or not sort["direction"]:
|
|
@@ -218,7 +218,7 @@ class Models(ABC, ConditionParser):
|
|
|
218
218
|
# down the line we may ask the model class what columns we can sort on, but we're good for now
|
|
219
219
|
return {"column": sort["column"], "direction": sort["direction"], "table": sort.get("table")}
|
|
220
220
|
|
|
221
|
-
def _validate_column(self, column_name, action, table=None):
|
|
221
|
+
def _validate_column(self: Self, column_name, action, table=None):
|
|
222
222
|
"""
|
|
223
223
|
Down the line we may use the model configuration to check what columns are valid sort/group/search targets
|
|
224
224
|
"""
|
|
@@ -246,19 +246,19 @@ class Models(ABC, ConditionParser):
|
|
|
246
246
|
+ "to your model definition"
|
|
247
247
|
)
|
|
248
248
|
|
|
249
|
-
def limit(self, limit) -> Self:
|
|
249
|
+
def limit(self: Self, limit) -> Self:
|
|
250
250
|
return self.clone().limit_in_place(limit)
|
|
251
251
|
|
|
252
|
-
def limit_in_place(self, limit) -> Self:
|
|
252
|
+
def limit_in_place(self: Self, limit) -> Self:
|
|
253
253
|
self.query_limit = limit
|
|
254
254
|
self.must_rexecute = True
|
|
255
255
|
self._next_page_data = None
|
|
256
256
|
return self
|
|
257
257
|
|
|
258
|
-
def pagination(self, **kwargs) -> Self:
|
|
258
|
+
def pagination(self: Self, **kwargs) -> Self:
|
|
259
259
|
return self.clone().pagination_in_place(**kwargs)
|
|
260
260
|
|
|
261
|
-
def pagination_in_place(self, **kwargs) -> Self:
|
|
261
|
+
def pagination_in_place(self: Self, **kwargs) -> Self:
|
|
262
262
|
error = self._backend.validate_pagination_kwargs(kwargs, str)
|
|
263
263
|
if error:
|
|
264
264
|
raise ValueError(
|
|
@@ -270,17 +270,17 @@ class Models(ABC, ConditionParser):
|
|
|
270
270
|
self._next_page_data = None
|
|
271
271
|
return self
|
|
272
272
|
|
|
273
|
-
def find(self, where: str) -> Self:
|
|
273
|
+
def find(self: Self, where: str) -> Self:
|
|
274
274
|
"""Returns the first model where condition"""
|
|
275
275
|
return self.clone().where(where).first()
|
|
276
276
|
|
|
277
|
-
def __len__(self):
|
|
277
|
+
def __len__(self: Self):
|
|
278
278
|
if self.must_recount:
|
|
279
279
|
self.count = self._backend.count(self.query_configuration, self.empty_model())
|
|
280
280
|
self.must_recount = False
|
|
281
281
|
return self.count
|
|
282
282
|
|
|
283
|
-
def __iter__(self) -> Iterator[Self]:
|
|
283
|
+
def __iter__(self: Self) -> Iterator[Self]:
|
|
284
284
|
self._next_page_data = {}
|
|
285
285
|
raw_rows = self._backend.records(
|
|
286
286
|
self.query_configuration,
|
|
@@ -290,7 +290,7 @@ class Models(ABC, ConditionParser):
|
|
|
290
290
|
models = iter([self.model(row) for row in raw_rows])
|
|
291
291
|
return models
|
|
292
292
|
|
|
293
|
-
def paginate_all(self) -> List[Self]:
|
|
293
|
+
def paginate_all(self: Self) -> List[Self]:
|
|
294
294
|
next_models = self.clone()
|
|
295
295
|
results = list(next_models.__iter__())
|
|
296
296
|
next_page_data = next_models.next_page_data()
|
|
@@ -300,51 +300,51 @@ class Models(ABC, ConditionParser):
|
|
|
300
300
|
next_page_data = next_models.next_page_data()
|
|
301
301
|
return results
|
|
302
302
|
|
|
303
|
-
def model(self, data) -> Self:
|
|
303
|
+
def model(self: Self, data) -> Self:
|
|
304
304
|
model = self._build_model()
|
|
305
305
|
model.data = data
|
|
306
306
|
return model
|
|
307
307
|
|
|
308
|
-
def _build_model(self) -> Self:
|
|
308
|
+
def _build_model(self: Self) -> Self:
|
|
309
309
|
model_class = self.model_class()
|
|
310
310
|
return model_class(self._backend, self._columns)
|
|
311
311
|
|
|
312
|
-
def empty_model(self) -> Self:
|
|
312
|
+
def empty_model(self: Self) -> Self:
|
|
313
313
|
return self.model({})
|
|
314
314
|
|
|
315
|
-
def create(self, data: Dict[str, Any]) -> Self:
|
|
315
|
+
def create(self: Self, data: Dict[str, Any]) -> Self:
|
|
316
316
|
empty = self.empty_model()
|
|
317
317
|
empty.save(data)
|
|
318
318
|
return empty
|
|
319
319
|
|
|
320
|
-
def first(self) -> Self:
|
|
320
|
+
def first(self: Self) -> Self:
|
|
321
321
|
iter = self.__iter__()
|
|
322
322
|
try:
|
|
323
323
|
return iter.__next__()
|
|
324
324
|
except StopIteration:
|
|
325
325
|
return self.empty_model()
|
|
326
326
|
|
|
327
|
-
def columns(self, overrides=None):
|
|
327
|
+
def columns(self: Self, overrides=None):
|
|
328
328
|
model = self.model({})
|
|
329
329
|
return model.columns(overrides=None)
|
|
330
330
|
|
|
331
|
-
def raw_columns_configuration(self):
|
|
331
|
+
def raw_columns_configuration(self: Self):
|
|
332
332
|
return self.model({}).all_columns()
|
|
333
333
|
|
|
334
|
-
def allowed_pagination_keys(self) -> List[str]:
|
|
334
|
+
def allowed_pagination_keys(self: Self) -> List[str]:
|
|
335
335
|
return self._backend.allowed_pagination_keys()
|
|
336
336
|
|
|
337
337
|
def validate_pagination_kwargs(self, kwargs: Dict[str, Any], case_mapping: Callable) -> str:
|
|
338
338
|
return self._backend.validate_pagination_kwargs(kwargs, case_mapping)
|
|
339
339
|
|
|
340
|
-
def next_page_data(self):
|
|
340
|
+
def next_page_data(self: Self):
|
|
341
341
|
return self._next_page_data
|
|
342
342
|
|
|
343
|
-
def documentation_pagination_next_page_response(self, case_mapping: Callable) -> List[Any]:
|
|
343
|
+
def documentation_pagination_next_page_response(self: Self, case_mapping: Callable) -> List[Any]:
|
|
344
344
|
return self._backend.documentation_pagination_next_page_response(case_mapping)
|
|
345
345
|
|
|
346
|
-
def documentation_pagination_next_page_example(self, case_mapping: Callable) -> Dict[str, Any]:
|
|
346
|
+
def documentation_pagination_next_page_example(self: Self, case_mapping: Callable) -> Dict[str, Any]:
|
|
347
347
|
return self._backend.documentation_pagination_next_page_example(case_mapping)
|
|
348
348
|
|
|
349
|
-
def documentation_pagination_parameters(self, case_mapping: Callable) -> List[Tuple[Any]]:
|
|
349
|
+
def documentation_pagination_parameters(self: Self, case_mapping: Callable) -> List[Tuple[Any]]:
|
|
350
350
|
return self._backend.documentation_pagination_parameters(case_mapping)
|
|
File without changes
|
|
File without changes
|