@@ -222,3 +222,139 @@ def shape_to_df(self, shape: rdflib.URIRef) -> pd.DataFrame:
222222 metadata .columns = [str (col ) for col in metadata .columns ]
223223 # convert the rdflib terms to Python types
224224 return metadata .map (lambda x : x .toPython ())
225+
226+
227+ class PyshiftyCompiledModel (CompiledModel ):
228+ """
229+ Specialized CompiledModel for the pyshifty SHACL engine.
230+ Keeps data and shape graphs separate and avoids shape skolemization.
231+ """
232+
233+ def __init__ (
234+ self ,
235+ model : Model ,
236+ shape_collections : List [ShapeCollection ],
237+ compiled_graph : rdflib .Graph ,
238+ shacl_engine : str = "default" ,
239+ ):
240+ self .model = model
241+ self .shape_collections = shape_collections
242+ ontology_graph = rdflib .Graph ()
243+ for shape_collection in shape_collections :
244+ ontology_graph += shape_collection .graph
245+
246+ ontology_graph = skolemize_shapes (ontology_graph )
247+
248+ shacl_engine = (
249+ self .model ._bm .shacl_engine
250+ if (shacl_engine == "default" or not shacl_engine )
251+ else shacl_engine
252+ )
253+
254+ self ._compiled_graph = shacl_inference (
255+ compiled_graph , ontology_graph , shacl_engine
256+ )
257+
258+ def _build_shape_graph (self , error_on_missing_imports : bool = True ) -> rdflib .Graph :
259+ shape_graph = rdflib .Graph ()
260+ for sc in self .shape_collections :
261+ shape_graph += sc .resolve_imports (
262+ error_on_missing_imports = error_on_missing_imports
263+ ).graph
264+ shape_graph = rewrite_shape_graph (shape_graph )
265+ shape_graph .remove ((None , OWL .imports , None ))
266+ return skolemize_shapes (shape_graph )
267+
268+ def validate_model_against_shapes (
269+ self ,
270+ shapes_to_test : List [rdflib .URIRef ],
271+ target_class : rdflib .URIRef ,
272+ ) -> Dict [rdflib .URIRef , "ValidationContext" ]:
273+ """Validates the model against a list of shapes and generates a
274+ validation report for each.
275+
276+ :param shapes_to_test: list of shape URIs to validate the model against
277+ :type shapes_to_test: List[URIRef]
278+ :param target_class: the class upon which to run the selected shapes
279+ :type target_class: URIRef
280+ :return: a dictionary that relates each shape to test URIRef to a
281+ ValidationContext
282+ :rtype: Dict[URIRef, ValidationContext]
283+ """
284+ model_graph = copy_graph (self ._compiled_graph )
285+
286+ results = {}
287+
288+ targets = model_graph .query (
289+ f"""
290+ PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
291+ PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
292+ SELECT ?target
293+ WHERE {{
294+ ?target rdf:type/rdfs:subClassOf* <{ target_class } >
295+
296+ }}
297+ """
298+ )
299+
300+ shape_graph = rdflib .Graph ()
301+ for shape_collection in self .shape_collections :
302+ shape_graph += shape_collection .graph
303+ shape_graph = skolemize_shapes (shape_graph )
304+
305+ for shape_uri in shapes_to_test :
306+ temp_model_graph = copy_graph (model_graph )
307+ for (s ,) in targets :
308+ temp_model_graph .add ((URIRef (s ), A , shape_uri ))
309+
310+ valid , report_g , report_str = shacl_validate (
311+ temp_model_graph , shape_graph , engine = self .model ._bm .shacl_engine
312+ )
313+ results [shape_uri ] = ValidationContext (
314+ self .shape_collections ,
315+ shape_graph ,
316+ valid ,
317+ report_g ,
318+ report_str ,
319+ self .model ,
320+ )
321+
322+ return results
323+
324+ def validate (
325+ self ,
326+ error_on_missing_imports : bool = True ,
327+ ) -> "ValidationContext" :
328+ """Validates this model against the given list of ShapeCollections.
329+ If no list is provided, the model will be validated against the model's "manifest".
330+ If a list of shape collections is provided, the manifest will *not* be automatically
331+ included in the set of shape collections.
332+
333+ Loads all of the ShapeCollections into a single graph.
334+
335+ :param error_on_missing_imports: if True, raises an error if any of the dependency
336+ ontologies are missing (i.e. they need to be loaded into BuildingMOTIF), defaults
337+ to True
338+ :type error_on_missing_imports: bool, optional
339+ :return: An object containing useful properties/methods to deal with
340+ the validation results
341+ :rtype: ValidationContext
342+ """
343+ data_graph = copy_graph (self ._compiled_graph )
344+ data_graph .remove ((None , OWL .imports , None ))
345+
346+ shape_graph = self ._build_shape_graph (
347+ error_on_missing_imports = error_on_missing_imports
348+ )
349+
350+ valid , report_g , report_str = shacl_validate (
351+ data_graph , shape_graph , engine = self .model ._bm .shacl_engine
352+ )
353+ return ValidationContext (
354+ self .shape_collections ,
355+ shape_graph ,
356+ valid ,
357+ report_g ,
358+ report_str ,
359+ self .model ,
360+ )
0 commit comments