diff --git a/src/music_kraken/objects/new_collection.py b/src/music_kraken/objects/new_collection.py index c6fa1aa..58f672a 100644 --- a/src/music_kraken/objects/new_collection.py +++ b/src/music_kraken/objects/new_collection.py @@ -1,45 +1,85 @@ -from typing import List, Iterable, Iterator, Optional, TypeVar, Generic -import guppy -from guppy.heapy import Path +from typing import List, Iterable, Iterator, Optional, TypeVar, Generic, Dict, Type +from collections import defaultdict from .parents import DatabaseObject +from ..utils.functions import replace_all_refs T = TypeVar('T', bound=DatabaseObject) -hp = guppy.hpy() - -def _replace_all_refs(replace_with, replace): - """ - NO - I have a very good reason to use this here - DONT use this anywhere else... - - This replaces **ALL** references to replace with a reference to replace_with. - - https://benkurtovic.com/2015/01/28/python-object-replacement.html - """ - for path in hp.iso(replace).pathsin: - relation = path.path[1] - if isinstance(relation, Path.R_INDEXVAL): - path.src.theone[relation.r] = replace_with - - class Collection(Generic[T]): _data: List[T] + _indexed_values: Dict[str, set] + _indexed_to_objects: Dict[any, list] + shallow_list = property(fget=lambda self: self.data) def __init__(self, data: Optional[Iterable[T]]) -> None: self._data = [] self.contained_collections: List[Collection[T]] = [] + + self._indexed_values = defaultdict(set) + self._indexed_to_objects = defaultdict(list) self.extend(data) - def append(self, __object: T): + def _map_element(self, __object: T): + for name, value in __object.indexing_values: + if value is None: + continue + + self._indexed_values[name].add(value) + self._indexed_to_objects[value].append(__object) + + def _unmap_element(self, __object: T): + for name, value in __object.indexing_values: + if value is None: + continue + if value not in self._indexed_values[name]: + continue + + try: + self._indexed_to_objects[value].remove(__object) + except ValueError: + continue + + if not len(self._indexed_to_objects[value]): + self._indexed_values[name].remove(value) + + def _contained_in_self(self, __object: T) -> bool: + for name, value in __object.indexing_values: + if value is None: + continue + if value in self._indexed_values[name]: + return True + return False + + def _contained_in(self, __object: T) -> Optional["Collection"]: + if self._contained_in_self(__object): + return self + + for collection in self.contained_collections: + if collection._contained_in_self(__object): + return collection + + return None + + def contains(self, __object: T) -> bool: + return self._contained_in(__object) is not None + + + def _append(self, __object: T): + self._map_element(__object) self._data.append(__object) + def append(self, __object: Optional[T]): + if __object is None: + return + + self._append(__object) + def extend(self, __iterable: Optional[Iterable[T]]): if __iterable is None: return @@ -69,7 +109,7 @@ class Collection(Generic[T]): # now the ugly part # replace all refs of the other element with this one - _replace_all_refs(self, equal_collection) + replace_all_refs(self, equal_collection) def contain_collection_inside(self, sub_collection: "Collection"): diff --git a/src/music_kraken/objects/parents.py b/src/music_kraken/objects/parents.py index 24642bf..808cff1 100644 --- a/src/music_kraken/objects/parents.py +++ b/src/music_kraken/objects/parents.py @@ -7,6 +7,7 @@ from .metadata import Metadata from .option import Options from ..utils.shared import HIGHEST_ID from ..utils.config import main_settings, logging_settings +from ..utils.functions import replace_all_refs LOGGER = logging_settings["object_logger"] @@ -25,9 +26,6 @@ class StaticAttribute(Generic[P]): is_upwards_collection: bool = False - - - class Attribute(Generic[P]): def __init__(self, database_object: "DatabaseObject", static_attribute: StaticAttribute) -> None: self.database_object: DatabaseObject = database_object @@ -148,7 +146,7 @@ class DatabaseObject: return list() - def merge(self, other, override: bool = False): + def merge(self, other, override: bool = False, replace_all_refs: bool = False): if other is None: return @@ -171,6 +169,9 @@ class DatabaseObject: if override or getattr(self, simple_attribute) == default_value: setattr(self, simple_attribute, getattr(other, simple_attribute)) + if replace_all_refs: + replace_all_refs(self, other) + def strip_details(self): for collection in type(self).DOWNWARDS_COLLECTION_STRING_ATTRIBUTES: getattr(self, collection).clear() diff --git a/src/music_kraken/utils/functions.py b/src/music_kraken/utils/functions.py index f773213..7e1362e 100644 --- a/src/music_kraken/utils/functions.py +++ b/src/music_kraken/utils/functions.py @@ -1,5 +1,25 @@ import os from datetime import datetime +import guppy +from guppy.heapy import Path + + +hp = guppy.hpy() + +def replace_all_refs(replace_with, replace): + """ + NO + I have a very good reason to use this here + DONT use this anywhere else... + + This replaces **ALL** references to replace with a reference to replace_with. + + https://benkurtovic.com/2015/01/28/python-object-replacement.html + """ + for path in hp.iso(replace).pathsin: + relation = path.path[1] + if isinstance(relation, Path.R_INDEXVAL): + path.src.theone[relation.r] = replace_with def clear_console():