Skip to content
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
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:
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