ic-python-db 0.7.5__tar.gz → 0.7.6__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 (24) hide show
  1. {ic_python_db-0.7.5/ic_python_db.egg-info → ic_python_db-0.7.6}/PKG-INFO +1 -1
  2. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/__init__.py +1 -1
  3. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/entity.py +72 -27
  4. {ic_python_db-0.7.5 → ic_python_db-0.7.6/ic_python_db.egg-info}/PKG-INFO +1 -1
  5. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/pyproject.toml +1 -1
  6. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/setup.py +1 -1
  7. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/LICENSE +0 -0
  8. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/MANIFEST.in +0 -0
  9. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/README.md +0 -0
  10. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/_cdk.py +0 -0
  11. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/constants.py +0 -0
  12. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/context.py +0 -0
  13. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/db_engine.py +0 -0
  14. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/hooks.py +0 -0
  15. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/mixins.py +0 -0
  16. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/properties.py +0 -0
  17. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/py.typed +0 -0
  18. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/storage.py +0 -0
  19. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db/system_time.py +0 -0
  20. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db.egg-info/SOURCES.txt +0 -0
  21. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db.egg-info/dependency_links.txt +0 -0
  22. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/ic_python_db.egg-info/top_level.txt +0 -0
  23. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/requirements-dev.txt +0 -0
  24. {ic_python_db-0.7.5 → ic_python_db-0.7.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ic_python_db
3
- Version: 0.7.5
3
+ Version: 0.7.6
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
@@ -19,7 +19,7 @@ from .properties import (
19
19
  from .storage import MemoryStorage, Storage
20
20
  from .system_time import SystemTime
21
21
 
22
- __version__ = "0.7.5"
22
+ __version__ = "0.7.6"
23
23
  __all__ = [
24
24
  "Database",
25
25
  "Entity",
@@ -264,8 +264,8 @@ class Entity:
264
264
  )
265
265
  self._update_timestamps(caller_id)
266
266
 
267
- # Save to database
268
- data = self.serialize()
267
+ # Save to database (full serialization preserves all relations)
268
+ data = self._serialize_full()
269
269
 
270
270
  if not self._do_not_save:
271
271
  logger.debug(f"Saving entity {self._type}@{self._id} to database")
@@ -511,9 +511,14 @@ class Entity:
511
511
 
512
512
  while len(ret) < count and from_id <= cls.max_id():
513
513
  logger.info(f"Loading entity {from_id}")
514
- entity = cls.load(str(from_id))
515
- if entity:
516
- ret.append(entity)
514
+ try:
515
+ entity = cls.load(str(from_id))
516
+ if entity:
517
+ ret.append(entity)
518
+ except (ValueError, AttributeError) as e:
519
+ # Skip entities with broken/dangling relation references
520
+ # (full fix: issue #4 — lazy relation resolution)
521
+ logger.warning(f"Skipping {cls.__name__}@{from_id}: {e}")
517
522
  from_id += 1
518
523
 
519
524
  return ret
@@ -559,19 +564,15 @@ class Entity:
559
564
  # Remove from context
560
565
  self.__class__._context.discard(self)
561
566
 
562
- def serialize(self) -> Dict[str, Any]:
563
- """Convert the entity to a serializable dictionary.
564
-
565
- Returns:
566
- Dict containing the entity's serializable data
567
- """
567
+ def _serialize_base(self) -> Dict[str, Any]:
568
+ """Shared serialization logic: core data, properties, and instance attributes."""
568
569
  # Get mixin data first if available
569
570
  data = super().serialize() if hasattr(super(), "serialize") else {}
570
571
 
571
572
  # Add core entity data
572
573
  data.update(
573
574
  {
574
- "_type": self._type, # Use the entity type
575
+ "_type": self._type,
575
576
  "_id": self._id,
576
577
  }
577
578
  )
@@ -589,30 +590,74 @@ class Entity:
589
590
  if not k.startswith("_"):
590
591
  data[k] = v
591
592
 
592
- # Add relations as references (prefer alias over _id if available)
593
- def get_entity_reference(entity):
594
- """Get the best reference for an entity: alias value if available, otherwise _id."""
595
- if hasattr(entity.__class__, "__alias__") and entity.__class__.__alias__:
596
- alias_field = entity.__class__.__alias__
597
- alias_value = getattr(entity, alias_field, None)
598
- if alias_value is not None:
599
- return alias_value
600
- return entity._id
593
+ return data
594
+
595
+ @staticmethod
596
+ def _get_entity_reference(entity):
597
+ """Get the best reference for an entity: alias value if available, otherwise _id."""
598
+ if hasattr(entity.__class__, "__alias__") and entity.__class__.__alias__:
599
+ alias_field = entity.__class__.__alias__
600
+ alias_value = getattr(entity, alias_field, None)
601
+ if alias_value is not None:
602
+ return alias_value
603
+ return entity._id
604
+
605
+ def _serialize_full(self) -> Dict[str, Any]:
606
+ """Full serialization including all relations. Used by _save() for persistence."""
607
+ data = self._serialize_base()
608
+
609
+ from ic_python_db.properties import ManyToMany, OneToMany
601
610
 
602
611
  for rel_name, rel_entities in self._relations.items():
603
612
  if rel_entities:
604
- # Check if this is a *ToMany relation that should always be a list
605
613
  rel_prop = getattr(self.__class__, rel_name, None)
606
- from ic_python_db.properties import ManyToMany, OneToMany
614
+ is_to_many = isinstance(rel_prop, (OneToMany, ManyToMany))
615
+
616
+ if len(rel_entities) == 1 and not is_to_many:
617
+ data[rel_name] = self._get_entity_reference(rel_entities[0])
618
+ else:
619
+ data[rel_name] = [
620
+ self._get_entity_reference(e) for e in rel_entities
621
+ ]
622
+
623
+ return data
624
+
625
+ def serialize(self) -> Dict[str, Any]:
626
+ """Convert the entity to a portable serializable dictionary.
627
+
628
+ OneToMany relations are skipped (reconstructed from reverse ManyToOne).
629
+ For bilateral OneToOne relations, only the alphabetically-earlier entity
630
+ type serializes the reference, avoiding circular dependencies.
631
+
632
+ Returns:
633
+ Dict containing the entity's serializable data
634
+ """
635
+ data = self._serialize_base()
636
+
637
+ from ic_python_db.properties import ManyToMany, OneToMany, OneToOne
638
+
639
+ for rel_name, rel_entities in self._relations.items():
640
+ if rel_entities:
641
+ rel_prop = getattr(self.__class__, rel_name, None)
642
+
643
+ # Skip OneToMany — always reconstructed from reverse ManyToOne
644
+ if isinstance(rel_prop, OneToMany):
645
+ continue
646
+ # For OneToOne bilateral, only serialize on one deterministic side:
647
+ # the entity whose type name is alphabetically <= the target type.
648
+ if isinstance(rel_prop, OneToOne):
649
+ target_type = rel_entities[0]._type if rel_entities else None
650
+ if target_type and self._type > target_type:
651
+ continue
607
652
 
608
653
  is_to_many = isinstance(rel_prop, (OneToMany, ManyToMany))
609
654
 
610
655
  if len(rel_entities) == 1 and not is_to_many:
611
- # Single relation for OneToOne/ManyToOne - store as single reference
612
- data[rel_name] = get_entity_reference(rel_entities[0])
656
+ data[rel_name] = self._get_entity_reference(rel_entities[0])
613
657
  else:
614
- # Multiple relations or *ToMany relations - store as list of references
615
- data[rel_name] = [get_entity_reference(e) for e in rel_entities]
658
+ data[rel_name] = [
659
+ self._get_entity_reference(e) for e in rel_entities
660
+ ]
616
661
 
617
662
  return data
618
663
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ic_python_db
3
- Version: 0.7.5
3
+ Version: 0.7.6
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.5"
7
+ version = "0.7.6"
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.5",
8
+ version="0.7.6",
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