Skip to content

Commit

Permalink
refactor: move MemoryBackend indexation logic into MemoryBackend
Browse files Browse the repository at this point in the history
  • Loading branch information
azmeuk committed May 3, 2024
1 parent 16c3021 commit cc9ed33
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 92 deletions.
115 changes: 109 additions & 6 deletions canaille/backends/memory/backend.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,35 @@
import copy
import datetime
import uuid
from typing import Any
from typing import Dict

from canaille.backends import Backend


class MemoryBackend(Backend):
indexes: Dict[str, Dict[str, Any]] = None
"""Associates ids and states."""

attribute_indexes = None
"""Associates attribute values and ids."""

def index(self, model):
if not self.indexes:
self.indexes = {}

model_name = model if isinstance(model, str) else model.__name__
return self.indexes.setdefault(model_name, {})

def attribute_index(self, model, attribute="id"):
if not self.attribute_indexes:
self.attribute_indexes = {}

model_name = model if isinstance(model, str) else model.__name__
return self.attribute_indexes.setdefault(model_name, {}).setdefault(
attribute, {}
)

@classmethod
def install(cls, config):
pass
Expand Down Expand Up @@ -47,19 +72,19 @@ def set_user_password(self, user, password):
def query(self, model, **kwargs):
# if there is no filter, return all models
if not kwargs:
states = model.index().values()
states = self.index(model).values()
return [model(**state) for state in states]

# get the ids from the attribute indexes
ids = {
id
for attribute, values in kwargs.items()
for value in model.serialize(model.listify(values))
for id in model.attribute_index(attribute).get(value, [])
for id in self.attribute_index(model, attribute).get(value, [])
}

# get the states from the ids
states = [model.index()[id] for id in ids]
states = [self.index(model)[id] for id in ids]

# initialize instances from the states
instances = [model(**state) for state in states]
Expand Down Expand Up @@ -105,16 +130,16 @@ def save(self, instance):
if not instance.created:
instance.created = instance.last_modified

instance.index_delete()
instance.index_save()
self.index_delete(instance)
self.index_save(instance)
instance._cache = {}

def delete(self, instance):
# run the instance delete callback if existing
delete_callback = instance.delete() if hasattr(instance, "delete") else iter([])
next(delete_callback, None)

instance.index_delete()
self.index_delete(instance)

# run the instance delete callback again if existing
next(delete_callback, None)
Expand All @@ -131,3 +156,81 @@ def reload(self, instance):

# run the instance reload callback again if existing
next(reload_callback, None)

def index_save(self, instance):
# update the id index
self.index(instance.__class__)[instance.id] = copy.deepcopy(instance._state)

# update the index for each attribute
for attribute in instance.attributes:
attribute_values = instance.listify(instance._state.get(attribute, []))
for value in attribute_values:
self.attribute_index(instance.__class__, attribute).setdefault(
value, set()
).add(instance.id)

# update the mirror attributes of the submodel instances
for attribute in instance.attributes:
model, mirror_attribute = instance.get_model_annotations(attribute)
if not model or not self.index(model) or not mirror_attribute:
continue

mirror_attribute_index = self.attribute_index(
model, mirror_attribute
).setdefault(instance.id, set())
for subinstance_id in instance.listify(instance._state.get(attribute, [])):
# add the current objet in the subinstance state
subinstance_state = self.index(model)[subinstance_id]
subinstance_state.setdefault(mirror_attribute, [])
subinstance_state[mirror_attribute].append(instance.id)

# add the current objet in the subinstance index
mirror_attribute_index.add(subinstance_id)

def index_delete(self, instance):
if instance.id not in self.index(instance.__class__):
return

old_state = self.index(instance.__class__)[instance.id]

# update the mirror attributes of the submodel instances
for attribute in instance.attributes:
attribute_values = instance.listify(old_state.get(attribute, []))
for value in attribute_values:
self.attribute_index(instance.__class__, attribute)[value].remove(
instance.id
)

# update the mirror attributes of the submodel instances
model, mirror_attribute = instance.get_model_annotations(attribute)
if not model or not self.index(model) or not mirror_attribute:
continue

mirror_attribute_index = self.attribute_index(
model, mirror_attribute
).setdefault(instance.id, set())
for subinstance_id in self.index(instance.__class__)[instance.id].get(
attribute, []
):
# remove the current objet from the subinstance state
subinstance_state = self.index(model)[subinstance_id]
subinstance_state[mirror_attribute].remove(instance.id)

# remove the current objet from the subinstance index
mirror_attribute_index.remove(subinstance_id)

# update the index for each attribute
for attribute in instance.attributes:
attribute_values = instance.listify(old_state.get(attribute, []))
for value in attribute_values:
if (
value in self.attribute_index(instance.__class__, attribute)
and instance.id
in self.attribute_index(instance.__class__, attribute)[value]
):
self.attribute_index(instance.__class__, attribute)[value].remove(
instance.id
)

# update the id index
del self.index(instance.__class__)[instance.id]
86 changes: 0 additions & 86 deletions canaille/backends/memory/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import copy
import typing

import canaille.core.models
Expand All @@ -9,12 +8,6 @@


class MemoryModel(BackendModel):
indexes = {}
"""Associates ids and states."""

attribute_indexes = {}
"""Associates attribute values and ids."""

def __init__(self, *args, **kwargs):
self._state = {}
self._cache = {}
Expand All @@ -24,16 +17,6 @@ def __init__(self, *args, **kwargs):
def __repr__(self):
return f"<{self.__class__.__name__} id={self.id}>"

@classmethod
def index(cls, class_name=None):
return MemoryModel.indexes.setdefault(class_name or cls.__name__, {})

@classmethod
def attribute_index(cls, attribute="id", class_name=None):
return MemoryModel.attribute_indexes.setdefault(
class_name or cls.__name__, {}
).setdefault(attribute, {})

@classmethod
def listify(cls, value):
return value if isinstance(value, list) else [value]
Expand Down Expand Up @@ -65,75 +48,6 @@ def deserialize(cls, attribute_name, value):

return value

def index_save(self):
# update the id index
self.index()[self.id] = copy.deepcopy(self._state)

# update the index for each attribute
for attribute in self.attributes:
attribute_values = self.listify(self._state.get(attribute, []))
for value in attribute_values:
self.attribute_index(attribute).setdefault(value, set()).add(self.id)

# update the mirror attributes of the submodel instances
for attribute in self.attributes:
model, mirror_attribute = self.get_model_annotations(attribute)
if not model or not self.index(model.__name__) or not mirror_attribute:
continue

mirror_attribute_index = self.attribute_index(
mirror_attribute, model.__name__
).setdefault(self.id, set())
for subinstance_id in self.listify(self._state.get(attribute, [])):
# add the current objet in the subinstance state
subinstance_state = self.index(model.__name__)[subinstance_id]
subinstance_state.setdefault(mirror_attribute, [])
subinstance_state[mirror_attribute].append(self.id)

# add the current objet in the subinstance index
mirror_attribute_index.add(subinstance_id)

def index_delete(self):
if self.id not in self.index():
return

old_state = self.index()[self.id]

# update the mirror attributes of the submodel instances
for attribute in self.attributes:
attribute_values = self.listify(old_state.get(attribute, []))
for value in attribute_values:
self.attribute_index(attribute)[value].remove(self.id)

# update the mirror attributes of the submodel instances
model, mirror_attribute = self.get_model_annotations(attribute)
if not model or not self.index(model.__name__) or not mirror_attribute:
continue

mirror_attribute_index = self.attribute_index(
mirror_attribute, model.__name__
).setdefault(self.id, set())
for subinstance_id in self.index()[self.id].get(attribute, []):
# remove the current objet from the subinstance state
subinstance_state = self.index(model.__name__)[subinstance_id]
subinstance_state[mirror_attribute].remove(self.id)

# remove the current objet from the subinstance index
mirror_attribute_index.remove(subinstance_id)

# update the index for each attribute
for attribute in self.attributes:
attribute_values = self.listify(old_state.get(attribute, []))
for value in attribute_values:
if (
value in self.attribute_index(attribute)
and self.id in self.attribute_index(attribute)[value]
):
self.attribute_index(attribute)[value].remove(self.id)

# update the id index
del self.index()[self.id]

def __eq__(self, other):
if other is None:
return False
Expand Down

0 comments on commit cc9ed33

Please sign in to comment.