diff --git a/pipeline/src/additional_methods/by_name.py.txt b/pipeline/src/additional_methods/by_name.py.txt index fb96249a..13491111 100644 --- a/pipeline/src/additional_methods/by_name.py.txt +++ b/pipeline/src/additional_methods/by_name.py.txt @@ -3,12 +3,52 @@ return [value for value in cls.__dict__.values() if isinstance(value, cls)] @classmethod - def by_name(cls, name): + def by_name( + cls, + name: str, + match: str = "equals", + all: bool = False, + ): + """ + Search for instances in the openMINDS instance library based on their name. + + This includes properties "name", "lookup_label", "family_name", "full_name", "short_name", "abbreviation", and "synonyms". + + Note that not all metadata classes have a name. + + Args: + name (str): a string to search for. + match (str, optional): either "equals" (exact match - default) or "contains". + all (bool, optional): Whether to return all objects that match the name, or only the first. Defaults to False. + """ + namelike_properties = ("name", "lookup_label", "family_name", "full_name", "short_name", "abbreviation") if cls._instance_lookup is None: cls._instance_lookup = {} for instance in cls.instances(): - cls._instance_lookup[instance.name] = instance - if instance.synonyms: - for synonym in instance.synonyms: - cls._instance_lookup[synonym] = instance - return cls._instance_lookup[name] + keys = [] + for prop_name in namelike_properties: + if hasattr(instance, prop_name): + keys.append(getattr(instance, prop_name)) + if hasattr(instance, "synonyms"): + for synonym in instance.synonyms or []: + keys.append(synonym) + for key in keys: + if key in cls._instance_lookup: + cls._instance_lookup[key].append(instance) + else: + cls._instance_lookup[key] = [instance] + if match == "equals": + matches = cls._instance_lookup.get(name, None) + elif match == "contains": + matches = [] + for key, instances in cls._instance_lookup.items(): + if name in key: + matches.extend(instances) + else: + raise ValueError("'match' must be either 'equals' or 'contains'") + if all: + return matches + elif len(matches) > 0: + return matches[0] + else: + return None diff --git a/pipeline/tests/test_regressions.py b/pipeline/tests/test_regressions.py index aa65d254..359df163 100644 --- a/pipeline/tests/test_regressions.py +++ b/pipeline/tests/test_regressions.py @@ -300,3 +300,34 @@ def test_issue0073b(om): ds1.is_variant_of = ds2 failures = ds1.validate() + + +@pytest.mark.parametrize("om", [openminds.latest]) +def test_issue0069(om): + # https://github.com/openMetadataInitiative/openMINDS_Python/issues/69 + # The License class has a classmethod "by_name()" which assumes License is a controlled term + # (i.e., it has properties "name" and "synonyms"). + # However License does not have these properties, it has "short_name" and "full_name". + + # Test with default arguments (single result, exact match) + result = om.core.License.by_name("CC-BY-4.0") + assert result.short_name == "CC-BY-4.0" + + result = om.sands.ParcellationEntity.by_name("NODa,b") + assert result.abbreviation == "NODa,b" + + result = om.sands.CommonCoordinateSpace.by_name("MEBRAINS population-based monkey brain template") + assert result.full_name == "MEBRAINS population-based monkey brain template" + + assert om.controlled_terms.BiologicalOrder.by_name("rodents") == om.controlled_terms.BiologicalOrder.by_name("Rodentia") != None + + # Test with "all=True" + results = om.sands.BrainAtlasVersion.by_name("Julich-Brain Atlas", all=True) + assert len(results) == 30 + assert all(r.short_name == "Julich-Brain Atlas" for r in results) + assert len(set(r.id for r in results)) == len(results) + + # Test with "match='contains'" + results = om.core.License.by_name("Creative Commons", all=True, match="contains") + assert len(results) == 7 + assert all("CC" in r.short_name for r in results)