From d7241ca90823577f9608a7b1bf4b8c7181741f01 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Fri, 1 Nov 2024 16:39:22 +1000 Subject: [PATCH 1/3] Remove Python 3.8 support, bump OWL-RL to newest version. --- .github/workflows/ci.yml | 1 - .gitignore | 3 +- .travis.yml | 56 ------------------------------------ poetry.lock | 61 ++++++++++++++++++---------------------- pyproject.toml | 14 ++++----- 5 files changed, 36 insertions(+), 99 deletions(-) delete mode 100644 .travis.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c0add7..fed572f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,6 @@ jobs: strategy: matrix: python-version: - - '3.8' - '3.9' - '3.10' - '3.11' diff --git a/.gitignore b/.gitignore index f014b56..094f526 100644 --- a/.gitignore +++ b/.gitignore @@ -416,7 +416,8 @@ celerybeat-schedule # Environments .env .venv/ -.venv38/ +.venv39/ +.venv311/ env/ venv/ ENV/ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 2524441..0000000 --- a/.travis.yml +++ /dev/null @@ -1,56 +0,0 @@ -sudo: false -language: python -dist: bionic -cache: - directories: - - $HOME/.cache/pip -matrix: - include: - - env: TOX_ENV=py37 - python: 3.7 - name: "Python 3.7" - - env: TOX_ENV=py38 - python: 3.8 - name: "Python 3.8" - - env: TOX_ENV=py39 - python: 3.9 - name: "Python 3.9" - - env: TOX_ENV=py310 - python: 3.10 - name: "Python 3.10" - - env: TOX_ENV=type-checking - python: 3.7 - name: "Python 3.7 Type checks" - - env: TOX_ENV=type-checking - python: 3.8 - name: "Python 3.8 Type checks" - - env: TOX_ENV=type-checking - python: 3.9 - name: "Python 3.9 Type checks" - - env: TOX_ENV=type-checking - python: 3.10 - name: "Python 3.10 Type checks" - - env: TOX_ENV=lint - python: 3.7 - name: "Python 3.7 Linter checks" - - -install: - - pip3 install --upgrade pip "poetry>=1.1.0" tox -script: travis_retry tox -e $TOX_ENV -before_deploy: - - poetry build -deploy: - provider: pypi - user: ashleysommer - skip_cleanup: true #Need skip cleanup because we build the artifacts in before_deploy - password: - secure: lq8L1Rc6jJW6URaZcmu5YOa9z8MqQkCiIxfivZlYBjbCkZLenU4/Hc7bK/ORVg+TPJZiyrfxobStFp1cX460zgQRw+3OW8f7whtRmj0qxLq9SF9szpORLrDcvSzHV/xU8GF6xAyJ83VS5UWC2TiA4yHIQyi7YkLYh9zdC370xkXMwKLQ+G2Bmqx5Y84h1wKYtpNU6RdrZiwcBTZEvZdWaoEjw5gZsd5wW8RmWDewS2SE2P6m7FXZwShB9XRGKkeC+UAXzDkp9DpZa94PQTktwXaM2yRo5Y2t1N2BBKzTT72ikFv/xr8vov157z0pdcOiEx7Xkd7Dov1fsy00KZ7If+opMgIWHaw/UnC5jwzILTAubBuFopV+SNJnLZ1EMMPsINpJF7eecj0/OoC+bEu/5Oxk8BeFO1UOoXZ4aOlzzDU6TpXP/0ULyoRtskc2UpXETRH2rapam41tbDX0JuXlfrrOFkPrB9O4jNMrm+6e9ldorGkpzKHKzZvOjp2WykMjiPzFk7GIordK96jmctwiEWG2N0HayLGsgdyT8YrhHatCZk51HZ+Jhk05zrmfPQqa+MhskIpcPfplei/sUcMD3TCN8Qw5mhyZPTbC6m/a6E3+wqw++Wos4ZMwcp71BiChdcIb/XhjG6oy/JHYV3Bth4ZuyRN1vnVzm4VDmdb5b2c= - on: - tags: true - python: 3.7 - condition: $DEPLOY = true - branch: release - distributions: "sdist bdist_wheel" - skip_existing: true - diff --git a/poetry.lock b/poetry.lock index 551d29d..c8759dd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -159,22 +159,16 @@ files = [ test = ["pytest (>=6)"] [[package]] -name = "html5lib-modern" -version = "1.2" +name = "html5rdf" +version = "1.2.1" description = "HTML parser based on the WHATWG HTML specification" optional = false python-versions = ">=3.8" files = [ - {file = "html5lib_modern-1.2-py2.py3-none-any.whl", hash = "sha256:3458b6e31525ede4fcaac0ff42d9eeb5efaf755473768103cb56e0275caa8d99"}, - {file = "html5lib_modern-1.2.tar.gz", hash = "sha256:1fadbfc27ea955431270e4e79a4a4c290ba11c3a3098a95cc22dc73e312a1768"}, + {file = "html5rdf-1.2.1-py2.py3-none-any.whl", hash = "sha256:1f519121bc366af3e485310dc8041d2e86e5173c1a320fac3dc9d2604069b83e"}, + {file = "html5rdf-1.2.1.tar.gz", hash = "sha256:ace9b420ce52995bb4f05e7425eedf19e433c981dfe7a831ab391e2fa2e1a195"}, ] -[package.extras] -all = ["chardet (>=2.2.1)", "genshi (>=0.7.1)", "lxml (>=3.4.0)"] -chardet = ["chardet (>=2.2.1)"] -genshi = ["genshi (>=0.7.1)"] -lxml = ["lxml (>=3.4.0)"] - [[package]] name = "httptools" version = "0.6.4" @@ -491,17 +485,17 @@ files = [ [[package]] name = "owlrl" -version = "6.0.2" -description = "OWL-RL and RDFS based RDF Closure inferencing for Python" +version = "7.1.2" +description = "A simple implementation of the OWL2 RL Profile, as well as a basic RDFS inference, on top of RDFLib. Based mechanical forward chaining." optional = false -python-versions = "*" +python-versions = "<4.0,>=3.8" files = [ - {file = "owlrl-6.0.2-py3-none-any.whl", hash = "sha256:57eca06b221edbbc682376c8d42e2ddffc99f61e82c0da02e26735592f08bacc"}, - {file = "owlrl-6.0.2.tar.gz", hash = "sha256:904e3310ff4df15101475776693d2427d1f8244ee9a6a9f9e13c3c57fae90b74"}, + {file = "owlrl-7.1.2-py3-none-any.whl", hash = "sha256:b4234191d1981ee7c551fa203cc0d21470c38e624e6795f3ecac55651dab26b1"}, + {file = "owlrl-7.1.2.tar.gz", hash = "sha256:966136f303f08f3eb190f6631c31a03b1bc5744d6c15189d7fb638a563ee61d2"}, ] [package.dependencies] -rdflib = ">=6.0.2" +rdflib = ">=7.1.1" [[package]] name = "packaging" @@ -558,13 +552,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "prettytable" -version = "3.11.0" +version = "3.12.0" description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd"}, - {file = "prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722"}, + {file = "prettytable-3.12.0-py3-none-any.whl", hash = "sha256:77ca0ad1c435b6e363d7e8623d7cc4fcf2cf15513bf77a1c1b2e814930ac57cc"}, + {file = "prettytable-3.12.0.tar.gz", hash = "sha256:f04b3e1ba35747ac86e96ec33e3bb9748ce08e254dc2a1c6253945901beec804"}, ] [package.dependencies] @@ -650,13 +644,13 @@ files = [ [[package]] name = "pyparsing" -version = "3.1.4" +version = "3.2.0" description = "pyparsing module - Classes and methods to define and execute parsing grammars" optional = false -python-versions = ">=3.6.8" +python-versions = ">=3.9" files = [ - {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, - {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, + {file = "pyparsing-3.2.0-py3-none-any.whl", hash = "sha256:93d9577b88da0bbea8cc8334ee8b918ed014968fd2ec383e868fb8afb1ccef84"}, + {file = "pyparsing-3.2.0.tar.gz", hash = "sha256:cbf74e27246d595d9a74b186b810f6fbb86726dbf3b9532efb343f6d7294fe9c"}, ] [package.extras] @@ -767,22 +761,23 @@ files = [ [[package]] name = "rdflib" -version = "7.1.0" +version = "7.1.1" description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." optional = false python-versions = "<4.0.0,>=3.8.1" files = [ - {file = "rdflib-7.1.0-py3-none-any.whl", hash = "sha256:240c25c6e1b573ffa67aed23aae128e253c443c15291c9a01d8d392ea80c05b6"}, - {file = "rdflib-7.1.0.tar.gz", hash = "sha256:a29a8fccebd3d3a5f1b7e88d92dace1c89829018c7d29a6114fff4449c188b3b"}, + {file = "rdflib-7.1.1-py3-none-any.whl", hash = "sha256:e590fa9a2c34ba33a667818b5a84be3fb8a4d85868f8038f17912ec84f912a25"}, + {file = "rdflib-7.1.1.tar.gz", hash = "sha256:164de86bd3564558802ca983d84f6616a4a1a420c7a17a8152f5016076b2913e"}, ] [package.dependencies] -html5lib-modern = ">=1.2,<2.0" +html5rdf = {version = ">=1.2,<2", optional = true, markers = "extra == \"html\""} isodate = {version = ">=0.7.2,<1.0.0", markers = "python_version < \"3.11\""} pyparsing = ">=2.1.0,<4" [package.extras] berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] +html = ["html5rdf (>=1.2,<2)"] lxml = ["lxml (>=4.3,<6.0)"] networkx = ["networkx (>=2,<4)"] orjson = ["orjson (>=3.9.14,<4)"] @@ -948,13 +943,13 @@ files = [ [[package]] name = "types-setuptools" -version = "75.2.0.20241019" +version = "75.2.0.20241025" description = "Typing stubs for setuptools" optional = true python-versions = ">=3.8" files = [ - {file = "types-setuptools-75.2.0.20241019.tar.gz", hash = "sha256:86ea31b5f6df2c6b8f2dc8ae3f72b213607f62549b6fa2ed5866e5299f968694"}, - {file = "types_setuptools-75.2.0.20241019-py3-none-any.whl", hash = "sha256:2e48ff3acd4919471e80d5e3f049cce5c177e108d5d36d2d4cee3fa4d4104258"}, + {file = "types-setuptools-75.2.0.20241025.tar.gz", hash = "sha256:2949913a518d5285ce00a3b7d88961c80a6e72ffb8f3da0a3f5650ea533bd45e"}, + {file = "types_setuptools-75.2.0.20241025-py3-none-any.whl", hash = "sha256:6721ac0f1a620321e2ccd87a9a747c4a383dc381f78d894ce37f2455b45fcf1c"}, ] [[package]] @@ -1240,5 +1235,5 @@ js = ["pyduktape2"] [metadata] lock-version = "2.0" -python-versions = "^3.8.1" -content-hash = "f8e7c569e4bed2f45ff09fa9c1ce34a0d68f2179971375266fa13508886372d1" +python-versions = ">=3.9,<4" +content-hash = "e8ab08d3d98a3bb0c30c9bf0e5ea4471658ce21ba038910317558b95af9c80ad" diff --git a/pyproject.toml b/pyproject.toml index 463c762..030e03a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "poetry.core.masonry.api" [project] name = "pyshacl" # Black and Ruff both now read target-version from [project.requires-python] -requires-python = ">=3.8.1" +requires-python = ">=3.9" [tool.poetry] name = "pyshacl" @@ -60,11 +60,10 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.8.1" # Poetry doesn't read from [project.requires-python] -# Note, 3.8.0 is not supported, only 3.8.1 or above. See: -# https://github.com/RDFLib/rdflib/blob/3bee979cd0e5b6efc57296b4fc43dd8ede8cf375/CHANGELOG.md?plain=1#L53 -rdflib = {version=">=6.3.2,<8.0", python = ">=3.8.1", extras=["html"]} -owlrl = ">=6.0.2,<7" +# The <4 is reauired for compatiblity with OWL-RL that requdires Python <4 +python = ">=3.9,<4" # Poetry doesn't read from [project.requires-python] +rdflib = {version=">=7.1.1,<8.0", extras=["html"]} +owlrl = ">=7.1.2,<8" prettytable = [ {version=">=3.5.0", python = ">=3.8,<3.12"}, {version=">=3.7.0", python = ">=3.12"} @@ -206,7 +205,7 @@ testpaths = [ legacy_tox_ini = """ [tox] skipsdist = true -envlist = py38, py39, py310, py311, py312, lint, type-checking +envlist = py39, py310, py311, py312, lint, type-checking toxworkdir={env:TOX_WORK_DIR:.tox} [testenv] @@ -259,6 +258,5 @@ python = 3.11 = py311 3.10 = py310 3.9 = py39 - 3.8 = py38 """ From 96ece15e8eed67ac2bf95bf169bf7f4d72bb7a55 Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Fri, 1 Nov 2024 16:40:16 +1000 Subject: [PATCH 2/3] Graph expansion using inoculation or SHACL TripleRules will now expand into a separate named graph if you are working with an RDFLib Dataset instead of a Graph. --- CHANGELOG.md | 21 +- pyshacl/inference/custom_rdfs_closure.py | 11 +- pyshacl/rdfutil/inoculate.py | 120 +++--------- pyshacl/rule_expand_runner.py | 11 +- pyshacl/rules/__init__.py | 13 +- pyshacl/rules/shacl_rule.py | 14 +- pyshacl/rules/sparql/__init__.py | 24 ++- pyshacl/rules/triple/__init__.py | 23 ++- pyshacl/run_type.py | 29 +-- pyshacl/validator.py | 11 +- test/test_dash_validate.py | 3 +- test/test_extra.py | 2 +- test/test_inoculate.py | 232 +++++++++++++++++++++++ 13 files changed, 376 insertions(+), 138 deletions(-) create mode 100644 test/test_inoculate.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 284a992..005ee82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Python PEP 440 Versioning](https://www.python.org/d ## [Unreleased] - Nothing yet +## [0.29.0] - 2024-11-01 + +### Added +- When validating a Dataset instead of a bare Graph, PySHACL will now expand RDFS and OWL-RL inferences into + a separate named graph, to avoid polluting the datagraph. +- When using SHACL Triple Rules from SHACL-AF spec, PySHACL will now add the expressed triples into + a separate named graph. This allows you to more easily get the expanded triples back out again afterward. + +### Changed +- PySHACL no longer supports older RDFLib versions + - PySHACL relies on the latest OWL-RL version, that in-turn relies on the latest RDFLib version + - Therefore PySHACL now requires RDFLib v7.1.1 or newer +- Dropped Python 3.8 support. + - Python developers discontinued Python 3.8 last month + - The next version of RDFLib and OWL-RL will not support Python 3.8 + - Removed Python 3.8 from the RDFLib test suite + - Python 3.9-specific typing changes will be incrementally introduced + ## [0.28.1] - 2024-10-25 ### Fixed @@ -1182,7 +1200,8 @@ just leaves the files open. Now it is up to the command-line client to close the - Initial version, limited functionality -[Unreleased]: https://github.com/RDFLib/pySHACL/compare/v0.28.1...HEAD +[Unreleased]: https://github.com/RDFLib/pySHACL/compare/v0.29.0...HEAD +[0.29.0]: https://github.com/RDFLib/pySHACL/compare/v0.28.1...v0.29.0 [0.28.1]: https://github.com/RDFLib/pySHACL/compare/v0.28.0...v0.28.1 [0.28.0]: https://github.com/RDFLib/pySHACL/compare/v0.27.0...v0.28.0 [0.27.0]: https://github.com/RDFLib/pySHACL/compare/v0.26.0...v0.27.0 diff --git a/pyshacl/inference/custom_rdfs_closure.py b/pyshacl/inference/custom_rdfs_closure.py index 3db3ebc..fd40d51 100644 --- a/pyshacl/inference/custom_rdfs_closure.py +++ b/pyshacl/inference/custom_rdfs_closure.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from typing import TYPE_CHECKING, Optional + try: from owlrl import OWL @@ -25,6 +27,9 @@ from owlrl.OWLRL import OWLRL_Semantics from owlrl.RDFSClosure import RDFS_Semantics as OrigRDFSSemantics +if TYPE_CHECKING: + from rdflib.graph import Graph + class CustomRDFSSemantics(OrigRDFSSemantics): def one_time_rules(self): @@ -49,9 +54,9 @@ class CustomRDFSOWLRLSemantics(CustomRDFSSemantics, OWLRL_Semantics): (OWL.DataRange, OWL.equivalentClass, RDFS.Datatype), ] - def __init__(self, graph, axioms, daxioms, rdfs=True): - OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs) - CustomRDFSSemantics.__init__(self, graph, axioms, daxioms, rdfs) + def __init__(self, graph, axioms, daxioms, rdfs: bool = True, destination: Optional['Graph'] = None): + OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) + CustomRDFSSemantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) self.rdfs = True # noinspection PyMethodMayBeStatic diff --git a/pyshacl/rdfutil/inoculate.py b/pyshacl/rdfutil/inoculate.py index 142d5af..28deff7 100644 --- a/pyshacl/rdfutil/inoculate.py +++ b/pyshacl/rdfutil/inoculate.py @@ -2,22 +2,20 @@ from typing import TYPE_CHECKING, Dict, Optional, Union import rdflib -from rdflib.graph import DATASET_DEFAULT_GRAPH_ID -from rdflib.namespace import NamespaceManager -from .clone import clone_blank_node, clone_graph, clone_node +from .clone import clone_blank_node, clone_dataset, clone_node from .consts import OWL, RDF, ConjunctiveLike, GraphLike, OWL_classes, OWL_properties, RDFS_classes, RDFS_properties if TYPE_CHECKING: from rdflib import BNode - from rdflib.term import IdentifiedNode + from rdflib.term import URIRef from .consts import RDFNode OWLNamedIndividual = OWL.NamedIndividual -def inoculate(data_graph: rdflib.Graph, ontology: rdflib.Graph) -> rdflib.Graph: +def inoculate(data_graph: rdflib.Graph, ontology: GraphLike) -> rdflib.Graph: """ Copies all RDFS and OWL axioms (classes, relationship definitions, and properties) from the ontology graph into the data_graph. @@ -33,6 +31,9 @@ def inoculate(data_graph: rdflib.Graph, ontology: rdflib.Graph) -> rdflib.Graph: ontology_ns = ontology.namespace_manager data_graph_ns = data_graph.namespace_manager + if isinstance(ontology, (rdflib.ConjunctiveGraph, rdflib.Dataset)): + # always set default context true on the ontology DS + ontology.default_context = True # Bind any missing ontology namespaces in the DataGraph NS manager. if ontology_ns is not data_graph_ns: data_graph_prefixes = {p: n for (p, n) in data_graph_ns.namespaces()} @@ -108,119 +109,46 @@ def inoculate(data_graph: rdflib.Graph, ontology: rdflib.Graph) -> rdflib.Graph: def inoculate_dataset( - base_ds: ConjunctiveLike, ontology_ds: GraphLike, target_ds: Optional[Union[ConjunctiveLike, str]] = None + base_ds: ConjunctiveLike, + ontology_ds: GraphLike, + target_ds: Optional[Union[ConjunctiveLike, str]] = None, + target_graph_identifier: Optional['URIRef'] = None, ): """ Make a clone of base_ds (dataset) and add RDFS and OWL triples from ontology_ds :param base_ds: :type base_ds: rdflib.Dataset :param ontology_ds: - :type ontology_ds: rdflib.Dataset + :type ontology_ds: rdflib.Dataset|rdflib.ConjunctiveGraph|rdflib.Graph :param target_ds: :type target_ds: rdflib.Dataset|str|NoneType + :param target_graph_identifier: + :type target_graph_identifier: rdflib.URIRef | None :return: The cloned Dataset with ontology triples from ontology_ds :rtype: rdflib.Dataset """ - # TODO: Decide whether we need to clone base_ds before calling this, - # or we clone base_ds as part of this function - default_union: bool = base_ds.default_union - base_named_graphs = [ - ( - rdflib.Graph(base_ds.store, i, namespace_manager=base_ds.namespace_manager) # type: ignore[arg-type] - if not isinstance(i, rdflib.Graph) - else i - ) - for i in base_ds.store.contexts(None) - ] - if isinstance(base_ds, rdflib.Dataset) and len(base_named_graphs) < 1: - base_named_graphs = [ - rdflib.Graph(base_ds.store, DATASET_DEFAULT_GRAPH_ID, namespace_manager=base_ds.namespace_manager) - ] - base_default_context_id = base_ds.default_context.identifier if target_ds is None: - target_ds = rdflib.Dataset(default_union=default_union) - target_ds.namespace_manager = NamespaceManager(target_ds, 'core') - target_ds.default_context.namespace_manager = target_ds.namespace_manager + target_ds = clone_dataset(base_ds) + elif target_ds is base_ds: + pass elif target_ds == "inplace" or target_ds == "base": target_ds = base_ds elif isinstance(target_ds, str): raise RuntimeError("target_ds cannot be a string (unless it is 'inplace' or 'base')") + if isinstance(target_ds, (rdflib.ConjunctiveGraph, rdflib.Dataset)): if not isinstance(target_ds, rdflib.Dataset): raise RuntimeError("Cannot inoculate ConjunctiveGraph, use Dataset instead.") else: raise RuntimeError("Cannot inoculate datasets if target_ds passed in is not a Dataset itself.") - ont_default_context_id: Union[IdentifiedNode, str, None] - if isinstance(ontology_ds, (rdflib.Dataset, rdflib.ConjunctiveGraph)): - ont_graphs = [ - ( - rdflib.Graph(ontology_ds.store, i, namespace_manager=ontology_ds.namespace_manager) # type: ignore[arg-type] - if not isinstance(i, rdflib.Graph) - else i - ) - for i in ontology_ds.store.contexts(None) - ] - ont_default_context_id = ontology_ds.default_context.identifier - else: - ont_graphs = [ontology_ds] - ont_default_context_id = None - if target_ds is base_ds or target_ds == "inplace" or target_ds == "base": - target_ds = base_ds - for bg in base_named_graphs: - if len(base_named_graphs) > 1 and bg.identifier == base_default_context_id and len(bg) < 1: - # skip empty default named graph in base_graph - continue - for og in ont_graphs: - if len(ont_graphs) > 1 and og.identifier == ont_default_context_id and len(og) < 1: - # skip empty default named graph in ontology_graph - continue - inoculate(bg, og) + + if target_graph_identifier: + dest_graph = target_ds.get_context(target_graph_identifier) else: - inoculated_graphs = {} - for bg in base_named_graphs: - if len(base_named_graphs) > 1 and bg.identifier == base_default_context_id and len(bg) < 1: - # skip empty default named graph in base_graph - continue - target_g = rdflib.Graph(store=target_ds.store, identifier=bg.identifier) - clone_g = clone_graph(bg, target_graph=target_g) - for og in ont_graphs: - if len(ont_graphs) > 1 and og.identifier == ont_default_context_id and len(og) < 1: - # skip empty default named graph in ontology_graph - continue - inoculate(clone_g, og) - inoculated_graphs[bg.identifier] = clone_g - - base_graph_identifiers = [bg.identifier for bg in base_named_graphs] - base_default_context_id = base_ds.default_context.identifier - target_default_context_id = target_ds.default_context.identifier - if base_default_context_id != target_default_context_id: - old_target_default_context = target_ds.default_context - old_target_default_context_id = old_target_default_context.identifier - if isinstance(target_ds, rdflib.Dataset): - new_target_default_context = target_ds.graph(base_default_context_id) - else: - new_target_default_context = target_ds.get_context(base_default_context_id) - target_ds.store.add_graph(new_target_default_context) - target_ds.default_context = new_target_default_context - if old_target_default_context_id not in base_graph_identifiers: - if isinstance(target_ds, rdflib.Dataset): - target_ds.remove_graph(old_target_default_context) - else: - target_ds.store.remove_graph(old_target_default_context) - target_default_context_id = new_target_default_context.identifier - else: - if isinstance(target_ds, rdflib.Dataset): - _ = target_ds.graph(target_default_context_id) - else: - t_default = target_ds.get_context(target_default_context_id) - target_ds.store.add_graph(t_default) - for i, ig in inoculated_graphs.items(): - if ig == target_ds.default_context or i == target_default_context_id: - continue - if isinstance(target_ds, rdflib.Dataset): - _ = target_ds.graph(ig) # alias to Dataset.add_graph() - else: - target_ds.store.add_graph(ig) + dest_graph = target_ds.default_context + + # inoculate() routine will set default_union on the ontology_ds if it is a Dataset + inoculate(dest_graph, ontology_ds) return target_ds diff --git a/pyshacl/rule_expand_runner.py b/pyshacl/rule_expand_runner.py index aef0359..565f4c3 100644 --- a/pyshacl/rule_expand_runner.py +++ b/pyshacl/rule_expand_runner.py @@ -92,7 +92,12 @@ def mix_in_ontology(self): else: to_graph = clone_graph(self.data_graph, identifier=self.data_graph.identifier) return inoculate(to_graph, self.ont_graph) - return inoculate_dataset(self.data_graph, self.ont_graph, self.data_graph if self.inplace else None) + return inoculate_dataset( + self.data_graph, + self.ont_graph, + self.data_graph if self.inplace else None, + URIRef("urn:pyshacl:inoculation"), + ) def make_executor(self) -> SHACLExecutor: return SHACLExecutor( @@ -134,7 +139,9 @@ def run(self) -> GraphLike: datagraph = clone_graph(datagraph) has_cloned = True self.logger.debug(f"Running pre-inferencing with option='{inference_option}'.") - self._run_pre_inference(datagraph, inference_option, logger=self.logger) + self._run_pre_inference( + datagraph, inference_option, URIRef("urn:pyshacl:inference"), logger=self.logger + ) self.pre_inferenced = True if not has_cloned and not self.inplace: # We still need to clone in advanced mode, because of triple rules diff --git a/pyshacl/rules/__init__.py b/pyshacl/rules/__init__.py index 3eae4cc..eba4a81 100644 --- a/pyshacl/rules/__init__.py +++ b/pyshacl/rules/__init__.py @@ -86,6 +86,9 @@ def gather_rules( return ret_rules +RULES_ITERATE_LIMIT = 100 + + def apply_rules( executor: SHACLExecutor, shapes_rules: Dict, @@ -98,11 +101,13 @@ def apply_rules( for shape, rules in sorted_shapes_rules: # sort the rules by the sh:order before execution rules = sorted(rules, key=lambda x: x.order) - iterate_limit = 100 + _iterate_limit = int(RULES_ITERATE_LIMIT) while True: - if iterate_limit < 1: - raise ReportableRuntimeError("SHACL Shape Rule iteration exceeded iteration limit of 100.") - iterate_limit -= 1 + if _iterate_limit < 1: + raise ReportableRuntimeError( + f"SHACL Shape Rule iteration exceeded iteration limit of {RULES_ITERATE_LIMIT}." + ) + _iterate_limit -= 1 this_modified = 0 for r in rules: if r.deactivated: diff --git a/pyshacl/rules/shacl_rule.py b/pyshacl/rules/shacl_rule.py index 0014823..e847acd 100644 --- a/pyshacl/rules/shacl_rule.py +++ b/pyshacl/rules/shacl_rule.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from decimal import Decimal -from typing import Sequence, Union +from typing import TYPE_CHECKING, Optional, Sequence from rdflib import RDF, Literal @@ -8,6 +8,11 @@ from pyshacl.errors import RuleLoadError from pyshacl.pytypes import RDFNode, SHACLExecutor +if TYPE_CHECKING: + from rdflib.term import URIRef + + from pyshacl.pytypes import GraphLike + RDF_first = RDF.first @@ -41,7 +46,7 @@ def __init__(self, executor: SHACLExecutor, shape, rule_node, iterate=False): self.executor = executor self.shape = shape self.node = rule_node - self.iterate = False + self.iterate = iterate deactivated_nodes = list(self.shape.sg.objects(self.node, SH_deactivated)) self._deactivated = len(deactivated_nodes) > 0 and bool(deactivated_nodes[0]) @@ -111,7 +116,8 @@ def filter_conditions(self, focus_nodes: Sequence[RDFNode], data_graph): def apply( self, - data_graph, - focus_nodes: Union[Sequence[RDFNode], None] = None, + data_graph: 'GraphLike', + focus_nodes: Optional[Sequence[RDFNode]] = None, + target_graph_identifier: Optional['URIRef'] = None, ): raise NotImplementedError() diff --git a/pyshacl/rules/sparql/__init__.py b/pyshacl/rules/sparql/__init__.py index 9942a9a..e4a871f 100644 --- a/pyshacl/rules/sparql/__init__.py +++ b/pyshacl/rules/sparql/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from typing import TYPE_CHECKING, List, Sequence, Union +from typing import TYPE_CHECKING, List, Optional, Sequence, Union import rdflib from rdflib import Literal @@ -13,11 +13,15 @@ from ..shacl_rule import SHACLRule if TYPE_CHECKING: + from rdflib.term import URIRef + from pyshacl.pytypes import GraphLike, RDFNode, SHACLExecutor from pyshacl.shape import Shape XSD_string = XSD.string +SPARQL_RULE_ITERATE_LIMIT = 100 + class SPARQLRule(SHACLRule): __slots__ = ("_constructs", "_qh") @@ -52,7 +56,8 @@ def __init__(self, executor: 'SHACLExecutor', shape: 'Shape', rule_node: 'rdflib def apply( self, data_graph: 'GraphLike', - focus_nodes: Union[Sequence['RDFNode'], None] = None, + focus_nodes: Optional[Sequence['RDFNode']] = None, + target_graph_identifier: Optional['URIRef'] = None, ) -> int: focus_list: Sequence['RDFNode'] if focus_nodes is not None: @@ -70,10 +75,12 @@ def apply( focus_list = filtered_focus_nodes all_added = 0 SPARQLQueryHelper = get_query_helper_cls() - iterate_limit = 100 + iterate_limit = int(SPARQL_RULE_ITERATE_LIMIT) while True: if iterate_limit < 1: - raise ReportableRuntimeError("Local SPARQLRule iteration exceeded iteration limit of 100.") + raise ReportableRuntimeError( + f"Local SPARQLRule iteration exceeded iteration limit of {SPARQL_RULE_ITERATE_LIMIT}." + ) iterate_limit -= 1 added = 0 applicable_nodes = self.filter_conditions(focus_list, data_graph) @@ -101,8 +108,15 @@ def apply( added += 1 construct_graphs.add(result_graph) if added > 0: + if isinstance(data_graph, (rdflib.Dataset, rdflib.ConjunctiveGraph)): + if target_graph_identifier is not None: + target_graph = data_graph.get_context(target_graph_identifier) + else: + target_graph = data_graph.default_context + else: + target_graph = data_graph for g in construct_graphs: - data_graph = clone_graph(g, target_graph=data_graph) + data_graph = clone_graph(g, target_graph=target_graph) all_added += added if self.iterate: continue # Jump up to iterate diff --git a/pyshacl/rules/triple/__init__.py b/pyshacl/rules/triple/__init__.py index 1dd25f2..e92eb20 100644 --- a/pyshacl/rules/triple/__init__.py +++ b/pyshacl/rules/triple/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- import itertools -from typing import TYPE_CHECKING, List, Sequence, Tuple, Union, cast +from typing import TYPE_CHECKING, List, Optional, Sequence, Tuple, Union, cast import rdflib @@ -10,10 +10,13 @@ from pyshacl.rules.shacl_rule import SHACLRule if TYPE_CHECKING: + from rdflib.term import URIRef from pyshacl.pytypes import GraphLike, RDFNode, SHACLExecutor from pyshacl.shape import Shape +TRIPLE_RULE_ITERATE_LIMIT = 100 + class TripleRule(SHACLRule): __slots__ = ("s", "p", "o") @@ -52,7 +55,8 @@ def __init__(self, executor: 'SHACLExecutor', shape: 'Shape', rule_node: 'rdflib def apply( self, data_graph: 'GraphLike', - focus_nodes: Union[Sequence['RDFNode'], None] = None, + focus_nodes: Optional[Sequence['RDFNode']] = None, + target_graph_identifier: Optional['URIRef'] = None, ) -> int: focus_list: Sequence['RDFNode'] if focus_nodes is not None: @@ -71,10 +75,12 @@ def apply( # uses target nodes to find focus nodes applicable_nodes = self.filter_conditions(focus_list, data_graph) all_added = 0 - iterate_limit = 100 + iterate_limit = int(TRIPLE_RULE_ITERATE_LIMIT) while True: if iterate_limit < 1: - raise ReportableRuntimeError("sh:rule iteration exceeded iteration limit of 100.") + raise ReportableRuntimeError( + f"sh:rule iteration exceeded iteration limit of {TRIPLE_RULE_ITERATE_LIMIT}." + ) iterate_limit -= 1 added = 0 to_add = [] @@ -91,8 +97,15 @@ def apply( if this_added: added += 1 if added > 0: + if isinstance(data_graph, (rdflib.Dataset, rdflib.ConjunctiveGraph)): + if target_graph_identifier is not None: + target_graph = data_graph.get_context(target_graph_identifier) + else: + target_graph = data_graph.default_context + else: + target_graph = data_graph for i in to_add: - data_graph.add(cast(Tuple['RDFNode', 'RDFNode', 'RDFNode'], i)) + target_graph.add(cast(Tuple['RDFNode', 'RDFNode', 'RDFNode'], i)) all_added += added if self.iterate: continue # Jump up to iterate diff --git a/pyshacl/run_type.py b/pyshacl/run_type.py index 1a4623a..d4e4443 100644 --- a/pyshacl/run_type.py +++ b/pyshacl/run_type.py @@ -7,6 +7,8 @@ from pyshacl.errors import ReportableRuntimeError if TYPE_CHECKING: + from rdflib.term import URIRef + from pyshacl.pytypes import GraphLike @@ -19,7 +21,11 @@ def run(self): @classmethod def _run_pre_inference( - cls, target_graph: 'GraphLike', inference_option: str, logger: Optional[logging.Logger] = None + cls, + target_graph: 'GraphLike', + inference_option: str, + destination_graph_identifier: Optional['URIRef'] = None, + logger: Optional[logging.Logger] = None, ): """ Note, this is the OWL/RDFS pre-inference, @@ -55,21 +61,16 @@ def _run_pre_inference( "Error during creation of OWL-RL Deductive Closure\n{}".format(str(e.args[0])) ) if isinstance(target_graph, (rdflib.Dataset, rdflib.ConjunctiveGraph)): - named_graphs = [] - for i in target_graph.store.contexts(None): - if isinstance(i, rdflib.Graph): - named_graphs.append(i) - else: - named_graphs.append( - rdflib.Graph(target_graph.store, i, namespace_manager=target_graph.namespace_manager) - ) + target_graph.default_union = True + if destination_graph_identifier is not None: + destination_graph = target_graph.get_context(destination_graph_identifier) + else: + destination_graph = target_graph.default_context else: - named_graphs = [target_graph] + destination_graph = None try: - # I'd prefer to not have to infer every namged graph individually, but OWL-RL doesn't - # support doing inference on a Dataset/ConjunctiveGraph yet. (New release will be soon?) - for g in named_graphs: - inferencer.expand(g) + inferencer.expand(target_graph, destination=destination_graph) except Exception as e: # pragma: no cover + raise logger.error("Error while running OWL-RL Deductive Closure") raise ReportableRuntimeError("Error while running OWL-RL Deductive Closure\n{}".format(str(e.args[0]))) diff --git a/pyshacl/validator.py b/pyshacl/validator.py index 0a8a014..6c57340 100644 --- a/pyshacl/validator.py +++ b/pyshacl/validator.py @@ -151,7 +151,12 @@ def mix_in_ontology(self): else: to_graph = clone_graph(self.data_graph, identifier=self.data_graph.identifier) return inoculate(to_graph, self.ont_graph) - return inoculate_dataset(self.data_graph, self.ont_graph, self.data_graph if self.inplace else None) + return inoculate_dataset( + self.data_graph, + self.ont_graph, + self.data_graph if self.inplace else None, + URIRef("urn:pyshacl:inoculation"), + ) def make_executor(self) -> SHACLExecutor: return SHACLExecutor( @@ -194,7 +199,9 @@ def run(self): datagraph = clone_graph(datagraph) has_cloned = True self.logger.debug(f"Running pre-inferencing with option='{inference_option}'.") - self._run_pre_inference(datagraph, inference_option, logger=self.logger) + self._run_pre_inference( + datagraph, inference_option, URIRef("urn:pyshacl:inference"), logger=self.logger + ) self.pre_inferenced = True if not has_cloned and not self.inplace and self.options['advanced']: if self.options.get('sparql_mode', False): diff --git a/test/test_dash_validate.py b/test/test_dash_validate.py index 4c45b20..08fa04f 100644 --- a/test/test_dash_validate.py +++ b/test/test_dash_validate.py @@ -29,7 +29,8 @@ for x in walk(path.join(dash_files_dir, 'core')): for y in glob.glob(path.join(x[0], '*.test.ttl')): - dash_core_files.append((y, None)) + if "node/datatype-002" in y: + dash_core_files.append((y, None)) @pytest.mark.parametrize('target_file, shacl_file', dash_core_files) diff --git a/test/test_extra.py b/test/test_extra.py index 0b1b23f..0f4fdd4 100644 --- a/test/test_extra.py +++ b/test/test_extra.py @@ -307,7 +307,7 @@ def test_blank_node_string_generation(): ) conforms, graph, string = res assert not conforms - rx = r"^\s*Focus Node\:\s+\[.+rdf:type\s+.+exOnt\:PreschoolTeacher.*\]$" + rx = r"^\s*Focus Node\:\s+\[.+rdf:type\s+exOnt\:PreschoolTeacher.*\]$" matches = re.search(rx, string, flags=re.MULTILINE) assert matches diff --git a/test/test_inoculate.py b/test/test_inoculate.py new file mode 100644 index 0000000..5959278 --- /dev/null +++ b/test/test_inoculate.py @@ -0,0 +1,232 @@ +# -*- coding: utf-8 -*- +# +# Extra tests which are not part of the SHT or DASH test suites, +# nor the discrete issues tests or the cmdline_test file. +# The need for these tests are discovered by doing coverage checks and these +# are added as required. +import os +import re + +from rdflib import Graph, Dataset + +from pyshacl import validate +from pyshacl.errors import ReportableRuntimeError + +ontology_graph_text = """ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix exOnt: . + + a owl:Ontology ; + rdfs:label "An example extra-ontology file."@en . + +exOnt:Animal a rdfs:Class ; + rdfs:comment "The parent class for Humans and Pets"@en ; + rdfs:subClassOf owl:Thing . + +exOnt:Human a rdfs:Class ; + rdfs:comment "A Human being"@en ; + rdfs:subClassOf exOnt:Animal . + +exOnt:Pet a rdfs:Class ; + rdfs:comment "An animal owned by a human"@en ; + rdfs:subClassOf exOnt:Animal . + +exOnt:hasPet a rdf:Property ; + rdfs:domain exOnt:Human ; + rdfs:range exOnt:Pet . + +exOnt:nlegs a rdf:Property ; + rdfs:domain exOnt:Animal ; + rdfs:range xsd:integer . + +exOnt:Teacher a rdfs:Class ; + rdfs:comment "A Human who is a teacher."@en ; + rdfs:subClassOf exOnt:Human . + +exOnt:PreschoolTeacher a rdfs:Class ; + rdfs:comment "A Teacher who teaches preschool."@en ; + rdfs:subClassOf exOnt:Teacher . + +exOnt:Lizard a rdfs:Class ; + rdfs:subClassOf exOnt:Pet . + +exOnt:Goanna a rdfs:Class ; + rdfs:subClassOf exOnt:Lizard . + +""" + +ontology_ds_text = """ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . +@prefix exOnt: . +# This is a TRIG file. + + { + a owl:Ontology ; + rdfs:label "An example extra-ontology file."@en . + +exOnt:Animal a rdfs:Class ; + rdfs:comment "The parent class for Humans and Pets"@en ; + rdfs:subClassOf owl:Thing . + +exOnt:Human a rdfs:Class ; + rdfs:comment "A Human being"@en ; + rdfs:subClassOf exOnt:Animal . + +exOnt:Pet a rdfs:Class ; + rdfs:comment "An animal owned by a human"@en ; + rdfs:subClassOf exOnt:Animal . + +exOnt:hasPet a rdf:Property ; + rdfs:domain exOnt:Human ; + rdfs:range exOnt:Pet . + +exOnt:nlegs a rdf:Property ; + rdfs:domain exOnt:Animal ; + rdfs:range xsd:integer . + +exOnt:Teacher a rdfs:Class ; + rdfs:comment "A Human who is a teacher."@en ; + rdfs:subClassOf exOnt:Human . + +exOnt:PreschoolTeacher a rdfs:Class ; + rdfs:comment "A Teacher who teaches preschool."@en ; + rdfs:subClassOf exOnt:Teacher . + +exOnt:Lizard a rdfs:Class ; + rdfs:subClassOf exOnt:Pet . + +exOnt:Goanna a rdfs:Class ; + rdfs:subClassOf exOnt:Lizard . +} +""" + +shacl_file_text = """ +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . +@prefix exShape: . +@prefix exOnt: . + + a owl:Ontology ; + rdfs:label "Example Shapes File"@en . + +exShape:HumanShape a sh:NodeShape ; + sh:property [ + sh:class exOnt:Pet ; + sh:path exOnt:hasPet ; + ] ; + sh:property [ + sh:datatype xsd:integer ; + sh:path exOnt:nLegs ; + sh:maxInclusive 2 ; + sh:minInclusive 2 ; + ] ; + sh:targetClass exOnt:Human . + +exShape:AnimalShape a sh:NodeShape ; + sh:property [ + sh:datatype xsd:integer ; + sh:path exOnt:nLegs ; + sh:maxInclusive 4 ; + sh:minInclusive 1 ; + ] ; + sh:targetClass exOnt:Animal . +""" + +data_file_text = """ +@prefix rdf: . +@prefix xsd: . +@prefix exOnt: . +@prefix ex: . + +ex:Human1 rdf:type exOnt:PreschoolTeacher ; + rdf:label "Amy" ; + exOnt:nLegs "2"^^xsd:integer ; + exOnt:hasPet ex:Pet1 . + +ex:Pet1 rdf:type exOnt:Goanna ; + rdf:label "Sebastian" ; + exOnt:nLegs "4"^^xsd:integer . +""" + +data_file_text_bad = """ +@prefix rdf: . +@prefix xsd: . +@prefix exOnt: . +@prefix ex: . + +ex:Human1 rdf:type exOnt:PreschoolTeacher ; + rdf:label "Amy" ; + exOnt:nLegs "2"^^xsd:integer ; + exOnt:hasPet "Sebastian"^^xsd:string . + +ex:Pet1 rdf:type exOnt:Goanna ; + rdf:label "Sebastian" ; + exOnt:nLegs "four"^^xsd:string . +""" + + +def test_validate_ds_with_graph_ontology(): + ds = Dataset() + ds.parse(data=data_file_text_bad, format='turtle') + extra_g = Graph() + extra_g.parse(data=ontology_graph_text, format='turtle') + + ds_len = len(ds) + res = validate( + ds, shacl_graph=shacl_file_text, shacl_graph_format='turtle', ont_graph=extra_g, inference='rdfs', debug=True + ) + conforms, graph, string = res + assert not conforms + # Assert that the dataset is unchanged + ds_len2 = len(ds) + assert ds_len2 == ds_len + +def test_validate_ds_with_ds_ontology(): + ds = Dataset() + ds.parse(data=data_file_text_bad, format='turtle') + extra_ds = Dataset() + extra_ds.parse(data=ontology_ds_text, format='trig') + + ds_len = len(ds) + res = validate( + ds, shacl_graph=shacl_file_text, shacl_graph_format='turtle', ont_graph=extra_ds, inference='rdfs', debug=True + ) + conforms, graph, string = res + assert not conforms + # Assert that the dataset is unchanged + ds_len2 = len(ds) + assert ds_len2 == ds_len + +def test_validate_ds_with_ds_ontology_inplace(): + ds = Dataset() + ds.parse(data=data_file_text_bad, format='turtle') + extra_ds = Dataset() + extra_ds.parse(data=ontology_ds_text, format='trig') + + ds_len = len(ds) + res = validate( + ds, + shacl_graph=shacl_file_text, + shacl_graph_format='turtle', + ont_graph=extra_ds, + inference='rdfs', + debug=True, + inplace=True + ) + conforms, graph, string = res + assert not conforms + # Assert that the dataset is changed + ds_len2 = len(ds) + assert ds_len2 != ds_len + a = ds.serialize(format='trig') + print(a) + From c5af68cabea0e6495d7fcb631e5c7e477a11ec7c Mon Sep 17 00:00:00 2001 From: Ashley Sommer Date: Fri, 1 Nov 2024 17:24:50 +1000 Subject: [PATCH 3/3] Bump to newest MyPy, run mypy in 3.9 mode, fix type errors --- Makefile | 4 +- poetry.lock | 165 ++++++----------------- pyproject.toml | 12 +- pyshacl/extras/js/rules.py | 25 +++- pyshacl/inference/custom_rdfs_closure.py | 5 +- pyshacl/rdfutil/inoculate.py | 4 +- 6 files changed, 68 insertions(+), 147 deletions(-) diff --git a/Makefile b/Makefile index 01a54e3..456e96a 100644 --- a/Makefile +++ b/Makefile @@ -56,9 +56,9 @@ endif .PHONY: type-check type-check: venvcheck ## Validate with MyPy in check-only mode ifeq ("$(FilePath)", "") - poetry run python3 -m mypy --ignore-missing-imports pyshacl + poetry run python3 -m mypy --python-version 3.9 --ignore-missing-imports pyshacl else - poetry run python3 -m mypy --ignore-missing-imports "$(FilePath)" + poetry run python3 -m mypy --python-version 3.9 --ignore-missing-imports "$(FilePath)" endif .PHONY: upgrade diff --git a/poetry.lock b/poetry.lock index c8759dd..ad8bcb5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -375,103 +375,57 @@ typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} [[package]] name = "mypy" -version = "0.812" +version = "1.13.0" description = "Optional static typing for Python" optional = true -python-versions = ">=3.5" -files = [ - {file = "mypy-0.812-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49"}, - {file = "mypy-0.812-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c"}, - {file = "mypy-0.812-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521"}, - {file = "mypy-0.812-cp35-cp35m-win_amd64.whl", hash = "sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb"}, - {file = "mypy-0.812-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a"}, - {file = "mypy-0.812-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c"}, - {file = "mypy-0.812-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6"}, - {file = "mypy-0.812-cp36-cp36m-win_amd64.whl", hash = "sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064"}, - {file = "mypy-0.812-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56"}, - {file = "mypy-0.812-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8"}, - {file = "mypy-0.812-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7"}, - {file = "mypy-0.812-cp37-cp37m-win_amd64.whl", hash = "sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564"}, - {file = "mypy-0.812-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506"}, - {file = "mypy-0.812-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5"}, - {file = "mypy-0.812-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66"}, - {file = "mypy-0.812-cp38-cp38-win_amd64.whl", hash = "sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e"}, - {file = "mypy-0.812-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a"}, - {file = "mypy-0.812-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a"}, - {file = "mypy-0.812-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97"}, - {file = "mypy-0.812-cp39-cp39-win_amd64.whl", hash = "sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df"}, - {file = "mypy-0.812-py3-none-any.whl", hash = "sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4"}, - {file = "mypy-0.812.tar.gz", hash = "sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119"}, -] - -[package.dependencies] -mypy-extensions = ">=0.4.3,<0.5.0" -typed-ast = ">=1.4.0,<1.5.0" -typing-extensions = ">=3.7.4" - -[package.extras] -dmypy = ["psutil (>=4.0)"] - -[[package]] -name = "mypy" -version = "0.991" -description = "Optional static typing for Python" -optional = true -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "mypy-0.991-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7d17e0a9707d0772f4a7b878f04b4fd11f6f5bcb9b3813975a9b13c9332153ab"}, - {file = "mypy-0.991-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0714258640194d75677e86c786e80ccf294972cc76885d3ebbb560f11db0003d"}, - {file = "mypy-0.991-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0c8f3be99e8a8bd403caa8c03be619544bc2c77a7093685dcf308c6b109426c6"}, - {file = "mypy-0.991-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9ec663ed6c8f15f4ae9d3c04c989b744436c16d26580eaa760ae9dd5d662eb"}, - {file = "mypy-0.991-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:4307270436fd7694b41f913eb09210faff27ea4979ecbcd849e57d2da2f65305"}, - {file = "mypy-0.991-cp310-cp310-win_amd64.whl", hash = "sha256:901c2c269c616e6cb0998b33d4adbb4a6af0ac4ce5cd078afd7bc95830e62c1c"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d13674f3fb73805ba0c45eb6c0c3053d218aa1f7abead6e446d474529aafc372"}, - {file = "mypy-0.991-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1c8cd4fb70e8584ca1ed5805cbc7c017a3d1a29fb450621089ffed3e99d1857f"}, - {file = "mypy-0.991-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:209ee89fbb0deed518605edddd234af80506aec932ad28d73c08f1400ef80a33"}, - {file = "mypy-0.991-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37bd02ebf9d10e05b00d71302d2c2e6ca333e6c2a8584a98c00e038db8121f05"}, - {file = "mypy-0.991-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:26efb2fcc6b67e4d5a55561f39176821d2adf88f2745ddc72751b7890f3194ad"}, - {file = "mypy-0.991-cp311-cp311-win_amd64.whl", hash = "sha256:3a700330b567114b673cf8ee7388e949f843b356a73b5ab22dd7cff4742a5297"}, - {file = "mypy-0.991-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:1f7d1a520373e2272b10796c3ff721ea1a0712288cafaa95931e66aa15798813"}, - {file = "mypy-0.991-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:641411733b127c3e0dab94c45af15fea99e4468f99ac88b39efb1ad677da5711"}, - {file = "mypy-0.991-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3d80e36b7d7a9259b740be6d8d906221789b0d836201af4234093cae89ced0cd"}, - {file = "mypy-0.991-cp37-cp37m-win_amd64.whl", hash = "sha256:e62ebaad93be3ad1a828a11e90f0e76f15449371ffeecca4a0a0b9adc99abcef"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b86ce2c1866a748c0f6faca5232059f881cda6dda2a893b9a8373353cfe3715a"}, - {file = "mypy-0.991-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ac6e503823143464538efda0e8e356d871557ef60ccd38f8824a4257acc18d93"}, - {file = "mypy-0.991-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0cca5adf694af539aeaa6ac633a7afe9bbd760df9d31be55ab780b77ab5ae8bf"}, - {file = "mypy-0.991-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12c56bf73cdab116df96e4ff39610b92a348cc99a1307e1da3c3768bbb5b135"}, - {file = "mypy-0.991-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:652b651d42f155033a1967739788c436491b577b6a44e4c39fb340d0ee7f0d70"}, - {file = "mypy-0.991-cp38-cp38-win_amd64.whl", hash = "sha256:4175593dc25d9da12f7de8de873a33f9b2b8bdb4e827a7cae952e5b1a342e243"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:98e781cd35c0acf33eb0295e8b9c55cdbef64fcb35f6d3aa2186f289bed6e80d"}, - {file = "mypy-0.991-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6d7464bac72a85cb3491c7e92b5b62f3dcccb8af26826257760a552a5e244aa5"}, - {file = "mypy-0.991-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c9166b3f81a10cdf9b49f2d594b21b31adadb3d5e9db9b834866c3258b695be3"}, - {file = "mypy-0.991-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8472f736a5bfb159a5e36740847808f6f5b659960115ff29c7cecec1741c648"}, - {file = "mypy-0.991-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5e80e758243b97b618cdf22004beb09e8a2de1af481382e4d84bc52152d1c476"}, - {file = "mypy-0.991-cp39-cp39-win_amd64.whl", hash = "sha256:74e259b5c19f70d35fcc1ad3d56499065c601dfe94ff67ae48b85596b9ec1461"}, - {file = "mypy-0.991-py3-none-any.whl", hash = "sha256:de32edc9b0a7e67c2775e574cb061a537660e51210fbf6006b0b36ea695ae9bb"}, - {file = "mypy-0.991.tar.gz", hash = "sha256:3c0165ba8f354a6d9881809ef29f1a9318a236a6d81c690094c5df32107bde06"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a"}, + {file = "mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80"}, + {file = "mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7"}, + {file = "mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f"}, + {file = "mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d"}, + {file = "mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d"}, + {file = "mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b"}, + {file = "mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73"}, + {file = "mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5"}, + {file = "mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e"}, + {file = "mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2"}, + {file = "mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0"}, + {file = "mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7"}, + {file = "mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62"}, + {file = "mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8"}, + {file = "mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7"}, + {file = "mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a"}, + {file = "mypy-1.13.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb"}, + {file = "mypy-1.13.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b"}, + {file = "mypy-1.13.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74"}, + {file = "mypy-1.13.0-cp38-cp38-win_amd64.whl", hash = "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc"}, + {file = "mypy-1.13.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732"}, + {file = "mypy-1.13.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc"}, + {file = "mypy-1.13.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d"}, + {file = "mypy-1.13.0-cp39-cp39-win_amd64.whl", hash = "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24"}, + {file = "mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a"}, + {file = "mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e"}, ] [package.dependencies] -mypy-extensions = ">=0.4.3" +mypy-extensions = ">=1.0.0" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = ">=3.10" +typing-extensions = ">=4.6.0" [package.extras] dmypy = ["psutil (>=4.0)"] +faster-cache = ["orjson"] install-types = ["pip"] -python2 = ["typed-ast (>=1.4.0,<2)"] +mypyc = ["setuptools (>=50)"] reports = ["lxml"] -[[package]] -name = "mypy-extensions" -version = "0.4.4" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -optional = true -python-versions = ">=2.7" -files = [ - {file = "mypy_extensions-0.4.4.tar.gz", hash = "sha256:c8b707883a96efe9b4bb3aaf0dcc07e7e217d7d8368eec4db4049ee9e142f4fd"}, -] - [[package]] name = "mypy-extensions" version = "1.0.0" @@ -902,45 +856,6 @@ files = [ {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] -[[package]] -name = "typed-ast" -version = "1.4.3" -description = "a fork of Python 2 and 3 ast modules with type comment support" -optional = true -python-versions = "*" -files = [ - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, - {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, - {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, - {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, - {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, - {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, - {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, - {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, - {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, - {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, - {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, - {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, - {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, - {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, - {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, - {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, - {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, - {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, -] - [[package]] name = "types-setuptools" version = "75.2.0.20241025" @@ -1229,11 +1144,11 @@ type = ["pytest-mypy"] [extras] dev-coverage = ["coverage", "platformdirs", "pytest-cov"] dev-lint = ["black", "platformdirs", "ruff"] -dev-type-checking = ["mypy", "mypy", "platformdirs", "types-setuptools"] +dev-type-checking = ["mypy", "platformdirs", "types-setuptools"] http = ["sanic", "sanic-cors", "sanic-ext"] js = ["pyduktape2"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<4" -content-hash = "e8ab08d3d98a3bb0c30c9bf0e5ea4471658ce21ba038910317558b95af9c80ad" +content-hash = "2bff6bd34272f776eec914e408c9f4b75740c6eecc6beb82f2d3cded1e7aa602" diff --git a/pyproject.toml b/pyproject.toml index 030e03a..af621a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,10 +76,7 @@ sanic-ext = {version=">=23.3, <23.6", optional=true} #For the HTTP service sanic-cors = {version="2.2.0", optional=true} #For the HTTP service ruff = {version="^0.1.5", optional=true} black = {version="24.3.0", optional=true} -mypy = [ - {version=">=0.812,<0.900", optional=true, python = "<3.10"}, - {version=">=0.900,<0.1000", optional=true, python = ">=3.10"} - ] +mypy = {version=">=1.13.0", optional=true} types-setuptools = {version="*", optional=true} platformdirs = {version="*", optional=true} coverage = {version=">6,<7,!=6.0.*,!=6.1,!=6.1.1", optional=true} @@ -91,10 +88,7 @@ coverage = {version=">6,<7,!=6.0.*,!=6.1,!=6.1.1", optional=true} pytest-cov = {version="^2.8.1", optional=true} ruff = {version="^0.1.5", optional=true} black = {version="24.3.0", optional=true} -mypy = [ - {version=">=0.812,<0.900", optional=true, python = "<3.10"}, - {version=">=0.900,<0.1000", optional=true, python = ">=3.10"} - ] +mypy = {version=">=1.13.0", optional=true} types-setuptools = {version="*", optional=true} platformdirs = {version="*", optional=true} @@ -249,7 +243,7 @@ commands_pre = poetry run pip3 install "mypy>=0.812" "types-setuptools" commands = - poetry show - poetry run python3 -m mypy --ignore-missing-imports pyshacl + poetry run python3 -m mypy --python-version 3.9 --ignore-missing-imports pyshacl [gh] # Don't include lint or type-checking in gh-actions matrix diff --git a/pyshacl/extras/js/rules.py b/pyshacl/extras/js/rules.py index bd16557..9ae2acc 100644 --- a/pyshacl/extras/js/rules.py +++ b/pyshacl/extras/js/rules.py @@ -1,7 +1,6 @@ # # -import typing -from typing import List, Sequence, Union +from typing import TYPE_CHECKING, List, Optional, Sequence, Union import rdflib @@ -11,7 +10,9 @@ from .js_executable import JSExecutable -if typing.TYPE_CHECKING: +if TYPE_CHECKING: + + from rdflib.term import URIRef from pyshacl.pytypes import GraphLike, RDFNode, SHACLExecutor from pyshacl.shape import Shape @@ -19,6 +20,8 @@ SH_JSRule = SH.JSRule +JS_RULE_ITERATE_LIMIT = 100 + class JSRule(SHACLRule): __slots__ = ('js_exe',) @@ -32,6 +35,7 @@ def apply( self, data_graph: 'GraphLike', focus_nodes: Union[Sequence['RDFNode'], None] = None, + target_graph_identifier: Optional['URIRef'] = None, ) -> int: focus_list: Sequence['RDFNode'] if focus_nodes is not None: @@ -48,10 +52,10 @@ def apply( return 0 focus_list = filtered_focus_nodes all_added = 0 - iterate_limit = 100 + iterate_limit = int(JS_RULE_ITERATE_LIMIT) while True: if iterate_limit < 1: - raise ReportableRuntimeError("Local rule iteration exceeded iteration limit of 100.") + raise ReportableRuntimeError(f"JS rule iteration exceeded iteration limit of {JS_RULE_ITERATE_LIMIT}.") iterate_limit -= 1 added = 0 applicable_nodes = self.filter_conditions(focus_list, data_graph) @@ -72,10 +76,17 @@ def apply( if this_added: added += 1 if added > 0: - all_added += added + if isinstance(data_graph, (rdflib.Dataset, rdflib.ConjunctiveGraph)): + if target_graph_identifier is not None: + target_graph = data_graph.get_context(target_graph_identifier) + else: + target_graph = data_graph.default_context + else: + target_graph = data_graph for s in sets_to_add: for t in s: - data_graph.add(t) + target_graph.add(t) + all_added += added if self.iterate: continue # Jump up to iterate else: diff --git a/pyshacl/inference/custom_rdfs_closure.py b/pyshacl/inference/custom_rdfs_closure.py index fd40d51..e222c84 100644 --- a/pyshacl/inference/custom_rdfs_closure.py +++ b/pyshacl/inference/custom_rdfs_closure.py @@ -55,8 +55,9 @@ class CustomRDFSOWLRLSemantics(CustomRDFSSemantics, OWLRL_Semantics): ] def __init__(self, graph, axioms, daxioms, rdfs: bool = True, destination: Optional['Graph'] = None): - OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) - CustomRDFSSemantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) + # MyPy thinks this is object.__init__ and says the kwargs are incorrect for __init__ + OWLRL_Semantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) # type: ignore[arg-type, call-arg] + CustomRDFSSemantics.__init__(self, graph, axioms, daxioms, rdfs=rdfs, destination=destination) # type: ignore[arg-type, call-arg] self.rdfs = True # noinspection PyMethodMayBeStatic diff --git a/pyshacl/rdfutil/inoculate.py b/pyshacl/rdfutil/inoculate.py index 28deff7..83e85fc 100644 --- a/pyshacl/rdfutil/inoculate.py +++ b/pyshacl/rdfutil/inoculate.py @@ -32,8 +32,8 @@ def inoculate(data_graph: rdflib.Graph, ontology: GraphLike) -> rdflib.Graph: data_graph_ns = data_graph.namespace_manager if isinstance(ontology, (rdflib.ConjunctiveGraph, rdflib.Dataset)): - # always set default context true on the ontology DS - ontology.default_context = True + # always set default_union true on the ontology DS + ontology.default_union = True # Bind any missing ontology namespaces in the DataGraph NS manager. if ontology_ns is not data_graph_ns: data_graph_prefixes = {p: n for (p, n) in data_graph_ns.namespaces()}