ic-python-db 0.7.5__tar.gz → 0.7.7__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.
- {ic_python_db-0.7.5/ic_python_db.egg-info → ic_python_db-0.7.7}/PKG-INFO +1 -1
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/__init__.py +1 -1
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/db_engine.py +6 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/entity.py +99 -27
- {ic_python_db-0.7.5 → ic_python_db-0.7.7/ic_python_db.egg-info}/PKG-INFO +1 -1
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/pyproject.toml +1 -1
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/setup.py +1 -1
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/LICENSE +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/MANIFEST.in +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/README.md +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/_cdk.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/constants.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/context.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/hooks.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/mixins.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/properties.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/py.typed +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/storage.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db/system_time.py +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db.egg-info/SOURCES.txt +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db.egg-info/dependency_links.txt +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/ic_python_db.egg-info/top_level.txt +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/requirements-dev.txt +0 -0
- {ic_python_db-0.7.5 → ic_python_db-0.7.7}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ic_python_db
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: A lightweight key-value database written in Python, intended for use on the Internet Computer (IC)
|
|
5
5
|
Home-page: https://github.com/smart-social-contracts/ic-python-db
|
|
6
6
|
Author: Smart Social Contracts
|
|
@@ -36,6 +36,12 @@ class Database:
|
|
|
36
36
|
if cls._instance:
|
|
37
37
|
raise RuntimeError("Database instance already exists")
|
|
38
38
|
cls._instance = cls(audit_enabled, db_storage, db_audit)
|
|
39
|
+
|
|
40
|
+
# Flush any Entity subclasses that were defined before Database existed
|
|
41
|
+
from .entity import Entity
|
|
42
|
+
|
|
43
|
+
Entity._flush_deferred_types()
|
|
44
|
+
|
|
39
45
|
return cls._instance
|
|
40
46
|
|
|
41
47
|
def __init__(
|
|
@@ -91,10 +91,37 @@ class Entity:
|
|
|
91
91
|
|
|
92
92
|
_entity_type = None # To be defined in subclasses
|
|
93
93
|
_context: Set["Entity"] = set() # Set of entities in current context
|
|
94
|
+
_deferred_types: List[Type["Entity"]] = [] # Types defined before DB exists
|
|
94
95
|
_do_not_save = False
|
|
95
96
|
__version__ = 1 # Default schema version
|
|
96
97
|
__namespace__: Optional[str] = None # Optional namespace for entity type
|
|
97
98
|
|
|
99
|
+
def __init_subclass__(cls, **kwargs):
|
|
100
|
+
"""Auto-register Entity subclasses with the Database at class definition time."""
|
|
101
|
+
super().__init_subclass__(**kwargs)
|
|
102
|
+
db = Database._instance
|
|
103
|
+
if db is not None:
|
|
104
|
+
db.register_entity_type(cls, cls.get_full_type_name())
|
|
105
|
+
else:
|
|
106
|
+
# Database not initialized yet — defer registration
|
|
107
|
+
Entity._deferred_types.append(cls)
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def _flush_deferred_types(cls):
|
|
111
|
+
"""Register any Entity subclasses that were defined before Database existed."""
|
|
112
|
+
if not cls._deferred_types:
|
|
113
|
+
return
|
|
114
|
+
try:
|
|
115
|
+
db = Database.get_instance()
|
|
116
|
+
except Exception:
|
|
117
|
+
return
|
|
118
|
+
for deferred_cls in cls._deferred_types:
|
|
119
|
+
try:
|
|
120
|
+
db.register_entity_type(deferred_cls, deferred_cls.get_full_type_name())
|
|
121
|
+
except Exception:
|
|
122
|
+
pass
|
|
123
|
+
cls._deferred_types.clear()
|
|
124
|
+
|
|
98
125
|
def __init__(self, **kwargs):
|
|
99
126
|
"""Initialize a new entity.
|
|
100
127
|
|
|
@@ -264,8 +291,8 @@ class Entity:
|
|
|
264
291
|
)
|
|
265
292
|
self._update_timestamps(caller_id)
|
|
266
293
|
|
|
267
|
-
# Save to database
|
|
268
|
-
data = self.
|
|
294
|
+
# Save to database (full serialization preserves all relations)
|
|
295
|
+
data = self._serialize_full()
|
|
269
296
|
|
|
270
297
|
if not self._do_not_save:
|
|
271
298
|
logger.debug(f"Saving entity {self._type}@{self._id} to database")
|
|
@@ -511,9 +538,14 @@ class Entity:
|
|
|
511
538
|
|
|
512
539
|
while len(ret) < count and from_id <= cls.max_id():
|
|
513
540
|
logger.info(f"Loading entity {from_id}")
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
541
|
+
try:
|
|
542
|
+
entity = cls.load(str(from_id))
|
|
543
|
+
if entity:
|
|
544
|
+
ret.append(entity)
|
|
545
|
+
except (ValueError, AttributeError) as e:
|
|
546
|
+
# Skip entities with broken/dangling relation references
|
|
547
|
+
# (full fix: issue #4 — lazy relation resolution)
|
|
548
|
+
logger.warning(f"Skipping {cls.__name__}@{from_id}: {e}")
|
|
517
549
|
from_id += 1
|
|
518
550
|
|
|
519
551
|
return ret
|
|
@@ -559,19 +591,15 @@ class Entity:
|
|
|
559
591
|
# Remove from context
|
|
560
592
|
self.__class__._context.discard(self)
|
|
561
593
|
|
|
562
|
-
def
|
|
563
|
-
"""
|
|
564
|
-
|
|
565
|
-
Returns:
|
|
566
|
-
Dict containing the entity's serializable data
|
|
567
|
-
"""
|
|
594
|
+
def _serialize_base(self) -> Dict[str, Any]:
|
|
595
|
+
"""Shared serialization logic: core data, properties, and instance attributes."""
|
|
568
596
|
# Get mixin data first if available
|
|
569
597
|
data = super().serialize() if hasattr(super(), "serialize") else {}
|
|
570
598
|
|
|
571
599
|
# Add core entity data
|
|
572
600
|
data.update(
|
|
573
601
|
{
|
|
574
|
-
"_type": self._type,
|
|
602
|
+
"_type": self._type,
|
|
575
603
|
"_id": self._id,
|
|
576
604
|
}
|
|
577
605
|
)
|
|
@@ -589,30 +617,74 @@ class Entity:
|
|
|
589
617
|
if not k.startswith("_"):
|
|
590
618
|
data[k] = v
|
|
591
619
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
620
|
+
return data
|
|
621
|
+
|
|
622
|
+
@staticmethod
|
|
623
|
+
def _get_entity_reference(entity):
|
|
624
|
+
"""Get the best reference for an entity: alias value if available, otherwise _id."""
|
|
625
|
+
if hasattr(entity.__class__, "__alias__") and entity.__class__.__alias__:
|
|
626
|
+
alias_field = entity.__class__.__alias__
|
|
627
|
+
alias_value = getattr(entity, alias_field, None)
|
|
628
|
+
if alias_value is not None:
|
|
629
|
+
return alias_value
|
|
630
|
+
return entity._id
|
|
631
|
+
|
|
632
|
+
def _serialize_full(self) -> Dict[str, Any]:
|
|
633
|
+
"""Full serialization including all relations. Used by _save() for persistence."""
|
|
634
|
+
data = self._serialize_base()
|
|
635
|
+
|
|
636
|
+
from ic_python_db.properties import ManyToMany, OneToMany
|
|
601
637
|
|
|
602
638
|
for rel_name, rel_entities in self._relations.items():
|
|
603
639
|
if rel_entities:
|
|
604
|
-
# Check if this is a *ToMany relation that should always be a list
|
|
605
640
|
rel_prop = getattr(self.__class__, rel_name, None)
|
|
606
|
-
|
|
641
|
+
is_to_many = isinstance(rel_prop, (OneToMany, ManyToMany))
|
|
642
|
+
|
|
643
|
+
if len(rel_entities) == 1 and not is_to_many:
|
|
644
|
+
data[rel_name] = self._get_entity_reference(rel_entities[0])
|
|
645
|
+
else:
|
|
646
|
+
data[rel_name] = [
|
|
647
|
+
self._get_entity_reference(e) for e in rel_entities
|
|
648
|
+
]
|
|
649
|
+
|
|
650
|
+
return data
|
|
651
|
+
|
|
652
|
+
def serialize(self) -> Dict[str, Any]:
|
|
653
|
+
"""Convert the entity to a portable serializable dictionary.
|
|
654
|
+
|
|
655
|
+
OneToMany relations are skipped (reconstructed from reverse ManyToOne).
|
|
656
|
+
For bilateral OneToOne relations, only the alphabetically-earlier entity
|
|
657
|
+
type serializes the reference, avoiding circular dependencies.
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
Dict containing the entity's serializable data
|
|
661
|
+
"""
|
|
662
|
+
data = self._serialize_base()
|
|
663
|
+
|
|
664
|
+
from ic_python_db.properties import ManyToMany, OneToMany, OneToOne
|
|
665
|
+
|
|
666
|
+
for rel_name, rel_entities in self._relations.items():
|
|
667
|
+
if rel_entities:
|
|
668
|
+
rel_prop = getattr(self.__class__, rel_name, None)
|
|
669
|
+
|
|
670
|
+
# Skip OneToMany — always reconstructed from reverse ManyToOne
|
|
671
|
+
if isinstance(rel_prop, OneToMany):
|
|
672
|
+
continue
|
|
673
|
+
# For OneToOne bilateral, only serialize on one deterministic side:
|
|
674
|
+
# the entity whose type name is alphabetically <= the target type.
|
|
675
|
+
if isinstance(rel_prop, OneToOne):
|
|
676
|
+
target_type = rel_entities[0]._type if rel_entities else None
|
|
677
|
+
if target_type and self._type > target_type:
|
|
678
|
+
continue
|
|
607
679
|
|
|
608
680
|
is_to_many = isinstance(rel_prop, (OneToMany, ManyToMany))
|
|
609
681
|
|
|
610
682
|
if len(rel_entities) == 1 and not is_to_many:
|
|
611
|
-
|
|
612
|
-
data[rel_name] = get_entity_reference(rel_entities[0])
|
|
683
|
+
data[rel_name] = self._get_entity_reference(rel_entities[0])
|
|
613
684
|
else:
|
|
614
|
-
|
|
615
|
-
|
|
685
|
+
data[rel_name] = [
|
|
686
|
+
self._get_entity_reference(e) for e in rel_entities
|
|
687
|
+
]
|
|
616
688
|
|
|
617
689
|
return data
|
|
618
690
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ic_python_db
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.7
|
|
4
4
|
Summary: A lightweight key-value database written in Python, intended for use on the Internet Computer (IC)
|
|
5
5
|
Home-page: https://github.com/smart-social-contracts/ic-python-db
|
|
6
6
|
Author: Smart Social Contracts
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ic_python_db"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.7"
|
|
8
8
|
description = "A lightweight key-value database written in Python, intended for use on the Internet Computer (IC)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name="ic_python_db",
|
|
8
|
-
version="0.7.
|
|
8
|
+
version="0.7.7",
|
|
9
9
|
author="Smart Social Contracts",
|
|
10
10
|
author_email="smartsocialcontracts@gmail.com",
|
|
11
11
|
description="A lightweight key-value database with entity relationships and audit logging",
|
|
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
|