statikk 0.1.7__py3-none-any.whl → 0.1.8__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.
- statikk/engine.py +32 -7
- statikk/expressions.py +1 -1
- statikk/models.py +63 -11
- {statikk-0.1.7.dist-info → statikk-0.1.8.dist-info}/METADATA +1 -1
- statikk-0.1.8.dist-info/RECORD +12 -0
- statikk-0.1.7.dist-info/RECORD +0 -12
- {statikk-0.1.7.dist-info → statikk-0.1.8.dist-info}/LICENSE.txt +0 -0
- {statikk-0.1.7.dist-info → statikk-0.1.8.dist-info}/WHEEL +0 -0
- {statikk-0.1.7.dist-info → statikk-0.1.8.dist-info}/top_level.txt +0 -0
statikk/engine.py
CHANGED
@@ -17,7 +17,7 @@ from statikk.models import (
|
|
17
17
|
KeySchema,
|
18
18
|
)
|
19
19
|
from statikk.fields import FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID
|
20
|
-
|
20
|
+
from copy import deepcopy
|
21
21
|
from aws_xray_sdk.core import patch_all
|
22
22
|
|
23
23
|
patch_all()
|
@@ -58,6 +58,7 @@ class Table:
|
|
58
58
|
for idx in self.indexes:
|
59
59
|
for model in self.models:
|
60
60
|
self._set_index_fields(model, idx)
|
61
|
+
model.model_rebuild(force=True)
|
61
62
|
model.set_table_ref(self)
|
62
63
|
self._client = None
|
63
64
|
self._dynamodb_table = None
|
@@ -162,7 +163,12 @@ class Table:
|
|
162
163
|
)
|
163
164
|
|
164
165
|
def _get_model_type_by_statikk_type(self, statikk_type: str) -> Type[DatabaseModel]:
|
165
|
-
|
166
|
+
model_type_filter = [model_type for model_type in self.models if model_type.type() == statikk_type]
|
167
|
+
if not model_type_filter:
|
168
|
+
raise InvalidModelTypeError(
|
169
|
+
f"Model type '{statikk_type}' not found. Make sure to register it through the models list."
|
170
|
+
)
|
171
|
+
return model_type_filter[0]
|
166
172
|
|
167
173
|
def delete(self):
|
168
174
|
"""Deletes the DynamoDB table."""
|
@@ -235,7 +241,7 @@ class Table:
|
|
235
241
|
|
236
242
|
with self.batch_write() as batch:
|
237
243
|
for item in model.split_to_simple_objects():
|
238
|
-
if item.
|
244
|
+
if item.should_delete:
|
239
245
|
batch.delete(item)
|
240
246
|
else:
|
241
247
|
batch.put(item)
|
@@ -288,11 +294,28 @@ class Table:
|
|
288
294
|
response = self._get_dynamodb_table().update_item(**request)
|
289
295
|
data = response["Attributes"]
|
290
296
|
for key, value in data.items():
|
291
|
-
if key
|
297
|
+
if key in [FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID]:
|
292
298
|
continue
|
293
299
|
data[key] = self._deserialize_value(value, model.model_fields[key])
|
294
300
|
return type(model)(**data)
|
295
301
|
|
302
|
+
def reparent_subtree(self, subtree_root: T, new_parent: T) -> T:
|
303
|
+
subtree_copy = deepcopy(subtree_root)
|
304
|
+
subtree_root._parent_changed = True
|
305
|
+
|
306
|
+
subtree_copy.set_parent_references(subtree_copy, force_override=True)
|
307
|
+
subtree_copy._parent = new_parent
|
308
|
+
for node in subtree_copy.dfs_traverse_hierarchy():
|
309
|
+
for idx in self.indexes:
|
310
|
+
new_index_values = self._compose_index_values(node, idx)
|
311
|
+
new_sort_key_value = new_index_values[idx.sort_key.name]
|
312
|
+
setattr(node, idx.sort_key.name, new_sort_key_value)
|
313
|
+
new_hash_key_value = new_index_values[idx.hash_key.name]
|
314
|
+
setattr(node, idx.hash_key.name, new_hash_key_value)
|
315
|
+
setattr(node, FIELD_STATIKK_PARENT_ID, new_parent.id)
|
316
|
+
|
317
|
+
return subtree_copy
|
318
|
+
|
296
319
|
def batch_write(self):
|
297
320
|
"""
|
298
321
|
Returns a context manager for batch writing items to the database. This method handles all the buffering of the
|
@@ -462,12 +485,11 @@ class Table:
|
|
462
485
|
self,
|
463
486
|
item: DatabaseModel,
|
464
487
|
indexes: List[GSI],
|
465
|
-
force_override_index_fields: bool = False,
|
466
488
|
) -> DatabaseModel:
|
467
489
|
for idx in indexes:
|
468
490
|
index_fields = self._compose_index_values(item, idx)
|
469
491
|
for key, value in index_fields.items():
|
470
|
-
if hasattr(item, key) and (getattr(item, key) is not None and not
|
492
|
+
if hasattr(item, key) and (getattr(item, key) is not None and not item._parent_changed):
|
471
493
|
continue
|
472
494
|
if value is not None:
|
473
495
|
setattr(item, key, value)
|
@@ -558,8 +580,11 @@ class Table:
|
|
558
580
|
return self.delimiter.join(sort_key_values)
|
559
581
|
|
560
582
|
def _compose_index_values(self, model: DatabaseModel, idx: GSI) -> Dict[str, Any]:
|
561
|
-
hash_key_fields = model.index_definitions().get(idx.name,
|
583
|
+
hash_key_fields = model.index_definitions().get(idx.name, None)
|
584
|
+
if hash_key_fields is None:
|
585
|
+
raise IncorrectHashKeyError(f"Model {model.__class__} does not have a hash key defined.")
|
562
586
|
|
587
|
+
hash_key_fields = hash_key_fields.pk_fields
|
563
588
|
if len(hash_key_fields) == 0 and not model.is_nested():
|
564
589
|
raise IncorrectHashKeyError(f"Model {model.__class__} does not have a hash key defined.")
|
565
590
|
|
statikk/expressions.py
CHANGED
@@ -597,7 +597,7 @@ class UpdateExpressionBuilder:
|
|
597
597
|
|
598
598
|
def _safe_name(self, key):
|
599
599
|
"""Replace reserved words with a safe placeholder."""
|
600
|
-
if key.upper() in RESERVED_WORDS:
|
600
|
+
if key.upper() in RESERVED_WORDS or key.startswith("_"):
|
601
601
|
safe_key = self.safe_name(key)
|
602
602
|
self.expression_attribute_names[safe_key] = key
|
603
603
|
return safe_key
|
statikk/models.py
CHANGED
@@ -163,6 +163,20 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
163
163
|
_parent: Optional[DatabaseModel] = None
|
164
164
|
_model_types_in_hierarchy: dict[str, Type[DatabaseModel]] = {}
|
165
165
|
_should_delete: bool = False
|
166
|
+
_parent_changed: bool = False
|
167
|
+
|
168
|
+
def is_parent_changed(self):
|
169
|
+
"""
|
170
|
+
Recursively check if this node or any of its ancestors has been marked as having changed parents.
|
171
|
+
Returns True if the node or any ancestor has _parent_changed=True, otherwise False.
|
172
|
+
"""
|
173
|
+
if self._parent_changed:
|
174
|
+
return True
|
175
|
+
|
176
|
+
if self._parent is not None:
|
177
|
+
return self._parent.is_parent_changed()
|
178
|
+
|
179
|
+
return False
|
166
180
|
|
167
181
|
@classmethod
|
168
182
|
def type(cls) -> str:
|
@@ -188,6 +202,10 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
188
202
|
def is_simple_object(self) -> bool:
|
189
203
|
return len(self._model_types_in_hierarchy) == 1
|
190
204
|
|
205
|
+
@property
|
206
|
+
def should_delete(self) -> bool:
|
207
|
+
return self._should_delete or self.is_parent_changed()
|
208
|
+
|
191
209
|
@classmethod
|
192
210
|
def query(
|
193
211
|
cls: Type[T],
|
@@ -260,6 +278,9 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
260
278
|
def mark_for_delete(self):
|
261
279
|
self._should_delete = True
|
262
280
|
|
281
|
+
def change_parent_to(self, new_parent: DatabaseModel) -> T:
|
282
|
+
return self._table.reparent_subtree(self, new_parent)
|
283
|
+
|
263
284
|
@classmethod
|
264
285
|
def scan(
|
265
286
|
cls,
|
@@ -280,7 +301,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
280
301
|
def initialize_tracking(self):
|
281
302
|
self._model_types_in_hierarchy[self.type()] = type(self)
|
282
303
|
if not self.is_nested():
|
283
|
-
self.
|
304
|
+
self.set_parent_references(self)
|
284
305
|
self.init_tracking()
|
285
306
|
|
286
307
|
return self
|
@@ -370,27 +391,58 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
370
391
|
def get_type_from_hierarchy_by_name(self, name: str) -> Optional[Type[DatabaseModel]]:
|
371
392
|
return self._model_types_in_hierarchy.get(name)
|
372
393
|
|
373
|
-
def _set_parent_to_field(
|
374
|
-
|
394
|
+
def _set_parent_to_field(
|
395
|
+
self, field: DatabaseModel, parent: DatabaseModel, root: DatabaseModel, force_override: bool = False
|
396
|
+
):
|
397
|
+
if field._parent and not force_override:
|
375
398
|
return # Already set
|
376
399
|
field._parent = parent
|
377
400
|
root._model_types_in_hierarchy[field.type()] = type(field)
|
378
|
-
field.
|
401
|
+
field.set_parent_references(root, force_override)
|
379
402
|
field.init_tracking()
|
380
403
|
|
381
|
-
def
|
404
|
+
def set_parent_references(self, root: DatabaseModel, force_override: bool = False):
|
405
|
+
"""
|
406
|
+
Sets parent references for all DatabaseModel objects in the hierarchy.
|
407
|
+
"""
|
408
|
+
for parent, field_name, model in self.traverse_hierarchy():
|
409
|
+
self._set_parent_to_field(model, parent, root, force_override)
|
410
|
+
|
411
|
+
def traverse_hierarchy(self):
|
412
|
+
"""
|
413
|
+
Traverses the object and yields tuples of (parent, field_name, field_value) for each DatabaseModel found.
|
414
|
+
"""
|
382
415
|
for field_name, field_value in self:
|
383
416
|
if isinstance(field_value, DatabaseModel):
|
384
|
-
self
|
385
|
-
elif isinstance(field_value, list):
|
417
|
+
yield self, field_name, field_value
|
418
|
+
elif isinstance(field_value, (list, set)):
|
386
419
|
for item in field_value:
|
387
420
|
if isinstance(item, DatabaseModel):
|
388
|
-
self
|
389
|
-
elif isinstance(field_value,
|
421
|
+
yield self, field_name, item
|
422
|
+
elif isinstance(field_value, dict):
|
423
|
+
for key, value in field_value.items():
|
424
|
+
if isinstance(value, DatabaseModel):
|
425
|
+
yield self, key, value
|
426
|
+
|
427
|
+
def dfs_traverse_hierarchy(self):
|
428
|
+
"""
|
429
|
+
Performs a depth-first traversal of the entire object hierarchy,
|
430
|
+
yielding each DatabaseModel in order from root to leaves.
|
431
|
+
"""
|
432
|
+
yield self
|
433
|
+
|
434
|
+
fields = []
|
435
|
+
for field_name, field_value in self:
|
436
|
+
fields.append((field_name, field_value))
|
437
|
+
|
438
|
+
for field_name, field_value in fields:
|
439
|
+
if isinstance(field_value, DatabaseModel):
|
440
|
+
yield from field_value.dfs_traverse_hierarchy()
|
441
|
+
elif isinstance(field_value, (list, set)):
|
390
442
|
for item in field_value:
|
391
443
|
if isinstance(item, DatabaseModel):
|
392
|
-
|
444
|
+
yield from item.dfs_traverse_hierarchy()
|
393
445
|
elif isinstance(field_value, dict):
|
394
446
|
for key, value in field_value.items():
|
395
447
|
if isinstance(value, DatabaseModel):
|
396
|
-
|
448
|
+
yield from value.dfs_traverse_hierarchy()
|
@@ -0,0 +1,12 @@
|
|
1
|
+
statikk/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
|
2
|
+
statikk/conditions.py,sha256=63FYMR-UUaE-ZJEb_8CU721CQTwhajq39-BbokmKeMA,2166
|
3
|
+
statikk/engine.py,sha256=x3TB4iMXSpLudwv0dRBGFapttbc8etg36SA_tSq0tGY,32362
|
4
|
+
statikk/expressions.py,sha256=boAeGxZj2cDsXxoiX3IIEzfX9voSMQngi4-rE_jYeuE,12233
|
5
|
+
statikk/fields.py,sha256=LkMP5NnX7WS0HSLxI3Q-dMOrfaJ0SD7SayZxJU5Acgg,86
|
6
|
+
statikk/models.py,sha256=X2MRFn3TAmKdxUQ9KDO9oNoekct4F3QBdcB1COtCpDU,15785
|
7
|
+
statikk/typing.py,sha256=qfpegORcdODuILK3gvuD4SdcZA1a7Myn0yvscOLPHOM,68
|
8
|
+
statikk-0.1.8.dist-info/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
|
9
|
+
statikk-0.1.8.dist-info/METADATA,sha256=9TYBs4MKqRMN2TJN6iRyl3g8nWSIG_zBlVnAAUtfClc,3160
|
10
|
+
statikk-0.1.8.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
11
|
+
statikk-0.1.8.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
|
12
|
+
statikk-0.1.8.dist-info/RECORD,,
|
statikk-0.1.7.dist-info/RECORD
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
statikk/__init__.py,sha256=pH5i4Fj1tbXLqLtTVIdoojiplZssQn0nnud8-HXodRE,577
|
2
|
-
statikk/conditions.py,sha256=63FYMR-UUaE-ZJEb_8CU721CQTwhajq39-BbokmKeMA,2166
|
3
|
-
statikk/engine.py,sha256=_iUJYyKNypqQeg6f4MC45XWhOrwsYRsI8FZe0Hs3fDA,31067
|
4
|
-
statikk/expressions.py,sha256=mF6Hmj3Kmj6KKXTymeTHSepVA7rhiSINpFgSAPeBTRY,12210
|
5
|
-
statikk/fields.py,sha256=LkMP5NnX7WS0HSLxI3Q-dMOrfaJ0SD7SayZxJU5Acgg,86
|
6
|
-
statikk/models.py,sha256=uGCfpmoagz98BlTWiyj8NFvd7PTcsj1XV2edTumxAqY,13862
|
7
|
-
statikk/typing.py,sha256=qfpegORcdODuILK3gvuD4SdcZA1a7Myn0yvscOLPHOM,68
|
8
|
-
statikk-0.1.7.dist-info/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
|
9
|
-
statikk-0.1.7.dist-info/METADATA,sha256=YiDH3MeMZuJypLtkjGb-prE-Aqe15kbKBDT9cAyBRmM,3160
|
10
|
-
statikk-0.1.7.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
11
|
-
statikk-0.1.7.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
|
12
|
-
statikk-0.1.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|