diff --git a/src/knowledge_mapper/ke/models.py b/src/knowledge_mapper/ke/models.py index 95af6fb..cd42b06 100644 --- a/src/knowledge_mapper/ke/models.py +++ b/src/knowledge_mapper/ke/models.py @@ -300,11 +300,18 @@ def dump_partial_binding(self, exclude_none: bool = True) -> dict[str, Any]: return self.model_dump(by_alias=True, exclude_none=exclude_none) def matches_with(self, **kwargs) -> bool: + """Return whether every set field equals the given N3-encoded value. + + Fields are compared in their serialized (N3) form, so ``Literal[T]`` and + ``Datatype`` fields compare the same way as ``Uri`` and ``RdfLiteral``. + Unset (``None``) fields match anything. + """ + own_values = self.model_dump() for key, other_value in kwargs.items(): - own_value = getattr(self, key) + own_value = own_values[key] if own_value is None: continue - if own_value.n3() == other_value: + if own_value == other_value: continue return False return True diff --git a/tests/test_bindings.py b/tests/test_bindings.py index e53afbd..b3ccf1e 100644 --- a/tests/test_bindings.py +++ b/tests/test_bindings.py @@ -306,6 +306,74 @@ class Binding(BindingModel): binding.temperature = '"12.5"^^' +# ---------- matches_with + + +class MatchBinding(BindingModel): + person: Uri + age: Literal[int] + name: Literal[str] + born: Literal[datetime] + remark: RdfLiteral + temperature: Annotated[Literal[float], Datatype(CELSIUS)] + + +FULL_MATCH_BINDING = MatchBinding( + person=URIRef("http://example.org/a"), + age=3, + name="Alice", + born=datetime(2020, 1, 2, 3, 4, 5), + remark=RDFLiteral("foo", lang="de"), + temperature=12.5, +) + + +@pytest.mark.parametrize( + ("field", "matching", "different"), + [ + ("person", "", ""), + ( + "age", + '"3"^^', + '"4"^^', + ), + ("name", '"Alice"', '"Bob"'), + ( + "born", + '"2020-01-02T03:04:05"^^', + '"2021-01-02T03:04:05"^^', + ), + ("remark", '"foo"@de', '"foo"@en'), + ( + "temperature", + '"12.5"^^', + '"12.5"^^', + ), + ], +) +def test_matches_with_per_field_type(field, matching, different): + assert FULL_MATCH_BINDING.matches_with(**{field: matching}) + assert not FULL_MATCH_BINDING.matches_with(**{field: different}) + + +def test_matches_with_ignores_unset_fields(): + binding = MatchBinding(person=URIRef("http://example.org/a")) + + assert binding.matches_with( + person="", + age='"4"^^', + temperature='"1.0"^^', + ) + + +def test_matches_with_binding_for_all_field_types(): + partial = MatchBinding(age=3, name="Alice", temperature=12.5) + other = MatchBinding(age=4, name="Alice", temperature=12.5) + + assert FULL_MATCH_BINDING.matches_with_binding(partial) + assert not FULL_MATCH_BINDING.matches_with_binding(other) + + # ---------- UNDEFINED sentinels UNDEFINED_N3 = ""