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 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, new_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
- return model_class(**reconstructed_dict)
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
- # Find the root item (the one with no parent ID)
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
- if not root_items:
657
- return None
660
+ processed_root = self._process_item(root_item, items_by_id, children_by_parent_id)
661
+ return processed_root
658
662
 
659
- if len(root_items) > 1:
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 reconstruct an item and its children using model class definitions.
665
+ Recursively processes an item and all its children to rebuild the hierarchical structure.
672
666
 
673
667
  Args:
674
- item: The item to reconstruct
675
- items_by_id: Map of all item IDs to items
676
- children_by_parent_id: Map of parent IDs to lists of child items
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 reconstructed item with all child references integrated
673
+ The processed item with all its child relationships resolved
681
674
  """
682
- if item["id"] in processed_items:
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
- children = children_by_parent_id.get(item["id"], [])
691
- if not children:
692
- return result
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
- parent_model_class = self._get_model_type_by_statikk_type(item[FIELD_STATIKK_TYPE])
683
+ # Get children of this item
684
+ children = children_by_parent_id.get(processed_item["id"], [])
695
685
 
696
- children_by_type = {}
686
+ # Group children by parent field name
687
+ children_by_field = {}
697
688
  for child in children:
698
- child_type = child[FIELD_STATIKK_TYPE]
699
- if child_type not in children_by_type:
700
- children_by_type[child_type] = []
701
- children_by_type[child_type].append(child)
702
-
703
- for child_type, child_items in children_by_type.items():
704
- child_model_class = self._get_model_type_by_statikk_type(child_type)
705
- matching_fields = []
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
- reconstructed_child = self._reconstruct_item_with_children(
743
- child, items_by_id, children_by_parent_id, processed_items
744
- )
699
+ field_children = children_by_field[field_name]
745
700
 
746
- result[field_name].append(reconstructed_child)
747
- existing_ids.add(child["id"])
701
+ field_type = field_info.annotation
702
+ is_optional = False
703
+ inner_type = field_type
748
704
 
749
- elif container_type == "set":
750
- if field_name not in result:
751
- result[field_name] = []
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
- existing_ids = {
754
- item.get("id") for item in result[field_name] if isinstance(item, dict) and "id" in item
755
- }
712
+ if hasattr(inner_type, "__origin__") and inner_type.__origin__ == list:
713
+ child_list = []
756
714
 
757
- for child in child_items:
758
- if child["id"] in existing_ids:
759
- continue
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
- result[field_name].append(reconstructed_child)
765
- existing_ids.add(child["id"])
719
+ processed_item[field_name] = child_list
766
720
 
767
- elif container_type == "single":
768
- if child_items:
769
- reconstructed_child = self._reconstruct_item_with_children(
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 result
725
+ return processed_item
775
726
 
776
727
 
777
728
  class BatchWriteContext:
statikk/fields.py CHANGED
@@ -1,2 +1,3 @@
1
1
  FIELD_STATIKK_TYPE = "__statikk_type"
2
2
  FIELD_STATIKK_PARENT_ID = "__statikk_parent_id"
3
+ FIELD_STATIKK_PARENT_FIELD_NAME = "__statikk_parent_field_name"
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, extra=Extra.allow):
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 change_parent_to(self, new_parent: DatabaseModel) -> T:
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.change_parent_to(self)
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.change_parent_to(self)
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.change_parent_to(self)
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, field: DatabaseModel, parent: DatabaseModel, root: DatabaseModel, force_override: bool = False
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, (list, set)):
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, (list, set)):
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statikk
3
- Version: 0.1.13
3
+ Version: 0.1.15
4
4
  Summary: statikk is a single table application (STA) library for DynamoDb.
5
5
  Home-page: https://github.com/terinia/statikk
6
6
  Author: Balint Biro
@@ -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,,
@@ -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,,