diff --git a/rdfproxy/utils/sparql_utils.py b/rdfproxy/utils/sparql_utils.py index fa192cc..a064898 100644 --- a/rdfproxy/utils/sparql_utils.py +++ b/rdfproxy/utils/sparql_utils.py @@ -8,7 +8,7 @@ from SPARQLWrapper import QueryResult, SPARQLWrapper from rdfproxy.utils._exceptions import QueryConstructionException -from rdfproxy.utils._types import ItemsQueryConstructor, _TModelInstance +from rdfproxy.utils._types import ItemsQueryConstructor, SPARQLBinding, _TModelInstance def construct_ungrouped_pagination_query(query: str, limit: int, offset: int) -> str: @@ -79,6 +79,12 @@ def get_items_query_constructor( if (group_by_value := model.model_config.get("group_by", None)) is None: return construct_ungrouped_pagination_query + + elif meta := model.model_fields[group_by_value].metadata: + group_by_value = next( + filter(lambda x: isinstance(x, SPARQLBinding), meta), group_by_value + ) + return partial(construct_grouped_pagination_query, group_by_value=group_by_value) @@ -96,7 +102,14 @@ def construct_count_query(query: str, model: type[_TModelInstance]) -> str: """Construct a generic count query from a SELECT query.""" try: group_by: str = model.model_config["group_by"] - count_query = construct_grouped_count_query(query, group_by) + group_by_binding = next( + filter( + lambda x: isinstance(x, SPARQLBinding), + model.model_fields[group_by].metadata, + ), + group_by, + ) + count_query = construct_grouped_count_query(query, group_by_binding) except KeyError: count_query = replace_query_select_clause(query, "select (count(*) as ?cnt)") diff --git a/tests/data/models/dummy_model.py b/tests/data/models/dummy_model.py index 2cab0d9..d9de028 100644 --- a/tests/data/models/dummy_model.py +++ b/tests/data/models/dummy_model.py @@ -9,3 +9,5 @@ class Dummy(BaseModel): class GroupedDummy(BaseModel): model_config = ConfigDict(group_by="x") + + x: int diff --git a/tests/tests_adapter/test_adapter_grouped_pagination.py b/tests/tests_adapter/test_adapter_grouped_pagination.py index ae444f7..4240f87 100644 --- a/tests/tests_adapter/test_adapter_grouped_pagination.py +++ b/tests/tests_adapter/test_adapter_grouped_pagination.py @@ -1,13 +1,25 @@ """Basic tests for rdfproxy.SPARQLModelAdapter pagination with grouped models.""" -from typing import Any, NamedTuple +from typing import Annotated, Any, NamedTuple import pytest from pydantic import BaseModel, ConfigDict -from rdfproxy import Page, QueryParameters, SPARQLModelAdapter +from rdfproxy import Page, QueryParameters, SPARQLBinding, SPARQLModelAdapter +binding_query = """ +select ?parentBinding ?child ?name +where { + values (?parentBinding ?child ?name) { + ('x' 'c' 'foo') + ('y' 'd' UNDEF) + ('y' 'e' UNDEF) + ('z' UNDEF UNDEF) + } +} +""" + query = """ select ?parent ?child ?name where { @@ -25,6 +37,13 @@ class Child(BaseModel): name: str | None = None +class BindingParent(BaseModel): + model_config = ConfigDict(group_by="parent") + + parent: Annotated[str, SPARQLBinding("parentBinding")] + children: list[Child] + + class Parent(BaseModel): model_config = ConfigDict(group_by="parent") @@ -32,7 +51,13 @@ class Parent(BaseModel): children: list[Child] -parent_adapter = SPARQLModelAdapter( +binding_adapter = SPARQLModelAdapter( + target="https://graphdb.r11.eu/repositories/RELEVEN", + query=binding_query, + model=BindingParent, +) + +adapter = SPARQLModelAdapter( target="https://graphdb.r11.eu/repositories/RELEVEN", query=query, model=Parent, @@ -47,7 +72,58 @@ class AdapterParameter(NamedTuple): adapter_parameters = [ AdapterParameter( - adapter=parent_adapter, + adapter=binding_adapter, + query_parameters={"page": 1, "size": 2}, + expected=Page[BindingParent]( + items=[ + {"parent": "x", "children": [{"name": "foo"}]}, + {"parent": "y", "children": []}, + ], + page=1, + size=2, + total=3, + pages=2, + ), + ), + AdapterParameter( + adapter=binding_adapter, + query_parameters={"page": 2, "size": 2}, + expected=Page[BindingParent]( + items=[{"parent": "z", "children": []}], + page=2, + size=2, + total=3, + pages=2, + ), + ), + AdapterParameter( + adapter=binding_adapter, + query_parameters={"page": 1, "size": 1}, + expected=Page[BindingParent]( + items=[{"parent": "x", "children": [{"name": "foo"}]}], + page=1, + size=1, + total=3, + pages=3, + ), + ), + AdapterParameter( + adapter=binding_adapter, + query_parameters={"page": 2, "size": 1}, + expected=Page[BindingParent]( + items=[{"parent": "y", "children": []}], page=2, size=1, total=3, pages=3 + ), + ), + AdapterParameter( + adapter=binding_adapter, + query_parameters={"page": 3, "size": 1}, + expected=Page[BindingParent]( + items=[{"parent": "z", "children": []}], page=3, size=1, total=3, pages=3 + ), + ), + # + AdapterParameter( + adapter=adapter, query_parameters={"page": 1, "size": 2}, expected=Page[Parent]( items=[ @@ -61,7 +137,7 @@ class AdapterParameter(NamedTuple): ), ), AdapterParameter( - adapter=parent_adapter, + adapter=adapter, query_parameters={"page": 2, "size": 2}, expected=Page[Parent]( items=[{"parent": "z", "children": []}], @@ -72,7 +148,7 @@ class AdapterParameter(NamedTuple): ), ), AdapterParameter( - adapter=parent_adapter, + adapter=adapter, query_parameters={"page": 1, "size": 1}, expected=Page[Parent]( items=[{"parent": "x", "children": [{"name": "foo"}]}], @@ -83,14 +159,14 @@ class AdapterParameter(NamedTuple): ), ), AdapterParameter( - adapter=parent_adapter, + adapter=adapter, query_parameters={"page": 2, "size": 1}, expected=Page[Parent]( items=[{"parent": "y", "children": []}], page=2, size=1, total=3, pages=3 ), ), AdapterParameter( - adapter=parent_adapter, + adapter=adapter, query_parameters={"page": 3, "size": 1}, expected=Page[Parent]( items=[{"parent": "z", "children": []}], page=3, size=1, total=3, pages=3