Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable debug option for pyschacl validation #281

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
425 changes: 425 additions & 0 deletions examples/notebooks/use-cases/LocalValidation.ipynb

Large diffs are not rendered by default.

8 changes: 4 additions & 4 deletions kgforge/core/archetypes/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,18 +150,18 @@ def schema_id(self, type: str) -> str:
not_supported()

def validate(self, data: Union[Resource, List[Resource]],
execute_actions_before: bool, type_: str) -> None:
execute_actions_before: bool, type_: str, debug: bool=False) -> None:
# Replace None by self._validate_many to switch to optimized bulk validation.
run(self._validate_one, None, data, execute_actions=execute_actions_before,
exception=ValidationError, monitored_status="_validated", type_=type_)
exception=ValidationError, monitored_status="_validated", type_=type_, debug=debug)

def _validate_many(self, resources: List[Resource], type_: str) -> None:
def _validate_many(self, resources: List[Resource], type_: str, debug: bool) -> None:
# Bulk validation could be optimized by overriding this method in the specialization.
# POLICY Should reproduce self._validate_one() and execution._run_one() behaviours.
not_supported()

@abstractmethod
def _validate_one(self, resource: Resource, type_: str) -> None:
def _validate_one(self, resource: Resource, type_: str, debug: bool) -> None:
# POLICY Should notify of failures with exception ValidationError including a message.
pass

Expand Down
19 changes: 14 additions & 5 deletions kgforge/core/archetypes/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,8 @@ def mapper(self) -> Optional[Callable]:
# [C]RUD.

def register(
self, data: Union[Resource, List[Resource]], schema_id: str = None
self, data: Union[Resource, List[Resource]], schema_id: str = None,
debug: bool = False
) -> None:
# Replace None by self._register_many to switch to optimized bulk registration.
run(
Expand All @@ -162,6 +163,7 @@ def register(
data,
required_synchronized=False,
execute_actions=True,
catch_exceptions=not debug,
exception=RegistrationError,
monitored_status="_synchronized",
schema_id=schema_id,
Expand Down Expand Up @@ -294,9 +296,10 @@ def _download_one(
# CR[U]D.

def update(
self, data: Union[Resource, List[Resource]], schema_id: Optional[str]
self, data: Union[Resource, List[Resource]], schema_id: Optional[str], debug: bool = False
) -> None:
# Replace None by self._update_many to switch to optimized bulk update.
catch_exceptions = False if debug else True
crisely09 marked this conversation as resolved.
Show resolved Hide resolved
run(
self._update_one,
None,
Expand All @@ -321,15 +324,17 @@ def _update_one(self, resource: Resource, schema_id: Optional[str]) -> None:
# TODO This operation might be abstracted here when other stores will be implemented.
pass

def tag(self, data: Union[Resource, List[Resource]], value: str) -> None:
def tag(self, data: Union[Resource, List[Resource]], value: str, debug: bool = True) -> None:
# Replace None by self._tag_many to switch to optimized bulk tagging.
# POLICY If tagging modify the resource, run() should have status='_synchronized'.
catch_exceptions = False if debug else True
run(
self._tag_one,
None,
data,
id_required=True,
required_synchronized=True,
catch_exceptions=catch_exceptions,
exception=TaggingError,
value=value,
)
Expand All @@ -347,14 +352,16 @@ def _tag_one(self, resource: Resource, value: str) -> None:

# CRU[D].

def deprecate(self, data: Union[Resource, List[Resource]]) -> None:
def deprecate(self, data: Union[Resource, List[Resource]], debug: bool = False) -> None:
# Replace None by self._deprecate_many to switch to optimized bulk deprecation.
catch_exceptions = False if debug else True
run(
self._deprecate_one,
None,
data,
id_required=True,
required_synchronized=True,
catch_exceptions=catch_exceptions,
exception=DeprecationError,
monitored_status="_synchronized",
)
Expand Down Expand Up @@ -439,14 +446,16 @@ def _elastic(self, query: str) -> List[Resource]:

# Versioning.

def freeze(self, data: Union[Resource, List[Resource]]) -> None:
def freeze(self, data: Union[Resource, List[Resource]], debug: bool = False) -> None:
# Replace None by self._freeze_many to switch to optimized bulk freezing.
catch_exceptions = False if debug else True
run(
self._freeze_one,
None,
data,
id_required=True,
required_synchronized=True,
catch_exceptions=catch_exceptions,
exception=FreezingError,
)

Expand Down
31 changes: 23 additions & 8 deletions kgforge/core/forge.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
from kgforge.core.commons.dictionaries import with_defaults
from kgforge.core.commons.exceptions import ResolvingError
from kgforge.core.commons.execution import catch
from kgforge.core.commons.actions import (collect_lazy_actions,
execute_lazy_actions)
from kgforge.core.commons.imports import import_class
from kgforge.core.commons.strategies import ResolvingStrategy
from kgforge.core.conversions.dataframe import as_dataframe, from_dataframe
Expand Down Expand Up @@ -283,7 +285,7 @@ def template(
def validate(
self,
data: Union[Resource, List[Resource]],
execute_actions_before: bool=False,
execute_actions_before: bool = False,
type_: str=None
) -> None:
"""
Expand All @@ -297,7 +299,8 @@ def validate(
:param type_: the type to validate the data against it. If None, the validation function will look for a type attribute in the Resource
:return: None
"""
self._model.validate(data, execute_actions_before, type_=type_)
debug = self._debug
self._model.validate(data, execute_actions_before, type_, debug)

# Resolving User Interface.

Expand Down Expand Up @@ -607,6 +610,19 @@ def elastic(
:return: List[Resource]
"""
return self._store.elastic(query, debug, limit, offset)

@catch
@staticmethod
def execute_lazy_actions(resources: Union[List[Resource], Resource]):
"""
Execute any lazy action present in a given resource or a list of resources

:param resources: The resources or list of resources from which actions will be executed
"""
resources = [resources] if not isinstance(resources, List) else resources
for resource in resources:
lazy_actions = collect_lazy_actions(resource)
execute_lazy_actions(resource, lazy_actions)

@catch
def download(
Expand Down Expand Up @@ -643,7 +659,7 @@ def register(
:param data: the resources to register
:param schema_id: an identifier of the schema the registered resources should conform to
"""
self._store.register(data, schema_id)
self._store.register(data, schema_id, debug=self._debug)

# No @catch because the error handling is done by execution.run().
def update(
Expand All @@ -655,7 +671,7 @@ def update(
:param data: the resources to update
:param schema_id: an identifier of the schema the updated resources should conform to
"""
self._store.update(data, schema_id)
self._store.update(data, schema_id, debug=self._debug)

# No @catch because the error handling is done by execution.run().
def deprecate(self, data: Union[Resource, List[Resource]]) -> None:
Expand All @@ -664,7 +680,7 @@ def deprecate(self, data: Union[Resource, List[Resource]]) -> None:

:param: the resources to deprecate
"""
self._store.deprecate(data)
self._store.deprecate(data, debug=self._debug)

# Versioning User Interface.

Expand All @@ -676,7 +692,7 @@ def tag(self, data: Union[Resource, List[Resource]], value: str) -> None:
:param data: the resources to tag
:param value: the tag value
"""
self._store.tag(data, value)
self._store.tag(data, value, debug=self._debug)

# No @catch because the error handling is done by execution.run().
def freeze(self, data: Union[Resource, List[Resource]]) -> None:
Expand All @@ -686,7 +702,7 @@ def freeze(self, data: Union[Resource, List[Resource]]) -> None:

:param data: the resources to freeze
"""
self._store.freeze(data)
self._store.freeze(data, debug=self._debug)

# Files Handling User Interface.

Expand Down Expand Up @@ -729,7 +745,6 @@ def as_json(
self._model.resolve_context,
)

@catch
@catch
def as_jsonld(
self,
Expand Down
2 changes: 1 addition & 1 deletion kgforge/specializations/models/demo_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ def mapping(self, entity: str, source: str, type: Callable) -> Mapping:

# Validation.

def _validate_one(self, resource: Resource, type_: str) -> None:
def _validate_one(self, resource: Resource, type_: str, debug: bool) -> None:
"""
Validates the model against a given type provided by type_ parameter.
If type_ is None then it looks for type attribute in resource.
Expand Down
8 changes: 4 additions & 4 deletions kgforge/specializations/models/rdf/directory_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from pathlib import Path
from typing import Dict, Tuple

from pyshacl import Validator
from pyshacl import validate
from rdflib import Graph, URIRef
from rdflib.util import guess_format

Expand Down Expand Up @@ -43,9 +43,9 @@ def materialize(self, iri: URIRef) -> NodeProperties:
attrs["properties"] = props
return NodeProperties(**attrs)

def _validate(self, iri: str, data_graph: Graph) -> Tuple[bool, Graph, str]:
validator = Validator(data_graph, shacl_graph=self._graph)
return validator.run()
def _validate(self, iri: str, data_graph: Graph, debug: bool) -> Tuple[bool, Graph, str]:
result = validate(data_graph, shacl_graph=self._graph, debug=debug)
return result

def resolve_context(self, iri: str) -> Dict:
if iri in self._context_cache:
Expand Down
6 changes: 3 additions & 3 deletions kgforge/specializations/models/rdf/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def materialize(self, iri: URIRef) -> NodeProperties:
"""
raise NotImplementedError()

def validate(self, resource: Resource, type_: str):
def validate(self, resource: Resource, type_: str, debug: bool):
try:
if isinstance(resource.type, list) and type_ is None:
raise ValueError("Resource has list of types as attribute and type_ parameter is not specified. "
Expand All @@ -168,10 +168,10 @@ def validate(self, resource: Resource, type_: str):
raise TypeError("resource requires a type attribute")
else:
data_graph = as_graph(resource, False, self.context, None, None)
return self._validate(shape_iri, data_graph)
return self._validate(shape_iri, data_graph, debug)

@abstractmethod
def _validate(self, iri: str, data_graph: Graph) -> Tuple[bool, Graph, str]:
def _validate(self, iri: str, data_graph: Graph, debug: bool) -> Tuple[bool, Graph, str]:
raise NotImplementedError()

@abstractmethod
Expand Down
8 changes: 4 additions & 4 deletions kgforge/specializations/models/rdf/store_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import json

from pyshacl import Validator
from pyshacl import validate

from kgforge.core import Resource
from kgforge.core.commons.exceptions import RetrievalError
Expand Down Expand Up @@ -57,11 +57,11 @@ def materialize(self, iri: URIRef) -> NodeProperties:
attrs["properties"] = props
return NodeProperties(**attrs)

def _validate(self, iri: str, data_graph: Graph) -> Tuple[bool, Graph, str]:
def _validate(self, iri: str, data_graph: Graph, debug: bool) -> Tuple[bool, Graph, str]:
# _type_shape will make sure all the shapes for this type are in the graph
self._type_shape(iri)
validator = Validator(data_graph, shacl_graph=self._graph)
return validator.run()
result = validate(data_graph, shacl_graph=self._graph, debug=debug)
return result

def resolve_context(self, iri: str) -> Dict:
if iri in self._context_cache:
Expand Down
15 changes: 9 additions & 6 deletions kgforge/specializations/models/rdf_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,16 @@ def schema_id(self, type: str) -> str:
except KeyError:
raise ValueError("type not found")

def validate(self, data: Union[Resource, List[Resource]], execute_actions_before: bool, type_: str) -> None:
def validate(self, data: Union[Resource, List[Resource]], execute_actions_before: bool, type_: str,
debug: bool) -> None:
crisely09 marked this conversation as resolved.
Show resolved Hide resolved
catch_exceptions = False if debug else True
run(self._validate_one, self._validate_many, data, execute_actions=execute_actions_before,
exception=ValidationError, monitored_status="_validated", type_=type_)
exception=ValidationError, monitored_status="_validated", catch_exceptions=catch_exceptions,
type_=type_, debug=debug)

def _validate_many(self, resources: List[Resource], type_: str) -> None:
def _validate_many(self, resources: List[Resource], type_: str, debug: bool) -> None:
for resource in resources:
conforms, graph, _ = self.service.validate(resource, type_=type_)
conforms, graph, _ = self.service.validate(resource, type_=type_, debug=debug)
if conforms:
resource._validated = True
action = Action(self._validate_many.__name__, conforms, None)
Expand All @@ -125,8 +128,8 @@ def _validate_many(self, resources: List[Resource], type_: str) -> None:
action = Action(self._validate_many.__name__, conforms, ValidationError(message))
resource._last_action = action

def _validate_one(self, resource: Resource, type_: str) -> None:
conforms, _, report = self.service.validate(resource, type_)
def _validate_one(self, resource: Resource, type_: str, debug: bool) -> None:
conforms, _, report = self.service.validate(resource, type_=type_, debug=debug)
if conforms is False:
raise ValidationError("\n" + report)

Expand Down
Loading