Source code for save_to_db.core.utils.mapper

from collections import defaultdict
from weakref import WeakSet


[docs]class Mapper(object): """ This class automatically sets up reverse relations for all items. """ def __init__(self): # [collection_id][item_cls] = list_of_items self._map = defaultdict(lambda: defaultdict(WeakSet)) def __contains__(self, item): return item in self.__get_item_set(item) def __get_item_set(self, item): collection_id = item.get_collection_id() item_cls = item.get_item_cls() return self._map[collection_id][item_cls] def __get_item_collection_set(self, item): collection_id = item.get_collection_id() return self._map[collection_id]
[docs] def item_created(self, item): """Called when an instance of :py:class:`~save_to_db.core.item_base.ItemBase` is created. :param item: An instance of created item. """ self.__get_item_set(item).add(item)
[docs] def item_value_set(self, item, key, old_value, new_value): """Called when an item attribute is set, for example: .. code-block:: Python item[key] = new_value :param item: An item whose attribute is set. :param key: Attribute name of the `item`. :param old_value: Old attribute value. :param new_value: New attribute value. """ if item.is_bulk_item(): return # default value was set if key not in item.relations: return # --- forward relation --- relation = item.relations[key] if new_value is not None and relation["relation_type"].is_one_to_x(): for that_key, that_item in self.iter_pointing_items( new_value, type(item), key ): if that_item is item: continue del that_item.data[that_key] # --- reverse relation --- reverse_key = relation["reverse_key"] this_item, that_item_new = item, new_value that_item_old = old_value if not reverse_key: return if relation["relation_type"].is_one_to_one(): # reverse for old if that_item_old and reverse_key in that_item_old.data: del that_item_old.data[reverse_key] # reverse for new if that_item_new is None: return that_item_data = that_item_new.data if reverse_key in that_item_data and that_item_data is this_item.data: return that_item_new.data[reverse_key] = this_item elif relation["relation_type"].is_one_to_many(): # reverse for old if that_item_old: for item_in_bulk in that_item_old.bulk: del item_in_bulk.data[reverse_key] # reverse for new if that_item_new: for item_in_bulk in that_item_new.bulk: item_in_bulk.data[reverse_key] = this_item elif relation["relation_type"].is_many_to_one(): # reverse for old item if that_item_old: if item in that_item_old[reverse_key].bulk: that_item_old[reverse_key].bulk.remove(item) # reverse for new item if that_item_new: if item not in that_item_new[reverse_key].bulk: that_item_new[reverse_key].bulk.append(item) elif relation["relation_type"].is_many_to_many(): # reverse for old item if that_item_old: for item_in_bulk in that_item_old.bulk: if item in item_in_bulk[reverse_key].bulk: item_in_bulk[reverse_key].bulk.remove(item) # reverse for new item if that_item_new: for item_in_bulk in that_item_new.bulk: if item not in item_in_bulk[reverse_key].bulk: item_in_bulk[reverse_key].bulk.append(item)
[docs] def item_value_deleted(self, item, key, old_value): """Called when an item attribute is deleted, for example: .. code-block:: Python del item[key] :param item: An item whose attribute is deleted. :param key: Deleted attribute name. :param old_value: Deleted attribute value. """ if item.is_bulk_item(): return # default value deleted if key not in item.relations or old_value is None: return relation = item.relations[key] if not relation["reverse_key"]: return if relation["relation_type"].is_one_to_one(): if relation["reverse_key"] in old_value: del old_value.data[relation["reverse_key"]] elif relation["relation_type"].is_one_to_many(): for item_in_bulk in old_value.bulk: del item_in_bulk.data[relation["reverse_key"]] elif relation["relation_type"].is_many_to_one(): old_value[relation["reverse_key"]].bulk.remove(item) elif relation["relation_type"].is_many_to_many(): for item_in_bulk in old_value: item_in_bulk[relation["reverse_key"]].bulk.remove(item)
[docs] def iter_pointing_items(self, item, pointing_cls=None, pointing_key=None): """A generator that yields items that point to a curtain item. to automatically adjust relationships. :param item: an instance of :py:class:`~save_to_db.core.item.Item` class to which other items must point. :param pointing_cls: Subclass of :py:class:`~save_to_db.core.item.Item`. If it is not `None` then only instances of this class are yielded. :param pointing_key: If it is not `None` then only items that point using the same key are yielded. :returns: A generator of tupltes with two items, pointing key and pointing item. """ collection_set = self.__get_item_collection_set(item) if pointing_cls: pointing_cls_list = [pointing_cls] else: pointing_cls_list = set(collection_set) # pointing single items for that_item_cls in pointing_cls_list: for that_item in collection_set[that_item_cls]: if pointing_key: if pointing_key not in that_item.data: continue pointing_key_list = [pointing_key] else: pointing_key_list = set(that_item.data) for key in pointing_key_list: if that_item.data[key] is item: yield key, that_item
[docs] def item_deleted(self, item): """Called when an item is deleted, for example: .. code-block:: Python item.delete() :param item: Deleted item. """ # clearing the item # (defaults in bulk items and directs in single items) item.data.clear() for key, that_item in self.iter_pointing_items(item): relation_type = that_item.relations[key]["relation_type"] reverse_key = that_item.relations[key]["reverse_key"] if reverse_key: if item.is_bulk_item() and relation_type.is_one_to_many(): for item_in_bulk in item.bulk: del item_in_bulk.data[reverse_key] elif ( item.is_bulk_item() and item.bulk and relation_type.is_many_to_many() ): for this_item_in_bulk in item.bulk: that_bulk = this_item_in_bulk.data[reverse_key] for that_item_in_bulk in that_bulk.bulk: if that_item_in_bulk.data[key] is item: that_bulk.bulk.remove(that_item_in_bulk) # defaults in bulk items and directs in single items del that_item.data[key] # removing from any bulks if item.is_single_item(): collection_set = self.__get_item_collection_set(item) for that_item_cls in collection_set: for that_item in collection_set[that_item_cls]: if that_item.is_bulk_item(): if item in that_item.bulk: that_item.bulk.remove(item) else: item.bulk.clear()
[docs] def item_added_to_bulk(self, item, bulk_item): """Called when an item is added to a bulk item, for example: .. code-block:: Python bulk_item.add(single_item) :param item: A single item added to the `bulk_item`. :param bulk_item: Bulk item containing `item`. """ collection_set = self.__get_item_collection_set(item) for that_item_cls in set(collection_set): for that_item in set(collection_set[that_item_cls]): if not that_item.is_single_item(): continue for key in set(that_item.data): if that_item.data[key] is not bulk_item: continue relation = that_item.relations[key] reverse_key = that_item.relations[key]["reverse_key"] if not reverse_key: continue if relation["relation_type"].is_one_to_many(): item.data[reverse_key] = that_item elif relation["relation_type"].is_many_to_many(): if that_item not in item[reverse_key].bulk: item[reverse_key].bulk.append(that_item)
[docs] def item_removed_from_bulk(self, item, bulk_item): """Called when an item is removed from a bulk item, for example: .. code-block:: Python bulk_item.remove(single_item) :param item: A single item removed from the `bulk_item`. :param bulk_item: Bulk item that was containing `item`. """ collection_set = self.__get_item_collection_set(item) for that_item_cls in collection_set: for that_item in collection_set[that_item_cls]: if not that_item.is_single_item(): continue for key in set(that_item.data): # `key not in that_item.data` can happen in case of self # relationships if ( key not in that_item.data or that_item.data[key] is not bulk_item ): continue relation = that_item.relations[key] reverse_key = that_item.relations[key]["reverse_key"] if reverse_key: if relation["relation_type"].is_one_to_many(): del item.data[reverse_key] elif relation["relation_type"].is_many_to_many(): if that_item in item.data[reverse_key].bulk: item.data[reverse_key].bulk.remove(that_item)
mapper = Mapper()