| 1 | from .persistance_backend import PersistanceBackend
|
|---|
| 2 | from .flexo_entity import FlexoEntity
|
|---|
| 3 |
|
|---|
| 4 |
|
|---|
| 5 | class CompositeBackend(PersistanceBackend):
|
|---|
| 6 | """
|
|---|
| 7 | Backend wrapper.
|
|---|
| 8 |
|
|---|
| 9 | Option A semantics:
|
|---|
| 10 | - Reads come from the primary backend only.
|
|---|
| 11 | - Writes propagate to primary and all sync backends.
|
|---|
| 12 | - All backends store/return dicts.
|
|---|
| 13 | """
|
|---|
| 14 |
|
|---|
| 15 | def __init__(self, authoritative_backend, sync_backends=None):
|
|---|
| 16 | # Validate entity_class existence and compatibility
|
|---|
| 17 | entity_class = getattr(authoritative_backend, "entity_class", None)
|
|---|
| 18 | if entity_class is None:
|
|---|
| 19 | raise TypeError("primary_backend must expose .entity_class")
|
|---|
| 20 |
|
|---|
| 21 | if not issubclass(entity_class, FlexoEntity):
|
|---|
| 22 | raise TypeError("entity_class must be a subclass of FlexoEntity")
|
|---|
| 23 |
|
|---|
| 24 | super().__init__(entity_class)
|
|---|
| 25 |
|
|---|
| 26 | self._primary = authoritative_backend
|
|---|
| 27 | self.sync_backends = list(sync_backends or [])
|
|---|
| 28 |
|
|---|
| 29 | for b in self.sync_backends:
|
|---|
| 30 | if b.entity_class != self._primary.entity_class:
|
|---|
| 31 | raise TypeError(
|
|---|
| 32 | f"Backend {b} does not match entity_class={self.entity_class.__name__}"
|
|---|
| 33 | )
|
|---|
| 34 |
|
|---|
| 35 | @property
|
|---|
| 36 | def primary(self):
|
|---|
| 37 | return self._primary
|
|---|
| 38 |
|
|---|
| 39 | def add_sync_backend(self, backend, clear=False):
|
|---|
| 40 | if backend.entity_class != self.primary.entity_class:
|
|---|
| 41 | raise TypeError("Backend entity_class mismatch")
|
|---|
| 42 |
|
|---|
| 43 | if clear:
|
|---|
| 44 | backend.clear()
|
|---|
| 45 |
|
|---|
| 46 | # Sync current data into backend
|
|---|
| 47 | for d in self.primary.load_all():
|
|---|
| 48 | backend.save(d)
|
|---|
| 49 |
|
|---|
| 50 | self.sync_backends.append(backend)
|
|---|
| 51 |
|
|---|
| 52 | def remove_backend(self, backend):
|
|---|
| 53 | if backend is self.primary:
|
|---|
| 54 | raise ValueError("Cannot remove the primary backend")
|
|---|
| 55 | self.sync_backends.remove(backend)
|
|---|
| 56 |
|
|---|
| 57 | # ---------------------------------------------------------
|
|---|
| 58 | # Write operations propagate to *all* backends (dicts)
|
|---|
| 59 | # ---------------------------------------------------------
|
|---|
| 60 |
|
|---|
| 61 | def save(self, entity_dict: dict):
|
|---|
| 62 | self.primary.save(entity_dict)
|
|---|
| 63 | for b in self.sync_backends:
|
|---|
| 64 | b.save(entity_dict)
|
|---|
| 65 |
|
|---|
| 66 | def update(self, entity_dict: dict):
|
|---|
| 67 | self.primary.update(entity_dict)
|
|---|
| 68 | for b in self.sync_backends:
|
|---|
| 69 | b.update(entity_dict)
|
|---|
| 70 |
|
|---|
| 71 | def delete(self, flexo_id: str):
|
|---|
| 72 | self.primary.delete(flexo_id)
|
|---|
| 73 | for b in self.sync_backends:
|
|---|
| 74 | b.delete(flexo_id)
|
|---|
| 75 |
|
|---|
| 76 | # ---------------------------------------------------------
|
|---|
| 77 | # Read operations from primary only
|
|---|
| 78 | # ---------------------------------------------------------
|
|---|
| 79 |
|
|---|
| 80 | def load(self, flexo_id: str):
|
|---|
| 81 | return self.primary.load(flexo_id)
|
|---|
| 82 |
|
|---|
| 83 | def load_all(self):
|
|---|
| 84 | return self.primary.load_all()
|
|---|
| 85 |
|
|---|
| 86 | # ---------------------------------------------------------
|
|---|
| 87 | # Sync helpers
|
|---|
| 88 | # ---------------------------------------------------------
|
|---|
| 89 |
|
|---|
| 90 | def sync_all(self, clear_targets=False):
|
|---|
| 91 | """
|
|---|
| 92 | Push all data from primary backend to the other backends.
|
|---|
| 93 | If clear_targets=True, wipe sync backends first.
|
|---|
| 94 | """
|
|---|
| 95 | if clear_targets:
|
|---|
| 96 | for b in self.sync_backends:
|
|---|
| 97 | b.clear()
|
|---|
| 98 |
|
|---|
| 99 | for d in self.primary.load_all():
|
|---|
| 100 | for b in self.sync_backends:
|
|---|
| 101 | b.save(d)
|
|---|
| 102 |
|
|---|
| 103 | def clear(self):
|
|---|
| 104 | self.primary.clear()
|
|---|
| 105 | for b in self.sync_backends:
|
|---|
| 106 | b.clear()
|
|---|
| 107 |
|
|---|
| 108 | def __repr__(self):
|
|---|
| 109 | names = ", ".join(b.__class__.__name__ for b in self.sync_backends)
|
|---|
| 110 | return f"<CompositeBackend primary={self.primary.__class__.__name__} sync=[{names}]>"
|
|---|