diff --git a/src/doc/tutorials/complex_data_models.rst b/src/doc/tutorials/complex_data_models.rst index 569acdae174a9df9d0d2b5eae9a0084d793cc90c..e2043ef1f4402d3f467fdeff2785defd9785ea26 100644 --- a/src/doc/tutorials/complex_data_models.rst +++ b/src/doc/tutorials/complex_data_models.rst @@ -75,3 +75,55 @@ Examples b = input("Press any key to cleanup.") # cleanup everything after the user presses any button. c.delete() + + +Finding parents and properties +-------- +To find a specific parent or property of an Entity, its +ParentList or PropertyList can be filtered using names, ids, or +entities. A short example: + +.. code-block:: python3 + + import linkahead as db + + # Setup a record with four properties + r = db.Record() + p1 = db.Property(id=101, name="Property 1") + p2 = db.Property(id=102, name="Property 2") + p3 = db.Property(name="Property") + p4 = db.Property(name="Property") + r.add_property(p1).add_property(p2).add_property(p3).add_property(p4) + properties = r.properties + + # As r only has one property with id 101, this returns a list containing only p1 + properties.filter(pid=101) + # Result: [p1] + + # Filtering with name="Property" returns both p3 and p4, as they share their name + properties.filter(name="Property") + # Result: [p3, p4] + + # Filtering with name="Property 1" and id=102 returns both p1 and p2, because + # any property matching either criterion is returned: + properties.filter(name="Property 1", pid="102") + # Result: [p1, p2] + + # If we want to find properties matching one specific property, we can also filter using + # the entity itself. In this case, only properties matching both name and id are returned: + p5 = db.Property(name="Property 2") + r.add_property(p5) + properties.filter(p5) + # Result: [p5] + properties.filter(name=p5.name, pid=p5.id) + # Result: [p2, p5], because p2 and p5 share a name + properties.filter(p3) + # Result: [p3, p4], because p3 and p4 share both their name and id + + # However, if you want to retrieve only instances of the property itself, this can be + # done using the parameter check_equality, which enforces that all entities in the returned + # list are equal to the parameter. + properties.filter(p3, check_equality=True) + # Result: [p3] + +The filter function of ParentList works analogously. diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index fe15b1a7f843af110471a624802b3a93efe8a035..44734252795f5c6a46bce5f472b1cd7314378775 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -2524,7 +2524,7 @@ class PropertyList(list): def filter(self, prop:Property = None, pid:Union[str, int] = None, name:str = None, check_equality: bool = False, - check_wrap: bool = True) -> list: + check_wrapped: bool = True) -> list: """ Return all Properties from the given PropertyList that match the selection criteria. @@ -2555,7 +2555,7 @@ class PropertyList(list): 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 + check_wrapped : 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. @@ -2565,7 +2565,7 @@ class PropertyList(list): list with all matching Properties """ return _filter_entity_list(self, pid=pid, name=name, entity=prop, - check_equality=check_equality, check_wrap=check_wrap) + check_equality=check_equality, check_wrapped=check_wrapped) def _get_entity_by_cuid(self, cuid: str): ''' @@ -2699,7 +2699,7 @@ class ParentList(list): def filter(self, parent:Parent = None, pid:Union[str, int] = None, name:str = None, check_equality: bool = False, - check_wrap: bool = True) -> list: + check_wrapped: bool = True) -> list: """ Return all Parents from the given ParentList that match the selection criteria. @@ -2730,7 +2730,7 @@ class ParentList(list): 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 + check_wrapped : 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. @@ -2740,7 +2740,7 @@ class ParentList(list): list with all matching Parents """ return _filter_entity_list(self, pid=pid, name=name, entity=parent, - check_equality=check_equality, check_wrap=check_wrap) + check_equality=check_equality, check_wrapped=check_wrapped) def remove(self, parent: Union[Entity, int, str]): """ @@ -2799,13 +2799,13 @@ class ParentList(list): class _Properties(PropertyList): def __init__(self, *args, **kwargs): - warnings.warn(DeprecationWarning("This class is depricated. Please use PropertyList.")) + warnings.warn(DeprecationWarning("This class is deprecated. Please use PropertyList.")) super().__init__(*args, **kwargs) class _ParentList(ParentList): def __init__(self, *args, **kwargs): - warnings.warn(DeprecationWarning("This class is depricated. Please use ParentList " + warnings.warn(DeprecationWarning("This class is deprecated. Please use ParentList " "(without underscore).")) super().__init__(*args, **kwargs) @@ -5508,7 +5508,7 @@ def delete(ids: Union[list[int], range], raise_exception_on_error: bool = True): 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: + check_wrapped:bool = True) -> list: """ Return all elements from the given list that match the selection criteria. @@ -5540,10 +5540,10 @@ def _filter_entity_list(listobject, entity:Entity = None, pid:Union[str, int] = 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 + check_wrapped : 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. + contained in the given list will be checked and + returned, not the original Entities they wrap. Returns ------- @@ -5570,7 +5570,7 @@ def _filter_entity_list(listobject, entity:Entity = None, pid:Union[str, int] = # 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: + if check_wrapped and not wrapped_is_checked: try: if candidate._wrapped_entity is not None: original_candidate = candidate @@ -5586,9 +5586,10 @@ def _filter_entity_list(listobject, entity:Entity = None, pid:Union[str, int] = potentials.append((original_candidate, True)) continue # Otherwise, check whether name/pid match - if pid is not None and candidate.id == pid: + # pid and candidate.id might be int and str, so cast to str (None safe) + if pid is not None and str(candidate.id) == str(pid): pid_match = True - elif match_entity and candidate.id == pid: + elif match_entity and str(candidate.id) == str(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 diff --git a/unittests/test_entity.py b/unittests/test_entity.py index cff6d8dc01a04688232f92ba5e9064755b5bff1b..ff2c79ead1e32e394e3b003ff239ccc3cbf57c1e 100644 --- a/unittests/test_entity.py +++ b/unittests/test_entity.py @@ -193,13 +193,14 @@ def test_filter(): 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), + 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(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]: @@ -236,5 +237,11 @@ def test_filter(): for ent in test_ents: assert ent in t_pars.filter(ent) assert ent in t_pars.filter(ent, check_equality=True) + for ent in [rt1, p1, p2, r1, r2]: + filtered = t_pars.filter(ent) + for ent2 in [rt1, p1, p2, r1, r2]: + assert ent2 in filtered + assert ent in t_pars.filter(pid=100) + assert ent in t_pars.filter(pid="100") # ToDo: Check whether duplicates are wanted and write tests for the # desirable outcome