From 96ee0cee3670f6701990e34943184db030f36e96 Mon Sep 17 00:00:00 2001 From: Lukas Plank Date: Fri, 30 Aug 2024 11:47:15 +0200 Subject: [PATCH] test: adapt tests for SPARQLModelAdapter redesign Add a basic test case for array collection behavior --- tests/__init__.py | 0 tests/conftest.py | 22 ----- .../data/init_model_from_kwargs_parameters.py | 22 ----- tests/data/models.py | 30 ------- .../models/author_array_collection_model.py | 22 +++++ tests/data/models/author_work_title_model.py | 26 ++++++ tests/data/models/basic_model.py | 18 ++++ tests/data/models/grouping_model.py | 21 +++++ tests/data/models/nested_grouping_model.py | 24 +++++ ...uthor_array_collection_model_parameters.py | 87 +++++++++++++++++++ .../author_work_title_model_parameters.py | 28 ++++++ .../data/parameters/basic_model_parameters.py | 49 +++++++++++ .../parameters/grouping_model_parameters.py | 42 +++++++++ .../nested_grouping_model_parameters.py | 27 ++++++ tests/test_get_bindings_from_query_result.py | 32 ------- tests/test_instantiate_model_from_bindings.py | 32 +++++++ tests/test_instantiate_model_from_kwargs.py | 25 ------ tests/test_sparql_model_adapter.py | 87 ------------------- tests/utils/_types.py | 6 ++ 19 files changed, 382 insertions(+), 218 deletions(-) delete mode 100644 tests/__init__.py delete mode 100644 tests/conftest.py delete mode 100644 tests/data/init_model_from_kwargs_parameters.py delete mode 100644 tests/data/models.py create mode 100644 tests/data/models/author_array_collection_model.py create mode 100644 tests/data/models/author_work_title_model.py create mode 100644 tests/data/models/basic_model.py create mode 100644 tests/data/models/grouping_model.py create mode 100644 tests/data/models/nested_grouping_model.py create mode 100644 tests/data/parameters/author_array_collection_model_parameters.py create mode 100644 tests/data/parameters/author_work_title_model_parameters.py create mode 100644 tests/data/parameters/basic_model_parameters.py create mode 100644 tests/data/parameters/grouping_model_parameters.py create mode 100644 tests/data/parameters/nested_grouping_model_parameters.py delete mode 100644 tests/test_get_bindings_from_query_result.py create mode 100644 tests/test_instantiate_model_from_bindings.py delete mode 100644 tests/test_instantiate_model_from_kwargs.py delete mode 100644 tests/test_sparql_model_adapter.py create mode 100644 tests/utils/_types.py diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/conftest.py b/tests/conftest.py deleted file mode 100644 index 8e9937d..0000000 --- a/tests/conftest.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Conftest.py for RDFProxy.""" - -import pytest - -from SPARQLWrapper import JSON, SPARQLWrapper - - -def _sparql_wrapper_fixture_factory(endpoint: str, return_format=JSON): - """Return a SPARQLWrapper fixture with the endpoint and return format_set.""" - - @pytest.fixture - def sparql_wrapper(): - sparql = SPARQLWrapper(endpoint) - sparql.setReturnFormat(return_format) - return sparql - - return sparql_wrapper - - -wikidata_wrapper = _sparql_wrapper_fixture_factory( - "https://query.wikidata.org/bigdata/namespace/wdq/sparql" -) diff --git a/tests/data/init_model_from_kwargs_parameters.py b/tests/data/init_model_from_kwargs_parameters.py deleted file mode 100644 index eaa1eba..0000000 --- a/tests/data/init_model_from_kwargs_parameters.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Model and kwargs data mappings for testing.""" - -from tests.data.models import ComplexModel, NestedModel, SimpleModel - - -init_model_from_kwargs_parameters = [ - (SimpleModel, [{"x": 1}, {"x": 1, "y": 2}]), - ( - NestedModel, - [ - {"x": 1, "a": "a value"}, - {"x": 1, "y": 2, "a": "a value"}, - ], - ), - ( - ComplexModel, - [ - {"p": "p value", "a": "a value", "x": 1}, - {"p": "p value", "a": "a value", "x": 1, "y": 2}, - ], - ), -] diff --git a/tests/data/models.py b/tests/data/models.py deleted file mode 100644 index 60af7f4..0000000 --- a/tests/data/models.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Pydantic model definitions for testing.""" - -from typing import Annotated - -from pydantic import BaseModel -from rdfproxy.utils._types import SPARQLBinding - - -class SimpleModel(BaseModel): - x: int - y: int = 3 - - -class NestedModel(BaseModel): - a: str - b: SimpleModel - - -class ComplexModel(BaseModel): - p: str - q: NestedModel - - -class Work(BaseModel): - name: Annotated[str, SPARQLBinding("title")] - - -class Person(BaseModel): - name: str - work: Work diff --git a/tests/data/models/author_array_collection_model.py b/tests/data/models/author_array_collection_model.py new file mode 100644 index 0000000..691b8ae --- /dev/null +++ b/tests/data/models/author_array_collection_model.py @@ -0,0 +1,22 @@ +from typing import Annotated + +from pydantic import BaseModel +from rdfproxy.utils._types import SPARQLBinding + + +class Work(BaseModel): + class Config: + group_by = "work_name" + + name: Annotated[str, SPARQLBinding("work_name")] + viafs: Annotated[list[str], SPARQLBinding("viaf")] + + +class Author(BaseModel): + class Config: + group_by = "nameLabel" + + gnd: str + surname: Annotated[str, SPARQLBinding("nameLabel")] + works: list[Work] + education: Annotated[list[str], SPARQLBinding("educated_atLabel")] diff --git a/tests/data/models/author_work_title_model.py b/tests/data/models/author_work_title_model.py new file mode 100644 index 0000000..cddddb6 --- /dev/null +++ b/tests/data/models/author_work_title_model.py @@ -0,0 +1,26 @@ +"""Toy Author/Work model for testing.""" + +from typing import Annotated + +from pydantic import BaseModel +from rdfproxy.utils._types import SPARQLBinding + + +class Title(BaseModel): + name: Annotated[str, SPARQLBinding("work")] + + +class Work(BaseModel): + class Config: + group_by = "year" + + year: int + titles: list[Title] + + +class Author(BaseModel): + class Config: + group_by = "author" + + name: Annotated[str, SPARQLBinding("author")] + works: list[Work] diff --git a/tests/data/models/basic_model.py b/tests/data/models/basic_model.py new file mode 100644 index 0000000..2148b8e --- /dev/null +++ b/tests/data/models/basic_model.py @@ -0,0 +1,18 @@ +"""Basic model for RDFProxy testing.""" + +from pydantic import BaseModel + + +class BasicSimpleModel(BaseModel): + x: int + y: int + + +class BasicNestedModel(BaseModel): + a: str + b: BasicSimpleModel + + +class BasicComplexModel(BaseModel): + p: str + q: BasicNestedModel diff --git a/tests/data/models/grouping_model.py b/tests/data/models/grouping_model.py new file mode 100644 index 0000000..1029ba8 --- /dev/null +++ b/tests/data/models/grouping_model.py @@ -0,0 +1,21 @@ +"""Grouping model for RDFProxy testing.""" + +from pydantic import BaseModel + + +class GroupingSimpleModel(BaseModel): + x: int + y: int + + +class GroupingNestedModel(BaseModel): + a: str + b: GroupingSimpleModel + + +class GroupingComplexModel(BaseModel): + class Config: + group_by = "x" + + p: str + q: list[GroupingNestedModel] diff --git a/tests/data/models/nested_grouping_model.py b/tests/data/models/nested_grouping_model.py new file mode 100644 index 0000000..7817a40 --- /dev/null +++ b/tests/data/models/nested_grouping_model.py @@ -0,0 +1,24 @@ +"""Nested grouping model for RDFProxy testing.""" + +from pydantic import BaseModel + + +class NestedGroupingSimpleModel(BaseModel): + x: int + y: int + + +class NestedGroupingNestedModel(BaseModel): + class Config: + group_by = "y" + + a: str + b: list[NestedGroupingSimpleModel] + + +class NestedGroupingComplexModel(BaseModel): + class Config: + group_by = "a" + + p: str + q: list[NestedGroupingNestedModel] diff --git a/tests/data/parameters/author_array_collection_model_parameters.py b/tests/data/parameters/author_array_collection_model_parameters.py new file mode 100644 index 0000000..2c0f9c5 --- /dev/null +++ b/tests/data/parameters/author_array_collection_model_parameters.py @@ -0,0 +1,87 @@ +from tests.data.models.author_array_collection_model import Author +from tests.utils._types import Parameter + + +author_array_collection_parameters = [ + Parameter( + model=Author, + bindings=[ + { + "work": "http://www.wikidata.org/entity/Q1497409", + "gnd": "119359464", + "work_name": "Geb\u00fcrtig", + "nameLabel": "Schindel", + }, + { + "work": "http://www.wikidata.org/entity/Q15805238", + "gnd": "115612815", + "work_name": "Der alte K\u00f6nig in seinem Exil", + "nameLabel": "Geiger", + "viaf": "299260555", + "educated_atLabel": "University of Vienna", + }, + { + "work": "http://www.wikidata.org/entity/Q15805238", + "gnd": "115612815", + "work_name": "Der alte K\u00f6nig in seinem Exil", + "nameLabel": "Geiger", + "viaf": "6762154387354230970008", + "educated_atLabel": "University of Vienna", + }, + { + "work": "http://www.wikidata.org/entity/Q58038819", + "gnd": "115612815", + "work_name": "Unter der Drachenwand", + "nameLabel": "Geiger", + "viaf": "2277151717053313900002", + "educated_atLabel": "University of Vienna", + }, + { + "work": "http://www.wikidata.org/entity/Q100266054", + "gnd": "1136992030", + "work_name": "Das fl\u00fcssige Land", + "nameLabel": "Edelbauer", + "educated_atLabel": "University of Vienna", + }, + { + "work": "http://www.wikidata.org/entity/Q100266054", + "gnd": "1136992030", + "work_name": "Das fl\u00fcssige Land", + "nameLabel": "Edelbauer", + "educated_atLabel": "University of Applied Arts Vienna", + }, + ], + expected=[ + { + "gnd": "119359464", + "surname": "Schindel", + "works": [{"name": "Geb\u00fcrtig", "viafs": []}], + "education": [], + }, + { + "gnd": "115612815", + "surname": "Geiger", + "works": [ + { + "name": "Der alte K\u00f6nig in seinem Exil", + "viafs": ["299260555", "6762154387354230970008"], + }, + { + "name": "Unter der Drachenwand", + "viafs": ["2277151717053313900002"], + }, + ], + "education": ["University of Vienna"], + }, + { + "gnd": "1136992030", + "surname": "Edelbauer", + "works": [{"name": "Das fl\u00fcssige Land", "viafs": []}], + "education": [ + "University of Vienna", + "University of Applied Arts Vienna", + ], + }, + ], + ) +] diff --git a/tests/data/parameters/author_work_title_model_parameters.py b/tests/data/parameters/author_work_title_model_parameters.py new file mode 100644 index 0000000..f3349fc --- /dev/null +++ b/tests/data/parameters/author_work_title_model_parameters.py @@ -0,0 +1,28 @@ +from tests.data.models.author_work_title_model import Author +from tests.utils._types import Parameter + + +author_work_title_parameters = [ + Parameter( + model=Author, + bindings=[ + {"author": "Author 1", "work": "Work 1", "year": 2000}, + {"author": "Author 2", "work": "Work 4", "year": 2000}, + {"author": "Author 1", "work": "Work 2", "year": 2000}, + {"author": "Author 1", "work": "Work 3", "year": 2001}, + ], + expected=[ + { + "name": "Author 1", + "works": [ + {"year": 2000, "titles": [{"name": "Work 1"}, {"name": "Work 2"}]}, + {"year": 2001, "titles": [{"name": "Work 3"}]}, + ], + }, + { + "name": "Author 2", + "works": [{"year": 2000, "titles": [{"name": "Work 4"}]}], + }, + ], + ) +] diff --git a/tests/data/parameters/basic_model_parameters.py b/tests/data/parameters/basic_model_parameters.py new file mode 100644 index 0000000..c24d926 --- /dev/null +++ b/tests/data/parameters/basic_model_parameters.py @@ -0,0 +1,49 @@ +"""Parameters for instantiate_model_from_bindings with basic models tests.""" + +from tests.data.models.basic_model import ( + BasicComplexModel, + BasicNestedModel, + BasicSimpleModel, +) +from tests.utils._types import Parameter + + +basic_parameters = [ + Parameter( + model=BasicSimpleModel, bindings=[{"x": 1, "y": 2}], expected=[{"x": 1, "y": 2}] + ), + Parameter( + model=BasicSimpleModel, + bindings=[{"x": 3, "y": 4}, {"x": 5, "y": 6}], + expected=[{"x": 3, "y": 4}, {"x": 5, "y": 6}], + ), + Parameter( + model=BasicNestedModel, + bindings=[{"a": "a value", "x": 1, "y": 2}], + expected=[{"a": "a value", "b": {"x": 1, "y": 2}}], + ), + Parameter( + model=BasicNestedModel, + bindings=[{"a": "a value", "x": 1, "y": 2}, {"a": "a value", "x": 3, "y": 4}], + expected=[ + {"a": "a value", "b": {"x": 1, "y": 2}}, + {"a": "a value", "b": {"x": 3, "y": 4}}, + ], + ), + Parameter( + model=BasicComplexModel, + bindings=[{"a": "a value", "x": 1, "y": 2, "p": "p value"}], + expected=[{"p": "p value", "q": {"a": "a value", "b": {"x": 1, "y": 2}}}], + ), + Parameter( + model=BasicComplexModel, + bindings=[ + {"a": "a value", "x": 1, "y": 2, "p": "p value"}, + {"a": "a value", "x": 3, "y": 4, "p": "p value"}, + ], + expected=[ + {"p": "p value", "q": {"a": "a value", "b": {"x": 1, "y": 2}}}, + {"p": "p value", "q": {"a": "a value", "b": {"x": 3, "y": 4}}}, + ], + ), +] diff --git a/tests/data/parameters/grouping_model_parameters.py b/tests/data/parameters/grouping_model_parameters.py new file mode 100644 index 0000000..6a5a407 --- /dev/null +++ b/tests/data/parameters/grouping_model_parameters.py @@ -0,0 +1,42 @@ +"""Parameters for instantiate_model_from_bindings with grouping models tests.""" + +from tests.data.models.grouping_model import GroupingComplexModel +from tests.utils._types import Parameter + + +grouping_parameters = [ + Parameter( + model=GroupingComplexModel, + bindings=[{"a": "a value", "x": 1, "y": 2, "p": "p value"}], + expected=[{"p": "p value", "q": [{"a": "a value", "b": {"x": 1, "y": 2}}]}], + ), + Parameter( + model=GroupingComplexModel, + bindings=[ + {"a": "a value", "x": 1, "y": 2, "p": "p value"}, + {"a": "a value", "x": 3, "y": 4, "p": "p value"}, + ], + expected=[ + {"p": "p value", "q": [{"a": "a value", "b": {"x": 1, "y": 2}}]}, + {"p": "p value", "q": [{"a": "a value", "b": {"x": 3, "y": 4}}]}, + ], + ), + Parameter( + model=GroupingComplexModel, + bindings=[ + {"a": "a value", "x": 1, "y": 2, "p": "p value"}, + {"a": "a value", "x": 3, "y": 4, "p": "p value"}, + {"a": "a value", "x": 1, "y": 4, "p": "p value"}, + ], + expected=[ + { + "p": "p value", + "q": [ + {"a": "a value", "b": {"x": 1, "y": 2}}, + {"a": "a value", "b": {"x": 1, "y": 4}}, + ], + }, + {"p": "p value", "q": [{"a": "a value", "b": {"x": 3, "y": 4}}]}, + ], + ), +] diff --git a/tests/data/parameters/nested_grouping_model_parameters.py b/tests/data/parameters/nested_grouping_model_parameters.py new file mode 100644 index 0000000..f35545f --- /dev/null +++ b/tests/data/parameters/nested_grouping_model_parameters.py @@ -0,0 +1,27 @@ +from tests.data.models.nested_grouping_model import NestedGroupingComplexModel +from tests.utils._types import Parameter + + +nested_grouping_parameters = [ + Parameter( + model=NestedGroupingComplexModel, + bindings=[ + {"x": 1, "y": 2, "a": "a value 1", "p": "p value 1"}, + {"x": 1, "y": 2, "a": "a value 2", "p": "p value 2"}, + {"x": 1, "y": 3, "a": "a value 3", "p": "p value 3"}, + {"x": 2, "y": 2, "a": "a value 1", "p": "p value 4"}, + ], + expected=[ + { + "p": "p value 1", + "q": [{"a": "a value 1", "b": [{"x": 1, "y": 2}, {"x": 2, "y": 2}]}], + }, + {"p": "p value 2", "q": [{"a": "a value 2", "b": [{"x": 1, "y": 2}]}]}, + {"p": "p value 3", "q": [{"a": "a value 3", "b": [{"x": 1, "y": 3}]}]}, + { + "p": "p value 4", + "q": [{"a": "a value 1", "b": [{"x": 1, "y": 2}, {"x": 2, "y": 2}]}], + }, + ], + ) +] diff --git a/tests/test_get_bindings_from_query_result.py b/tests/test_get_bindings_from_query_result.py deleted file mode 100644 index 655b8f6..0000000 --- a/tests/test_get_bindings_from_query_result.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Pytest entry point for rdfproxy.get_bindings_query_result tests.""" - -from collections.abc import Iterator - -from SPARQLWrapper import QueryResult -import pytest -from rdfproxy.utils.utils import get_bindings_from_query_result - - -@pytest.mark.remote -def test_get_bindings_from_query_result_basic(wikidata_wrapper): - """Simple base test for get_bindings_from_query_result. - - Run a VALUES query and check keys and values of the bindings dict. - """ - query = """ - select ?x ?y ?a ?p - where { - values (?x ?y ?a ?p) { - (1 2 "a value" "p value") - } - } - """ - - wikidata_wrapper.setQuery(query) - result: QueryResult = wikidata_wrapper.query() - - bindings: Iterator[dict] = get_bindings_from_query_result(result) - binding = next(bindings) - - assert all(var in binding.keys() for var in ["x", "y", "a", "p"]) - assert all(value in binding.values() for value in ["1", "2", "a value", "p value"]) diff --git a/tests/test_instantiate_model_from_bindings.py b/tests/test_instantiate_model_from_bindings.py new file mode 100644 index 0000000..89d25cb --- /dev/null +++ b/tests/test_instantiate_model_from_bindings.py @@ -0,0 +1,32 @@ +"""Pytest entry point for rdfproxy.utils.utils.instantiate_model_from_bindings tests.""" + +import pytest + +from rdfproxy.mapper import ModelBindingsMapper +from tests.data.parameters.author_array_collection_model_parameters import ( + author_array_collection_parameters, +) +from tests.data.parameters.author_work_title_model_parameters import ( + author_work_title_parameters, +) +from tests.data.parameters.basic_model_parameters import basic_parameters +from tests.data.parameters.grouping_model_parameters import grouping_parameters +from tests.data.parameters.nested_grouping_model_parameters import ( + nested_grouping_parameters, +) + + +@pytest.mark.parametrize( + ["model", "bindings", "expected"], + [ + *basic_parameters, + *grouping_parameters, + *nested_grouping_parameters, + *author_work_title_parameters, + *author_array_collection_parameters, + ], +) +def test_basic_instantiate_model_from_bindings(model, bindings, expected): + mapper = ModelBindingsMapper(model, *bindings) + models = mapper.get_models() + assert [model.model_dump() for model in models] == expected diff --git a/tests/test_instantiate_model_from_kwargs.py b/tests/test_instantiate_model_from_kwargs.py deleted file mode 100644 index af193a0..0000000 --- a/tests/test_instantiate_model_from_kwargs.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Pytest entry point for rdfproxy.instantiate_model_from_kwargs tests.""" - -import pytest -from rdfproxy.utils.utils import instantiate_model_from_kwargs -from tests.data.init_model_from_kwargs_parameters import ( - init_model_from_kwargs_parameters, -) -from tests.data.models import Person - - -@pytest.mark.parametrize(("model", "kwargs"), init_model_from_kwargs_parameters) -def test_init_model_from_kwargs(model, kwargs): - """Check if the init_model_from_kwargs constructor successfully inits a model based on kwargs.""" - for _kwargs in kwargs: - model_instance = instantiate_model_from_kwargs(model, **_kwargs) - assert isinstance(model_instance, model) - - -def test_explicit_sparql_binding_allocation(): - bindings: dict = {"title": "Test Title", "name": "Test Name"} - expected: dict = {"name": "Test Name", "work": {"name": "Test Title"}} - - model = instantiate_model_from_kwargs(Person, **bindings) - - assert expected == model.model_dump() diff --git a/tests/test_sparql_model_adapter.py b/tests/test_sparql_model_adapter.py deleted file mode 100644 index 82a198d..0000000 --- a/tests/test_sparql_model_adapter.py +++ /dev/null @@ -1,87 +0,0 @@ -"""Pytest entry point for rdfproxy.SPARQLModelAdapter tests.""" - -import secrets -import string - -import pytest - -from rdfproxy import SPARQLModelAdapter -from rdfproxy.utils._exceptions import UndefinedBindingException -from tests.data.models import ComplexModel - - -@pytest.mark.remote -def test_sparql_model_adapter_basic(): - """Simple base test for SPARQLModelAdapter.""" - query = """ - select ?x ?y ?a ?p - where { - values (?x ?y ?a ?p) { - (1 2 "a value" "p value") - } - } - """ - adapter = SPARQLModelAdapter( - target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", - query=query, - model=ComplexModel, - ) - - assert all(isinstance(model, ComplexModel) for model in adapter.query()) - - -@pytest.mark.remote -def test_sparql_model_adapter_grouping_basic(): - query = """ - select ?x ?y ?a ?p ?z - where { - values (?x ?y ?a ?p ?z) { - (1 2 "a value" "p value" 3) - (1 22 "other value" "yet another value" 3) - (2 22 "other value" "yet another value" 3) - } - } - """ - - adapter = SPARQLModelAdapter( - target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", - query=query, - model=ComplexModel, - ) - - group_x = adapter._query_group_by("x") - group_y = adapter._query_group_by("y") - group_z = adapter._query_group_by("z") - - assert {1, 2} == set(len(v) for _, v in group_x.items()) - assert {1, 2} == set(len(v) for _, v in group_y.items()) - assert {3} == set(len(v) for _, v in group_z.items()) - - -@pytest.mark.remote -@pytest.mark.parametrize( - "var", - ["".join(secrets.choice(string.ascii_letters) for i in range(8)) for e in range(10)] - + ["and", "as", "assert"], -) -def test_sparql_model_adapter_grouping_basic_fail(var): - query = """ - select ?x ?y ?a ?p ?z - where { - values (?x ?y ?a ?p ?z) { - (1 2 "a value" "p value" 3) - (1 22 "other value" "yet another value" 3) - (2 22 "other value" "yet another value" 3) - } - } - """ - - adapter = SPARQLModelAdapter( - target="https://query.wikidata.org/bigdata/namespace/wdq/sparql", - query=query, - model=ComplexModel, - ) - - if var not in ["x", "y", "a", "p", "z"]: - with pytest.raises(UndefinedBindingException): - group = adapter._query_group_by(var) # noqa: F841 diff --git a/tests/utils/_types.py b/tests/utils/_types.py new file mode 100644 index 0000000..292c668 --- /dev/null +++ b/tests/utils/_types.py @@ -0,0 +1,6 @@ +"""Types for testing.""" + +from collections import namedtuple + + +Parameter = namedtuple("Parameter", ["model", "bindings", "expected"])