diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index fe15b1a7f843af110471a624802b3a93efe8a035..0df706bd6e8bd1be7c7504d9de2590614f9fef27 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -2522,50 +2522,23 @@ class PropertyList(list): return xml2str(xml) - def filter(self, prop:Property = None, pid:Union[str, int] = None, - name:str = None, check_equality: bool = False, - check_wrap: bool = True) -> list: + def filter(self, pid:Union[str, int]=None, name:str =None, prop:Property=None): """ - Return all Properties from the given PropertyList that match the - selection criteria. + Filters all Properties from this PropertyList that match either name or ID. - You can provide name or ID and all matching elements will be returned. - If both name and ID are given, elements matching either criterion will - be returned. - - If a Property is given, neither name nor ID may be set. In this case, - only elements matching both name and ID of the Property are returned. - - Also checks the original Properties wrapped within the elements of - PropertyList and will return the original Property if both wrapper and - original match. - - Params - ------ - listobject : Iterable(Property) - List to be filtered - prop : Property - Property to match name and ID with. Cannot be - set simultaneously with ID or name. - pid : str, int - Property ID to match - name : str - Property name to match - check_equality : bool, default: False - If set to True, potential matches will be checked - using the equality operator instead of ID and name. - Will be ignored if the prop parameter is not set. - check_wrap : bool, default: True - If set to False, only the wrapper elements - contained in the given PropertyList will be - checked, not the original Properties they wrap. + You can provide name and or ID via the corresponding arguments or you + pass a Property object to this function. + Parameters + ---------- + pid: Union[str,int], ID of the Properties to be returned + name: str, name of the Properties to be returned + prop: Property, name of the Properties to be returned Returns ------- - list with all matching Properties + list, a list with all matching Properties """ - return _filter_entity_list(self, pid=pid, name=name, entity=prop, - check_equality=check_equality, check_wrap=check_wrap) + return _filter_entity_list(self, "Property", pid=pid, name=name, element=prop) def _get_entity_by_cuid(self, cuid: str): ''' @@ -2697,50 +2670,18 @@ class ParentList(list): return xml2str(xml) - def filter(self, parent:Parent = None, pid:Union[str, int] = None, - name:str = None, check_equality: bool = False, - check_wrap: bool = True) -> list: + def filter(self, pid:Union[str, int]=None, name:str =None, parent:Parent=None): """ - Return all Parents from the given ParentList that match the selection - criteria. + Filters all Parents from this ParentList that match either name or ID. - You can provide name or ID and all matching elements will be returned. - If both name and ID are given, elements matching either criterion will - be returned. - - If a Parent is given, neither name nor ID may be set. In this case, - only elements matching both name and ID of the Parent are returned. - - Also checks the original Parents wrapped within the elements of - ParentList, will return the original Parent if both wrapper and - original match. - - Params - ------ - listobject : Iterable(Parent) - List to be filtered - parent : Parent - Parent to match name and ID with. Cannot be set - pid : str, int - Parent ID to match - name : str - Parent name to match - simultaneously with ID or name. - check_equality : bool, default: False - If set to True, potential matches will be checked - using the equality operator instead of ID and name. - Will be ignored if the entity parameter is not set. - check_wrap : bool, default: True - If set to False, only the wrapper elements - contained in the given ParentList will be - checked, not the original Parents they wrap. + You can provide name and or ID via the corresponding arguments or you + pass a Parent object to this function. Returns ------- - list with all matching Parents + a list with all matching Parents """ - return _filter_entity_list(self, pid=pid, name=name, entity=parent, - check_equality=check_equality, check_wrap=check_wrap) + return _filter_entity_list(self, "Parent", pid=pid, name=name, element=parent) def remove(self, parent: Union[Entity, int, str]): """ @@ -5505,108 +5446,29 @@ def delete(ids: Union[list[int], range], raise_exception_on_error: bool = True): return c.delete(raise_exception_on_error=raise_exception_on_error) +def _filter_entity_list(listobject, element_type, pid:Union[str, int]=None, name:str =None, element:Any=None): + """ + Filterss all elements from the given list that match either name or ID. -def _filter_entity_list(listobject, entity:Entity = None, pid:Union[str, int] = None, - name:str = None, check_equality:bool = False, - check_wrap:bool = True) -> list: - """ - Return all elements from the given list that match the selection criteria. - - You can provide name or ID and all matching elements will be returned. - If both name and ID are given, elements matching either criterion will be - returned. - - If an Entity is given, neither name nor ID may be set. In this case, only - elements matching both name and ID of the Entity are returned. - - In case the elements contained in the given list are wrapped, the function - in its default configuration checks both the wrapped and wrapper Entity - against the match criteria, and will return the wrapped Entity if both - match. Note that this is currently not iterative, meaning that only the - first layer of wrapped entity is considered. - - Params - ------ - listobject : Iterable(Entity) - List to be filtered - entity : Entity - Entity to match name and ID for. Cannot be set - simultaneously with ID or name. - pid : str, int - Entity ID to match - name : str - Entity name to match - check_equality : bool, default: False - If set to True, potential matches will be checked - using the equality operator instead of ID and name. - Will be ignored if the entity parameter is not set. - check_wrap : bool, default: True - If set to False, only the wrapper elements - contained in the given list will be checked, not - the original Entities they wrap. - - Returns - ------- - list with all matching Entities - """ - # Check correct input params and setup - match_entity = False - if entity is not None: - if pid is not None or name is not None: - raise ValueError("Please provide either Entity, pid or name.") - pid = entity.id - name = entity.name - match_entity = True - else: - check_equality = False - - # Iterate through list and match based on given criteria - matches = [] - potentials = list(zip(listobject.copy(), [False]*len(listobject))) - for candidate, wrapped_is_checked in potentials: - name_match, pid_match, original_candidate = False, False, None - - # Parents/Properties may be wrapped - if indicated, try to match original - # Note: if we want to check all wrapped Entities, this should be switched. - # First check the wrap, then append wrapped. In this case we also - # don't need wrapped_checked, but preferentially append the wrapper. - if check_wrap and not wrapped_is_checked: - try: - if candidate._wrapped_entity is not None: - original_candidate = candidate - candidate = candidate._wrapped_entity - except: - pass + You can provide name and or ID via the corresponding arguments or you + pass an object to this function that has id and/or name. - # If indicated, only consider equality - if check_equality: - if candidate == entity: - matches.append(candidate) - elif original_candidate is not None: - potentials.append((original_candidate, True)) - continue - # Otherwise, check whether name/pid match - if pid is not None and candidate.id == pid: - pid_match = True - elif match_entity and candidate.id == pid: - # If we are matching the entity, both being Null is also satisfactory - pid_match = True - if (name is not None and candidate.name is not None - and candidate.name.lower() == name.lower()): - name_match = True - elif match_entity and candidate.name == name: - # If we are matching the entity, both being Null is also satisfactory - name_match = True - - # If the criteria are satisfied, append the match. Otherwise, check - # the wrapper if applicable - # ToDo: Check whether it would make sense to also check RecordType - # for match_entity - if name_match and pid_match: - matches.append(candidate) - elif not match_entity and (name_match or pid_match): - matches.append(candidate) - else: - if original_candidate is not None: - potentials.append((original_candidate, True)) - return matches + Returns + ------- + a list with all matching elements + """ + if element is not None: + if pid is not None or name is not None: + raise ValueError(f"Please provide either a {element_type} or one of " + "pid or name") + pid = element.id + name = element.name + + candidates = [] + if name is not None: + candidates.extend( + [p for p in listobject if p.name is not None and p.name.lower() == name.lower()]) + if pid is not None: + candidates.extend( + [p for p in listobject if p.id == pid]) + return candidates diff --git a/unittests/test_entity.py b/unittests/test_entity.py index cff6d8dc01a04688232f92ba5e9064755b5bff1b..1b3e9d24e3eb8c79d05bd372e18ea27e3f55ddb6 100644 --- a/unittests/test_entity.py +++ b/unittests/test_entity.py @@ -119,9 +119,17 @@ def test_parent_list(): pl.append(p2) assert p2 in pl assert len(pl) == 2 + assert p1 in pl.filter(name="A") + assert p2 in pl.filter(pid=101) + assert p2 in pl.filter(pid=101, name="A") + assert p1 in pl.filter(pid=101, name="A") + assert p1 in pl.filter(parent=Parent(id=101, name="A")) + assert p2 in pl.filter(parent=Parent(id=101, name="A")) p3 = RecordType(id=103, name='B') pl.append(p3) assert len(pl) == 3 + assert p3 in pl.filter(name="B") + assert p3 in pl.filter(pid=103) # test removal # remove by id only, even though element in parent list has name and id @@ -145,6 +153,8 @@ def test_parent_list(): # TODO also check pl1 == pl2 + + def test_property_list(): # TODO: Resolve parent-list TODOs, then transfer to here. # TODO: What other considerations have to be done with properties? @@ -160,81 +170,11 @@ def test_property_list(): p3 = Property(id=103, name='B') pl.append(p3) - -def test_filter(): - rt1 = RecordType(id=100) - rt2 = RecordType(id=101, name="RT") - rt3 = RecordType() - p1 = Property(id=100) - p2 = Property(id=100) - p3 = Property(id=101, name="RT") - p4 = Property(id=102, name="P") - p5 = Property(id=103, name="P") - p6 = Property() - r1 = Record(id=100) - r2 = Record(id=100) - r3 = Record(id=101, name="RT") - r4 = Record(id=101, name="R") - r5 = Record(id=104, name="R") - r6 = Record(id=105, name="R") - test_ents = [rt1, rt2, rt3, p1, p2, p3, p4, p5, p6, r1, r2, r3, r4, r5, r6] - - ### Setup - t1 = Property() - t1_props, t1_pars = t1.properties, t1.parents - t2 = Record() - t2_props, t2_pars = t2.properties, t2.parents - t3 = RecordType() - t3_props, t3_pars = t3.properties, t3.parents - test_colls = [t1_props, t1_pars, t2_props, t2_pars, t3_props, t3_pars] - for coll in test_colls: - for ent in test_ents: - assert ent not in coll - assert ent not in coll.filter(ent) - - ### Checks with each type - for t ,t_props, t_pars in [(t1, t1_props, t1_pars), (t2, t2_props, t2_pars), - (t3, t3_props, t3_pars)]: - ## Properties - # Basic Checks - t.add_property(p1) - t.add_property(p3) - assert p1 in t_props.filter(pid=100) - assert p1 in t_props.filter(p1, check_equality=True) - assert p1 not in t_props.filter(pid=101, name="RT") - for entity in [rt1, p2, r1, r2]: - assert entity not in t_props.filter(pid=100) - assert p1 in t_props.filter(entity) - assert p1 not in t_props.filter(entity, check_equality=True) - # Check that direct addition (not wrapped) works - t_props.append(p2) - assert p2 in t_props.filter(pid=100) - assert p2 in t_props.filter(p2, check_equality=True) - assert p2 not in t_props.filter(pid=101, name="RT") - for entity in [rt1, r1, r2]: - assert entity not in t_props.filter(pid=100) - assert p2 in t_props.filter(entity) - assert p2 not in t_props.filter(entity, check_equality=True) - - ## Parents - # Filtering with both name and id - t.add_parent(r3) - t.add_parent(r5) - assert r3 in t_pars.filter(pid=101) - assert r5 not in t_pars.filter(pid=101) - assert r3 not in t_pars.filter(name="R") - assert r5 in t_pars.filter(name="R") - assert r3 in t_pars.filter(pid=101, name="R") - assert r5 in t_pars.filter(pid=101, name="R") - assert r3 in t_pars.filter(pid=104, name="RT") - assert r5 in t_pars.filter(pid=104, name="RT") - assert r3 not in t_pars.filter(pid=105, name="T") - assert r5 not in t_pars.filter(pid=105, name="T") - # Works also without id / name and with duplicate parents - for ent in test_ents: - t.add_parent(ent) - for ent in test_ents: - assert ent in t_pars.filter(ent) - assert ent in t_pars.filter(ent, check_equality=True) - # ToDo: Check whether duplicates are wanted and write tests for the - # desirable outcome + assert p1 in pl.filter(name="A") + assert p2 in pl.filter(pid=101) + assert p2 in pl.filter(pid=101, name="A") + assert p1 in pl.filter(pid=101, name="A") + assert p1 in pl.filter(prop=Property(id=101, name="A")) + assert p2 in pl.filter(prop=Property(id=101, name="A")) + assert p3 in pl.filter(name="B") + assert p3 in pl.filter(pid=103)