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
|