statikk 0.1.8__tar.gz → 0.1.10__tar.gz

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.
Files changed (43) hide show
  1. {statikk-0.1.8 → statikk-0.1.10}/PKG-INFO +1 -1
  2. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/engine.py +34 -7
  3. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/models.py +14 -0
  4. {statikk-0.1.8 → statikk-0.1.10}/src/statikk.egg-info/PKG-INFO +1 -1
  5. {statikk-0.1.8 → statikk-0.1.10}/tests/test_engine.py +52 -2
  6. {statikk-0.1.8 → statikk-0.1.10}/tests/test_models.py +1 -1
  7. {statikk-0.1.8 → statikk-0.1.10}/.coveragerc +0 -0
  8. {statikk-0.1.8 → statikk-0.1.10}/.gitignore +0 -0
  9. {statikk-0.1.8 → statikk-0.1.10}/.readthedocs.yml +0 -0
  10. {statikk-0.1.8 → statikk-0.1.10}/AUTHORS.rst +0 -0
  11. {statikk-0.1.8 → statikk-0.1.10}/CHANGELOG.rst +0 -0
  12. {statikk-0.1.8 → statikk-0.1.10}/CONTRIBUTING.rst +0 -0
  13. {statikk-0.1.8 → statikk-0.1.10}/LICENSE.txt +0 -0
  14. {statikk-0.1.8 → statikk-0.1.10}/README.rst +0 -0
  15. {statikk-0.1.8 → statikk-0.1.10}/assets/favicon.png +0 -0
  16. {statikk-0.1.8 → statikk-0.1.10}/assets/logo.png +0 -0
  17. {statikk-0.1.8 → statikk-0.1.10}/docs/Makefile +0 -0
  18. {statikk-0.1.8 → statikk-0.1.10}/docs/_static/.gitignore +0 -0
  19. {statikk-0.1.8 → statikk-0.1.10}/docs/authors.rst +0 -0
  20. {statikk-0.1.8 → statikk-0.1.10}/docs/changelog.rst +0 -0
  21. {statikk-0.1.8 → statikk-0.1.10}/docs/conf.py +0 -0
  22. {statikk-0.1.8 → statikk-0.1.10}/docs/contributing.rst +0 -0
  23. {statikk-0.1.8 → statikk-0.1.10}/docs/index.rst +0 -0
  24. {statikk-0.1.8 → statikk-0.1.10}/docs/license.rst +0 -0
  25. {statikk-0.1.8 → statikk-0.1.10}/docs/readme.rst +0 -0
  26. {statikk-0.1.8 → statikk-0.1.10}/docs/requirements.txt +0 -0
  27. {statikk-0.1.8 → statikk-0.1.10}/docs/usage.rst +0 -0
  28. {statikk-0.1.8 → statikk-0.1.10}/pyproject.toml +0 -0
  29. {statikk-0.1.8 → statikk-0.1.10}/setup.cfg +0 -0
  30. {statikk-0.1.8 → statikk-0.1.10}/setup.py +0 -0
  31. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/__init__.py +0 -0
  32. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/conditions.py +0 -0
  33. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/expressions.py +0 -0
  34. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/fields.py +0 -0
  35. {statikk-0.1.8 → statikk-0.1.10}/src/statikk/typing.py +0 -0
  36. {statikk-0.1.8 → statikk-0.1.10}/src/statikk.egg-info/SOURCES.txt +0 -0
  37. {statikk-0.1.8 → statikk-0.1.10}/src/statikk.egg-info/dependency_links.txt +0 -0
  38. {statikk-0.1.8 → statikk-0.1.10}/src/statikk.egg-info/not-zip-safe +0 -0
  39. {statikk-0.1.8 → statikk-0.1.10}/src/statikk.egg-info/requires.txt +0 -0
  40. {statikk-0.1.8 → statikk-0.1.10}/src/statikk.egg-info/top_level.txt +0 -0
  41. {statikk-0.1.8 → statikk-0.1.10}/tests/conftest.py +0 -0
  42. {statikk-0.1.8 → statikk-0.1.10}/tests/test_expressions.py +0 -0
  43. {statikk-0.1.8 → statikk-0.1.10}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: statikk
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -1,6 +1,6 @@
1
1
  import os
2
2
  from datetime import datetime
3
- from typing import Any, Dict, Type, Optional, List, Union
3
+ from typing import Any, Dict, Type, Optional, List, Union, get_type_hints, get_origin, get_args
4
4
 
5
5
  import boto3
6
6
  from botocore.config import Config
@@ -307,15 +307,20 @@ class Table:
307
307
  subtree_copy._parent = new_parent
308
308
  for node in subtree_copy.dfs_traverse_hierarchy():
309
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)
310
+ self.build_model_indexes(node)
315
311
  setattr(node, FIELD_STATIKK_PARENT_ID, new_parent.id)
316
312
 
317
313
  return subtree_copy
318
314
 
315
+ def build_model_indexes(self, model: T) -> T:
316
+ for idx in self.indexes:
317
+ new_index_values = self._compose_index_values(model, idx)
318
+ new_sort_key_value = new_index_values[idx.sort_key.name]
319
+ setattr(model, idx.sort_key.name, new_sort_key_value)
320
+ new_hash_key_value = new_index_values[idx.hash_key.name]
321
+ setattr(model, idx.hash_key.name, new_hash_key_value)
322
+ return model
323
+
319
324
  def batch_write(self):
320
325
  """
321
326
  Returns a context manager for batch writing items to the database. This method handles all the buffering of the
@@ -623,6 +628,26 @@ class Table:
623
628
  data = self._serialize_item(enriched_item)
624
629
  batch.delete_item(Key=data)
625
630
 
631
+ def inspect_optional_field(self, model_class, field_name):
632
+ field_type = model_class.model_fields[field_name].annotation
633
+
634
+ is_optional = False
635
+ inner_type = field_type
636
+
637
+ if get_origin(field_type) is Union:
638
+ args = get_args(field_type)
639
+ if len(args) == 2 and args[1] is type(None):
640
+ is_optional = True
641
+ inner_type = args[0]
642
+
643
+ elif hasattr(field_type, "__origin__") and field_type.__origin__ is Union:
644
+ args = getattr(field_type, "__args__", [])
645
+ if len(args) == 2 and args[1] is type(None):
646
+ is_optional = True
647
+ inner_type = args[0]
648
+
649
+ return (is_optional, inner_type)
650
+
626
651
  def reconstruct_hierarchy(self, items: list[dict]) -> Optional[dict]:
627
652
  """
628
653
  Reconstructs a hierarchical dictionary structure from a flat list of dictionaries
@@ -704,7 +729,9 @@ class Table:
704
729
  if field_name.startswith("_"):
705
730
  continue
706
731
 
707
- field_type = field_info.annotation
732
+ is_optional, inner_type = self.inspect_optional_field(parent_model_class, field_name)
733
+
734
+ field_type = inner_type if is_optional else field_info.annotation
708
735
 
709
736
  if field_type == child_model_class:
710
737
  matching_fields.append((field_name, "single"))
@@ -204,8 +204,22 @@ class DatabaseModel(BaseModel, TrackingMixin, extra=Extra.allow):
204
204
 
205
205
  @property
206
206
  def should_delete(self) -> bool:
207
+ if self._is_any_parent_marked_for_deletion():
208
+ return True
209
+
207
210
  return self._should_delete or self.is_parent_changed()
208
211
 
212
+ def _is_any_parent_marked_for_deletion(self) -> bool:
213
+ current = self._parent
214
+ while current is not None:
215
+ if current._should_delete:
216
+ return True
217
+ current = current._parent
218
+ return False
219
+
220
+ def build_model_indexes(self) -> T:
221
+ return self._table.build_model_indexes(self)
222
+
209
223
  @classmethod
210
224
  def query(
211
225
  cls: Type[T],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: statikk
3
- Version: 0.1.8
3
+ Version: 0.1.10
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
@@ -775,8 +775,20 @@ def test_nested_raw_models():
775
775
 
776
776
 
777
777
  def test_nested_hierarchies():
778
+ class OptionalModel(DatabaseModel):
779
+ xax: str = "xax"
780
+
781
+ @classmethod
782
+ def index_definitions(cls) -> dict[str, IndexFieldConfig]:
783
+ return {"main-index": IndexFieldConfig(sk_fields=["xax"])}
784
+
785
+ @classmethod
786
+ def is_nested(cls) -> bool:
787
+ return True
788
+
778
789
  class TriplyNested(DatabaseModel):
779
790
  faz: str = "faz"
791
+ optional: Optional[OptionalModel] = None
780
792
 
781
793
  @classmethod
782
794
  def index_definitions(cls) -> dict[str, IndexFieldConfig]:
@@ -833,10 +845,10 @@ def test_nested_hierarchies():
833
845
  sort_key=Key(name="gsi_sk"),
834
846
  )
835
847
  ],
836
- models=[ModelHierarchy, NestedModel, DoublyNestedModel, TriplyNested],
848
+ models=[ModelHierarchy, NestedModel, DoublyNestedModel, TriplyNested, OptionalModel],
837
849
  )
838
850
  _create_dynamodb_table(table)
839
- triple_nested_model = TriplyNested(faz="faz")
851
+ triple_nested_model = TriplyNested(faz="faz", optional=OptionalModel(xax="xax"))
840
852
  doubly_nested = DoublyNestedModel(bar="bar", items=[triple_nested_model])
841
853
  double_nested_no_write = DoublyNestedModel(bar="far", items=[TriplyNested(faz="faz")])
842
854
  nested = NestedModel(foo="foo", doubly_nested=[doubly_nested, double_nested_no_write])
@@ -851,6 +863,15 @@ def test_nested_hierarchies():
851
863
  assert hierarchy.nested.doubly_nested[0].gsi_pk == "foo_id"
852
864
  assert hierarchy.nested.doubly_nested[0].gsi_sk == "ModelHierarchy|state|NestedModel|foo|DoublyNestedModel|bar"
853
865
  assert hierarchy.nested.doubly_nested[0].items[0].gsi_pk == "foo_id"
866
+ assert (
867
+ hierarchy.nested.doubly_nested[0].items[0].gsi_sk
868
+ == "ModelHierarchy|state|NestedModel|foo|DoublyNestedModel|bar|TriplyNested|faz"
869
+ )
870
+ assert hierarchy.nested.doubly_nested[0].items[0].optional.gsi_pk == "foo_id"
871
+ assert (
872
+ hierarchy.nested.doubly_nested[0].items[0].optional.gsi_sk
873
+ == "ModelHierarchy|state|NestedModel|foo|DoublyNestedModel|bar|TriplyNested|faz|OptionalModel|xax"
874
+ )
854
875
  hierarchy.nested.doubly_nested[0].items[0].mark_for_delete()
855
876
  hierarchy.save()
856
877
  hierarchy = ModelHierarchy.query_hierarchy(hash_key=Equals("foo_id"))
@@ -930,3 +951,32 @@ def test_update_parent():
930
951
  hierarchy.save()
931
952
  hierarchy = Model.query_hierarchy(hash_key=Equals("model-1"))
932
953
  assert len(hierarchy.nested) == 0
954
+
955
+
956
+ def test_rebuild_model_indexes():
957
+ class MyDatabaseModel(DatabaseModel):
958
+ foo: str = "foo"
959
+ bar: str = "bar"
960
+
961
+ @classmethod
962
+ def index_definitions(cls) -> dict[str, IndexFieldConfig]:
963
+ return {"main-index": IndexFieldConfig(pk_fields=["foo"], sk_fields=["bar"])}
964
+
965
+ mock_dynamodb().start()
966
+ table = Table(
967
+ name="my-dynamodb-table",
968
+ key_schema=KeySchema(hash_key="id"),
969
+ indexes=[
970
+ GSI(
971
+ name="main-index",
972
+ hash_key=Key(name="gsi_pk"),
973
+ sort_key=Key(name="gsi_sk"),
974
+ )
975
+ ],
976
+ models=[MyDatabaseModel],
977
+ )
978
+ _create_dynamodb_table(table)
979
+ my_database_model = MyDatabaseModel(foo="foo", bar="bar")
980
+ my_database_model.build_model_indexes()
981
+ assert my_database_model.gsi_pk == "foo"
982
+ assert my_database_model.gsi_sk == "MyDatabaseModel|bar"
@@ -1,5 +1,5 @@
1
1
  from pydantic import BaseModel
2
- from statikk.models import DatabaseModel
2
+ from statikk.models import DatabaseModel, IndexFieldConfig
3
3
 
4
4
 
5
5
  class SimpleObject(BaseModel):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes