diff --git a/poetry.lock b/poetry.lock index d3bd3a7..fbabf38 100644 --- a/poetry.lock +++ b/poetry.lock @@ -31,6 +31,24 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "beartype" +version = "0.19.0" +description = "Unbearably fast near-real-time hybrid runtime-static type-checking in pure Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "beartype-0.19.0-py3-none-any.whl", hash = "sha256:33b2694eda0daf052eb2aff623ed9a8a586703bbf0a90bbc475a83bbf427f699"}, + {file = "beartype-0.19.0.tar.gz", hash = "sha256:de42dfc1ba5c3710fde6c3002e3bd2cad236ed4d2aabe876345ab0b4234a6573"}, +] + +[package.extras] +dev = ["autoapi (>=0.9.0)", "coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pydata-sphinx-theme (<=0.7.2)", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] +doc-rtd = ["autoapi (>=0.9.0)", "pydata-sphinx-theme (<=0.7.2)", "sphinx (>=4.2.0,<6.0.0)", "sphinxext-opengraph (>=0.7.5)"] +test = ["coverage (>=5.5)", "equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "tox (>=3.20.1)", "typing-extensions (>=3.10.0.0)"] +test-tox = ["equinox", "jax[cpu]", "jaxtyping", "mypy (>=0.800)", "numba", "numpy", "pandera", "pygments", "pyright (>=1.1.370)", "pytest (>=4.0.0)", "sphinx", "typing-extensions (>=3.10.0.0)"] +test-tox-coverage = ["coverage (>=5.5)"] + [[package]] name = "certifi" version = "2024.7.4" @@ -1246,4 +1264,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.11" -content-hash = "4e42db8b57c13c2a19b9920a69d9d853bb73bd0c7d34fcf6fb9fbfacc93c5d1d" +content-hash = "492a4ae1580df06e9b092f869173903f0bf78e88af367f80bce35ea59782e329" diff --git a/pyproject.toml b/pyproject.toml index ee5a8e4..022b9c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ sparqlwrapper = "^2.0.0" pydantic = "^2.9.2" +beartype = "^0.19.0" [tool.poetry.group.dev.dependencies] ruff = "^0.7.0" deptry = "^0.20.0" diff --git a/rdfproxy/__init__.py b/rdfproxy/__init__.py index caaad1e..27a2f4a 100644 --- a/rdfproxy/__init__.py +++ b/rdfproxy/__init__.py @@ -1,4 +1,8 @@ -from rdfproxy.adapter import SPARQLModelAdapter # noqa: F401 -from rdfproxy.mapper import ModelBindingsMapper # noqa: F401 -from rdfproxy.utils._types import SPARQLBinding # noqa: F401 -from rdfproxy.utils.models import Page # noqa: F401 +from beartype.claw import beartype_this_package + +beartype_this_package() + +from rdfproxy.adapter import SPARQLModelAdapter # noqa: F401, E402 +from rdfproxy.mapper import ModelBindingsMapper # noqa: F401, E402 +from rdfproxy.utils._types import SPARQLBinding # noqa: F401, E402 +from rdfproxy.utils.models import Page # noqa: F401, E402 diff --git a/rdfproxy/utils/_types.py b/rdfproxy/utils/_types.py index 93b59d1..0e076b2 100644 --- a/rdfproxy/utils/_types.py +++ b/rdfproxy/utils/_types.py @@ -9,6 +9,7 @@ _TModelInstance = TypeVar("_TModelInstance", bound=BaseModel) +@runtime_checkable class ItemsQueryConstructor(Protocol): def __call__(self, query: str, limit: int, offset: int) -> str: ... diff --git a/rdfproxy/utils/utils.py b/rdfproxy/utils/utils.py index 0615d7d..ae15a12 100644 --- a/rdfproxy/utils/utils.py +++ b/rdfproxy/utils/utils.py @@ -9,20 +9,25 @@ MissingModelConfigException, UnboundGroupingKeyException, ) -from rdfproxy.utils._types import ModelBoolPredicate, SPARQLBinding, _TModelBoolValue +from rdfproxy.utils._types import ( + ModelBoolPredicate, + SPARQLBinding, + _TModelBoolValue, + _TModelInstance, +) -def _is_type(obj: type | None, _type: type) -> bool: +def _is_type(obj: Any, _type: type) -> bool: """Check if an obj is type _type or a GenericAlias with origin _type.""" return (obj is _type) or (get_origin(obj) is _type) -def _is_list_type(obj: type | None) -> bool: +def _is_list_type(obj: Any) -> bool: """Check if obj is a list type.""" return _is_type(obj, list) -def _is_list_basemodel_type(obj: type | None) -> bool: +def _is_list_basemodel_type(obj: Any) -> bool: """Check if a type is list[pydantic.BaseModel].""" return (get_origin(obj) is list) and all( issubclass(cls, BaseModel) for cls in get_args(obj) @@ -104,7 +109,7 @@ def _get_model_bool_predicate_from_config_value( ) -def get_model_bool_predicate(model: BaseModel) -> ModelBoolPredicate: +def get_model_bool_predicate(model: type[_TModelInstance]) -> ModelBoolPredicate: """Get the applicable model_bool predicate function given a model.""" if (model_bool_value := model.model_config.get("model_bool", None)) is None: model_bool_predicate = default_model_bool_predicate diff --git a/tests/unit/test_sad_path_get_bindings_from_query_result.py b/tests/unit/test_sad_path_get_bindings_from_query_result.py index 2bc655c..fc61756 100644 --- a/tests/unit/test_sad_path_get_bindings_from_query_result.py +++ b/tests/unit/test_sad_path_get_bindings_from_query_result.py @@ -4,11 +4,14 @@ import pytest +from SPARQLWrapper.Wrapper import QueryResult from rdfproxy.utils.sparql_utils import get_bindings_from_query_result def test_basic_sad_path_get_bindings_from_query_result(): with mock.patch("SPARQLWrapper.QueryResult") as mock_query_result: + mock_query_result.__class__ = QueryResult + mock_query_result.return_value.requestedFormat = "xml" exception_message = ( "Only QueryResult objects with JSON format are currently supported."