diff --git a/rdfproxy/constructor.py b/rdfproxy/constructor.py index 26317d5..063d52f 100644 --- a/rdfproxy/constructor.py +++ b/rdfproxy/constructor.py @@ -36,6 +36,9 @@ def __init__( self.group_by: str | None = self.bindings_map.get( model.model_config.get("group_by") ) + self.order_by: str | None = self.bindings_map.get(query_parameters.order_by) + + print("DEBUG: ", self.order_by) def get_items_query(self) -> str: """Construct a SPARQL items query for use in rdfproxy.SPARQLModelAdapter.""" @@ -118,7 +121,13 @@ def _compute_select_clause(self): return f"select distinct ?{self.group_by}" def _compute_order_by_value(self): - """Stub: Only basic logic for now.""" - if self.group_by is None: - return get_query_projection(self.query)[0] - return f"{self.group_by}" + """Compute a value for ORDER BY used in RDFProxy query modification.""" + match (self.group_by, self.order_by): + case None, None: + return f"?{get_query_projection(self.query)[0]}" + case group_by, None: + return f"?{group_by}" + case _, order_by: + return f"{'DESC' if self.query_parameters.desc else 'ASC'}(?{order_by})" + case _: # pragma: no cover + assert False, "Unreachable case in _compute_order_by_value" diff --git a/rdfproxy/utils/models.py b/rdfproxy/utils/models.py index 9eef6a9..e01840e 100644 --- a/rdfproxy/utils/models.py +++ b/rdfproxy/utils/models.py @@ -1,8 +1,10 @@ """Pydantic Model definitions for rdfproxy.""" -from typing import Generic +from enum import StrEnum, auto +from typing import Any, Generic, get_origin -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator +from pydantic.fields import FieldInfo from rdfproxy.utils._types import _TModelInstance @@ -23,7 +25,10 @@ class Page(BaseModel, Generic[_TModelInstance]): pages: int -class QueryParameters(BaseModel): +class QueryParameters( + BaseModel, + Generic[_TModelInstance], +): """Query parameter model for SPARQLModelAdapter.query. See https://fastapi.tiangolo.com/tutorial/query-param-models/ @@ -31,3 +36,28 @@ class QueryParameters(BaseModel): page: int = Field(default=1, gt=0) size: int = Field(default=100, ge=1) + + order_by: str | None = Field(default=None) + desc: bool = Field(default=False) + + @model_validator(mode="after") + @classmethod + def _check_desc_order_by_dependency(cls, data: Any) -> Any: + """Check the dependency of desc on order_by.""" + _desc_defined, _order_by_defined = data.desc, data.order_by + + if _desc_defined and not _order_by_defined: + raise ValueError("Field 'desc' requires on Field 'order_by'.") + return data + + def __class_getitem__(cls, model: type[_TModelInstance]): + _order_by_fields = [ + (k, auto()) + for k, v in model.model_fields.items() + if get_origin(v.annotation) is not list + ] + + OrderByEnum = StrEnum("OrderByEnum", _order_by_fields) + cls.model_fields["order_by"] = FieldInfo(annotation=OrderByEnum, default=None) + + return cls