Repository: RDFLib/pySHACL (language: python)

Issue:
pySHACL just a 'driver'?
given some [discussion](https://github.com/oxigraph/oxigraph/issues/130), would it make sense to just have this project just drive some graph implementation via  sparql?

on a [related note](https://github.com/RDFLib/pySHACL/issues/60), the most core functionality exhibited here is that it's some entailment engine based on shacl but it could also be based on sparql . (shacl constraints/rules can be sparql ask/construct right?)


Discussion / hints:
Hi @majidaldo 
You are right that it is possible to implement all SHACL constraints using standardized SPARQL implementations. Indeed the W3C SHACL Spec document shows example SPARQL implementations of each constraint. So it would be simple to use those given.

PySHACL operates in exactly the opposite manner, and does so deliberately. PySHACL uses the python RDFLib library under the hood, to perform RDF functionality. The SPARQL parser and SPARQL execution engine in RDFLib are famously slow. It is a design goal since the very early stages of PySHACL to prioritise speed of execution, so it is a deliberate decision to avoid using the SPARQL engine as much as possible. Each constraint is hand-written and hand-tweaked using Python to perform raw graph lookups, to extract maximum performance from the validation engine.

I agree that developing a new implementation of a Python SHACL Validation engine that operates as simply a driver over a set of canonical SPARQL Constraints from a given managed constraint registry, sounds like a great project to undertake, and would be useful to have to compare engines. However PySHACL is not the place to do that.

Fix patch:
diff --git a/README.md b/README.md
index e757916..18707b8 100644
--- a/README.md
+++ b/README.md
@@ -185,6 +185,33 @@ You can get an equivalent of the Command Line Tool using the Python3 executable
 $ python3 -m pyshacl
 ```
 
+## Errors
+Under certain circumstances pySHACL can produce a [`Validation Failure`](https://www.w3.org/TR/shacl/#failures). This is a formal error [defined by the SHACL specification](https://www.w3.org/TR/shacl/#failures) and is required to be produced as a result of specific conditions within the SHACL graph that leads to the inability to complete the validation.
+If the validator produces a [`Validation Failure`](https://www.w3.org/TR/shacl/#failures), the `results_graph` variable returned by the `validate()` function will be an instance of `ValidationFailure`.
+See the `message` attribute on that instance to get more information about the validation failure.
+
+Other errors the validator can generate:
+- `ShapeLoadError`: This error is thrown when a SHACL Shape in the SHACL graph is in an invalid state and cannot be loaded into the validation engine.
+- `ConstraintLoadError`: This error is thrown when a SHACL Constraint Component is in an invalid state and cannot be loaded into the validation engine.
+- `ReportableRuntimeError`: An error occurred for a different reason, and the reason should be communicated back to the user of the validator.
+- `RuntimeError`: The validator encountered a situation that caused it to throw an error, but the reason does not concern the user.
+
+Unlike `ValidationFailure`, these errors are not passed back as a result by the `validate()` function, but thrown as exceptions by the validation engine and must be
+caught in a `try ... except` block.
+In the case of `ShapeLoadError` and `ConstraintLoadError`, see the `str()` string representation of the exception instance for the error message along with a link to the relevant section in the SHACL spec document.
+
+## SPARQL Remote Graph Mode
+
+_**PySHACL now has a built-in SPARQL Remote Graph Mode, which allows you to validate a data graph that is stored on a remote server.**_
+
+- In this mode, PySHAL operates strictly in read-only mode, and does not modify the remote data graph.
+- Some features are disabled when using the SPARQL Remote Graph Mode:
+    - "rdfs" and "owl" inferencing is not allowed (because the remote graph is read-only, it cannot be expanded)
+    - Extra Ontology file (Inoculation or Mix-In mode) is disabled (because the remote graph is read-only)
+    - SHACL Rules (Advanced mode SPARQL-Rules) are not allowed (because the remote graph is read-only)
+    - All SHACL-JS features are disabled (this is not safe when operating on a remote graph)
+    - "inplace" mode is disabled (actually all operations on the remote data graph are inherently performed in-place)
+
 ## Integrated OpenAPI-3.0-compatible HTTP REST Service
 
 PySHACL now has a built-in validation service, exposed via an OpenAPI3.0-compatible REST API.
@@ -223,23 +250,6 @@ To view the OpenAPI3 schema see `http://127.0.0.1:8099/docs/openapi.json`
 - `PYSHACL_SERVER_HOSTNAME=example.org` when you are hosting the server behind a reverse-proxy or in a containerised environment, use this so PySHACL server knows what your externally facing hostname is
 
 
-
-## Errors
-Under certain circumstances pySHACL can produce a `Validation Failure`. This is a formal error defined by the SHACL specification and is required to be produced as a result of specific conditions within the SHACL graph.
-If the validator produces a `Validation Failure`, the `results_graph` variable returned by the `validate()` function will be an instance of `ValidationFailure`.
-See the `message` attribute on that instance to get more information about the validation failure.
-
-Other errors the validator can generate:
-- `ShapeLoadError`: This error is thrown when a SHACL Shape in the SHACL graph is in an invalid state and cannot be loaded into the validation engine.
-- `ConstraintLoadError`: This error is thrown when a SHACL Constraint Component is in an invalid state and cannot be loaded into the validation engine.
-- `ReportableRuntimeError`: An error occurred for a different reason, and the reason should be communicated back to the user of the validator.
-- `RuntimeError`: The validator encountered a situation that caused it to throw an error, but the reason does concern the user.
-
-Unlike `ValidationFailure`, these errors are not passed back as a result by the `validate()` function, but thrown as exceptions by the validation engine and must be
-caught in a `try ... except` block.
-In the case of `ShapeLoadError` and `ConstraintLoadError`, see the `str()` string representation of the exception instance for the error message along with a link to the relevant section in the SHACL spec document.
-
-
 ## Windows CLI
 
 [Pyinstaller](https://www.pyinstaller.org/) can be
diff --git a/poetry.lock b/poetry.lock
index 4511efa..3f6c1bb 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand.
+# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
 
 [[package]]
 name = "aiofiles"
@@ -146,13 +146,13 @@ toml = ["tomli"]
 
 [[package]]
 name = "exceptiongroup"
-version = "1.2.0"
+version = "1.2.1"
 description = "Backport of PEP 654 (exception groups)"
 optional = false
 python-versions = ">=3.7"
 files = [
-    {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"},
-    {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"},
+    {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"},
+    {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"},
 ]
 
 [package.extras]
@@ -518,28 +518,29 @@ files = [
 
 [[package]]
 name = "platformdirs"
-version = "4.2.0"
-description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "4.2.2"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"},
-    {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"},
+    {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
+    {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
 ]
 
 [package.extras]
 docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
 test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
+type = ["mypy (>=1.8)"]
 
 [[package]]
 name = "pluggy"
-version = "1.4.0"
+version = "1.5.0"
 description = "plugin and hook calling mechanisms for python"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
-    {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
+    {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+    {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
 ]
 
 [package.extras]
@@ -945,98 +946,111 @@ files = [
 
 [[package]]
 name = "types-setuptools"
-version = "69.2.0.20240317"
+version = "70.0.0.20240524"
 description = "Typing stubs for setuptools"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "types-setuptools-69.2.0.20240317.tar.gz", hash = "sha256:b607c4c48842ef3ee49dc0c7fe9c1bad75700b071e1018bb4d7e3ac492d47048"},
-    {file = "types_setuptools-69.2.0.20240317-py3-none-any.whl", hash = "sha256:cf91ff7c87ab7bf0625c3f0d4d90427c9da68561f3b0feab77977aaf0bbf7531"},
+    {file = "types-setuptools-70.0.0.20240524.tar.gz", hash = "sha256:e31fee7b9d15ef53980526579ac6089b3ae51a005a281acf97178e90ac71aff6"},
+    {file = "types_setuptools-70.0.0.20240524-py3-none-any.whl", hash = "sha256:8f5379b9948682d72a9ab531fbe52932e84c4f38deda570255f9bae3edd766bc"},
 ]
 
 [[package]]
 name = "typing-extensions"
-version = "4.10.0"
+version = "4.12.1"
 description = "Backported and Experimental Type Hints for Python 3.8+"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"},
-    {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"},
+    {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"},
+    {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"},
 ]
 
 [[package]]
 name = "ujson"
-version = "5.9.0"
+version = "5.10.0"
 description = "Ultra fast JSON encoder and decoder for Python"
 optional = true
 python-versions = ">=3.8"
 files = [
-    {file = "ujson-5.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ab71bf27b002eaf7d047c54a68e60230fbd5cd9da60de7ca0aa87d0bccead8fa"},
-    {file = "ujson-5.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a365eac66f5aa7a7fdf57e5066ada6226700884fc7dce2ba5483538bc16c8c5"},
-    {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e015122b337858dba5a3dc3533af2a8fc0410ee9e2374092f6a5b88b182e9fcc"},
-    {file = "ujson-5.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:779a2a88c53039bebfbccca934430dabb5c62cc179e09a9c27a322023f363e0d"},
-    {file = "ujson-5.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10ca3c41e80509fd9805f7c149068fa8dbee18872bbdc03d7cca928926a358d5"},
-    {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a566e465cb2fcfdf040c2447b7dd9718799d0d90134b37a20dff1e27c0e9096"},
-    {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f833c529e922577226a05bc25b6a8b3eb6c4fb155b72dd88d33de99d53113124"},
-    {file = "ujson-5.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b68a0caab33f359b4cbbc10065c88e3758c9f73a11a65a91f024b2e7a1257106"},
-    {file = "ujson-5.9.0-cp310-cp310-win32.whl", hash = "sha256:7cc7e605d2aa6ae6b7321c3ae250d2e050f06082e71ab1a4200b4ae64d25863c"},
-    {file = "ujson-5.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:a6d3f10eb8ccba4316a6b5465b705ed70a06011c6f82418b59278fbc919bef6f"},
-    {file = "ujson-5.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b23bbb46334ce51ddb5dded60c662fbf7bb74a37b8f87221c5b0fec1ec6454b"},
-    {file = "ujson-5.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6974b3a7c17bbf829e6c3bfdc5823c67922e44ff169851a755eab79a3dd31ec0"},
-    {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5964ea916edfe24af1f4cc68488448fbb1ec27a3ddcddc2b236da575c12c8ae"},
-    {file = "ujson-5.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ba7cac47dd65ff88571eceeff48bf30ed5eb9c67b34b88cb22869b7aa19600d"},
-    {file = "ujson-5.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6bbd91a151a8f3358c29355a491e915eb203f607267a25e6ab10531b3b157c5e"},
-    {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:829a69d451a49c0de14a9fecb2a2d544a9b2c884c2b542adb243b683a6f15908"},
-    {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a807ae73c46ad5db161a7e883eec0fbe1bebc6a54890152ccc63072c4884823b"},
-    {file = "ujson-5.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8fc2aa18b13d97b3c8ccecdf1a3c405f411a6e96adeee94233058c44ff92617d"},
-    {file = "ujson-5.9.0-cp311-cp311-win32.whl", hash = "sha256:70e06849dfeb2548be48fdd3ceb53300640bc8100c379d6e19d78045e9c26120"},
-    {file = "ujson-5.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:7309d063cd392811acc49b5016728a5e1b46ab9907d321ebbe1c2156bc3c0b99"},
-    {file = "ujson-5.9.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:20509a8c9f775b3a511e308bbe0b72897ba6b800767a7c90c5cca59d20d7c42c"},
-    {file = "ujson-5.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b28407cfe315bd1b34f1ebe65d3bd735d6b36d409b334100be8cdffae2177b2f"},
-    {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d302bd17989b6bd90d49bade66943c78f9e3670407dbc53ebcf61271cadc399"},
-    {file = "ujson-5.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f21315f51e0db8ee245e33a649dd2d9dce0594522de6f278d62f15f998e050e"},
-    {file = "ujson-5.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5635b78b636a54a86fdbf6f027e461aa6c6b948363bdf8d4fbb56a42b7388320"},
-    {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:82b5a56609f1235d72835ee109163c7041b30920d70fe7dac9176c64df87c164"},
-    {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:5ca35f484622fd208f55041b042d9d94f3b2c9c5add4e9af5ee9946d2d30db01"},
-    {file = "ujson-5.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:829b824953ebad76d46e4ae709e940bb229e8999e40881338b3cc94c771b876c"},
-    {file = "ujson-5.9.0-cp312-cp312-win32.whl", hash = "sha256:25fa46e4ff0a2deecbcf7100af3a5d70090b461906f2299506485ff31d9ec437"},
-    {file = "ujson-5.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:60718f1720a61560618eff3b56fd517d107518d3c0160ca7a5a66ac949c6cf1c"},
-    {file = "ujson-5.9.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d581db9db9e41d8ea0b2705c90518ba623cbdc74f8d644d7eb0d107be0d85d9c"},
-    {file = "ujson-5.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ff741a5b4be2d08fceaab681c9d4bc89abf3c9db600ab435e20b9b6d4dfef12e"},
-    {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdcb02cabcb1e44381221840a7af04433c1dc3297af76fde924a50c3054c708c"},
-    {file = "ujson-5.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e208d3bf02c6963e6ef7324dadf1d73239fb7008491fdf523208f60be6437402"},
-    {file = "ujson-5.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4b3917296630a075e04d3d07601ce2a176479c23af838b6cf90a2d6b39b0d95"},
-    {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0c4d6adb2c7bb9eb7c71ad6f6f612e13b264942e841f8cc3314a21a289a76c4e"},
-    {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0b159efece9ab5c01f70b9d10bbb77241ce111a45bc8d21a44c219a2aec8ddfd"},
-    {file = "ujson-5.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f0cb4a7814940ddd6619bdce6be637a4b37a8c4760de9373bac54bb7b229698b"},
-    {file = "ujson-5.9.0-cp38-cp38-win32.whl", hash = "sha256:dc80f0f5abf33bd7099f7ac94ab1206730a3c0a2d17549911ed2cb6b7aa36d2d"},
-    {file = "ujson-5.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:506a45e5fcbb2d46f1a51fead991c39529fc3737c0f5d47c9b4a1d762578fc30"},
-    {file = "ujson-5.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d0fd2eba664a22447102062814bd13e63c6130540222c0aa620701dd01f4be81"},
-    {file = "ujson-5.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bdf7fc21a03bafe4ba208dafa84ae38e04e5d36c0e1c746726edf5392e9f9f36"},
-    {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2f909bc08ce01f122fd9c24bc6f9876aa087188dfaf3c4116fe6e4daf7e194f"},
-    {file = "ujson-5.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd4ea86c2afd41429751d22a3ccd03311c067bd6aeee2d054f83f97e41e11d8f"},
-    {file = "ujson-5.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:63fb2e6599d96fdffdb553af0ed3f76b85fda63281063f1cb5b1141a6fcd0617"},
-    {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:32bba5870c8fa2a97f4a68f6401038d3f1922e66c34280d710af00b14a3ca562"},
-    {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37ef92e42535a81bf72179d0e252c9af42a4ed966dc6be6967ebfb929a87bc60"},
-    {file = "ujson-5.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f69f16b8f1c69da00e38dc5f2d08a86b0e781d0ad3e4cc6a13ea033a439c4844"},
-    {file = "ujson-5.9.0-cp39-cp39-win32.whl", hash = "sha256:3382a3ce0ccc0558b1c1668950008cece9bf463ebb17463ebf6a8bfc060dae34"},
-    {file = "ujson-5.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:6adef377ed583477cf005b58c3025051b5faa6b8cc25876e594afbb772578f21"},
-    {file = "ujson-5.9.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ffdfebd819f492e48e4f31c97cb593b9c1a8251933d8f8972e81697f00326ff1"},
-    {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4eec2ddc046360d087cf35659c7ba0cbd101f32035e19047013162274e71fcf"},
-    {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fbb90aa5c23cb3d4b803c12aa220d26778c31b6e4b7a13a1f49971f6c7d088e"},
-    {file = "ujson-5.9.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba0823cb70866f0d6a4ad48d998dd338dce7314598721bc1b7986d054d782dfd"},
-    {file = "ujson-5.9.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:4e35d7885ed612feb6b3dd1b7de28e89baaba4011ecdf995e88be9ac614765e9"},
-    {file = "ujson-5.9.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b048aa93eace8571eedbd67b3766623e7f0acbf08ee291bef7d8106210432427"},
-    {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:323279e68c195110ef85cbe5edce885219e3d4a48705448720ad925d88c9f851"},
-    {file = "ujson-5.9.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9ac92d86ff34296f881e12aa955f7014d276895e0e4e868ba7fddebbde38e378"},
-    {file = "ujson-5.9.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6eecbd09b316cea1fd929b1e25f70382917542ab11b692cb46ec9b0a26c7427f"},
-    {file = "ujson-5.9.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:473fb8dff1d58f49912323d7cb0859df5585cfc932e4b9c053bf8cf7f2d7c5c4"},
-    {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f91719c6abafe429c1a144cfe27883eace9fb1c09a9c5ef1bcb3ae80a3076a4e"},
-    {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1c0991c4fe256f5fdb19758f7eac7f47caac29a6c57d0de16a19048eb86bad"},
-    {file = "ujson-5.9.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8ea0f55a1396708e564595aaa6696c0d8af532340f477162ff6927ecc46e21"},
-    {file = "ujson-5.9.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:07e0cfdde5fd91f54cd2d7ffb3482c8ff1bf558abf32a8b953a5d169575ae1cd"},
-    {file = "ujson-5.9.0.tar.gz", hash = "sha256:89cc92e73d5501b8a7f48575eeb14ad27156ad092c2e9fc7e3cf949f07e75532"},
+    {file = "ujson-5.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2601aa9ecdbee1118a1c2065323bda35e2c5a2cf0797ef4522d485f9d3ef65bd"},
+    {file = "ujson-5.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:348898dd702fc1c4f1051bc3aacbf894caa0927fe2c53e68679c073375f732cf"},
+    {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22cffecf73391e8abd65ef5f4e4dd523162a3399d5e84faa6aebbf9583df86d6"},
+    {file = "ujson-5.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26b0e2d2366543c1bb4fbd457446f00b0187a2bddf93148ac2da07a53fe51569"},
+    {file = "ujson-5.10.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:caf270c6dba1be7a41125cd1e4fc7ba384bf564650beef0df2dd21a00b7f5770"},
+    {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a245d59f2ffe750446292b0094244df163c3dc96b3ce152a2c837a44e7cda9d1"},
+    {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94a87f6e151c5f483d7d54ceef83b45d3a9cca7a9cb453dbdbb3f5a6f64033f5"},
+    {file = "ujson-5.10.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:29b443c4c0a113bcbb792c88bea67b675c7ca3ca80c3474784e08bba01c18d51"},
+    {file = "ujson-5.10.0-cp310-cp310-win32.whl", hash = "sha256:c18610b9ccd2874950faf474692deee4223a994251bc0a083c114671b64e6518"},
+    {file = "ujson-5.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:924f7318c31874d6bb44d9ee1900167ca32aa9b69389b98ecbde34c1698a250f"},
+    {file = "ujson-5.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5b366812c90e69d0f379a53648be10a5db38f9d4ad212b60af00bd4048d0f00"},
+    {file = "ujson-5.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:502bf475781e8167f0f9d0e41cd32879d120a524b22358e7f205294224c71126"},
+    {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b91b5d0d9d283e085e821651184a647699430705b15bf274c7896f23fe9c9d8"},
+    {file = "ujson-5.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:129e39af3a6d85b9c26d5577169c21d53821d8cf68e079060602e861c6e5da1b"},
+    {file = "ujson-5.10.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f77b74475c462cb8b88680471193064d3e715c7c6074b1c8c412cb526466efe9"},
+    {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7ec0ca8c415e81aa4123501fee7f761abf4b7f386aad348501a26940beb1860f"},
+    {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab13a2a9e0b2865a6c6db9271f4b46af1c7476bfd51af1f64585e919b7c07fd4"},
+    {file = "ujson-5.10.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:57aaf98b92d72fc70886b5a0e1a1ca52c2320377360341715dd3933a18e827b1"},
+    {file = "ujson-5.10.0-cp311-cp311-win32.whl", hash = "sha256:2987713a490ceb27edff77fb184ed09acdc565db700ee852823c3dc3cffe455f"},
+    {file = "ujson-5.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:f00ea7e00447918ee0eff2422c4add4c5752b1b60e88fcb3c067d4a21049a720"},
+    {file = "ujson-5.10.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:98ba15d8cbc481ce55695beee9f063189dce91a4b08bc1d03e7f0152cd4bbdd5"},
+    {file = "ujson-5.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9d2edbf1556e4f56e50fab7d8ff993dbad7f54bac68eacdd27a8f55f433578e"},
+    {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6627029ae4f52d0e1a2451768c2c37c0c814ffc04f796eb36244cf16b8e57043"},
+    {file = "ujson-5.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8ccb77b3e40b151e20519c6ae6d89bfe3f4c14e8e210d910287f778368bb3d1"},
+    {file = "ujson-5.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3caf9cd64abfeb11a3b661329085c5e167abbe15256b3b68cb5d914ba7396f3"},
+    {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6e32abdce572e3a8c3d02c886c704a38a1b015a1fb858004e03d20ca7cecbb21"},
+    {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a65b6af4d903103ee7b6f4f5b85f1bfd0c90ba4eeac6421aae436c9988aa64a2"},
+    {file = "ujson-5.10.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:604a046d966457b6cdcacc5aa2ec5314f0e8c42bae52842c1e6fa02ea4bda42e"},
+    {file = "ujson-5.10.0-cp312-cp312-win32.whl", hash = "sha256:6dea1c8b4fc921bf78a8ff00bbd2bfe166345f5536c510671bccececb187c80e"},
+    {file = "ujson-5.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:38665e7d8290188b1e0d57d584eb8110951a9591363316dd41cf8686ab1d0abc"},
+    {file = "ujson-5.10.0-cp313-cp313-macosx_10_9_x86_64.whl", hash = "sha256:618efd84dc1acbd6bff8eaa736bb6c074bfa8b8a98f55b61c38d4ca2c1f7f287"},
+    {file = "ujson-5.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38d5d36b4aedfe81dfe251f76c0467399d575d1395a1755de391e58985ab1c2e"},
+    {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67079b1f9fb29ed9a2914acf4ef6c02844b3153913eb735d4bf287ee1db6e557"},
+    {file = "ujson-5.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7d0e0ceeb8fe2468c70ec0c37b439dd554e2aa539a8a56365fd761edb418988"},
+    {file = "ujson-5.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:59e02cd37bc7c44d587a0ba45347cc815fb7a5fe48de16bf05caa5f7d0d2e816"},
+    {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a890b706b64e0065f02577bf6d8ca3b66c11a5e81fb75d757233a38c07a1f20"},
+    {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:621e34b4632c740ecb491efc7f1fcb4f74b48ddb55e65221995e74e2d00bbff0"},
+    {file = "ujson-5.10.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9500e61fce0cfc86168b248104e954fead61f9be213087153d272e817ec7b4f"},
+    {file = "ujson-5.10.0-cp313-cp313-win32.whl", hash = "sha256:4c4fc16f11ac1612f05b6f5781b384716719547e142cfd67b65d035bd85af165"},
+    {file = "ujson-5.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:4573fd1695932d4f619928fd09d5d03d917274381649ade4328091ceca175539"},
+    {file = "ujson-5.10.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a984a3131da7f07563057db1c3020b1350a3e27a8ec46ccbfbf21e5928a43050"},
+    {file = "ujson-5.10.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73814cd1b9db6fc3270e9d8fe3b19f9f89e78ee9d71e8bd6c9a626aeaeaf16bd"},
+    {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61e1591ed9376e5eddda202ec229eddc56c612b61ac6ad07f96b91460bb6c2fb"},
+    {file = "ujson-5.10.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2c75269f8205b2690db4572a4a36fe47cd1338e4368bc73a7a0e48789e2e35a"},
+    {file = "ujson-5.10.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7223f41e5bf1f919cd8d073e35b229295aa8e0f7b5de07ed1c8fddac63a6bc5d"},
+    {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d4dc2fd6b3067c0782e7002ac3b38cf48608ee6366ff176bbd02cf969c9c20fe"},
+    {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:232cc85f8ee3c454c115455195a205074a56ff42608fd6b942aa4c378ac14dd7"},
+    {file = "ujson-5.10.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:cc6139531f13148055d691e442e4bc6601f6dba1e6d521b1585d4788ab0bfad4"},
+    {file = "ujson-5.10.0-cp38-cp38-win32.whl", hash = "sha256:e7ce306a42b6b93ca47ac4a3b96683ca554f6d35dd8adc5acfcd55096c8dfcb8"},
+    {file = "ujson-5.10.0-cp38-cp38-win_amd64.whl", hash = "sha256:e82d4bb2138ab05e18f089a83b6564fee28048771eb63cdecf4b9b549de8a2cc"},
+    {file = "ujson-5.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfef2814c6b3291c3c5f10065f745a1307d86019dbd7ea50e83504950136ed5b"},
+    {file = "ujson-5.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4734ee0745d5928d0ba3a213647f1c4a74a2a28edc6d27b2d6d5bd9fa4319e27"},
+    {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ebb01bd865fdea43da56254a3930a413f0c5590372a1241514abae8aa7c76"},
+    {file = "ujson-5.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dee5e97c2496874acbf1d3e37b521dd1f307349ed955e62d1d2f05382bc36dd5"},
+    {file = "ujson-5.10.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7490655a2272a2d0b072ef16b0b58ee462f4973a8f6bbe64917ce5e0a256f9c0"},
+    {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ba17799fcddaddf5c1f75a4ba3fd6441f6a4f1e9173f8a786b42450851bd74f1"},
+    {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2aff2985cef314f21d0fecc56027505804bc78802c0121343874741650a4d3d1"},
+    {file = "ujson-5.10.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ad88ac75c432674d05b61184178635d44901eb749786c8eb08c102330e6e8996"},
+    {file = "ujson-5.10.0-cp39-cp39-win32.whl", hash = "sha256:2544912a71da4ff8c4f7ab5606f947d7299971bdd25a45e008e467ca638d13c9"},
+    {file = "ujson-5.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:3ff201d62b1b177a46f113bb43ad300b424b7847f9c5d38b1b4ad8f75d4a282a"},
+    {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5b6fee72fa77dc172a28f21693f64d93166534c263adb3f96c413ccc85ef6e64"},
+    {file = "ujson-5.10.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:61d0af13a9af01d9f26d2331ce49bb5ac1fb9c814964018ac8df605b5422dcb3"},
+    {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecb24f0bdd899d368b715c9e6664166cf694d1e57be73f17759573a6986dd95a"},
+    {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbd8fd427f57a03cff3ad6574b5e299131585d9727c8c366da4624a9069ed746"},
+    {file = "ujson-5.10.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:beeaf1c48e32f07d8820c705ff8e645f8afa690cca1544adba4ebfa067efdc88"},
+    {file = "ujson-5.10.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:baed37ea46d756aca2955e99525cc02d9181de67f25515c468856c38d52b5f3b"},
+    {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7663960f08cd5a2bb152f5ee3992e1af7690a64c0e26d31ba7b3ff5b2ee66337"},
+    {file = "ujson-5.10.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:d8640fb4072d36b08e95a3a380ba65779d356b2fee8696afeb7794cf0902d0a1"},
+    {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78778a3aa7aafb11e7ddca4e29f46bc5139131037ad628cc10936764282d6753"},
+    {file = "ujson-5.10.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0111b27f2d5c820e7f2dbad7d48e3338c824e7ac4d2a12da3dc6061cc39c8e6"},
+    {file = "ujson-5.10.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:c66962ca7565605b355a9ed478292da628b8f18c0f2793021ca4425abf8b01e5"},
+    {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ba43cc34cce49cf2d4bc76401a754a81202d8aa926d0e2b79f0ee258cb15d3a4"},
+    {file = "ujson-5.10.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:ac56eb983edce27e7f51d05bc8dd820586c6e6be1c5216a6809b0c668bb312b8"},
+    {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f44bd4b23a0e723bf8b10628288c2c7c335161d6840013d4d5de20e48551773b"},
+    {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c10f4654e5326ec14a46bcdeb2b685d4ada6911050aa8baaf3501e57024b804"},
+    {file = "ujson-5.10.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0de4971a89a762398006e844ae394bd46991f7c385d7a6a3b93ba229e6dac17e"},
+    {file = "ujson-5.10.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e1402f0564a97d2a52310ae10a64d25bcef94f8dd643fcf5d310219d915484f7"},
+    {file = "ujson-5.10.0.tar.gz", hash = "sha256:b3cd8f3c5d8c7738257f1018880444f7b7d9b66232c64649f562d7ba86ad4bc1"},
 ]
 
 [[package]]
@@ -1188,18 +1202,18 @@ files = [
 
 [[package]]
 name = "zipp"
-version = "3.18.1"
+version = "3.19.1"
 description = "Backport of pathlib-compatible object wrapper for zip files"
 optional = false
 python-versions = ">=3.8"
 files = [
-    {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"},
-    {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"},
+    {file = "zipp-3.19.1-py3-none-any.whl", hash = "sha256:2828e64edb5386ea6a52e7ba7cdb17bb30a73a858f5eb6eb93d8d36f5ea26091"},
+    {file = "zipp-3.19.1.tar.gz", hash = "sha256:35427f6d5594f4acf82d25541438348c26736fa9b3afa2754bcd63cdb99d8e8f"},
 ]
 
 [package.extras]
-docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
-testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+test = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
 
 [extras]
 dev-coverage = ["coverage", "platformdirs", "pytest-cov"]
diff --git a/pyshacl/cli.py b/pyshacl/cli.py
index c91c943..2c59f21 100644
--- a/pyshacl/cli.py
+++ b/pyshacl/cli.py
@@ -7,8 +7,6 @@
 from typing import Union
 
 from prettytable import PrettyTable
-from rdflib import Graph
-from rdflib.namespace import SH
 
 from pyshacl import __version__, validate
 from pyshacl.errors import (
@@ -42,8 +40,7 @@ def str_is_true(s_var: str):
 parser.add_argument(
     'data',
     metavar='DataGraph',
-    type=argparse.FileType('rb'),
-    help='The file containing the Target Data Graph.',
+    help='The file or endpoint containing the Target Data Graph.',
     default=None,
     nargs='?',
 )
@@ -82,6 +79,14 @@ def str_is_true(s_var: str):
     default=False,
     help='Validate the SHACL Shapes graph against the shacl-shacl Shapes Graph before validating the Data Graph.',
 )
+parser.add_argument(
+    '-q',
+    '--sparql-mode',
+    dest='sparql_mode',
+    action='store_true',
+    default=False,
+    help='Treat the DataGraph as a SPARQL endpoint, validate the graph at the SPARQL endpoint.',
+)
 parser.add_argument(
     '-im',
     '--imports',
@@ -211,13 +216,33 @@ def main(prog: Union[str, None] = None) -> None:
     if str_is_true(do_server) or args.server:
         from pyshacl.sh_http import main as http_main
 
+        # http_main calls sys.exit(0) and never returns
         http_main()
-    elif not args.data:
+    if not args.data:
         # No datafile give, and not starting in server mode.
-        sys.stderr.write('Validation Error. No DataGraph file supplied.\n')
+        sys.stderr.write('Input Error. No DataGraph file or endpoint supplied.\n')
         parser.print_usage(sys.stderr)
         sys.exit(1)
     validator_kwargs = {'debug': args.debug}
+    data_file = None
+    if args.sparql_mode is not None and args.sparql_mode is True:
+        endpoint = str(args.data).strip()
+        if not endpoint.lower().startswith("http:") and not endpoint.lower().startswith("https:"):
+            sys.stderr.write("Input Error. SPARQL Endpoint must start with http:// or https://.\n")
+            sys.exit(1)
+        data_graph = endpoint
+        validator_kwargs['sparql_mode'] = True
+    else:
+        try:
+            data_file = open(args.data, 'rb')
+        except FileNotFoundError:
+            sys.stderr.write('Input Error. DataGraph file not found.\n')
+            sys.exit(1)
+        except PermissionError:
+            sys.stderr.write('Input Error. DataGraph file not readable.\n')
+            sys.exit(1)
+        else:
+            data_graph = data_file
     if args.shacl is not None:
         validator_kwargs['shacl_graph'] = args.shacl
     if args.ont is not None:
@@ -257,37 +282,38 @@ def main(prog: Union[str, None] = None) -> None:
         _f = args.data_file_format
         if _f != "auto":
             validator_kwargs['data_graph_format'] = _f
+    exit_code: Union[int, None] = None
     try:
-        is_conform, v_graph, v_text = validate(args.data, **validator_kwargs)
+        is_conform, v_graph, v_text = validate(data_graph, **validator_kwargs)
         if isinstance(v_graph, BaseException):
             raise v_graph
     except ValidationFailure as vf:
         args.output.write("Validator generated a Validation Failure result:\n")
         args.output.write(str(vf.message))
         args.output.write("\n")
-        sys.exit(1)
+        exit_code = 1
     except ShapeLoadError as sle:
         sys.stderr.write("Validator encountered a Shape Load Error:\n")
         sys.stderr.write(str(sle))
-        sys.exit(2)
+        exit_code = 2
     except ConstraintLoadError as cle:
         sys.stderr.write("Validator encountered a Constraint Load Error:\n")
         sys.stderr.write(str(cle))
-        sys.exit(2)
+        exit_code = 2
     except RuleLoadError as rle:
         sys.stderr.write("Validator encountered a Rule Load Error:\n")
         sys.stderr.write(str(rle))
-        sys.exit(2)
+        exit_code = 2
     except ReportableRuntimeError as rre:
         sys.stderr.write("Validator encountered a Runtime Error:\n")
         sys.stderr.write(str(rre.message))
         sys.stderr.write("\nIf you believe this is a bug in pyshacl, open an Issue on the pyshacl github page.\n")
-        sys.exit(2)
+        exit_code = 2
     except NotImplementedError as nie:
         sys.stderr.write("Validator feature is not implemented:\n")
         sys.stderr.write(str(nie.args[0]))
         sys.stderr.write("\nIf your use-case requires this feature, open an Issue on the pyshacl github page.\n")
-        sys.exit(3)
+        exit_code = 3
     except RuntimeError as re:
         import traceback
 
@@ -295,8 +321,16 @@ def main(prog: Union[str, None] = None) -> None:
         sys.stderr.write(
             "\n\nValidator encountered a Runtime Error. Please report this to the PySHACL issue tracker.\n"
         )
-        sys.exit(2)
-
+        exit_code = 2
+    finally:
+        if data_file is not None:
+            try:
+                data_file.close()
+            except Exception as e:
+                sys.stderr.write("Error closing data file:\n")
+                sys.stderr.write(str(e))
+        if exit_code is not None:
+            sys.exit(exit_code)
     if args.format == 'human':
         args.output.write(v_text)
     elif args.format == 'table':
@@ -317,6 +351,9 @@ def col_widther(s, w):
             return '\n'.join(s2)
 
         if not is_conform:
+            from rdflib import Graph
+            from rdflib.namespace import SH
+
             t2 = PrettyTable()
             t2.field_names = ['No.', 'Severity', 'Focus Node', 'Result Path', 'Message', 'Component', 'Shape', 'Value']
             t2.align = "l"
diff --git a/pyshacl/constraints/core/other_constraints.py b/pyshacl/constraints/core/other_constraints.py
index e4c5db3..d96a81d 100644
--- a/pyshacl/constraints/core/other_constraints.py
+++ b/pyshacl/constraints/core/other_constraints.py
@@ -174,21 +174,86 @@ def evaluate(
             if p:
                 working_paths.add(p)
 
-        for f, value_nodes in focus_value_nodes.items():
-            for v in value_nodes:
-                pred_obs = target_graph.predicate_objects(v)
-                for p, o in pred_obs:
-                    if (p, o) in self.ALWAYS_IGNORE:
-                        continue
-                    elif p in self.ignored_props:
-                        continue
-                    elif p in working_paths:
-                        continue
-                    non_conformant = True
-                    o_node = cast(RDFNode, o)
-                    p_node = cast(RDFNode, p)
-                    rept = self.make_v_result(target_graph, f, value_node=o_node, result_path=p_node)
-                    reports.append(rept)
+        if executor.sparql_mode:
+            select_vars_string = ""
+            filter_props_list = []
+            bgp_list = []
+            init_bindings = {}
+            if len(self.ignored_props) > 0:
+                filter_template = "("
+                if len(self.ignored_props) == 1:
+                    filter_template += f"{{P}} != {next(iter(self.ignored_props)).n3()}"
+                else:
+                    this_filter_parts = []
+                    for ig in self.ignored_props:
+                        this_filter_parts.append(f"({{P}} != {ig.n3()})")
+                    filter_template += " && ".join(this_filter_parts)
+                filter_template += ")"
+            else:
+                filter_template = ""
+            for i, f in enumerate(focus_value_nodes.keys()):
+                for j, v in enumerate(focus_value_nodes[f]):
+                    select_vars_string += f"?p{i}_{j} ?o{i}_{j} "
+                    bgp_line = f"OPTIONAL {{ $v{i}_{j} ?p{i}_{j} ?o{i}_{j} . }}"
+                    bgp_list.append(bgp_line)
+                    if filter_template:
+                        filter_props_line = filter_template.replace("{P}", f"?p{i}_{j}")
+                        filter_props_list.append(filter_props_line)
+                    init_bindings[f"v{i}_{j}"] = v
+            bgp_string = "\n".join(bgp_list)
+            if len(filter_props_list) > 1:
+                filter_props_string = "FILTER (" + " && ".join(filter_props_list) + ")"
+            elif len(filter_props_list) == 1:
+                filter_props_string = "FILTER " + filter_props_list[0]
+            else:
+                filter_props_string = ""
+            closed_query = f"SELECT DISTINCT {select_vars_string} {{\n\t{bgp_string}\n\t{filter_props_string}\n}}"
+            try:
+                results = target_graph.query(closed_query, initBindings=init_bindings)
+            except Exception as e:
+                print(e)
+                raise
+            found_fvpo = []
+            if len(results) > 0:
+                for r in results:
+                    for i, f in enumerate(focus_value_nodes.keys()):
+                        for j, v in enumerate(focus_value_nodes[f]):
+                            p = r[f"p{i}_{j}"]
+                            o = r[f"o{i}_{j}"]
+                            if p is None or o is None or p == "UNDEF" or o == "UNDEF":
+                                continue
+                            fvpo = (f, v, p, o)
+                            if fvpo in found_fvpo:
+                                continue
+                            found_fvpo.append(fvpo)
+                            # TODO: remove code duplication
+                            if (p, o) in self.ALWAYS_IGNORE:
+                                continue
+                            elif p in self.ignored_props:
+                                continue
+                            elif p in working_paths:
+                                continue
+                            non_conformant = True
+                            o_node = cast(RDFNode, o)
+                            p_node = cast(RDFNode, p)
+                            rept = self.make_v_result(target_graph, f, value_node=o_node, result_path=p_node)
+                            reports.append(rept)
+        else:
+            for f, value_nodes in focus_value_nodes.items():
+                for v in value_nodes:
+                    pred_obs = target_graph.predicate_objects(v)
+                    for p, o in pred_obs:
+                        if (p, o) in self.ALWAYS_IGNORE:
+                            continue
+                        elif p in self.ignored_props:
+                            continue
+                        elif p in working_paths:
+                            continue
+                        non_conformant = True
+                        o_node = cast(RDFNode, o)
+                        p_node = cast(RDFNode, p)
+                        rept = self.make_v_result(target_graph, f, value_node=o_node, result_path=p_node)
+                        reports.append(rept)
         return (not non_conformant), reports
 
 
diff --git a/pyshacl/constraints/core/property_pair_constraints.py b/pyshacl/constraints/core/property_pair_constraints.py
index b96deed..29c3e5a 100644
--- a/pyshacl/constraints/core/property_pair_constraints.py
+++ b/pyshacl/constraints/core/property_pair_constraints.py
@@ -9,6 +9,7 @@
 from pyshacl.constraints.constraint_component import ConstraintComponent
 from pyshacl.consts import SH
 from pyshacl.errors import ConstraintLoadError, ReportableRuntimeError
+from pyshacl.helper.path_helper import shacl_path_to_sparql_path
 from pyshacl.pytypes import GraphLike, SHACLExecutor
 from pyshacl.rdfutil import stringify_node
 
@@ -79,12 +80,57 @@ def evaluate(
         non_conformant = False
 
         for eq in iter(self.property_compare_set):
-            _nc, _r = self._evaluate_property_equals(eq, target_graph, focus_value_nodes)
+            if executor.sparql_mode:
+                _nc, _r = self._evaluate_property_equals_sparql(eq, target_graph, focus_value_nodes)
+            else:
+                _nc, _r = self._evaluate_property_equals_rdflib(eq, target_graph, focus_value_nodes)
             non_conformant = non_conformant or _nc
             reports.extend(_r)
         return (not non_conformant), reports
 
-    def _evaluate_property_equals(self, eq, target_graph, f_v_dict):
+    def _evaluate_property_equals_sparql(self, eq, target_graph, f_v_dict):
+        reports = []
+        non_conformant = False
+        prefixes = dict(target_graph.namespaces())
+        eq_path = shacl_path_to_sparql_path(self.shape.sg, eq, prefixes=prefixes)
+        eq_lookup_query = f"SELECT DISTINCT {' '.join(f'?v{i}' for i,_ in enumerate(f_v_dict))} WHERE {{\n"
+        init_bindings = {}
+        f_eq_results = {}
+        for i, f in enumerate(f_v_dict.keys()):
+            eq_lookup_query += f"OPTIONAL {{ $f{i} {eq_path} ?v{i} . }}\n"
+            init_bindings[f"f{i}"] = f
+            f_eq_results[f] = set()
+        eq_lookup_query += "}"
+        try:
+            results = target_graph.query(eq_lookup_query, initBindings=init_bindings)
+        except Exception as e:
+            print(e)
+            raise
+        if len(results) > 0:
+            for r in results:
+                for i, f in enumerate(f_v_dict.keys()):
+                    val_i = r[i]
+                    if val_i is None or val_i == "UNDEF":
+                        continue
+                    f_eq_results[f].add(val_i)
+        for i, f in enumerate(f_v_dict.keys()):
+            value_node_set = set(f_v_dict[f])
+            compare_values = f_eq_results[f]
+            value_nodes_missing = value_node_set.difference(compare_values)
+            compare_values_missing = compare_values.difference(value_node_set)
+            if len(value_nodes_missing) > 0 or len(compare_values_missing) > 0:
+                non_conformant = True
+            else:
+                continue
+            for value_node in value_nodes_missing:
+                rept = self.make_v_result(target_graph, f, value_node=value_node)
+                reports.append(rept)
+            for compare_value in compare_values_missing:
+                rept = self.make_v_result(target_graph, f, value_node=compare_value)
+                reports.append(rept)
+        return non_conformant, reports
+
+    def _evaluate_property_equals_rdflib(self, eq, target_graph, f_v_dict):
         reports = []
         non_conformant = False
         for f, value_nodes in f_v_dict.items():
@@ -161,12 +207,53 @@ def evaluate(
         non_conformant = False
 
         for dj in iter(self.property_compare_set):
-            _nc, _r = self._evaluate_property_disjoint(dj, target_graph, focus_value_nodes)
+            if executor.sparql_mode:
+                _nc, _r = self._evaluate_property_disjoint_sparql(dj, target_graph, focus_value_nodes)
+            else:
+                _nc, _r = self._evaluate_property_disjoint_rdflib(dj, target_graph, focus_value_nodes)
             non_conformant = non_conformant or _nc
             reports.extend(_r)
         return (not non_conformant), reports
 
-    def _evaluate_property_disjoint(self, dj, target_graph, f_v_dict):
+    def _evaluate_property_disjoint_sparql(self, dj, target_graph, f_v_dict):
+        reports = []
+        non_conformant = False
+        prefixes = dict(target_graph.namespaces())
+        dj_path = shacl_path_to_sparql_path(self.shape.sg, dj, prefixes=prefixes)
+        dj_lookup_query = f"SELECT DISTINCT {' '.join(f'?v{i}' for i,_ in enumerate(f_v_dict))} WHERE {{\n"
+        init_bindings = {}
+        f_dj_results = {}
+        for i, f in enumerate(f_v_dict.keys()):
+            dj_lookup_query += f"OPTIONAL {{ $f{i} {dj_path} ?v{i} . }}\n"
+            init_bindings[f"f{i}"] = f
+            f_dj_results[f] = set()
+        dj_lookup_query += "}"
+        try:
+            results = target_graph.query(dj_lookup_query, initBindings=init_bindings)
+        except Exception as e:
+            print(e)
+            raise
+        if len(results) > 0:
+            for r in results:
+                for i, f in enumerate(f_v_dict.keys()):
+                    val_i = r[i]
+                    if val_i is None or val_i == "UNDEF":
+                        continue
+                    f_dj_results[f].add(val_i)
+        for i, f in enumerate(f_v_dict.keys()):
+            value_node_set = set(f_v_dict[f])
+            compare_values = f_dj_results[f]
+            common_nodes = value_node_set.intersection(compare_values)
+            if len(common_nodes) > 0:
+                non_conformant = True
+            else:
+                continue
+            for common_node in common_nodes:
+                rept = self.make_v_result(target_graph, f, value_node=common_node)
+                reports.append(rept)
+        return non_conformant, reports
+
+    def _evaluate_property_disjoint_rdflib(self, dj, target_graph, f_v_dict):
         reports = []
         non_conformant = False
         for f, value_nodes in f_v_dict.items():
@@ -247,48 +334,91 @@ def evaluate(
         for lt in iter(self.property_compare_set):
             if isinstance(lt, rdflib.Literal) or isinstance(lt, rdflib.BNode):
                 raise ReportableRuntimeError("Value of sh:lessThan MUST be a URI Identifier.")
-            _nc, _r = self._evaluate_less_than(lt, target_graph, focus_value_nodes)
+            if executor.sparql_mode:
+                _nc, _r = self._evaluate_less_than_sparql(lt, target_graph, focus_value_nodes)
+            else:
+                _nc, _r = self._evaluate_less_than_rdflib(lt, target_graph, focus_value_nodes)
             non_conformant = non_conformant or _nc
             reports.extend(_r)
         return (not non_conformant), reports
 
-    def _evaluate_less_than(self, lt, target_graph, f_v_dict):
+    def _compare_lt(self, value_node_set, compare_values, datagraph, f):
+        non_conformant = False
+        reports = []
+        for value_node in iter(value_node_set):
+            if isinstance(value_node, rdflib.BNode):
+                raise ReportableRuntimeError("Cannot use sh:lessThan to compare a BlankNode.")
+            value_is_string = False
+            orig_value_node = value_node
+            if isinstance(value_node, rdflib.URIRef):
+                value_node = str(value_node)
+                value_is_string = True
+            elif isinstance(value_node, rdflib.Literal) and isinstance(value_node.value, str):
+                value_node = value_node.value
+                value_is_string = True
+
+            for compare_value in compare_values:
+                if isinstance(compare_value, rdflib.BNode):
+                    raise ReportableRuntimeError("Cannot use sh:lessThan to compare a BlankNode.")
+                compare_is_string = False
+                if isinstance(compare_value, rdflib.URIRef):
+                    compare_value = str(compare_value)
+                    compare_is_string = True
+                elif isinstance(compare_value, rdflib.Literal) and isinstance(compare_value.value, str):
+                    compare_value = compare_value.value
+                    compare_is_string = True
+                if (value_is_string and not compare_is_string) or (compare_is_string and not value_is_string):
+                    non_conformant = True
+                elif not value_node < compare_value:
+                    non_conformant = True
+                else:
+                    continue
+                rept = self.make_v_result(datagraph, f, value_node=orig_value_node)
+                reports.append(rept)
+        return non_conformant, reports
+
+    def _evaluate_less_than_sparql(self, lt, target_graph, f_v_dict):
+        reports = []
+        non_conformant = False
+        prefixes = dict(target_graph.namespaces())
+        lt_path = shacl_path_to_sparql_path(self.shape.sg, lt, prefixes=prefixes)
+        lt_lookup_query = f"SELECT DISTINCT {' '.join(f'?v{i}' for i,_ in enumerate(f_v_dict))} WHERE {{\n"
+        init_bindings = {}
+        f_lt_results = {}
+        for i, f in enumerate(f_v_dict.keys()):
+            lt_lookup_query += f"OPTIONAL {{ $f{i} {lt_path} ?v{i} . }}\n"
+            init_bindings[f"f{i}"] = f
+            f_lt_results[f] = set()
+        lt_lookup_query += "}"
+        try:
+            results = target_graph.query(lt_lookup_query, initBindings=init_bindings)
+        except Exception as e:
+            print(e)
+            raise
+        if len(results) > 0:
+            for r in results:
+                for i, f in enumerate(f_v_dict.keys()):
+                    val_i = r[i]
+                    if val_i is None or val_i == "UNDEF":
+                        continue
+                    f_lt_results[f].add(val_i)
+        for i, f in enumerate(f_v_dict.keys()):
+            value_node_set = set(f_v_dict[f])
+            compare_values = f_lt_results[f]
+            _nc, _r = self._compare_lt(value_node_set, compare_values, target_graph, f)
+            non_conformant = non_conformant or _nc
+            reports.extend(_r)
+        return non_conformant, reports
+
+    def _evaluate_less_than_rdflib(self, lt, target_graph, f_v_dict):
         reports = []
         non_conformant = False
         for f, value_nodes in f_v_dict.items():
             value_node_set = set(value_nodes)
             compare_values = set(target_graph.objects(f, lt))
-
-            for value_node in iter(value_node_set):
-                if isinstance(value_node, rdflib.BNode):
-                    raise ReportableRuntimeError("Cannot use sh:lessThan to compare a BlankNode.")
-                value_is_string = False
-                orig_value_node = value_node
-                if isinstance(value_node, rdflib.URIRef):
-                    value_node = str(value_node)
-                    value_is_string = True
-                elif isinstance(value_node, rdflib.Literal) and isinstance(value_node.value, str):
-                    value_node = value_node.value
-                    value_is_string = True
-
-                for compare_value in compare_values:
-                    if isinstance(compare_value, rdflib.BNode):
-                        raise ReportableRuntimeError("Cannot use sh:lessThan to compare a BlankNode.")
-                    compare_is_string = False
-                    if isinstance(compare_value, rdflib.URIRef):
-                        compare_value = str(compare_value)
-                        compare_is_string = True
-                    elif isinstance(compare_value, rdflib.Literal) and isinstance(compare_value.value, str):
-                        compare_value = compare_value.value
-                        compare_is_string = True
-                    if (value_is_string and not compare_is_string) or (compare_is_string and not value_is_string):
-                        non_conformant = True
-                    elif not value_node < compare_value:
-                        non_conformant = True
-                    else:
-                        continue
-                    rept = self.make_v_result(target_graph, f, value_node=orig_value_node)
-                    reports.append(rept)
+            _nc, _r = self._compare_lt(value_node_set, compare_values, target_graph, f)
+            non_conformant = non_conformant or _nc
+            reports.extend(_r)
         return non_conformant, reports
 
 
@@ -355,46 +485,89 @@ def evaluate(
         for lt in iter(self.property_compare_set):
             if isinstance(lt, rdflib.Literal) or isinstance(lt, rdflib.BNode):
                 raise ReportableRuntimeError("Value of sh:lessThanOrEquals MUST be a URI Identifier.")
-            _nc, _r = self._evaluate_ltoe(lt, target_graph, focus_value_nodes)
+            if executor.sparql_mode:
+                _nc, _r = self._evaluate_ltoe_sparql(lt, target_graph, focus_value_nodes)
+            else:
+                _nc, _r = self._evaluate_ltoe_rdflib(lt, target_graph, focus_value_nodes)
             non_conformant = non_conformant or _nc
             reports.extend(_r)
         return (not non_conformant), reports
 
-    def _evaluate_ltoe(self, lt, target_graph, f_v_dict):
+    def _compare_ltoe(self, value_node_set, compare_values, datagraph, f):
+        non_conformant = False
+        reports = []
+        for value_node in iter(value_node_set):
+            if isinstance(value_node, rdflib.BNode):
+                raise ReportableRuntimeError("Cannot use sh:lessThanOrEquals to compare a BlankNode.")
+            value_is_string = False
+            orig_value_node = value_node
+            if isinstance(value_node, rdflib.URIRef):
+                value_node = str(value_node)
+                value_is_string = True
+            elif isinstance(value_node, rdflib.Literal) and isinstance(value_node.value, str):
+                value_node = value_node.value
+                value_is_string = True
+
+            for compare_value in compare_values:
+                if isinstance(compare_value, rdflib.BNode):
+                    raise ReportableRuntimeError("Cannot use sh:lessThanOrEquals to compare a BlankNode.")
+                compare_is_string = False
+                if isinstance(compare_value, rdflib.URIRef):
+                    compare_value = str(compare_value)
+                    compare_is_string = True
+                elif isinstance(compare_value, rdflib.Literal) and isinstance(compare_value.value, str):
+                    compare_value = compare_value.value
+                    compare_is_string = True
+                if (value_is_string and not compare_is_string) or (compare_is_string and not value_is_string):
+                    non_conformant = True
+                elif not value_node <= compare_value:
+                    non_conformant = True
+                else:
+                    continue
+                rept = self.make_v_result(datagraph, f, value_node=orig_value_node)
+                reports.append(rept)
+        return non_conformant, reports
+
+    def _evaluate_ltoe_sparql(self, ltoe, target_graph, f_v_dict):
+        reports = []
+        non_conformant = False
+        prefixes = dict(target_graph.namespaces())
+        ltoe_path = shacl_path_to_sparql_path(self.shape.sg, ltoe, prefixes=prefixes)
+        ltoe_lookup_query = f"SELECT DISTINCT {' '.join(f'?v{i}' for i,_ in enumerate(f_v_dict))} WHERE {{\n"
+        init_bindings = {}
+        f_ltoe_results = {}
+        for i, f in enumerate(f_v_dict.keys()):
+            ltoe_lookup_query += f"OPTIONAL {{ $f{i} {ltoe_path} ?v{i} . }}\n"
+            init_bindings[f"f{i}"] = f
+            f_ltoe_results[f] = set()
+        ltoe_lookup_query += "}"
+        try:
+            results = target_graph.query(ltoe_lookup_query, initBindings=init_bindings)
+        except Exception as e:
+            print(e)
+            raise
+        if len(results) > 0:
+            for r in results:
+                for i, f in enumerate(f_v_dict.keys()):
+                    val_i = r[i]
+                    if val_i is None or val_i == "UNDEF":
+                        continue
+                    f_ltoe_results[f].add(val_i)
+        for i, f in enumerate(f_v_dict.keys()):
+            value_node_set = set(f_v_dict[f])
+            compare_values = f_ltoe_results[f]
+            _nc, _r = self._compare_ltoe(value_node_set, compare_values, target_graph, f)
+            non_conformant = non_conformant or _nc
+            reports.extend(_r)
+        return non_conformant, reports
+
+    def _evaluate_ltoe_rdflib(self, ltoe, target_graph, f_v_dict):
         reports = []
         non_conformant = False
         for f, value_nodes in f_v_dict.items():
             value_node_set = set(value_nodes)
-            compare_values = set(target_graph.objects(f, lt))
-
-            for value_node in iter(value_node_set):
-                if isinstance(value_node, rdflib.BNode):
-                    raise ReportableRuntimeError("Cannot use sh:lessThanOrEquals to compare a BlankNode.")
-                value_is_string = False
-                orig_value_node = value_node
-                if isinstance(value_node, rdflib.URIRef):
-                    value_node = str(value_node)
-                    value_is_string = True
-                elif isinstance(value_node, rdflib.Literal) and isinstance(value_node.value, str):
-                    value_node = value_node.value
-                    value_is_string = True
-
-                for compare_value in compare_values:
-                    if isinstance(compare_value, rdflib.BNode):
-                        raise ReportableRuntimeError("Cannot use sh:lessThanOrEquals to compare a BlankNode.")
-                    compare_is_string = False
-                    if isinstance(compare_value, rdflib.URIRef):
-                        compare_value = str(compare_value)
-                        compare_is_string = True
-                    elif isinstance(compare_value, rdflib.Literal) and isinstance(compare_value.value, str):
-                        compare_value = compare_value.value
-                        compare_is_string = True
-                    if (value_is_string and not compare_is_string) or (compare_is_string and not value_is_string):
-                        non_conformant = True
-                    elif not value_node <= compare_value:
-                        non_conformant = True
-                    else:
-                        continue
-                    rept = self.make_v_result(target_graph, f, value_node=orig_value_node)
-                    reports.append(rept)
+            compare_values = set(target_graph.objects(f, ltoe))
+            _nc, _r = self._compare_ltoe(value_node_set, compare_values, target_graph, f)
+            non_conformant = non_conformant or _nc
+            reports.extend(_r)
         return non_conformant, reports
diff --git a/pyshacl/constraints/core/value_constraints.py b/pyshacl/constraints/core/value_constraints.py
index 28ae7e9..ce83eb7 100644
--- a/pyshacl/constraints/core/value_constraints.py
+++ b/pyshacl/constraints/core/value_constraints.py
@@ -96,13 +96,40 @@ def evaluate(
         """
         reports = []
         non_conformant = False
-        for c in self.class_rules:
-            _n, _r = self._evaluate_class_rules(target_graph, focus_value_nodes, c)
-            non_conformant = non_conformant or _n
-            reports.extend(_r)
+        if executor.sparql_mode:
+            for c in self.class_rules:
+                _n, _r = self._evaluate_class_rules_sparql(target_graph, focus_value_nodes, c)
+                non_conformant = non_conformant or _n
+                reports.extend(_r)
+        else:
+            for c in self.class_rules:
+                _n, _r = self._evaluate_class_rules_rdflib(target_graph, focus_value_nodes, c)
+                non_conformant = non_conformant or _n
+                reports.extend(_r)
         return (not non_conformant), reports
 
-    def _evaluate_class_rules(self, target_graph, f_v_dict, class_rule):
+    def _evaluate_class_rules_sparql(self, target_graph, f_v_dict, class_rule):
+        reports = []
+        non_conformant = False
+        sparql_ask = """ASK {$value rdf:type/rdfs:subClassOf* $class .}"""
+        for f, value_nodes in f_v_dict.items():
+            for v in value_nodes:
+                found = False
+                if isinstance(v, Literal):
+                    self.shape.logger.debug(
+                        "Class Constraint won't work with Literals. "
+                        "Attempting to match Literal node {} to class of {} will fail.".format(v, class_rule)
+                    )
+                else:
+                    resp = target_graph.query(sparql_ask, initBindings={"value": v, "class": class_rule})
+                    found = resp.askAnswer
+                if not found:
+                    non_conformant = True
+                    rept = self.make_v_result(target_graph, f, value_node=v)
+                    reports.append(rept)
+        return non_conformant, reports
+
+    def _evaluate_class_rules_rdflib(self, target_graph, f_v_dict, class_rule):
         reports = []
         non_conformant = False
         for f, value_nodes in f_v_dict.items():
@@ -305,9 +332,3 @@ def evaluate(
                     rept = self.make_v_result(target_graph, f, value_node=v)
                     reports.append(rept)
         return (not non_conformant), reports
-
-    def _evaluate_nodekind_rules(self, target_graph, f_v_pairs, nodekind_rule):
-        reports = []
-        non_conformant = False
-
-        return non_conformant, reports
diff --git a/pyshacl/pytypes.py b/pyshacl/pytypes.py
index c746e55..3b28ccf 100644
--- a/pyshacl/pytypes.py
+++ b/pyshacl/pytypes.py
@@ -15,9 +15,11 @@
 @dataclass
 class SHACLExecutor:
     validator: Optional[object] = None
+    advanced_mode: bool = False
     abort_on_first: bool = False
     allow_infos: bool = False
     allow_warnings: bool = False
     iterate_rules: bool = False
     debug: bool = False
+    sparql_mode: bool = False
     max_validation_depth: int = 15
diff --git a/pyshacl/shape.py b/pyshacl/shape.py
index edbd9ba..c520b4c 100644
--- a/pyshacl/shape.py
+++ b/pyshacl/shape.py
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 #
+import itertools
 import logging
 import sys
 from decimal import Decimal
@@ -38,6 +39,7 @@
 from .errors import ConstraintLoadError, ConstraintLoadWarning, ReportableRuntimeError, ShapeLoadError
 from .helper import get_query_helper_cls
 from .helper.expression_helper import value_nodes_from_path
+from .helper.path_helper import shacl_path_to_sparql_path
 from .pytypes import GraphLike, SHACLExecutor
 
 if TYPE_CHECKING:
@@ -386,23 +388,217 @@ def focus_nodes(self, data_graph, debug=False):
             self.logger.debug(f"Milliseconds to find focus nodes: {elapsed*1000.0:.3f}ms")
         return found_node_targets
 
-    def value_nodes(self, target_graph, focus):
+    @classmethod
+    def make_focus_nodes_sparql_values(
+        cls, target_classes_s: Set, implicit_classes_s: Set, target_objects_of_s: Set, target_subjects_of_s: Set
+    ):
+        init_bindings = {}
+        values_keys = []
+        values_vals = []
+        if len(target_classes_s) > 1:
+            values_keys.append("$targetClass")
+            values_vals.append(list(target_classes_s))
+        else:
+            init_bindings["targetClass"] = next(iter(target_classes_s)) if len(target_classes_s) > 0 else "UNDEF"
+        if len(implicit_classes_s) > 1:
+            values_keys.append("$implicitClass")
+            values_vals.append(list(implicit_classes_s))
+        else:
+            init_bindings["implicitClass"] = next(iter(implicit_classes_s)) if len(implicit_classes_s) > 0 else "UNDEF"
+        if len(target_subjects_of_s) > 1:
+            values_keys.append("$targetSubjectsOf")
+            values_vals.append(list(target_subjects_of_s))
+        else:
+            init_bindings["targetSubjectsOf"] = (
+                next(iter(target_subjects_of_s)) if len(target_subjects_of_s) > 0 else "UNDEF"
+            )
+        if len(target_objects_of_s) > 1:
+            values_keys.append("$targetObjectsOf")
+            values_vals.append(list(target_objects_of_s))
+        else:
+            init_bindings["targetObjectsOf"] = (
+                next(iter(target_objects_of_s)) if len(target_objects_of_s) > 0 else "UNDEF"
+            )
+        if len(values_keys) < 1:
+            return "", init_bindings
+        else:
+            values_clause = f"VALUES ({' '.join(values_keys)}) {{\n"
+            product = itertools.product(*values_vals)
+            for p in product:
+                values_clause += f"\t( {' '.join(p_x.n3() for p_x in p)} )\n"
+            values_clause += "}"
+            return values_clause, init_bindings
+
+    def focus_nodes_sparql(self, data_graph, debug=False):
+        """
+        The set of focus nodes for a shape may be identified as follows:
+
+        specified in a shape using target declarations
+        specified in any constraint that references a shape in parameters of shape-expecting constraint parameters (e.g. sh:node)
+        specified as explicit input to the SHACL processor for validating a specific RDF term against a shape
+        :return:
+        """
+        t1 = 0.0
+        if debug:
+            t1 = perf_counter()
+        (target_nodes, target_classes, implicit_classes, target_objects_of, target_subjects_of) = self.target()
+        if self._advanced:
+            advanced_targets = self.advanced_target()
+        else:
+            advanced_targets = False
+        found_node_targets = set()
+        target_nodes = set(target_nodes)
+        target_classes = set(target_classes)
+        implicit_classes = set(implicit_classes)
+        target_objects_of = set(target_objects_of)
+        target_subjects_of = set(target_subjects_of)
+        if all(
+            (
+                advanced_targets is False,
+                len(target_nodes) < 1,
+                len(target_classes) < 1,
+                len(implicit_classes) < 1,
+                len(target_objects_of) < 1,
+                len(target_subjects_of) < 1,
+            )
+        ):
+            return found_node_targets
+
+        found_node_targets.update(target_nodes)
+        if (
+            advanced_targets is False
+            and len(target_classes) < 1
+            and len(implicit_classes) < 1
+            and len(target_objects_of) < 1
+            and len(target_subjects_of) < 1
+        ):
+            return found_node_targets
+        if (
+            len(target_classes) > 0
+            or len(implicit_classes) > 0
+            or len(target_objects_of) > 0
+            or len(target_subjects_of) > 0
+        ):
+            focus_query = """\
+            SELECT ?targetClass_F ?targetSubjectsOf_F ?targetObjectsOf_F WHERE {
+                {VALUES_CLAUSE}
+                OPTIONAL { {
+                ?targetClass_F rdf:type/rdfs:subClassOf* $targetClass .
+                } UNION {
+                ?targetClass_F rdf:type/rdfs:subClassOf* $implicitClass .
+                }. }
+                OPTIONAL { ?targetSubjectsOf_F $targetSubjectsOf ?anyA . }
+                OPTIONAL {?anyB $targetObjectsOf ?targetObjectsOf_F . }
+            }
+            """
+            values_clause, init_bindings = self.make_focus_nodes_sparql_values(
+                target_classes, implicit_classes, target_objects_of, target_subjects_of
+            )
+            new_query = focus_query.replace("{VALUES_CLAUSE}", values_clause)
+            try:
+                resp = data_graph.query(new_query, initBindings=init_bindings)
+            except Exception as e:
+                print(new_query)
+                raise e
+            if len(resp) > 0:
+                for result_set in resp:
+                    target_class_f, target_subjects_of_f, target_objects_of_f = result_set
+                    if target_class_f is not None and target_class_f != "UNDEF":
+                        found_node_targets.add(target_class_f)
+                    if target_subjects_of_f is not None and target_subjects_of_f != "UNDEF":
+                        found_node_targets.add(target_subjects_of_f)
+                    if target_objects_of_f is not None and target_objects_of_f != "UNDEF":
+                        found_node_targets.add(target_objects_of_f)
+        if advanced_targets:
+            for at_node, at in advanced_targets.items():
+                if at['type'] == SH_SPARQLTarget:
+                    qh = at['qh']
+                    select = qh.apply_prefixes(qh.select_text)
+                    results = data_graph.query(select, initBindings=None)
+                    if not results or len(results.bindings) < 1:
+                        continue
+                    for r in results:
+                        t = r['this']
+                        found_node_targets.add(t)
+                elif at['type'] in (SH_JSTarget, SH_JSTargetType):
+                    raise ReportableRuntimeError(
+                        "SHACL Advanced Targets with JSTargets are not yet implemented in SPARQL Remote Graph Mode."
+                    )
+                else:
+                    results = at['qt'].find_targets(data_graph)
+                    if not results or len(results.bindings) < 1:
+                        continue
+                    for r in results:
+                        t = r['this']
+                        found_node_targets.add(t)
+        if debug:
+            t2 = perf_counter()
+            elapsed = t2 - t1
+            self.logger.debug(f"Milliseconds to find focus nodes: {elapsed*1000.0:.3f}ms")
+        return found_node_targets
+
+    def value_nodes(self, target_graph, focus, sparql_mode: bool = False, debug: bool = False):
         """
         For each focus node, you can get a set of value nodes.
         For a Node Shape, each focus node has just one value node,
             which is just the focus_node
         :param target_graph:
         :param focus:
+        :param sparql_mode:
+        :type sparql_mode: bool
+        :param debug:
+        :type debug: bool
         :return:
         """
+        t1 = 0.0
+        if debug:
+            t1 = perf_counter()
         if not isinstance(focus, (tuple, list, set)):
             focus = [focus]
         if not self.is_property_shape:
+            if debug:
+                t2 = perf_counter()
+                elapsed = t2 - t1
+                self.logger.debug(f"Milliseconds to find value nodes for focus nodes: {elapsed * 1000.0:.3f}ms")
             return {f: set((f,)) for f in focus}
         path_val = self.path()
+
         focus_dict = {}
-        for f in focus:
-            focus_dict[f] = value_nodes_from_path(self.sg, f, path_val, target_graph)
+        if sparql_mode:
+            # Shortcut for simple URI path, path rewriting and everything else
+            if isinstance(path_val, URIRef):
+                sparql_path = path_val.n3(namespace_manager=target_graph.namespace_manager)
+            else:
+                prefixes = dict(target_graph.namespace_manager.namespaces())
+                sparql_path = shacl_path_to_sparql_path(self.sg, path_val, prefixes=prefixes)
+            values_query = f"SELECT {' '.join(f'?v{i}' for i,_ in enumerate(focus))} WHERE {{\n"
+            init_bindings = {}
+            for i, f in enumerate(focus):
+                focus_dict[f] = set()
+                values_query += f"OPTIONAL {{ \t$f{i} {sparql_path} ?v{i} . }}\n"
+                init_bindings[f"f{i}"] = f
+            values_query += "}"
+            try:
+                results = target_graph.query(values_query, initBindings=init_bindings)
+            except Exception as e:
+                print(e)
+                raise
+            if len(results) > 0:
+                for r in results:
+                    for i, f in enumerate(focus):
+                        row_focus_result = r[i]
+                        if row_focus_result is None or row_focus_result == "UNDEF":
+                            continue
+                        focus_dict[f].add(row_focus_result)
+            else:
+                pass
+        else:
+            for f in focus:
+                focus_dict[f] = value_nodes_from_path(self.sg, f, path_val, target_graph)
+        if debug:
+            t2 = perf_counter()
+            elapsed = t2 - t1
+            self.logger.debug(f"Milliseconds to find value nodes for focus nodes: {elapsed*1000.0:.3f}ms")
         return focus_dict
 
     def find_custom_constraints(self):
@@ -452,7 +648,10 @@ def validate(
             rh_shape = False
             self.logger.debug(f"Checking if Shape {str(self)} defines its own targets.")
             self.logger.debug("Identifying targets to find focus nodes.")
-            focus = self.focus_nodes(target_graph, debug=executor.debug)
+            if executor.sparql_mode:
+                focus = self.focus_nodes_sparql(target_graph, debug=executor.debug)
+            else:
+                focus = self.focus_nodes(target_graph, debug=executor.debug)
             self.logger.debug(f"Found {len(focus)} Focus Nodes to evaluate.")
             if len(focus) < 1:
                 # It's possible for shapes to have _no_ focus nodes
@@ -503,7 +702,9 @@ def validate(
             constraint_map = PARAMETER_MAP
         parameters = (p for p, v in self.sg.predicate_objects(self.node) if p in search_parameters)
         reports = []
-        focus_value_nodes = self.value_nodes(target_graph, focus)
+        focus_value_nodes = self.value_nodes(
+            target_graph, focus, sparql_mode=executor.sparql_mode, debug=executor.debug
+        )
         filter_reports: bool = False
         allow_conform: bool = False
         allowed_severities: Set[URIRef] = set()
diff --git a/pyshacl/validate.py b/pyshacl/validate.py
index 59f0b55..67d9555 100644
--- a/pyshacl/validate.py
+++ b/pyshacl/validate.py
@@ -1,27 +1,21 @@
 # -*- coding: utf-8 -*-
 #
 import logging
+import os
 import sys
 from functools import wraps
 from io import BufferedIOBase, TextIOBase
 from os import getenv, path
 from sys import stderr
-from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Set, Tuple, Union, cast
+from typing import Dict, List, Optional, Tuple, Union
 
 import rdflib
 from rdflib import BNode, Literal, URIRef
-from rdflib.util import from_n3
 
 from .consts import (
-    RDF_object,
-    RDF_predicate,
-    RDF_subject,
     RDF_type,
-    RDFS_Resource,
     SH_conforms,
-    SH_detail,
     SH_result,
-    SH_resultMessage,
     SH_ValidationReport,
     env_truths,
 )
@@ -34,14 +28,11 @@
     add_baked_in,
     clone_blank_node,
     clone_graph,
-    compare_blank_node,
-    compare_node,
     inoculate,
     inoculate_dataset,
     load_from_source,
     mix_datasets,
     mix_graphs,
-    order_graph_literal,
 )
 from .rules import apply_rules, gather_rules
 from .shapes_graph import ShapesGraph
@@ -71,6 +62,7 @@ def _load_default_options(cls, options_dict: dict):
         options_dict.setdefault('abort_on_first', False)
         options_dict.setdefault('allow_infos', False)
         options_dict.setdefault('allow_warnings', False)
+        options_dict.setdefault('sparql_mode', False)
         options_dict.setdefault('max_validation_depth', 15)
         if 'logger' not in options_dict:
             options_dict['logger'] = logging.getLogger(__name__)
@@ -193,13 +185,20 @@ def __init__(
         self.data_graph_is_multigraph = isinstance(self.data_graph, (rdflib.Dataset, rdflib.ConjunctiveGraph))
         if self.ont_graph is not None and isinstance(self.ont_graph, (rdflib.Dataset, rdflib.ConjunctiveGraph)):
             self.ont_graph.default_union = True
-
+        if self.ont_graph is not None and options['sparql_mode']:
+            raise ReportableRuntimeError("Cannot use SPARQL Remote Graph Mode with extra Ontology Graph inoculation.")
         if shacl_graph is None:
+            if options['sparql_mode']:
+                raise ReportableRuntimeError(
+                    "SHACL Shapes Graph must be a separate local graph or file when in SPARQL Remote Graph Mode."
+                )
             shacl_graph = clone_graph(data_graph, identifier='shacl')
         assert isinstance(shacl_graph, rdflib.Graph), "shacl_graph must be a rdflib Graph object"
         self.shacl_graph = ShapesGraph(shacl_graph, self.debug, self.logger)  # type: ShapesGraph
 
         if options['use_js']:
+            if options['sparql_mode']:
+                raise ReportableRuntimeError("Cannot use SHACL-JS in SPARQL Remote Graph Mode.")
             is_js_installed = check_extra_installed('js')
             if is_js_installed:
                 self.shacl_graph.enable_js()
@@ -224,10 +223,12 @@ def mix_in_ontology(self):
     def make_executor(self) -> SHACLExecutor:
         return SHACLExecutor(
             validator=self,
+            advanced_mode=bool(self.options.get('advanced', False)),
             abort_on_first=bool(self.options.get("abort_on_first", False)),
             allow_infos=bool(self.options.get("allow_infos", False)),
             allow_warnings=bool(self.options.get("allow_warnings", False)),
             iterate_rules=bool(self.options.get("iterate_rules", False)),
+            sparql_mode=bool(self.options.get("sparql_mode", False)),
             max_validation_depth=self.options.get("max_validation_depth", 15),
             debug=self.debug,
         )
@@ -249,8 +250,10 @@ def run(self):
                 the_target_graph = self.data_graph
             inference_option = self.options.get('inference', 'none')
             if self.inplace and self.debug:
-                self.logger.debug("Skipping DataGraph clone because inplace option is passed.")
+                self.logger.debug("Skipping DataGraph clone because PySHACL is operating in inplace mode.")
             if inference_option and not self.pre_inferenced and str(inference_option) != "none":
+                if self.options.get('sparql_mode', False):
+                    raise ReportableRuntimeError("Cannot use any pre-inference option in SPARQL Remote Graph Mode.")
                 if not has_cloned and not self.inplace:
                     self.logger.debug("Cloning DataGraph to temporary memory graph before pre-inferencing.")
                     the_target_graph = clone_graph(the_target_graph)
@@ -259,6 +262,8 @@ def run(self):
                 self._run_pre_inference(the_target_graph, inference_option, 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):
+                    raise ReportableRuntimeError("Cannot clone DataGraph in SPARQL Remote Graph Mode.")
                 # We still need to clone in advanced mode, because of triple rules
                 self.logger.debug("Forcing clone of DataGraph because advanced mode is enabled.")
                 the_target_graph = clone_graph(the_target_graph)
@@ -271,7 +276,7 @@ def run(self):
 
         shapes = self.shacl_graph.shapes  # This property getter triggers shapes harvest.
         executor = self.make_executor()
-        if self.options['advanced']:
+        if executor.advanced_mode:
             self.logger.debug("Activating SHACL-AF Features.")
             target_types = gather_target_types(self.shacl_graph)
             advanced = {
@@ -308,8 +313,13 @@ def run(self):
             if self.debug:
                 self.logger.debug(f"Validating DataGraph named {g.identifier}")
             if advanced:
-                apply_functions(executor, advanced['functions'], g)
-                apply_rules(executor, advanced['rules'], g)
+                if advanced['functions']:
+                    apply_functions(executor, advanced['functions'], g)
+                if advanced['rules']:
+                    if executor.sparql_mode:
+                        self.logger.warning("Skipping SHACL Rules because operating in SPARQL Remote Graph Mode.")
+                    else:
+                        apply_rules(executor, advanced['rules'], g)
             try:
                 for s in shapes:
                     _is_conform, _reports = s.validate(executor, g)
@@ -395,6 +405,7 @@ def validate(
     allow_infos: Optional[bool] = False,
     allow_warnings: Optional[bool] = False,
     max_validation_depth: Optional[int] = None,
+    sparql_mode: Optional[bool] = False,
     **kwargs,
 ):
     """
@@ -411,7 +422,7 @@ def validate(
     :type advanced: bool | None
     :param inference: One of "rdfs", "owlrl", "both", "none", or None
     :type inference: str | None
-    :param inplace: If this is enabled, do not clone the datagraph, manipulate it inplace
+    :param inplace: If this is enabled, do not clone the datagraph, manipulate it in-place
     :type inplace: bool
     :param abort_on_first: Stop evaluating constraints after first violation is found
     :type abort_on_first: bool | None
@@ -421,6 +432,8 @@ def validate(
     :type allow_warnings: bool | None
     :param max_validation_depth: The maximum number of SHACL shapes "deep" that the validator can go before reaching an "endpoint" constraint.
     :type max_validation_depth: int | None
+    :param sparql_mode: Treat the DataGraph as a SPARQL endpoint, validate the graph at the SPARQL endpoint.
+    :type sparql_mode: bool | None
     :param kwargs:
     :return:
     """
@@ -446,29 +459,45 @@ def validate(
         # DataGraph is passed in as Text. It is not an rdflib.Graph
         # That means we load it into an ephemeral graph at runtime
         # that means we don't need to make a copy to prevent polluting it.
-        if isinstance(data_graph, str) and (
-            data_graph.startswith("http:/")
-            or data_graph.startswith("https:/")
-            or data_graph.startswith("file:/")
-            or data_graph.startswith("urn:")
-        ):
-            ephemeral = False
-        elif isinstance(data_graph, bytes) and (
-            data_graph.startswith(b"http:/")
-            or data_graph.startswith(b"https:/")
-            or data_graph.startswith(b"file:/")
-            or data_graph.startswith(b"urn:")
-        ):
-            ephemeral = False
-        else:
-            ephemeral = True
+        ephemeral = True
     else:
         ephemeral = False
-
-    # force no owl imports on data_graph
-    loaded_dg = load_from_source(
-        data_graph, rdf_format=data_graph_format, multigraph=True, do_owl_imports=False, logger=log
-    )
+    use_js = kwargs.pop('js', None)
+    if sparql_mode:
+        if use_js:
+            raise ReportableRuntimeError("Cannot use SHACL-JS in SPARQL Remote Graph Mode.")
+        if inplace:
+            raise ReportableRuntimeError("Cannot use inplace mode in SPARQL Remote Graph Mode.")
+        if ont_graph is not None:
+            raise ReportableRuntimeError("Cannot use SPARQL Remote Graph Mode with extra Ontology Graph inoculation.")
+        if isinstance(data_graph, bytes):
+            data_graph: str = data_graph.decode('utf-8')
+        else:
+            data_graph = data_graph
+        ephemeral = False
+        inplace = True
+    if (
+        sparql_mode
+        and isinstance(data_graph, str)
+        and (data_graph.lower().startswith("http:") or data_graph.lower().startswith("https:"))
+    ):
+        from rdflib.plugins.stores.sparqlstore import SPARQLStore
+
+        query_endpoint: str = data_graph
+        username = os.getenv("PYSHACL_SPARQL_USERNAME", "")
+        method = os.getenv("PYSHACL_SPARQL_METHOD", "GET")
+        if username:
+            password = os.getenv("PYSHACL_SPARQL_PASSWORD", "")
+            auth = (username, None if not password else password)
+        else:
+            auth = None
+        store = SPARQLStore(query_endpoint=query_endpoint, auth=auth, method=method)
+        loaded_dg = rdflib.Dataset(store=store, default_union=True)
+    else:
+        # force no owl imports on data_graph
+        loaded_dg = load_from_source(
+            data_graph, rdf_format=data_graph_format, multigraph=True, do_owl_imports=False, logger=log
+        )
     ont_graph_format = kwargs.pop('ont_graph_format', None)
     if ont_graph is not None:
         loaded_og = load_from_source(
@@ -485,7 +514,6 @@ def validate(
         rdflib_bool_unpatch()
     else:
         loaded_sg = None
-    use_js = kwargs.pop('js', None)
     iterate_rules = kwargs.pop('iterate_rules', False)
     if "abort_on_error" in kwargs:
         log.warning("Usage of abort_on_error is deprecated. Use abort_on_first instead.")
@@ -501,6 +529,7 @@ def validate(
         'advanced': advanced,
         'iterate_rules': iterate_rules,
         'use_js': use_js,
+        'sparql_mode': sparql_mode,
         'logger': log,
     }
     if max_validation_depth is not None:


Test patch:
diff --git a/test/test_dash_validate.py b/test/test_dash_validate.py
index 8a6764d..fda25bd 100644
--- a/test/test_dash_validate.py
+++ b/test/test_dash_validate.py
@@ -28,6 +28,7 @@
     for y in glob.glob(path.join(x[0], '*.test.ttl')):
         dash_core_files.append((y, None))
 
+
 @pytest.mark.parametrize('target_file, shacl_file', dash_core_files)
 def test_dash_validate_all_core(target_file, shacl_file):
     try:
@@ -41,6 +42,21 @@ def test_dash_validate_all_core(target_file, shacl_file):
     print(v_text)
 
 
+@pytest.mark.parametrize('target_file, shacl_file', dash_core_files)
+def test_dash_validate_all_core_sparql_mode(target_file, shacl_file):
+    try:
+        if shacl_file is None:
+            # shacl_file cannot be None in SPARQL Remote Graph Mode
+            shacl_file = target_file
+        val, _, v_text = pyshacl.validate(
+            target_file, shacl_graph=shacl_file, inference='none', check_dash_result=True, debug=True, sparql_mode=True, meta_shacl=False)
+    except (NotImplementedError, ReportableRuntimeError) as e:
+        print(e)
+        val = False
+        v_text = ""
+    assert val
+    print(v_text)
+
 
 for x in walk(path.join(dash_files_dir, 'sparql')):
     for y in glob.glob(path.join(x[0], '*.test.ttl')):
@@ -58,6 +74,20 @@ def test_dash_validate_all_sparql(target_file, shacl_file):
     assert val
     print(v_text)
 
+@pytest.mark.parametrize('target_file, shacl_file', dash_sparql_files)
+def test_dash_validate_all_sparql_sparql_mode(target_file, shacl_file):
+    try:
+        if shacl_file is None:
+            # shacl_file cannot be None in SPARQL Remote Graph Mode
+            shacl_file = target_file
+        val, _, v_text = pyshacl.validate(
+            target_file, shacl_graph=shacl_file, inference='none', check_dash_result=True, debug=True, sparql_mode=True, meta_shacl=False)
+    except (NotImplementedError, ReportableRuntimeError) as e:
+        print(e)
+        val = False
+        v_text = ""
+    assert val
+    print(v_text)
 
 # Tests for SHACL Advanced Features: https://www.w3.org/TR/shacl-af
 
@@ -96,6 +126,7 @@ def test_dash_validate_all_sparql_rules(target_file, shacl_file):
     assert val
     print(v_text)
 
+
 # Get all triple-rules tests.
 for x in walk(path.join(dash_files_dir, 'rules', 'triple')):
     for y in glob.glob(path.join(x[0], '*.test.ttl')):
@@ -136,6 +167,7 @@ def test_dash_validate_all_triple_rules(target_file, shacl_file):
 
     print(v_text)
 
+
 # Get all SHACL-AF sh:target tests.
 for x in walk(path.join(dash_files_dir, 'target')):
     for y in glob.glob(path.join(x[0], '*.test.ttl')):
@@ -176,6 +208,46 @@ def test_dash_validate_target(target_file, shacl_file):
 
     print(v_text)
 
+
+@pytest.mark.parametrize('target_file, shacl_file', dash_target_files)
+def test_dash_validate_target_sparql_mode(target_file, shacl_file):
+    test_name = shacl_file or target_file
+    try:
+        if shacl_file is None:
+            # shacl_file cannot be None in SPARQL Remote Graph Mode
+            shacl_file = target_file
+        val, _, v_text = pyshacl.validate(
+            target_file, shacl_graph=shacl_file, advanced=True, inference='none', check_dash_result=True, debug=True, sparql_mode=True, meta_shacl=False)
+    except NotImplementedError as ne:
+        for ani in ALLOWABLE_NOT_IMPLEMENTED:
+            if test_name.endswith(ani):
+                v_text = "Skipping not implemented feature in test: {}".format(test_name)
+                print(v_text)
+                val = True
+                break
+        else:
+            print(ne)
+            val = False
+            v_text = ""
+    except ReportableRuntimeError as e:
+        import traceback
+        print(e)
+        traceback.print_tb(e.__traceback__)
+        val = False
+        v_text = ""
+    try:
+        assert val
+    except AssertionError as ae:
+        for af in ALLOWABLE_FAILURES:
+            if test_name.endswith(af):
+                v_text = "Allowing failure in test: {}".format(test_name)
+                print(v_text)
+                break
+        else:
+            raise ae
+
+    print(v_text)
+
 # Get all SHACL-AF sh:expression tests.
 for x in walk(path.join(dash_files_dir, 'expression')):
     for y in glob.glob(path.join(x[0], '*.test.ttl')):
diff --git a/test/test_sht_validate.py b/test/test_sht_validate.py
index 6873b77..df6c499 100644
--- a/test/test_sht_validate.py
+++ b/test/test_sht_validate.py
@@ -52,6 +52,18 @@ def test_sht_all(base, index, caplog) -> None:
     })
 
 
+@pytest.mark.parametrize("base, index", test_index_map)
+def test_sht_all_sparql_mode(base, index, caplog) -> None:
+    caplog.set_level(logging.DEBUG)
+    tests = tests_found_in_manifests[base]
+    test = tests[index]
+    run_sht_test(test, {
+        "inference": 'none',
+        "debug": True,
+        "sparql_mode": True,
+        "meta_shacl": False
+    })
+
 def run_sht_test(sht_test, validate_args: dict) -> None:
     logger = logging.getLogger()  # pytest uses the root logger with a capturing handler
     if platform.system() == "Windows":
@@ -61,6 +73,10 @@ def run_sht_test(sht_test, validate_args: dict) -> None:
     label = sht_test.label
     data_file = sht_test.data_graph
     shacl_file = sht_test.shapes_graph
+    sparql_mode = validate_args.get('sparql_mode', False)
+    if sparql_mode and shacl_file is None:
+        # shacl_file cannot be None in SPARQL Remote Graph Mode
+        shacl_file = data_file
     if label:
         logger.info("testing: ".format(label))
     try: