statikk 0.1.13__py3-none-any.whl → 0.1.15__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 +61 -110
- statikk/fields.py +1 -0
- statikk/models.py +114 -51
- {statikk-0.1.13.dist-info → statikk-0.1.15.dist-info}/METADATA +1 -1
- statikk-0.1.15.dist-info/RECORD +12 -0
- statikk-0.1.13.dist-info/RECORD +0 -12
- {statikk-0.1.13.dist-info → statikk-0.1.15.dist-info}/WHEEL +0 -0
- {statikk-0.1.13.dist-info → statikk-0.1.15.dist-info}/licenses/LICENSE.txt +0 -0
- {statikk-0.1.13.dist-info → statikk-0.1.15.dist-info}/top_level.txt +0 -0
statikk/engine.py
CHANGED
@@ -16,7 +16,7 @@ from statikk.models import (
|
|
16
16
|
GSI,
|
17
17
|
KeySchema,
|
18
18
|
)
|
19
|
-
from statikk.fields import FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID
|
19
|
+
from statikk.fields import FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID, FIELD_STATIKK_PARENT_FIELD_NAME
|
20
20
|
from copy import deepcopy
|
21
21
|
from aws_xray_sdk.core import patch_all
|
22
22
|
|
@@ -246,6 +246,8 @@ class Table:
|
|
246
246
|
else:
|
247
247
|
batch.put(item)
|
248
248
|
|
249
|
+
model._session.reset()
|
250
|
+
|
249
251
|
def update_item(
|
250
252
|
self,
|
251
253
|
hash_key: str,
|
@@ -294,20 +296,23 @@ class Table:
|
|
294
296
|
response = self._get_dynamodb_table().update_item(**request)
|
295
297
|
data = response["Attributes"]
|
296
298
|
for key, value in data.items():
|
297
|
-
if key in [FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID]:
|
299
|
+
if key in [FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID, FIELD_STATIKK_PARENT_FIELD_NAME]:
|
298
300
|
continue
|
299
301
|
data[key] = self._deserialize_value(value, model.model_fields[key])
|
300
302
|
return type(model)(**data)
|
301
303
|
|
302
|
-
def reparent_subtree(self, subtree_root: T, new_parent: T) -> T:
|
304
|
+
def reparent_subtree(self, subtree_root: T, new_parent: T, field_name: str) -> T:
|
303
305
|
subtree_copy = deepcopy(subtree_root)
|
304
306
|
subtree_root._parent_changed = True
|
305
307
|
|
306
308
|
subtree_copy.set_parent_references(subtree_copy, force_override=True)
|
307
309
|
subtree_copy._parent = new_parent
|
310
|
+
subtree_copy._parent_field_name = field_name
|
311
|
+
parent = subtree_copy._parent
|
308
312
|
for node in subtree_copy.dfs_traverse_hierarchy():
|
309
313
|
self.build_model_indexes(node)
|
310
|
-
setattr(node, FIELD_STATIKK_PARENT_ID,
|
314
|
+
setattr(node, FIELD_STATIKK_PARENT_ID, parent.id)
|
315
|
+
parent = node
|
311
316
|
|
312
317
|
return subtree_copy
|
313
318
|
|
@@ -425,7 +430,9 @@ class Table:
|
|
425
430
|
model_class = self._get_model_type_by_statikk_type(model_type)
|
426
431
|
|
427
432
|
reconstructed_dict.pop(FIELD_STATIKK_TYPE, None)
|
428
|
-
|
433
|
+
model = model_class.model_validate(reconstructed_dict)
|
434
|
+
model._is_persisted = True
|
435
|
+
return model
|
429
436
|
|
430
437
|
def scan(
|
431
438
|
self,
|
@@ -624,6 +631,7 @@ class Table:
|
|
624
631
|
continue
|
625
632
|
if not enriched_item.should_write_to_database():
|
626
633
|
continue
|
634
|
+
enriched_item._is_persisted = True
|
627
635
|
data = self._serialize_item(enriched_item)
|
628
636
|
batch.put_item(Item=data)
|
629
637
|
|
@@ -638,9 +646,6 @@ class Table:
|
|
638
646
|
Returns:
|
639
647
|
The top-level dictionary with its hierarchy fully reconstructed, or None if the list is empty
|
640
648
|
"""
|
641
|
-
if not items:
|
642
|
-
return None
|
643
|
-
|
644
649
|
items_by_id = {item["id"]: item for item in items}
|
645
650
|
children_by_parent_id = {}
|
646
651
|
for item in items:
|
@@ -650,128 +655,74 @@ class Table:
|
|
650
655
|
children_by_parent_id[parent_id] = []
|
651
656
|
children_by_parent_id[parent_id].append(item)
|
652
657
|
|
653
|
-
|
654
|
-
root_items = [item for item in items if FIELD_STATIKK_PARENT_ID not in item]
|
658
|
+
root_item = [item for item in items if FIELD_STATIKK_PARENT_ID not in item][0]
|
655
659
|
|
656
|
-
|
657
|
-
|
660
|
+
processed_root = self._process_item(root_item, items_by_id, children_by_parent_id)
|
661
|
+
return processed_root
|
658
662
|
|
659
|
-
|
660
|
-
root_item = root_items[0]
|
661
|
-
else:
|
662
|
-
root_item = root_items[0]
|
663
|
-
|
664
|
-
processed_items = set()
|
665
|
-
return self._reconstruct_item_with_children(root_item, items_by_id, children_by_parent_id, processed_items)
|
666
|
-
|
667
|
-
def _reconstruct_item_with_children(
|
668
|
-
self, item: dict, items_by_id: dict, children_by_parent_id: dict, processed_items: set
|
669
|
-
) -> dict:
|
663
|
+
def _process_item(self, item: dict, items_by_id: dict, children_by_parent_id: dict) -> dict:
|
670
664
|
"""
|
671
|
-
Recursively
|
665
|
+
Recursively processes an item and all its children to rebuild the hierarchical structure.
|
672
666
|
|
673
667
|
Args:
|
674
|
-
item: The item to
|
675
|
-
items_by_id:
|
676
|
-
children_by_parent_id:
|
677
|
-
processed_items: Set of already processed item IDs to avoid duplicates
|
668
|
+
item: The current item to process
|
669
|
+
items_by_id: Dictionary mapping item IDs to items
|
670
|
+
children_by_parent_id: Dictionary mapping parent IDs to lists of child items
|
678
671
|
|
679
672
|
Returns:
|
680
|
-
The
|
673
|
+
The processed item with all its child relationships resolved
|
681
674
|
"""
|
682
|
-
|
683
|
-
return item
|
684
|
-
processed_items.add(item["id"])
|
685
|
-
result = item.copy()
|
686
|
-
|
687
|
-
if FIELD_STATIKK_PARENT_ID in result:
|
688
|
-
result.pop(FIELD_STATIKK_PARENT_ID)
|
675
|
+
processed_item = deepcopy(item)
|
689
676
|
|
690
|
-
|
691
|
-
|
692
|
-
|
677
|
+
if FIELD_STATIKK_TYPE in processed_item:
|
678
|
+
model_class = self._get_model_type_by_statikk_type(processed_item[FIELD_STATIKK_TYPE])
|
679
|
+
model_fields = model_class.model_fields
|
680
|
+
else:
|
681
|
+
return processed_item
|
693
682
|
|
694
|
-
|
683
|
+
# Get children of this item
|
684
|
+
children = children_by_parent_id.get(processed_item["id"], [])
|
695
685
|
|
696
|
-
|
686
|
+
# Group children by parent field name
|
687
|
+
children_by_field = {}
|
697
688
|
for child in children:
|
698
|
-
|
699
|
-
if
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
for field_name, field_info in parent_model_class.model_fields.items():
|
708
|
-
if field_name.startswith("_"):
|
709
|
-
continue
|
710
|
-
|
711
|
-
is_optional, inner_type = inspect_optional_field(parent_model_class, field_name)
|
712
|
-
|
713
|
-
field_type = inner_type if is_optional else field_info.annotation
|
714
|
-
|
715
|
-
if field_type == child_model_class:
|
716
|
-
matching_fields.append((field_name, "single"))
|
717
|
-
|
718
|
-
elif hasattr(field_type, "__origin__") and field_type.__origin__ == list:
|
719
|
-
args = getattr(field_type, "__args__", [])
|
720
|
-
if args and args[0] == child_model_class:
|
721
|
-
matching_fields.append((field_name, "list"))
|
722
|
-
|
723
|
-
elif hasattr(field_type, "__origin__") and field_type.__origin__ == set:
|
724
|
-
args = getattr(field_type, "__args__", [])
|
725
|
-
if args and args[0] == child_model_class:
|
726
|
-
matching_fields.append((field_name, "set"))
|
727
|
-
|
728
|
-
if matching_fields:
|
729
|
-
for field_name, container_type in matching_fields:
|
730
|
-
if container_type == "list":
|
731
|
-
if field_name not in result:
|
732
|
-
result[field_name] = []
|
733
|
-
|
734
|
-
existing_ids = {
|
735
|
-
item.get("id") for item in result[field_name] if isinstance(item, dict) and "id" in item
|
736
|
-
}
|
737
|
-
|
738
|
-
for child in child_items:
|
739
|
-
if child["id"] in existing_ids:
|
740
|
-
continue
|
689
|
+
field_name = child.get(FIELD_STATIKK_PARENT_FIELD_NAME)
|
690
|
+
if field_name:
|
691
|
+
if field_name not in children_by_field:
|
692
|
+
children_by_field[field_name] = []
|
693
|
+
children_by_field[field_name].append(child)
|
694
|
+
|
695
|
+
for field_name, field_info in model_fields.items():
|
696
|
+
if field_name not in children_by_field:
|
697
|
+
continue
|
741
698
|
|
742
|
-
|
743
|
-
child, items_by_id, children_by_parent_id, processed_items
|
744
|
-
)
|
699
|
+
field_children = children_by_field[field_name]
|
745
700
|
|
746
|
-
|
747
|
-
|
701
|
+
field_type = field_info.annotation
|
702
|
+
is_optional = False
|
703
|
+
inner_type = field_type
|
748
704
|
|
749
|
-
|
750
|
-
|
751
|
-
|
705
|
+
if hasattr(field_type, "__origin__") and field_type.__origin__ is Union:
|
706
|
+
args = field_type.__args__
|
707
|
+
if type(None) in args:
|
708
|
+
is_optional = True
|
709
|
+
# Get the non-None type
|
710
|
+
inner_type = next(arg for arg in args if arg is not type(None))
|
752
711
|
|
753
|
-
|
754
|
-
|
755
|
-
}
|
712
|
+
if hasattr(inner_type, "__origin__") and inner_type.__origin__ == list:
|
713
|
+
child_list = []
|
756
714
|
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
reconstructed_child = self._reconstruct_item_with_children(
|
761
|
-
child, items_by_id, children_by_parent_id, processed_items
|
762
|
-
)
|
715
|
+
for child in field_children:
|
716
|
+
processed_child = self._process_item(child, items_by_id, children_by_parent_id)
|
717
|
+
child_list.append(processed_child)
|
763
718
|
|
764
|
-
|
765
|
-
existing_ids.add(child["id"])
|
719
|
+
processed_item[field_name] = child_list
|
766
720
|
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
child_items[0], items_by_id, children_by_parent_id, processed_items
|
771
|
-
)
|
772
|
-
result[field_name] = reconstructed_child
|
721
|
+
elif len(field_children) == 1:
|
722
|
+
processed_child = self._process_item(field_children[0], items_by_id, children_by_parent_id)
|
723
|
+
processed_item[field_name] = processed_child
|
773
724
|
|
774
|
-
return
|
725
|
+
return processed_item
|
775
726
|
|
776
727
|
|
777
728
|
class BatchWriteContext:
|
statikk/fields.py
CHANGED
statikk/models.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from datetime import datetime
|
3
4
|
import typing
|
4
5
|
import logging
|
5
6
|
from uuid import uuid4
|
@@ -13,7 +14,7 @@ from pydantic_core._pydantic_core import PydanticUndefined
|
|
13
14
|
|
14
15
|
from statikk.conditions import Condition
|
15
16
|
from statikk.expressions import DatabaseModelUpdateExpressionBuilder
|
16
|
-
from statikk.fields import FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID
|
17
|
+
from statikk.fields import FIELD_STATIKK_TYPE, FIELD_STATIKK_PARENT_ID, FIELD_STATIKK_PARENT_FIELD_NAME
|
17
18
|
|
18
19
|
if typing.TYPE_CHECKING:
|
19
20
|
from statikk.engine import Table
|
@@ -53,6 +54,43 @@ class IndexFieldConfig(BaseModel):
|
|
53
54
|
sk_fields: list[str] = []
|
54
55
|
|
55
56
|
|
57
|
+
class TreeStructureChange(BaseModel):
|
58
|
+
new_parent: Optional[Any]
|
59
|
+
new_parent_field_name: Optional[str]
|
60
|
+
subtree: Any
|
61
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
62
|
+
|
63
|
+
|
64
|
+
class Session(BaseModel):
|
65
|
+
_changes: list[TreeStructureChange] = []
|
66
|
+
|
67
|
+
def add_change(self, new_parent: Optional[T], new_parent_field_name: Optional[str], subtree: T):
|
68
|
+
self._changes.append(
|
69
|
+
TreeStructureChange(new_parent=new_parent, new_parent_field_name=new_parent_field_name, subtree=subtree)
|
70
|
+
)
|
71
|
+
|
72
|
+
def get_subtree_changes_by_parent_id(
|
73
|
+
self, parent_id: str, subtree_id: str, field_name: str
|
74
|
+
) -> Optional[TreeStructureChange]:
|
75
|
+
sorted_changes = sorted(self._changes, key=lambda change: change.timestamp, reverse=True)
|
76
|
+
return next(
|
77
|
+
filter(
|
78
|
+
lambda change: change.new_parent.id == parent_id
|
79
|
+
and change.subtree.id == subtree_id
|
80
|
+
and change.new_parent_field_name == field_name,
|
81
|
+
sorted_changes,
|
82
|
+
),
|
83
|
+
None,
|
84
|
+
)
|
85
|
+
|
86
|
+
def get_last_change_for(self, subtree_id):
|
87
|
+
sorted_changes = sorted(self._changes, key=lambda change: change.timestamp, reverse=True)
|
88
|
+
return next(filter(lambda change: change.subtree.id == subtree_id, sorted_changes), None)
|
89
|
+
|
90
|
+
def reset(self):
|
91
|
+
self._changes = []
|
92
|
+
|
93
|
+
|
56
94
|
class TrackingMixin:
|
57
95
|
_original_hash: int = Field(exclude=True)
|
58
96
|
|
@@ -158,12 +196,22 @@ class TrackingMixin:
|
|
158
196
|
return {}
|
159
197
|
|
160
198
|
|
161
|
-
class DatabaseModel(BaseModel, TrackingMixin
|
199
|
+
class DatabaseModel(BaseModel, TrackingMixin):
|
162
200
|
id: str = Field(default_factory=lambda: str(uuid4()))
|
163
201
|
_parent: Optional[DatabaseModel] = None
|
202
|
+
_parent_field_name: Optional[str] = None
|
164
203
|
_model_types_in_hierarchy: dict[str, Type[DatabaseModel]] = {}
|
165
204
|
_should_delete: bool = False
|
166
205
|
_parent_changed: bool = False
|
206
|
+
_is_persisted: bool = False
|
207
|
+
_session = Session()
|
208
|
+
|
209
|
+
class Config:
|
210
|
+
extra = Extra.allow
|
211
|
+
arbitrary_types_allowed = True
|
212
|
+
|
213
|
+
def __eq__(self, other):
|
214
|
+
return self.id == other.id
|
167
215
|
|
168
216
|
def is_parent_changed(self):
|
169
217
|
"""
|
@@ -182,6 +230,17 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
182
230
|
def type(cls) -> str:
|
183
231
|
return cls.__name__
|
184
232
|
|
233
|
+
@property
|
234
|
+
def should_track_session(self) -> bool:
|
235
|
+
"""
|
236
|
+
If set to True, subtree movements across the database model will be tracked in a session.
|
237
|
+
Use this if you need to move a subtree across multiple parent-child relationships within a single session.
|
238
|
+
Session is reset after each save.
|
239
|
+
"""
|
240
|
+
if self._parent is not None:
|
241
|
+
return self._parent.should_track_session
|
242
|
+
return False
|
243
|
+
|
185
244
|
@classmethod
|
186
245
|
def index_definitions(cls) -> dict[str, IndexFieldConfig]:
|
187
246
|
return {"main_index": IndexFieldConfig(pk_fields=[], sk_fields=[])}
|
@@ -202,12 +261,19 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
202
261
|
def is_simple_object(self) -> bool:
|
203
262
|
return len(self._model_types_in_hierarchy) == 1
|
204
263
|
|
264
|
+
@property
|
265
|
+
def is_persisted(self) -> bool:
|
266
|
+
if self._parent is not None:
|
267
|
+
return self._parent.is_persisted
|
268
|
+
|
269
|
+
return self._is_persisted
|
270
|
+
|
205
271
|
@property
|
206
272
|
def should_delete(self) -> bool:
|
207
273
|
if self._is_any_parent_marked_for_deletion():
|
208
274
|
return True
|
209
275
|
|
210
|
-
return self._should_delete or self.is_parent_changed()
|
276
|
+
return self.is_persisted and (self._should_delete or self.is_parent_changed())
|
211
277
|
|
212
278
|
def _is_any_parent_marked_for_deletion(self) -> bool:
|
213
279
|
current = self._parent
|
@@ -293,8 +359,25 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
293
359
|
def mark_for_delete(self):
|
294
360
|
self._should_delete = True
|
295
361
|
|
296
|
-
def
|
297
|
-
return self._table.reparent_subtree(self, new_parent)
|
362
|
+
def _change_parent_to(self, new_parent: DatabaseModel, field_name: str) -> T:
|
363
|
+
return self._table.reparent_subtree(self, new_parent, field_name)
|
364
|
+
|
365
|
+
def _remove_from_parent(self, parent, field_name, subtree):
|
366
|
+
is_optional, inner_type = inspect_optional_field(parent.__class__, field_name)
|
367
|
+
field_type = inner_type if is_optional else parent.model_fields[field_name].annotation
|
368
|
+
field = getattr(self, field_name)
|
369
|
+
if hasattr(field_type, "__origin__") and field_type.__origin__ == list:
|
370
|
+
if not isinstance(field, list):
|
371
|
+
setattr(self, field_name, [])
|
372
|
+
field.remove(next(filter(lambda item: item.id == subtree.id, getattr(parent, field_name)), None))
|
373
|
+
|
374
|
+
elif hasattr(field_type, "__origin__") and field_type.__origin__ == set:
|
375
|
+
if not isinstance(field, set):
|
376
|
+
setattr(self, field_name, set())
|
377
|
+
field.remove(next(filter(lambda item: item.id == subtree.id, getattr(parent, field_name)), None))
|
378
|
+
|
379
|
+
elif issubclass(field_type, DatabaseModel):
|
380
|
+
setattr(parent, field_name, None)
|
298
381
|
|
299
382
|
def add_child_node(self, field_name: str, child_node: DatabaseModel):
|
300
383
|
if not child_node.is_nested():
|
@@ -303,26 +386,33 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
303
386
|
if not hasattr(self, field_name):
|
304
387
|
raise ValueError(f"Field {field_name} does not exist on {self.__class__.__name__}")
|
305
388
|
|
389
|
+
if self.should_track_session:
|
390
|
+
previous_change = self._session.get_subtree_changes_by_parent_id(self.id, child_node.id, field_name)
|
391
|
+
if previous_change:
|
392
|
+
self._remove_from_parent(previous_change.new_parent, previous_change.new_parent_field_name, child_node)
|
393
|
+
|
306
394
|
is_optional, inner_type = inspect_optional_field(self.__class__, field_name)
|
307
395
|
field_type = inner_type if is_optional else self.model_fields[field_name].annotation
|
308
|
-
|
396
|
+
reparented = None
|
309
397
|
if hasattr(field_type, "__origin__") and field_type.__origin__ == list:
|
310
398
|
if not isinstance(getattr(self, field_name), list):
|
311
399
|
setattr(self, field_name, [])
|
312
|
-
reparented = child_node.
|
400
|
+
reparented = child_node._change_parent_to(self, field_name)
|
313
401
|
getattr(self, field_name).append(reparented)
|
314
|
-
return reparented
|
315
402
|
|
316
403
|
elif hasattr(field_type, "__origin__") and field_type.__origin__ == set:
|
317
404
|
if not isinstance(getattr(self, field_name), set):
|
318
405
|
setattr(self, field_name, set())
|
319
|
-
reparented = child_node.
|
406
|
+
reparented = child_node._change_parent_to(self, field_name)
|
320
407
|
getattr(self, field_name).add(reparented)
|
321
|
-
return reparented
|
322
408
|
|
323
409
|
elif issubclass(field_type, DatabaseModel):
|
324
|
-
reparented = child_node.
|
410
|
+
reparented = child_node._change_parent_to(self, field_name)
|
325
411
|
setattr(self, field_name, reparented)
|
412
|
+
|
413
|
+
if reparented:
|
414
|
+
if self.should_track_session:
|
415
|
+
self._session.add_change(self, field_name, reparented)
|
326
416
|
return reparented
|
327
417
|
|
328
418
|
raise ValueError(f"Unsupported field type: {field_type}")
|
@@ -341,6 +431,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
341
431
|
data[FIELD_STATIKK_TYPE] = self.type()
|
342
432
|
if self._parent:
|
343
433
|
data[FIELD_STATIKK_PARENT_ID] = self._parent.id
|
434
|
+
data[FIELD_STATIKK_PARENT_FIELD_NAME] = self._parent_field_name
|
344
435
|
return data
|
345
436
|
|
346
437
|
@model_validator(mode="after")
|
@@ -371,9 +462,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
371
462
|
if self not in items:
|
372
463
|
items.append(self)
|
373
464
|
|
374
|
-
# Iterate through all fields of the model
|
375
465
|
for field_name, field_value in self:
|
376
|
-
# Skip fields that start with underscore (private fields)
|
377
466
|
if field_name.startswith("_"):
|
378
467
|
continue
|
379
468
|
|
@@ -383,7 +472,6 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
383
472
|
items.append(field_value)
|
384
473
|
field_value.split_to_simple_objects(items)
|
385
474
|
|
386
|
-
# Handle lists containing DatabaseModel instances
|
387
475
|
elif isinstance(field_value, list):
|
388
476
|
for item in field_value:
|
389
477
|
if hasattr(item, "__class__") and issubclass(item.__class__, DatabaseModel):
|
@@ -391,23 +479,6 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
391
479
|
items.append(item)
|
392
480
|
item.split_to_simple_objects(items)
|
393
481
|
|
394
|
-
# Handle sets containing DatabaseModel instances
|
395
|
-
elif isinstance(field_value, set):
|
396
|
-
for item in field_value:
|
397
|
-
if hasattr(item, "__class__") and issubclass(item.__class__, DatabaseModel):
|
398
|
-
if item not in items:
|
399
|
-
items.append(item)
|
400
|
-
item.split_to_simple_objects(items)
|
401
|
-
|
402
|
-
# Handle dictionaries that may contain DatabaseModel instances
|
403
|
-
elif isinstance(field_value, dict):
|
404
|
-
# Check dictionary values
|
405
|
-
for value in field_value.values():
|
406
|
-
if hasattr(value, "__class__") and issubclass(value.__class__, DatabaseModel):
|
407
|
-
if value not in items:
|
408
|
-
items.append(value)
|
409
|
-
value.split_to_simple_objects(items)
|
410
|
-
|
411
482
|
return items
|
412
483
|
|
413
484
|
def get_attribute(self, attribute_name: str):
|
@@ -424,25 +495,25 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
424
495
|
for item in field_value:
|
425
496
|
if issubclass(item.__class__, DatabaseModel) and item.is_nested():
|
426
497
|
nested_models.append(field_name)
|
427
|
-
elif isinstance(field_value, set):
|
428
|
-
for item in field_value:
|
429
|
-
if issubclass(item.__class__, DatabaseModel) and item.is_nested():
|
430
|
-
nested_models.append(field_name)
|
431
|
-
elif isinstance(field_value, dict):
|
432
|
-
for key, value in field_value.items():
|
433
|
-
if issubclass(value.__class__, DatabaseModel) and value.is_nested():
|
434
|
-
nested_models.append(field_name)
|
435
498
|
return set(nested_models)
|
436
499
|
|
437
500
|
def get_type_from_hierarchy_by_name(self, name: str) -> Optional[Type[DatabaseModel]]:
|
438
501
|
return self._model_types_in_hierarchy.get(name)
|
439
502
|
|
440
503
|
def _set_parent_to_field(
|
441
|
-
self,
|
504
|
+
self,
|
505
|
+
field: DatabaseModel,
|
506
|
+
field_name: str,
|
507
|
+
parent: DatabaseModel,
|
508
|
+
root: DatabaseModel,
|
509
|
+
force_override: bool = False,
|
442
510
|
):
|
443
511
|
if field._parent and not force_override:
|
444
512
|
return # Already set
|
445
513
|
field._parent = parent
|
514
|
+
field._parent_field_name = field_name
|
515
|
+
if field.should_track_session:
|
516
|
+
field._session.add_change(parent, field_name, field)
|
446
517
|
root._model_types_in_hierarchy[field.type()] = type(field)
|
447
518
|
field.set_parent_references(root, force_override)
|
448
519
|
field.init_tracking()
|
@@ -452,7 +523,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
452
523
|
Sets parent references for all DatabaseModel objects in the hierarchy.
|
453
524
|
"""
|
454
525
|
for parent, field_name, model in self.traverse_hierarchy():
|
455
|
-
self._set_parent_to_field(model, parent, root, force_override)
|
526
|
+
self._set_parent_to_field(model, field_name, parent, root, force_override)
|
456
527
|
|
457
528
|
def traverse_hierarchy(self):
|
458
529
|
"""
|
@@ -461,14 +532,10 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
461
532
|
for field_name, field_value in self:
|
462
533
|
if isinstance(field_value, DatabaseModel):
|
463
534
|
yield self, field_name, field_value
|
464
|
-
elif isinstance(field_value,
|
535
|
+
elif isinstance(field_value, list):
|
465
536
|
for item in field_value:
|
466
537
|
if isinstance(item, DatabaseModel):
|
467
538
|
yield self, field_name, item
|
468
|
-
elif isinstance(field_value, dict):
|
469
|
-
for key, value in field_value.items():
|
470
|
-
if isinstance(value, DatabaseModel):
|
471
|
-
yield self, key, value
|
472
539
|
|
473
540
|
def dfs_traverse_hierarchy(self):
|
474
541
|
"""
|
@@ -484,11 +551,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
|
|
484
551
|
for field_name, field_value in fields:
|
485
552
|
if isinstance(field_value, DatabaseModel):
|
486
553
|
yield from field_value.dfs_traverse_hierarchy()
|
487
|
-
elif isinstance(field_value,
|
554
|
+
elif isinstance(field_value, list):
|
488
555
|
for item in field_value:
|
489
556
|
if isinstance(item, DatabaseModel):
|
490
557
|
yield from item.dfs_traverse_hierarchy()
|
491
|
-
elif isinstance(field_value, dict):
|
492
|
-
for key, value in field_value.items():
|
493
|
-
if isinstance(value, DatabaseModel):
|
494
|
-
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=ccQ57Xsg1brgbDZMHewPzTuCEvz0vRkEVHCRhvX2m5Q,30502
|
4
|
+
statikk/expressions.py,sha256=boAeGxZj2cDsXxoiX3IIEzfX9voSMQngi4-rE_jYeuE,12233
|
5
|
+
statikk/fields.py,sha256=uLGD3xEnz2nqQY6EwG8Dd7pnRMMVP4PU2Z2I-uGNaTA,150
|
6
|
+
statikk/models.py,sha256=3vDyq_ox6zCGsSw72PcIbOEf4pgs3oJskLhvf--EtMQ,19740
|
7
|
+
statikk/typing.py,sha256=laOlOpWOm9_sOj4hhdZnGTUZRiq8760_B9I9B3wBhz8,750
|
8
|
+
statikk-0.1.15.dist-info/licenses/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
|
9
|
+
statikk-0.1.15.dist-info/METADATA,sha256=sL1Y0GF7pUk3leDfgyTlhV69F5wnOiCPm5jO3WPLkcg,3183
|
10
|
+
statikk-0.1.15.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
11
|
+
statikk-0.1.15.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
|
12
|
+
statikk-0.1.15.dist-info/RECORD,,
|
statikk-0.1.13.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=xH8vbUcup3FmZ3h3RL6-j1hxVDSQwrRASp8ILhoKXn8,32604
|
4
|
-
statikk/expressions.py,sha256=boAeGxZj2cDsXxoiX3IIEzfX9voSMQngi4-rE_jYeuE,12233
|
5
|
-
statikk/fields.py,sha256=LkMP5NnX7WS0HSLxI3Q-dMOrfaJ0SD7SayZxJU5Acgg,86
|
6
|
-
statikk/models.py,sha256=fU5SwxKP1Afsy91RhYiJSD58CCF92s4YxX-fwH6p7ZI,17705
|
7
|
-
statikk/typing.py,sha256=laOlOpWOm9_sOj4hhdZnGTUZRiq8760_B9I9B3wBhz8,750
|
8
|
-
statikk-0.1.13.dist-info/licenses/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
|
9
|
-
statikk-0.1.13.dist-info/METADATA,sha256=pv7MrB9Svi22O1z--jDhW8DkEvdxNP4nErQLt7HqQpg,3183
|
10
|
-
statikk-0.1.13.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
|
11
|
-
statikk-0.1.13.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
|
12
|
-
statikk-0.1.13.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|