statikk 0.1.14__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 +59 -110
- statikk/fields.py +1 -0
- statikk/models.py +25 -33
- {statikk-0.1.14.dist-info → statikk-0.1.15.dist-info}/METADATA +1 -1
- statikk-0.1.15.dist-info/RECORD +12 -0
- statikk-0.1.14.dist-info/RECORD +0 -12
- {statikk-0.1.14.dist-info → statikk-0.1.15.dist-info}/WHEEL +0 -0
- {statikk-0.1.14.dist-info → statikk-0.1.15.dist-info}/licenses/LICENSE.txt +0 -0
- {statikk-0.1.14.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 |  | 
| @@ -296,20 +296,23 @@ class Table: | |
| 296 296 | 
             
                    response = self._get_dynamodb_table().update_item(**request)
         | 
| 297 297 | 
             
                    data = response["Attributes"]
         | 
| 298 298 | 
             
                    for key, value in data.items():
         | 
| 299 | 
            -
                        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]:
         | 
| 300 300 | 
             
                            continue
         | 
| 301 301 | 
             
                        data[key] = self._deserialize_value(value, model.model_fields[key])
         | 
| 302 302 | 
             
                    return type(model)(**data)
         | 
| 303 303 |  | 
| 304 | 
            -
                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:
         | 
| 305 305 | 
             
                    subtree_copy = deepcopy(subtree_root)
         | 
| 306 306 | 
             
                    subtree_root._parent_changed = True
         | 
| 307 307 |  | 
| 308 308 | 
             
                    subtree_copy.set_parent_references(subtree_copy, force_override=True)
         | 
| 309 309 | 
             
                    subtree_copy._parent = new_parent
         | 
| 310 | 
            +
                    subtree_copy._parent_field_name = field_name
         | 
| 311 | 
            +
                    parent = subtree_copy._parent
         | 
| 310 312 | 
             
                    for node in subtree_copy.dfs_traverse_hierarchy():
         | 
| 311 313 | 
             
                        self.build_model_indexes(node)
         | 
| 312 | 
            -
                        setattr(node, FIELD_STATIKK_PARENT_ID,  | 
| 314 | 
            +
                        setattr(node, FIELD_STATIKK_PARENT_ID, parent.id)
         | 
| 315 | 
            +
                        parent = node
         | 
| 313 316 |  | 
| 314 317 | 
             
                    return subtree_copy
         | 
| 315 318 |  | 
| @@ -427,7 +430,9 @@ class Table: | |
| 427 430 | 
             
                    model_class = self._get_model_type_by_statikk_type(model_type)
         | 
| 428 431 |  | 
| 429 432 | 
             
                    reconstructed_dict.pop(FIELD_STATIKK_TYPE, None)
         | 
| 430 | 
            -
                     | 
| 433 | 
            +
                    model = model_class.model_validate(reconstructed_dict)
         | 
| 434 | 
            +
                    model._is_persisted = True
         | 
| 435 | 
            +
                    return model
         | 
| 431 436 |  | 
| 432 437 | 
             
                def scan(
         | 
| 433 438 | 
             
                    self,
         | 
| @@ -626,6 +631,7 @@ class Table: | |
| 626 631 | 
             
                                    continue
         | 
| 627 632 | 
             
                                if not enriched_item.should_write_to_database():
         | 
| 628 633 | 
             
                                    continue
         | 
| 634 | 
            +
                                enriched_item._is_persisted = True
         | 
| 629 635 | 
             
                                data = self._serialize_item(enriched_item)
         | 
| 630 636 | 
             
                                batch.put_item(Item=data)
         | 
| 631 637 |  | 
| @@ -640,9 +646,6 @@ class Table: | |
| 640 646 | 
             
                    Returns:
         | 
| 641 647 | 
             
                        The top-level dictionary with its hierarchy fully reconstructed, or None if the list is empty
         | 
| 642 648 | 
             
                    """
         | 
| 643 | 
            -
                    if not items:
         | 
| 644 | 
            -
                        return None
         | 
| 645 | 
            -
             | 
| 646 649 | 
             
                    items_by_id = {item["id"]: item for item in items}
         | 
| 647 650 | 
             
                    children_by_parent_id = {}
         | 
| 648 651 | 
             
                    for item in items:
         | 
| @@ -652,128 +655,74 @@ class Table: | |
| 652 655 | 
             
                                children_by_parent_id[parent_id] = []
         | 
| 653 656 | 
             
                            children_by_parent_id[parent_id].append(item)
         | 
| 654 657 |  | 
| 655 | 
            -
                     | 
| 656 | 
            -
                    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]
         | 
| 657 659 |  | 
| 658 | 
            -
                     | 
| 659 | 
            -
             | 
| 660 | 
            -
             | 
| 661 | 
            -
                    if len(root_items) > 1:
         | 
| 662 | 
            -
                        root_item = root_items[0]
         | 
| 663 | 
            -
                    else:
         | 
| 664 | 
            -
                        root_item = root_items[0]
         | 
| 660 | 
            +
                    processed_root = self._process_item(root_item, items_by_id, children_by_parent_id)
         | 
| 661 | 
            +
                    return processed_root
         | 
| 665 662 |  | 
| 666 | 
            -
             | 
| 667 | 
            -
                    return self._reconstruct_item_with_children(root_item, items_by_id, children_by_parent_id, processed_items)
         | 
| 668 | 
            -
             | 
| 669 | 
            -
                def _reconstruct_item_with_children(
         | 
| 670 | 
            -
                    self, item: dict, items_by_id: dict, children_by_parent_id: dict, processed_items: set
         | 
| 671 | 
            -
                ) -> dict:
         | 
| 663 | 
            +
                def _process_item(self, item: dict, items_by_id: dict, children_by_parent_id: dict) -> dict:
         | 
| 672 664 | 
             
                    """
         | 
| 673 | 
            -
                    Recursively  | 
| 665 | 
            +
                    Recursively processes an item and all its children to rebuild the hierarchical structure.
         | 
| 674 666 |  | 
| 675 667 | 
             
                    Args:
         | 
| 676 | 
            -
                        item: The item to  | 
| 677 | 
            -
                        items_by_id:  | 
| 678 | 
            -
                        children_by_parent_id:  | 
| 679 | 
            -
                        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
         | 
| 680 671 |  | 
| 681 672 | 
             
                    Returns:
         | 
| 682 | 
            -
                        The  | 
| 673 | 
            +
                        The processed item with all its child relationships resolved
         | 
| 683 674 | 
             
                    """
         | 
| 684 | 
            -
                     | 
| 685 | 
            -
                        return item
         | 
| 686 | 
            -
                    processed_items.add(item["id"])
         | 
| 687 | 
            -
                    result = item.copy()
         | 
| 688 | 
            -
             | 
| 689 | 
            -
                    if FIELD_STATIKK_PARENT_ID in result:
         | 
| 690 | 
            -
                        result.pop(FIELD_STATIKK_PARENT_ID)
         | 
| 675 | 
            +
                    processed_item = deepcopy(item)
         | 
| 691 676 |  | 
| 692 | 
            -
                     | 
| 693 | 
            -
             | 
| 694 | 
            -
                         | 
| 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
         | 
| 695 682 |  | 
| 696 | 
            -
                     | 
| 683 | 
            +
                    # Get children of this item
         | 
| 684 | 
            +
                    children = children_by_parent_id.get(processed_item["id"], [])
         | 
| 697 685 |  | 
| 698 | 
            -
                     | 
| 686 | 
            +
                    # Group children by parent field name
         | 
| 687 | 
            +
                    children_by_field = {}
         | 
| 699 688 | 
             
                    for child in children:
         | 
| 700 | 
            -
                         | 
| 701 | 
            -
                        if  | 
| 702 | 
            -
                             | 
| 703 | 
            -
             | 
| 704 | 
            -
             | 
| 705 | 
            -
             | 
| 706 | 
            -
             | 
| 707 | 
            -
                         | 
| 708 | 
            -
             | 
| 709 | 
            -
                        for field_name, field_info in parent_model_class.model_fields.items():
         | 
| 710 | 
            -
                            if field_name.startswith("_"):
         | 
| 711 | 
            -
                                continue
         | 
| 712 | 
            -
             | 
| 713 | 
            -
                            is_optional, inner_type = inspect_optional_field(parent_model_class, field_name)
         | 
| 714 | 
            -
             | 
| 715 | 
            -
                            field_type = inner_type if is_optional else field_info.annotation
         | 
| 716 | 
            -
             | 
| 717 | 
            -
                            if field_type == child_model_class:
         | 
| 718 | 
            -
                                matching_fields.append((field_name, "single"))
         | 
| 719 | 
            -
             | 
| 720 | 
            -
                            elif hasattr(field_type, "__origin__") and field_type.__origin__ == list:
         | 
| 721 | 
            -
                                args = getattr(field_type, "__args__", [])
         | 
| 722 | 
            -
                                if args and args[0] == child_model_class:
         | 
| 723 | 
            -
                                    matching_fields.append((field_name, "list"))
         | 
| 724 | 
            -
             | 
| 725 | 
            -
                            elif hasattr(field_type, "__origin__") and field_type.__origin__ == set:
         | 
| 726 | 
            -
                                args = getattr(field_type, "__args__", [])
         | 
| 727 | 
            -
                                if args and args[0] == child_model_class:
         | 
| 728 | 
            -
                                    matching_fields.append((field_name, "set"))
         | 
| 729 | 
            -
             | 
| 730 | 
            -
                        if matching_fields:
         | 
| 731 | 
            -
                            for field_name, container_type in matching_fields:
         | 
| 732 | 
            -
                                if container_type == "list":
         | 
| 733 | 
            -
                                    if field_name not in result:
         | 
| 734 | 
            -
                                        result[field_name] = []
         | 
| 735 | 
            -
             | 
| 736 | 
            -
                                    existing_ids = {
         | 
| 737 | 
            -
                                        item.get("id") for item in result[field_name] if isinstance(item, dict) and "id" in item
         | 
| 738 | 
            -
                                    }
         | 
| 739 | 
            -
             | 
| 740 | 
            -
                                    for child in child_items:
         | 
| 741 | 
            -
                                        if child["id"] in existing_ids:
         | 
| 742 | 
            -
                                            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
         | 
| 743 698 |  | 
| 744 | 
            -
             | 
| 745 | 
            -
                                            child, items_by_id, children_by_parent_id, processed_items
         | 
| 746 | 
            -
                                        )
         | 
| 699 | 
            +
                        field_children = children_by_field[field_name]
         | 
| 747 700 |  | 
| 748 | 
            -
             | 
| 749 | 
            -
             | 
| 701 | 
            +
                        field_type = field_info.annotation
         | 
| 702 | 
            +
                        is_optional = False
         | 
| 703 | 
            +
                        inner_type = field_type
         | 
| 750 704 |  | 
| 751 | 
            -
             | 
| 752 | 
            -
             | 
| 753 | 
            -
             | 
| 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))
         | 
| 754 711 |  | 
| 755 | 
            -
             | 
| 756 | 
            -
             | 
| 757 | 
            -
                                    }
         | 
| 712 | 
            +
                        if hasattr(inner_type, "__origin__") and inner_type.__origin__ == list:
         | 
| 713 | 
            +
                            child_list = []
         | 
| 758 714 |  | 
| 759 | 
            -
             | 
| 760 | 
            -
             | 
| 761 | 
            -
             | 
| 762 | 
            -
                                        reconstructed_child = self._reconstruct_item_with_children(
         | 
| 763 | 
            -
                                            child, items_by_id, children_by_parent_id, processed_items
         | 
| 764 | 
            -
                                        )
         | 
| 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)
         | 
| 765 718 |  | 
| 766 | 
            -
             | 
| 767 | 
            -
                                        existing_ids.add(child["id"])
         | 
| 719 | 
            +
                            processed_item[field_name] = child_list
         | 
| 768 720 |  | 
| 769 | 
            -
             | 
| 770 | 
            -
             | 
| 771 | 
            -
             | 
| 772 | 
            -
                                            child_items[0], items_by_id, children_by_parent_id, processed_items
         | 
| 773 | 
            -
                                        )
         | 
| 774 | 
            -
                                        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
         | 
| 775 724 |  | 
| 776 | 
            -
                    return  | 
| 725 | 
            +
                    return processed_item
         | 
| 777 726 |  | 
| 778 727 |  | 
| 779 728 | 
             
            class BatchWriteContext:
         | 
    
        statikk/fields.py
    CHANGED
    
    
    
        statikk/models.py
    CHANGED
    
    | @@ -14,7 +14,7 @@ from pydantic_core._pydantic_core import PydanticUndefined | |
| 14 14 |  | 
| 15 15 | 
             
            from statikk.conditions import Condition
         | 
| 16 16 | 
             
            from statikk.expressions import DatabaseModelUpdateExpressionBuilder
         | 
| 17 | 
            -
            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
         | 
| 18 18 |  | 
| 19 19 | 
             
            if typing.TYPE_CHECKING:
         | 
| 20 20 | 
             
                from statikk.engine import Table
         | 
| @@ -196,14 +196,20 @@ class TrackingMixin: | |
| 196 196 | 
             
                    return {}
         | 
| 197 197 |  | 
| 198 198 |  | 
| 199 | 
            -
            class DatabaseModel(BaseModel, TrackingMixin | 
| 199 | 
            +
            class DatabaseModel(BaseModel, TrackingMixin):
         | 
| 200 200 | 
             
                id: str = Field(default_factory=lambda: str(uuid4()))
         | 
| 201 201 | 
             
                _parent: Optional[DatabaseModel] = None
         | 
| 202 | 
            +
                _parent_field_name: Optional[str] = None
         | 
| 202 203 | 
             
                _model_types_in_hierarchy: dict[str, Type[DatabaseModel]] = {}
         | 
| 203 204 | 
             
                _should_delete: bool = False
         | 
| 204 205 | 
             
                _parent_changed: bool = False
         | 
| 206 | 
            +
                _is_persisted: bool = False
         | 
| 205 207 | 
             
                _session = Session()
         | 
| 206 208 |  | 
| 209 | 
            +
                class Config:
         | 
| 210 | 
            +
                    extra = Extra.allow
         | 
| 211 | 
            +
                    arbitrary_types_allowed = True
         | 
| 212 | 
            +
             | 
| 207 213 | 
             
                def __eq__(self, other):
         | 
| 208 214 | 
             
                    return self.id == other.id
         | 
| 209 215 |  | 
| @@ -255,12 +261,19 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 255 261 | 
             
                def is_simple_object(self) -> bool:
         | 
| 256 262 | 
             
                    return len(self._model_types_in_hierarchy) == 1
         | 
| 257 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 | 
            +
             | 
| 258 271 | 
             
                @property
         | 
| 259 272 | 
             
                def should_delete(self) -> bool:
         | 
| 260 273 | 
             
                    if self._is_any_parent_marked_for_deletion():
         | 
| 261 274 | 
             
                        return True
         | 
| 262 275 |  | 
| 263 | 
            -
                    return self._should_delete or self.is_parent_changed()
         | 
| 276 | 
            +
                    return self.is_persisted and (self._should_delete or self.is_parent_changed())
         | 
| 264 277 |  | 
| 265 278 | 
             
                def _is_any_parent_marked_for_deletion(self) -> bool:
         | 
| 266 279 | 
             
                    current = self._parent
         | 
| @@ -346,8 +359,8 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 346 359 | 
             
                def mark_for_delete(self):
         | 
| 347 360 | 
             
                    self._should_delete = True
         | 
| 348 361 |  | 
| 349 | 
            -
                def  | 
| 350 | 
            -
                    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)
         | 
| 351 364 |  | 
| 352 365 | 
             
                def _remove_from_parent(self, parent, field_name, subtree):
         | 
| 353 366 | 
             
                    is_optional, inner_type = inspect_optional_field(parent.__class__, field_name)
         | 
| @@ -384,17 +397,17 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 384 397 | 
             
                    if hasattr(field_type, "__origin__") and field_type.__origin__ == list:
         | 
| 385 398 | 
             
                        if not isinstance(getattr(self, field_name), list):
         | 
| 386 399 | 
             
                            setattr(self, field_name, [])
         | 
| 387 | 
            -
                        reparented = child_node. | 
| 400 | 
            +
                        reparented = child_node._change_parent_to(self, field_name)
         | 
| 388 401 | 
             
                        getattr(self, field_name).append(reparented)
         | 
| 389 402 |  | 
| 390 403 | 
             
                    elif hasattr(field_type, "__origin__") and field_type.__origin__ == set:
         | 
| 391 404 | 
             
                        if not isinstance(getattr(self, field_name), set):
         | 
| 392 405 | 
             
                            setattr(self, field_name, set())
         | 
| 393 | 
            -
                        reparented = child_node. | 
| 406 | 
            +
                        reparented = child_node._change_parent_to(self, field_name)
         | 
| 394 407 | 
             
                        getattr(self, field_name).add(reparented)
         | 
| 395 408 |  | 
| 396 409 | 
             
                    elif issubclass(field_type, DatabaseModel):
         | 
| 397 | 
            -
                        reparented = child_node. | 
| 410 | 
            +
                        reparented = child_node._change_parent_to(self, field_name)
         | 
| 398 411 | 
             
                        setattr(self, field_name, reparented)
         | 
| 399 412 |  | 
| 400 413 | 
             
                    if reparented:
         | 
| @@ -418,6 +431,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 418 431 | 
             
                    data[FIELD_STATIKK_TYPE] = self.type()
         | 
| 419 432 | 
             
                    if self._parent:
         | 
| 420 433 | 
             
                        data[FIELD_STATIKK_PARENT_ID] = self._parent.id
         | 
| 434 | 
            +
                        data[FIELD_STATIKK_PARENT_FIELD_NAME] = self._parent_field_name
         | 
| 421 435 | 
             
                    return data
         | 
| 422 436 |  | 
| 423 437 | 
             
                @model_validator(mode="after")
         | 
| @@ -465,13 +479,6 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 465 479 | 
             
                                        items.append(item)
         | 
| 466 480 | 
             
                                    item.split_to_simple_objects(items)
         | 
| 467 481 |  | 
| 468 | 
            -
                        elif isinstance(field_value, set):
         | 
| 469 | 
            -
                            for item in field_value:
         | 
| 470 | 
            -
                                if hasattr(item, "__class__") and issubclass(item.__class__, DatabaseModel):
         | 
| 471 | 
            -
                                    if item not in items:
         | 
| 472 | 
            -
                                        items.append(item)
         | 
| 473 | 
            -
                                    item.split_to_simple_objects(items)
         | 
| 474 | 
            -
             | 
| 475 482 | 
             
                    return items
         | 
| 476 483 |  | 
| 477 484 | 
             
                def get_attribute(self, attribute_name: str):
         | 
| @@ -488,14 +495,6 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 488 495 | 
             
                            for item in field_value:
         | 
| 489 496 | 
             
                                if issubclass(item.__class__, DatabaseModel) and item.is_nested():
         | 
| 490 497 | 
             
                                    nested_models.append(field_name)
         | 
| 491 | 
            -
                        elif isinstance(field_value, set):
         | 
| 492 | 
            -
                            for item in field_value:
         | 
| 493 | 
            -
                                if issubclass(item.__class__, DatabaseModel) and item.is_nested():
         | 
| 494 | 
            -
                                    nested_models.append(field_name)
         | 
| 495 | 
            -
                        elif isinstance(field_value, dict):
         | 
| 496 | 
            -
                            for key, value in field_value.items():
         | 
| 497 | 
            -
                                if issubclass(value.__class__, DatabaseModel) and value.is_nested():
         | 
| 498 | 
            -
                                    nested_models.append(field_name)
         | 
| 499 498 | 
             
                    return set(nested_models)
         | 
| 500 499 |  | 
| 501 500 | 
             
                def get_type_from_hierarchy_by_name(self, name: str) -> Optional[Type[DatabaseModel]]:
         | 
| @@ -512,6 +511,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 512 511 | 
             
                    if field._parent and not force_override:
         | 
| 513 512 | 
             
                        return  # Already set
         | 
| 514 513 | 
             
                    field._parent = parent
         | 
| 514 | 
            +
                    field._parent_field_name = field_name
         | 
| 515 515 | 
             
                    if field.should_track_session:
         | 
| 516 516 | 
             
                        field._session.add_change(parent, field_name, field)
         | 
| 517 517 | 
             
                    root._model_types_in_hierarchy[field.type()] = type(field)
         | 
| @@ -532,14 +532,10 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 532 532 | 
             
                    for field_name, field_value in self:
         | 
| 533 533 | 
             
                        if isinstance(field_value, DatabaseModel):
         | 
| 534 534 | 
             
                            yield self, field_name, field_value
         | 
| 535 | 
            -
                        elif isinstance(field_value,  | 
| 535 | 
            +
                        elif isinstance(field_value, list):
         | 
| 536 536 | 
             
                            for item in field_value:
         | 
| 537 537 | 
             
                                if isinstance(item, DatabaseModel):
         | 
| 538 538 | 
             
                                    yield self, field_name, item
         | 
| 539 | 
            -
                        elif isinstance(field_value, dict):
         | 
| 540 | 
            -
                            for key, value in field_value.items():
         | 
| 541 | 
            -
                                if isinstance(value, DatabaseModel):
         | 
| 542 | 
            -
                                    yield self, key, value
         | 
| 543 539 |  | 
| 544 540 | 
             
                def dfs_traverse_hierarchy(self):
         | 
| 545 541 | 
             
                    """
         | 
| @@ -555,11 +551,7 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow): | |
| 555 551 | 
             
                    for field_name, field_value in fields:
         | 
| 556 552 | 
             
                        if isinstance(field_value, DatabaseModel):
         | 
| 557 553 | 
             
                            yield from field_value.dfs_traverse_hierarchy()
         | 
| 558 | 
            -
                        elif isinstance(field_value,  | 
| 554 | 
            +
                        elif isinstance(field_value, list):
         | 
| 559 555 | 
             
                            for item in field_value:
         | 
| 560 556 | 
             
                                if isinstance(item, DatabaseModel):
         | 
| 561 557 | 
             
                                    yield from item.dfs_traverse_hierarchy()
         | 
| 562 | 
            -
                        elif isinstance(field_value, dict):
         | 
| 563 | 
            -
                            for key, value in field_value.items():
         | 
| 564 | 
            -
                                if isinstance(value, DatabaseModel):
         | 
| 565 | 
            -
                                    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.14.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=-YmezdiYViz1sR60VL7ryDCvrhp-WWGVCtPAakKRzg8,32636
         | 
| 4 | 
            -
            statikk/expressions.py,sha256=boAeGxZj2cDsXxoiX3IIEzfX9voSMQngi4-rE_jYeuE,12233
         | 
| 5 | 
            -
            statikk/fields.py,sha256=LkMP5NnX7WS0HSLxI3Q-dMOrfaJ0SD7SayZxJU5Acgg,86
         | 
| 6 | 
            -
            statikk/models.py,sha256=zLImQUtUURbAQ3gYAiotkypxaDUqAIiCL1edNzuey3k,20447
         | 
| 7 | 
            -
            statikk/typing.py,sha256=laOlOpWOm9_sOj4hhdZnGTUZRiq8760_B9I9B3wBhz8,750
         | 
| 8 | 
            -
            statikk-0.1.14.dist-info/licenses/LICENSE.txt,sha256=uSH_2Hpb2Bigy5_HhBliN2fZbBU64G3ERM5zzhKPUEE,1078
         | 
| 9 | 
            -
            statikk-0.1.14.dist-info/METADATA,sha256=8NHWnMMS3Qy53_I9V3EDWOY6x3wTzQ_juoYSPS9QeII,3183
         | 
| 10 | 
            -
            statikk-0.1.14.dist-info/WHEEL,sha256=1tXe9gY0PYatrMPMDd6jXqjfpz_B-Wqm32CPfRC58XU,91
         | 
| 11 | 
            -
            statikk-0.1.14.dist-info/top_level.txt,sha256=etKmBbjzIlLpSefXoiOfhWGEgvqUEALaFwCjFDBD9YI,8
         | 
| 12 | 
            -
            statikk-0.1.14.dist-info/RECORD,,
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |