# Dependances pre-provisionnees (rdflib pyshacl) : voir SemanticWeb/requirements.txt ; imports dans les cellules suivantes.SW-8-Python-SHACL
Navigation : << 7b-OWL | Index | 9-JSONLD >>
SHACL : Validation et Qualite des Données RDF
Ce notebook explore SHACL (Shapes Constraint Language), la recommandation W3C pour la validation de données RDF. Contrairement a OWL qui raisonne sous l’hypothese du monde ouvert, SHACL permet de définir des contraintes concretes et de verifier que les données d’un graphe RDF les respectent.
Objectifs d’apprentissage
A la fin de ce notebook, vous saurez : 1. Définir des shapes SHACL pour contraindre un graphe RDF 2. Valider des données avec pySHACL et interpreter le rapport 3. Créer des shapes custom programmatiquement 4. Utiliser SHACL pour garantir la qualite des données
Prerequis
- Notebook SW-7b-OWL complete ### Duree estimee : 45 minutes
0. Installation des dependances
Installons les deux bibliotheques necessaires : - rdflib : manipulation de graphes RDF (déjà utilisee dans SW-2b) - pyshacl : moteur de validation SHACL pour Python
Verifions que les installations se sont bien passees en important les modules et en affichant leurs versions.
import rdflib
import pyshacl
print(f"rdflib : {rdflib.__version__}")
print(f"pyshacl : {pyshacl.__version__}")
print("Installation OK.")rdflib : 7.6.0
pyshacl : 0.31.0
Installation OK.
1. Introduction a SHACL
Le problème : l’hypothese du monde ouvert
En RDF et OWL, on travaille sous l’hypothese du monde ouvert (Open World Assumption, OWA) : l’absence d’information ne signifie pas que cette information est fausse. Si un graphe RDF ne mentionne pas l’age d’une personne, OWL considere simplement que l’age est inconnu, pas qu’il est absent.
C’est un problème pour la qualite des données. Dans une application reelle, on veut pouvoir dire : - “Chaque personne doit avoir un nom” - “L’age doit etre un entier positif” - “Un email doit respecter un format précis”
OWL ne peut pas exprimer ces contraintes de validation. C’est la raison d’etre de SHACL (Shapes Constraint Language).
SHACL : Shapes Constraint Language
SHACL est une recommandation W3C publiee en juillet 2017. Elle définit un vocabulaire RDF pour decrire des contraintes (“shapes”) que les données RDF doivent respecter.
| Aspect | OWL (monde ouvert) | SHACL (monde ferme) |
|---|---|---|
| Hypothese | Monde ouvert (OWA) | Monde ferme (CWA) |
| Objectif | Inference, raisonnement | Validation, qualite |
| Absence de donnee | Inconnue | Violation potentielle |
minCount 1 |
Implique l’existence | Exige la presence |
| Analogie | Definition d’un concept | Formulaire de saisie |
| Standard W3C | OWL 2 (2012) | SHACL 1.0 (2017) |
Point cle : OWL decrit ce qui est vrai dans un domaine. SHACL decrit ce que les données doivent respecter. Les deux sont complementaires : OWL pour modeliser, SHACL pour valider.
2. Concepts fondamentaux de SHACL
SHACL repose sur deux types de formes (shapes) :
NodeShape et PropertyShape
| Concept | URI SHACL | Description |
|---|---|---|
| NodeShape | sh:NodeShape |
Forme qui s’applique a un noeud RDF (une ressource) |
| PropertyShape | sh:PropertyShape |
Forme qui contraint une propriete spécifique |
| targetClass | sh:targetClass |
Cible tous les noeuds d’une classe donnee |
| targetNode | sh:targetNode |
Cible un noeud spécifique par son URI |
| property | sh:property |
Lie une NodeShape a ses PropertyShapes |
| path | sh:path |
Designe la propriete RDF a contraindre |
Architecture d’une shape SHACL
ex:PersonShape a sh:NodeShape ; # Forme pour les personnes
sh:targetClass foaf:Person ; # Cible : toutes les foaf:Person
sh:property [ # Contrainte sur une propriete
sh:path foaf:name ; # Propriete ciblee
sh:minCount 1 ; # Au moins une valeur
sh:datatype xsd:string ; # Type attendu
] .
Principaux types de contraintes
| Catégorie | Contrainte | Propriete SHACL | Exemple |
|---|---|---|---|
| Cardinalite | Minimum / Maximum | sh:minCount, sh:maxCount |
sh:minCount 1 |
| Type | Type de donnee | sh:datatype |
sh:datatype xsd:string |
| Classe | Classe RDF | sh:class |
sh:class foaf:Person |
| Chaîne | Longueur minimale | sh:minLength |
sh:minLength 2 |
| Chaîne | Motif regex | sh:pattern |
sh:pattern "^[A-Z]" |
| Numérique | Borne inclusive | sh:minInclusive, sh:maxInclusive |
sh:minInclusive 0 |
3. Explorer une shape SHACL existante
Le fichier data/person-shape.ttl contient une forme SHACL pour valider des instances de foaf:Person. Chargeons-le et examinons les contraintes définies.
from rdflib import Graph, Namespace, URIRef, Literal
from rdflib.namespace import RDF, RDFS, XSD, FOAF
# Namespaces
SH = Namespace("http://www.w3.org/ns/shacl#")
EX = Namespace("http://example.org/")
# Charger les formes SHACL
shapes_graph = Graph()
shapes_graph.parse("data/person-shape.ttl", format="turtle")
print(f"Graphe des formes charge : {len(shapes_graph)} triples")
print()
print("=" * 60)
print("Contenu de person-shape.ttl :")
print("=" * 60)
print(shapes_graph.serialize(format="turtle"))Graphe des formes charge : 25 triples
============================================================
Contenu de person-shape.ttl :
============================================================
@prefix ex: <http://example.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:PersonShape a sh:NodeShape ;
sh:property [ sh:datatype xsd:string ;
sh:maxCount 1 ;
sh:message "Une personne doit avoir exactement un nom (string, min 2 caracteres)"@fr ;
sh:minCount 1 ;
sh:minLength 2 ;
sh:path foaf:name ],
[ sh:maxCount 1 ;
sh:message "L'email doit etre au format mailto:user@domain.tld"@fr ;
sh:path foaf:mbox ;
sh:pattern "^mailto:.+@.+\\..+$" ],
[ sh:datatype xsd:integer ;
sh:maxCount 1 ;
sh:maxInclusive 150 ;
sh:message "L'age doit etre un entier entre 0 et 150"@fr ;
sh:minInclusive 0 ;
sh:path foaf:age ],
[ sh:class foaf:Person ;
sh:message "La propriete knows doit pointer vers une autre Person"@fr ;
sh:path foaf:knows ] ;
sh:targetClass foaf:Person .
Analyse structurelle par programmation
Plutot que de lire le fichier Turtle a l’oeil, parcourons le graphe SHACL avec rdflib pour identifier automatiquement les NodeShapes, leurs cibles et les contraintes de chaque propriete.
# Parcourir les NodeShapes et leurs contraintes
for shape in shapes_graph.subjects(RDF.type, SH.NodeShape):
print(f"NodeShape : {shape}")
# Cible
for target in shapes_graph.objects(shape, SH.targetClass):
print(f" Cible (targetClass) : {target}")
# Proprietes contraintes
print(f" Contraintes de propriete :")
for prop_shape in shapes_graph.objects(shape, SH.property):
path = list(shapes_graph.objects(prop_shape, SH.path))
path_str = str(path[0]) if path else "?"
# Collecter les contraintes SHACL (hors path et message)
constraints = []
for pred, obj in shapes_graph.predicate_objects(prop_shape):
pred_local = str(pred).split("#")[-1] if "#" in str(pred) else str(pred).split("/")[-1]
if pred_local not in ("path", "message") and str(pred).startswith(str(SH)):
constraints.append(f"{pred_local}={obj}")
# Message
messages = list(shapes_graph.objects(prop_shape, SH.message))
msg = str(messages[0]) if messages else "-"
print(f" - {path_str.split('/')[-1]}")
for c in constraints:
print(f" {c}")
print(f" Message: {msg}")NodeShape : http://example.org/PersonShape
Cible (targetClass) : http://xmlns.com/foaf/0.1/Person
Contraintes de propriete :
- name
minCount=1
maxCount=1
datatype=http://www.w3.org/2001/XMLSchema#string
minLength=2
Message: Une personne doit avoir exactement un nom (string, min 2 caracteres)
- mbox
maxCount=1
pattern=^mailto:.+@.+\..+$
Message: L'email doit etre au format mailto:user@domain.tld
- age
maxCount=1
datatype=http://www.w3.org/2001/XMLSchema#integer
minInclusive=0
maxInclusive=150
Message: L'age doit etre un entier entre 0 et 150
- knows
class=http://xmlns.com/foaf/0.1/Person
Message: La propriete knows doit pointer vers une autre Person
Interpretation : Structure de la forme PersonShape
La forme ex:PersonShape cible toutes les instances de foaf:Person et impose 4 contraintes :
| Propriete | Contraintes | Signification |
|---|---|---|
foaf:name |
minCount=1, maxCount=1, datatype=string, minLength=2 | Exactement un nom, chaîne d’au moins 2 caractères |
foaf:mbox |
maxCount=1, pattern=^mailto:.+@.+\\..+$ |
Au plus un email, format mailto: valide |
foaf:age |
maxCount=1, datatype=integer, minInclusive=0, maxInclusive=150 | Au plus un age, entier entre 0 et 150 |
foaf:knows |
class=foaf:Person | Les personnes connues doivent etre des foaf:Person |
Note :
foaf:mboxetfoaf:agen’ont pas deminCount, ils sont donc optionnels. Seulfoaf:nameest obligatoire (minCount=1).
4. pySHACL : Validation en Python
La fonction validate()
pySHACL est l’implementation Python de reference pour la validation SHACL. Sa fonction principale validate() accepte les paramètres suivants :
| Paramètre | Type | Description |
|---|---|---|
data_graph |
Graph / str | Le graphe de données a valider (1er argument positionnel) |
shacl_graph |
Graph / str | Le graphe de formes SHACL |
inference |
str | Mode d’inference : 'none', 'rdfs', 'owlrl', 'both' |
abort_on_first |
bool | Arreter a la première violation (defaut: False) |
advanced |
bool | Activer SHACL Advanced Features (règles, etc.) |
inplace |
bool | Modifier le graphe de données en place (pour les règles) |
La fonction retourne un triplet (conforms, results_graph, results_text) : - conforms : booléen, True si toutes les contraintes sont satisfaites - results_graph : graphe RDF contenant les résultats detailles - results_text : rapport texte lisible
Charger les données de test
Le fichier data/person-data.ttl contient 7 personnes dont 5 presentent des erreurs volontaires. Chargeons-le et examinons son contenu.
# Charger les donnees de test
data_graph = Graph()
data_graph.parse("data/person-data.ttl", format="turtle")
print(f"Graphe de donnees charge : {len(data_graph)} triples")
print()
# Lister les personnes avec leurs proprietes
print("Personnes dans le graphe :")
print(f"{'Personne':<12s} {'Nom':<20s} {'Age':<8s} {'Email':<25s} {'Knows':<12s}")
print("-" * 77)
for person in sorted(data_graph.subjects(RDF.type, FOAF.Person), key=str):
name = list(data_graph.objects(person, FOAF.name))
age = list(data_graph.objects(person, FOAF.age))
mbox = list(data_graph.objects(person, FOAF.mbox))
knows = list(data_graph.objects(person, FOAF.knows))
name_str = str(name[0]) if name else "[ABSENT]"
age_str = str(age[0]) if age else "-"
mbox_str = str(mbox[0]).split('/')[-1] if mbox else "-"
knows_str = str(knows[0]).split('/')[-1] if knows else "-"
print(f"{str(person).split('/')[-1]:<12s} {name_str:<20s} {age_str:<8s} {mbox_str:<25s} {knows_str:<12s}")Graphe de donnees charge : 25 triples
Personnes dans le graphe :
Personne Nom Age Email Knows
-----------------------------------------------------------------------------
alice Alice Dupont 30 mailto:alice@example.com bob
bob Bob Martin 25 - alice
charlie [ABSENT] 40 - -
diana Diana Leroy -5 - -
eve Eve Bernard - not-an-email -
frank F 55 - -
grace Grace Moreau - - myDog
Exécution de la validation
Validons les données contre les formes SHACL. L’option inference='rdfs' permet a pySHACL de prendre en compte les inferences RDFS (sous-classes, sous-proprietes) lors de la validation.
from pyshacl import validate
# Validation SHACL
conforms, results_graph, results_text = validate(
data_graph,
shacl_graph=shapes_graph,
inference='rdfs',
abort_on_first=False
)
print(f"Les donnees sont conformes : {conforms}")
print()
print("=" * 60)
print("Rapport de validation :")
print("=" * 60)
print(results_text)Les donnees sont conformes : False
============================================================
Rapport de validation :
============================================================
Validation Report
Conforms: False
Results (5):
Constraint Violation in ClassConstraintComponent (http://www.w3.org/ns/shacl#ClassConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:class foaf:Person ; sh:message Literal("La propriete knows doit pointer vers une autre Person", lang=fr) ; sh:path foaf:knows ]
Focus Node: ex:grace
Value Node: ex:myDog
Result Path: foaf:knows
Message: La propriete knows doit pointer vers une autre Person
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:message Literal("Une personne doit avoir exactement un nom (string, min 2 caracteres)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("2", datatype=xsd:integer) ; sh:path foaf:name ]
Focus Node: ex:charlie
Result Path: foaf:name
Message: Une personne doit avoir exactement un nom (string, min 2 caracteres)
Constraint Violation in MinInclusiveConstraintComponent (http://www.w3.org/ns/shacl#MinInclusiveConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:integer ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:maxInclusive Literal("150", datatype=xsd:integer) ; sh:message Literal("L'age doit etre un entier entre 0 et 150", lang=fr) ; sh:minInclusive Literal("0", datatype=xsd:integer) ; sh:path foaf:age ]
Focus Node: ex:diana
Value Node: Literal("-5", datatype=xsd:integer)
Result Path: foaf:age
Message: L'age doit etre un entier entre 0 et 150
Constraint Violation in MinLengthConstraintComponent (http://www.w3.org/ns/shacl#MinLengthConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:message Literal("Une personne doit avoir exactement un nom (string, min 2 caracteres)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("2", datatype=xsd:integer) ; sh:path foaf:name ]
Focus Node: ex:frank
Value Node: Literal("F")
Result Path: foaf:name
Message: Une personne doit avoir exactement un nom (string, min 2 caracteres)
Constraint Violation in PatternConstraintComponent (http://www.w3.org/ns/shacl#PatternConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:maxCount Literal("1", datatype=xsd:integer) ; sh:message Literal("L'email doit etre au format mailto:user@domain.tld", lang=fr) ; sh:path foaf:mbox ; sh:pattern Literal("^mailto:.+@.+\..+$") ]
Focus Node: ex:eve
Value Node: <file:///<repo>MyIA.AI.Notebooks/SymbolicAI/SemanticWeb/data/not-an-email>
Result Path: foaf:mbox
Message: L'email doit etre au format mailto:user@domain.tld
Analyse structuree du graphe de résultats
Le rapport texte est utile pour un humain, mais on peut aussi analyser le graphe de résultats par programmation. Chaque violation est un noeud de type sh:ValidationResult avec des proprietes structurees.
# Analyser le graphe de resultats par programmation
violations = []
for result in results_graph.subjects(RDF.type, SH.ValidationResult):
focus = list(results_graph.objects(result, SH.focusNode))
path = list(results_graph.objects(result, SH.resultPath))
severity = list(results_graph.objects(result, SH.resultSeverity))
message = list(results_graph.objects(result, SH.resultMessage))
value = list(results_graph.objects(result, SH.value))
violations.append({
"focus": str(focus[0]).split("/")[-1] if focus else "?",
"path": str(path[0]).split("/")[-1] if path else "-",
"severity": str(severity[0]).split("#")[-1] if severity else "?",
"message": str(message[0]) if message else "-",
"value": str(value[0]) if value else "-"
})
# Affichage tabulaire
print(f"Nombre total de violations : {len(violations)}")
print()
print(f"{'Noeud':<12} {'Propriete':<10} {'Severite':<12} {'Message'}")
print("-" * 90)
for v in sorted(violations, key=lambda x: x["focus"]):
print(f"{v['focus']:<12} {v['path']:<10} {v['severity']:<12} {v['message'][:55]}")Nombre total de violations : 5
Noeud Propriete Severite Message
------------------------------------------------------------------------------------------
charlie name Violation Une personne doit avoir exactement un nom (string, min
diana age Violation L'age doit etre un entier entre 0 et 150
eve mbox Violation L'email doit etre au format mailto:user@domain.tld
frank name Violation Une personne doit avoir exactement un nom (string, min
grace knows Violation La propriete knows doit pointer vers une autre Person
Interpretation : Les 5 erreurs detectees
Le fichier person-data.ttl contenait 5 erreurs volontaires, toutes detectees par pySHACL :
| Personne | Propriete | Type de violation | Explication |
|---|---|---|---|
| alice | - | Aucune | Conforme : toutes les proprietes respectent les contraintes |
| bob | - | Aucune | Conforme (email optionnel, pas de minCount sur mbox) |
| charlie | foaf:name |
minCount |
Pas de nom alors que minCount=1 est exige |
| diana | foaf:age |
minInclusive |
Age negatif (-5) alors que minInclusive=0 |
| eve | foaf:mbox |
pattern |
Email not-an-email ne correspond pas au motif ^mailto: |
| frank | foaf:name |
minLength |
Nom “F” (1 caractère) trop court, minLength=2 |
| grace | foaf:knows |
class |
ex:myDog est un ex:Dog, pas un foaf:Person |
Points cles : 1. Alice et Bob passent la validation car leurs données sont completes et conformes 2. Bob n’a pas d’email mais c’est conforme : foaf:mbox n’a pas de minCount 3. Chaque violation est independante : un même noeud pourrait cumuler plusieurs violations
5. Ecrire des shapes custom en Python
Plutot que de charger des fichiers Turtle, on peut construire des formes SHACL directement en Python avec rdflib. Cela permet de créer des validations dynamiques, generees par du code.
Catalogue des contraintes
| Catégorie | Contrainte | Propriete SHACL | Description |
|---|---|---|---|
| Cardinalite | Minimum | sh:minCount |
Nombre minimum de valeurs |
| Cardinalite | Maximum | sh:maxCount |
Nombre maximum de valeurs |
| Type | Type de donnee | sh:datatype |
Type XSD attendu |
| Type | Classe RDF | sh:class |
La valeur doit etre d’une classe donnee |
| Type | Type de noeud | sh:nodeKind |
IRI, BlankNode ou Literal |
| Chaîne | Motif regex | sh:pattern |
Expression reguliere |
| Chaîne | Longueur min | sh:minLength |
Nombre minimum de caractères |
| Chaîne | Longueur max | sh:maxLength |
Nombre maximum de caractères |
| Numérique | Borne min | sh:minInclusive |
Valeur minimale (inclusive) |
| Numérique | Borne max | sh:maxInclusive |
Valeur maximale (inclusive) |
| Valeur | Valeur exacte | sh:hasValue |
Doit contenir cette valeur |
| Valeur | Liste | sh:in |
Doit etre dans cette liste |
| Unicite | Langue unique | sh:uniqueLang |
Une seule valeur par tag de langue |
Construire une ArticleShape
Creons une forme SHACL pour valider des articles (schema:Article) avec les contraintes suivantes : - Titre obligatoire (string, min 5 caractères) - Au moins un auteur (doit etre un schema:Person) - Date de publication optionnelle (format xsd:date) - Nombre de mots optionnel (entier entre 100 et 50000)
from rdflib import Graph, Namespace, URIRef, Literal, BNode
from rdflib.namespace import RDF, XSD, FOAF
SH = Namespace("http://www.w3.org/ns/shacl#")
EX = Namespace("http://example.org/")
SCHEMA = Namespace("http://schema.org/")
# Creer un nouveau graphe de formes
custom_shapes = Graph()
custom_shapes.bind("sh", SH)
custom_shapes.bind("ex", EX)
custom_shapes.bind("schema", SCHEMA)
custom_shapes.bind("xsd", XSD)
# --- NodeShape pour un Article ---
article_shape = EX.ArticleShape
custom_shapes.add((article_shape, RDF.type, SH.NodeShape))
custom_shapes.add((article_shape, SH.targetClass, SCHEMA.Article))
# Contrainte 1 : titre obligatoire (minCount + datatype + minLength)
title_prop = BNode()
custom_shapes.add((article_shape, SH.property, title_prop))
custom_shapes.add((title_prop, SH.path, SCHEMA.headline))
custom_shapes.add((title_prop, SH.minCount, Literal(1)))
custom_shapes.add((title_prop, SH.maxCount, Literal(1)))
custom_shapes.add((title_prop, SH.datatype, XSD.string))
custom_shapes.add((title_prop, SH.minLength, Literal(5)))
custom_shapes.add((title_prop, SH.message, Literal("Un article doit avoir un titre (string, min 5 caracteres)", lang="fr")))
# Contrainte 2 : auteur obligatoire, doit etre un schema:Person
author_prop = BNode()
custom_shapes.add((article_shape, SH.property, author_prop))
custom_shapes.add((author_prop, SH.path, SCHEMA.author))
custom_shapes.add((author_prop, SH.minCount, Literal(1)))
custom_shapes.add((author_prop, SH["class"], SCHEMA.Person))
custom_shapes.add((author_prop, SH.message, Literal("Un article doit avoir au moins un auteur (schema:Person)", lang="fr")))
# Contrainte 3 : date de publication optionnelle (xsd:date)
date_prop = BNode()
custom_shapes.add((article_shape, SH.property, date_prop))
custom_shapes.add((date_prop, SH.path, SCHEMA.datePublished))
custom_shapes.add((date_prop, SH.maxCount, Literal(1)))
custom_shapes.add((date_prop, SH.datatype, XSD.date))
custom_shapes.add((date_prop, SH.message, Literal("La date de publication doit etre au format xsd:date", lang="fr")))
# Contrainte 4 : nombre de mots optionnel (entier entre 100 et 50000)
words_prop = BNode()
custom_shapes.add((article_shape, SH.property, words_prop))
custom_shapes.add((words_prop, SH.path, SCHEMA.wordCount))
custom_shapes.add((words_prop, SH.maxCount, Literal(1)))
custom_shapes.add((words_prop, SH.datatype, XSD.integer))
custom_shapes.add((words_prop, SH.minInclusive, Literal(100)))
custom_shapes.add((words_prop, SH.maxInclusive, Literal(50000)))
custom_shapes.add((words_prop, SH.message, Literal("Le nombre de mots doit etre entre 100 et 50000", lang="fr")))
print("Forme ArticleShape creee avec 4 contraintes de propriete")
print()
print(custom_shapes.serialize(format="turtle"))Forme ArticleShape creee avec 4 contraintes de propriete
@prefix ex: <http://example.org/> .
@prefix schema1: <http://schema.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:ArticleShape a sh:NodeShape ;
sh:property [ sh:datatype xsd:string ;
sh:maxCount 1 ;
sh:message "Un article doit avoir un titre (string, min 5 caracteres)"@fr ;
sh:minCount 1 ;
sh:minLength 5 ;
sh:path schema1:headline ],
[ sh:class schema1:Person ;
sh:message "Un article doit avoir au moins un auteur (schema:Person)"@fr ;
sh:minCount 1 ;
sh:path schema1:author ],
[ sh:datatype xsd:integer ;
sh:maxCount 1 ;
sh:maxInclusive 50000 ;
sh:message "Le nombre de mots doit etre entre 100 et 50000"@fr ;
sh:minInclusive 100 ;
sh:path schema1:wordCount ],
[ sh:datatype xsd:date ;
sh:maxCount 1 ;
sh:message "La date de publication doit etre au format xsd:date"@fr ;
sh:path schema1:datePublished ] ;
sh:targetClass schema1:Article .
Test de la forme personnalisee
Creons un jeu de données avec des articles valides et invalides pour verifier notre forme ArticleShape.
# Creer des donnees de test pour ArticleShape
test_data = Graph()
test_data.bind("ex", EX)
test_data.bind("schema", SCHEMA)
# Auteur valide
test_data.add((EX.jean, RDF.type, SCHEMA.Person))
test_data.add((EX.jean, SCHEMA.name, Literal("Jean Dupont")))
# Article 1 : valide (toutes les proprietes correctes)
test_data.add((EX.article1, RDF.type, SCHEMA.Article))
test_data.add((EX.article1, SCHEMA.headline, Literal("Introduction au Web Semantique", datatype=XSD.string)))
test_data.add((EX.article1, SCHEMA.author, EX.jean))
test_data.add((EX.article1, SCHEMA.datePublished, Literal("2024-01-15", datatype=XSD.date)))
test_data.add((EX.article1, SCHEMA.wordCount, Literal(5000, datatype=XSD.integer)))
# Article 2 : titre trop court ("RDF" = 3 caracteres < 5)
test_data.add((EX.article2, RDF.type, SCHEMA.Article))
test_data.add((EX.article2, SCHEMA.headline, Literal("RDF", datatype=XSD.string)))
test_data.add((EX.article2, SCHEMA.author, EX.jean))
# Article 3 : pas de titre, pas d'auteur
test_data.add((EX.article3, RDF.type, SCHEMA.Article))
test_data.add((EX.article3, SCHEMA.wordCount, Literal(200, datatype=XSD.integer)))
# Article 4 : nombre de mots hors bornes (10 < 100)
test_data.add((EX.article4, RDF.type, SCHEMA.Article))
test_data.add((EX.article4, SCHEMA.headline, Literal("Article avec trop peu de mots", datatype=XSD.string)))
test_data.add((EX.article4, SCHEMA.author, EX.jean))
test_data.add((EX.article4, SCHEMA.wordCount, Literal(10, datatype=XSD.integer)))
# Valider
conforms2, results2, text2 = validate(
test_data,
shacl_graph=custom_shapes,
inference='rdfs',
abort_on_first=False
)
print(f"Conforme : {conforms2}")
print()
print(text2)Conforme : False
Validation Report
Conforms: False
Results (4):
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:class schema1:Person ; sh:message Literal("Un article doit avoir au moins un auteur (schema:Person)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:author ]
Focus Node: ex:article3
Result Path: schema1:author
Message: Un article doit avoir au moins un auteur (schema:Person)
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:message Literal("Un article doit avoir un titre (string, min 5 caracteres)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("5", datatype=xsd:integer) ; sh:path schema1:headline ]
Focus Node: ex:article3
Result Path: schema1:headline
Message: Un article doit avoir un titre (string, min 5 caracteres)
Constraint Violation in MinInclusiveConstraintComponent (http://www.w3.org/ns/shacl#MinInclusiveConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:integer ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:maxInclusive Literal("50000", datatype=xsd:integer) ; sh:message Literal("Le nombre de mots doit etre entre 100 et 50000", lang=fr) ; sh:minInclusive Literal("100", datatype=xsd:integer) ; sh:path schema1:wordCount ]
Focus Node: ex:article4
Value Node: Literal("10", datatype=xsd:integer)
Result Path: schema1:wordCount
Message: Le nombre de mots doit etre entre 100 et 50000
Constraint Violation in MinLengthConstraintComponent (http://www.w3.org/ns/shacl#MinLengthConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:message Literal("Un article doit avoir un titre (string, min 5 caracteres)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("5", datatype=xsd:integer) ; sh:path schema1:headline ]
Focus Node: ex:article2
Value Node: Literal("RDF", datatype=xsd:string)
Result Path: schema1:headline
Message: Un article doit avoir un titre (string, min 5 caracteres)
Interpretation : Validation des articles
| Article | Violations | Detail |
|---|---|---|
| article1 | 0 | Conforme : titre, auteur, date et wordCount respectent toutes les contraintes |
| article2 | 1 | Titre “RDF” trop court (3 < 5 caractères requis par minLength) |
| article3 | 2 | Pas de titre (minCount=1) + pas d’auteur (minCount=1) |
| article4 | 1 | wordCount=10 < minInclusive=100 |
Bonne pratique : Ajoutez toujours des messages explicites (
sh:message) pour faciliter le diagnostic. Les messages en francais ou en anglais sont plus utiles que les messages generiques de pySHACL.
6. Severites SHACL
SHACL définit trois niveaux de severite pour les violations. Par defaut, toutes les contraintes ont la severite sh:Violation, mais on peut les ajuster.
| Severite | URI | Comportement | Usage typique |
|---|---|---|---|
| Violation | sh:Violation |
Erreur bloquante (defaut) | Donnee manquante obligatoire |
| Warning | sh:Warning |
Avertissement non bloquant | Donnee recommandee mais optionnelle |
| Info | sh:Info |
Information, suggestion | Bonne pratique non imposee |
Le booléen conforms retourne False uniquement pour les sh:Violation. Les warnings et infos n’affectent pas la conformite.
# Demonstrer les 3 niveaux de severite
severity_shapes_ttl = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <http://example.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
ex:EmployeeShape a sh:NodeShape ;
sh:targetClass ex:Employee ;
# VIOLATION : le nom est obligatoire
sh:property [
sh:path foaf:name ;
sh:minCount 1 ;
sh:severity sh:Violation ;
sh:message "Le nom est obligatoire (Violation)"@fr ;
] ;
# WARNING : l'email est recommande mais pas obligatoire
sh:property [
sh:path foaf:mbox ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "L'email est recommande (Warning)"@fr ;
] ;
# INFO : le telephone est souhaitable
sh:property [
sh:path ex:phone ;
sh:minCount 1 ;
sh:severity sh:Info ;
sh:message "Le telephone est souhaitable (Info)"@fr ;
] .
"""
severity_shapes = Graph()
severity_shapes.parse(data=severity_shapes_ttl, format="turtle")
# Employe sans nom, sans email, sans telephone
severity_data = Graph()
severity_data.bind("ex", EX)
severity_data.add((EX.emp1, RDF.type, EX.Employee))
# Pas de nom, pas d'email, pas de telephone
conforms_sev, results_sev, text_sev = validate(
severity_data,
shacl_graph=severity_shapes,
inference='rdfs',
abort_on_first=False
)
print(f"Conforme : {conforms_sev}")
print()
# Classer les resultats par severite
for result in results_sev.subjects(RDF.type, SH.ValidationResult):
severity = list(results_sev.objects(result, SH.resultSeverity))
message = list(results_sev.objects(result, SH.resultMessage))
sev_str = str(severity[0]).split("#")[-1] if severity else "?"
msg_str = str(message[0]) if message else "-"
print(f" [{sev_str:<10s}] {msg_str}")Conforme : False
[Violation ] Le nom est obligatoire (Violation)
[Warning ] L'email est recommande (Warning)
[Info ] Le telephone est souhaitable (Info)
Interpretation : Impact de la severite
Bien que les 3 contraintes soient violees, le booléen conforms ne depend que de sh:Violation :
| Severite | Contrainte violee | Impact sur conforms |
|---|---|---|
sh:Violation |
Pas de nom | conforms = False |
sh:Warning |
Pas d’email | Pas d’impact (avertissement seulement) |
sh:Info |
Pas de telephone | Pas d’impact (information seulement) |
Bonne pratique : Utilisez
sh:Warningpour les proprietes recommandees (best practices) etsh:Infopour les suggestions. Reservezsh:Violationaux exigences absolues.
7. Contraintes avancees : opérateurs logiques
SHACL permet de combiner des contraintes avec des opérateurs logiques :
| Opérateur | Propriete SHACL | Sémantique |
|---|---|---|
| OU | sh:or |
Au moins une des sous-formes doit etre satisfaite |
| ET | sh:and |
Toutes les sous-formes doivent etre satisfaites |
| NON | sh:not |
La sous-forme ne doit PAS etre satisfaite |
| OU exclusif | sh:xone |
Exactement une sous-forme satisfaite |
Exemple : un Contact doit avoir un email OU un telephone
from rdflib.collection import Collection
# Forme avancee avec sh:or
adv_shapes = Graph()
adv_shapes.bind("sh", SH)
adv_shapes.bind("ex", EX)
adv_shapes.bind("schema", SCHEMA)
adv_shapes.bind("xsd", XSD)
contact_shape = EX.ContactShape
adv_shapes.add((contact_shape, RDF.type, SH.NodeShape))
adv_shapes.add((contact_shape, SH.targetClass, EX.Contact))
# sh:or - Au moins email OU telephone
# Option A : a un email
option_email = BNode()
email_inner = BNode()
adv_shapes.add((option_email, SH.property, email_inner))
adv_shapes.add((email_inner, SH.path, SCHEMA.email))
adv_shapes.add((email_inner, SH.minCount, Literal(1)))
# Option B : a un telephone
option_phone = BNode()
phone_inner = BNode()
adv_shapes.add((option_phone, SH.property, phone_inner))
adv_shapes.add((phone_inner, SH.path, SCHEMA.telephone))
adv_shapes.add((phone_inner, SH.minCount, Literal(1)))
# Creer la liste RDF pour sh:or
or_list = BNode()
Collection(adv_shapes, or_list, [option_email, option_phone])
adv_shapes.add((contact_shape, SH["or"], or_list))
adv_shapes.add((contact_shape, SH.message, Literal("Un contact doit avoir un email ou un telephone", lang="fr")))
print("Forme ContactShape avec sh:or creee")
print()
print(adv_shapes.serialize(format="turtle"))Forme ContactShape avec sh:or creee
@prefix ex: <http://example.org/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix schema1: <http://schema.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:ContactShape a sh:NodeShape ;
sh:message "Un contact doit avoir un email ou un telephone"@fr ;
sh:or ( [ sh:property [ sh:minCount 1 ;
sh:path schema1:email ] ] [ sh:property [ sh:minCount 1 ;
sh:path schema1:telephone ] ] ) ;
sh:targetClass ex:Contact .
Test des contraintes avancees
Creons des contacts qui testent différents scénarios : avec email seul, telephone seul, les deux, ou aucun.
# Donnees de test pour les contraintes avancees
contact_data = Graph()
contact_data.bind("ex", EX)
contact_data.bind("schema", SCHEMA)
# Contact 1 : email seulement -> OK
contact_data.add((EX.contact1, RDF.type, EX.Contact))
contact_data.add((EX.contact1, SCHEMA.name, Literal("Alice")))
contact_data.add((EX.contact1, SCHEMA.email, Literal("alice@example.com")))
# Contact 2 : telephone seulement -> OK
contact_data.add((EX.contact2, RDF.type, EX.Contact))
contact_data.add((EX.contact2, SCHEMA.name, Literal("Bob")))
contact_data.add((EX.contact2, SCHEMA.telephone, Literal("+33 1 23 45 67 89")))
# Contact 3 : ni email ni telephone -> VIOLATION (sh:or)
contact_data.add((EX.contact3, RDF.type, EX.Contact))
contact_data.add((EX.contact3, SCHEMA.name, Literal("Charlie")))
# Contact 4 : les deux -> OK
contact_data.add((EX.contact4, RDF.type, EX.Contact))
contact_data.add((EX.contact4, SCHEMA.name, Literal("Diana")))
contact_data.add((EX.contact4, SCHEMA.email, Literal("diana@example.com")))
contact_data.add((EX.contact4, SCHEMA.telephone, Literal("+33 6 12 34 56 78")))
# Valider
conforms3, results3, text3 = validate(
contact_data,
shacl_graph=adv_shapes,
inference='rdfs',
abort_on_first=False
)
print(f"Conforme : {conforms3}")
print()
print(text3)Conforme : False
Validation Report
Conforms: False
Results (1):
Constraint Violation in OrConstraintComponent (http://www.w3.org/ns/shacl#OrConstraintComponent):
Severity: sh:Violation
Source Shape: ex:ContactShape
Focus Node: ex:contact3
Value Node: ex:contact3
Message: Un contact doit avoir un email ou un telephone
Interpretation : Contraintes logiques
| Contact | Telephone | Résultat | Raison | |
|---|---|---|---|---|
| contact1 | Oui | Non | Conforme | sh:or satisfait par email |
| contact2 | Non | Oui | Conforme | sh:or satisfait par telephone |
| contact3 | Non | Non | Violation | sh:or non satisfait (aucune branche valide) |
| contact4 | Oui | Oui | Conforme | sh:or satisfait par les deux (OU inclusif) |
Point cle :
sh:orest un OU inclusif : avoir les deux options satisfaites est aussi valide. Pour exiger exactement une option, utilisezsh:xone.
8. Exemples et Exercices
Exemple guide 1 : Corriger les erreurs de person-data.ttl
Les 5 personnes invalides dans person-data.ttl ont chacune une erreur. Voici comment les corriger par programmation en modifiant le graphe data_graph, puis relancer la validation pour verifier que toutes les personnes passent.
Rappel des erreurs : - charlie : pas de nom - diana : age negatif (-5) - eve : email mal forme - frank : nom trop court (“F”) - grace : knows pointe vers un non-Person
# Exemple guide 1 : Corriger les erreurs dans le graphe de donnees
# Recharger les donnees pour partir d'une copie propre
fixed_data = Graph()
fixed_data.parse("data/person-data.ttl", format="turtle")
# Correction 1 : Ajouter un nom a charlie
fixed_data.add((EX.charlie, FOAF.name, Literal("Charlie Durand")))
# Correction 2 : Corriger l'age de diana (remplacer -5 par 25)
for _,_, o in fixed_data.triples((EX.diana, FOAF.age, None)):
fixed_data.remove((EX.diana, FOAF.age, o))
fixed_data.add((EX.diana, FOAF.age, Literal(25, datatype=XSD.integer)))
# Correction 3 : Corriger l'email d'eve
for _, _, o in fixed_data.triples((EX.eve, FOAF.mbox, None)):
fixed_data.remove((EX.eve, FOAF.mbox, o))
fixed_data.add((EX.eve, FOAF.mbox, URIRef("mailto:eve@example.com")))
# Correction 4 : Allonger le nom de frank
for _, _, o in fixed_data.triples((EX.frank, FOAF.name, None)):
fixed_data.remove((EX.frank, FOAF.name, o))
fixed_data.add((EX.frank, FOAF.name, Literal("Frank")))
# Correction 5 : Faire de myDog un Person (ou changer le knows)
for _, _, o in fixed_data.triples((EX.grace, FOAF.knows, None)):
fixed_data.remove((EX.grace, FOAF.knows, o))
# Valider le graphe corrige
conforms_fix, _, text_fix = validate(
fixed_data, shacl_graph=shapes_graph, inference='rdfs', abort_on_first=False
)
print(f"Conforme apres corrections : {conforms_fix}")
if not conforms_fix:
print(text_fix)
else:
print("Toutes les personnes sont maintenant conformes.")Conforme apres corrections : True
Toutes les personnes sont maintenant conformes.
Exemple guide 2 : Forme SHACL pour un Livre
Construction d’une forme SHACL ex:BookShape pour valider des instances de schema:Book avec les contraintes suivantes : - Titre (schema:name) : obligatoire, chaîne, min 1 caractère - ISBN (schema:isbn) : optionnel, doit respecter le motif EAN-13 ^97[89]-\d-\d{4}-\d{4}-\d$ - Annee (schema:datePublished) : optionnel, entier entre 1450 (Gutenberg) et 2030 - Auteur (schema:author) : au moins un, doit etre un schema:Person
Puis creation de 4 livres (2 valides, 2 invalides) et validation.
# Exemple guide 2 : Construction de la forme BookShape
book_shapes = Graph()
book_shapes.bind("sh", SH)
book_shapes.bind("ex", EX)
book_shapes.bind("schema", SCHEMA)
book_shapes.bind("xsd", XSD)
book_shape = EX.BookShape
book_shapes.add((book_shape, RDF.type, SH.NodeShape))
book_shapes.add((book_shape, SH.targetClass, SCHEMA.Book))
# Contrainte 1 : Titre obligatoire (minCount, datatype, minLength)
title_shape = EX.titleShape
book_shapes.add((book_shape, SH.property, title_shape))
book_shapes.add((title_shape, SH.path, SCHEMA.name))
book_shapes.add((title_shape, SH.minCount, Literal(1)))
book_shapes.add((title_shape, SH.datatype, XSD.string))
book_shapes.add((title_shape, SH.minLength, Literal(1)))
# Contrainte 2 : ISBN avec pattern (format EAN-13 : 978-X-XXXX-XXXX-X)
isbn_shape = EX.isbnShape
book_shapes.add((book_shape, SH.property, isbn_shape))
book_shapes.add((isbn_shape, SH.path, SCHEMA.isbn))
book_shapes.add((isbn_shape, SH.datatype, XSD.string))
book_shapes.add((isbn_shape, SH.pattern,
Literal(r"^97[89]-\d-\d{4}-\d{4}-\d$")))
# Contrainte 3 : Annee entre 1450 et 2030 (minInclusive, maxInclusive)
year_shape = EX.yearShape
book_shapes.add((book_shape, SH.property, year_shape))
book_shapes.add((year_shape, SH.path, SCHEMA.datePublished))
book_shapes.add((year_shape, SH.datatype, XSD.integer))
book_shapes.add((year_shape, SH.minInclusive, Literal(1450)))
book_shapes.add((year_shape, SH.maxInclusive, Literal(2030)))
# Contrainte 4 : Au moins un auteur de type schema:Person
author_shape = EX.authorShape
book_shapes.add((book_shape, SH.property, author_shape))
book_shapes.add((author_shape, SH.path, SCHEMA.author))
book_shapes.add((author_shape, SH.minCount, Literal(1)))
book_shapes.add((author_shape, SH.nodeKind, SH.IRI))
book_shapes.add((author_shape, SH["class"], SCHEMA.Person))
print("Forme BookShape :")
print(book_shapes.serialize(format="turtle"))
# Donnees de test (2 valides + 2 invalides)
book_data = Graph()
book_data.add((EX.book1, RDF.type, SCHEMA.Book))
book_data.add((EX.book1, SCHEMA.name, Literal("Clean Code")))
book_data.add((EX.book1, SCHEMA.isbn, Literal("978-0-1323-5088-4")))
book_data.add((EX.book1, SCHEMA.datePublished, Literal(2008)))
book_data.add((EX.book1, SCHEMA.author, EX.author1))
book_data.add((EX.author1, RDF.type, SCHEMA.Person))
book_data.add((EX.book2, RDF.type, SCHEMA.Book))
book_data.add((EX.book2, SCHEMA.name, Literal("Design Patterns")))
book_data.add((EX.book2, SCHEMA.isbn, Literal("978-0-2016-3361-0")))
book_data.add((EX.book2, SCHEMA.datePublished, Literal(1994)))
book_data.add((EX.book2, SCHEMA.author, EX.author2))
book_data.add((EX.author2, RDF.type, SCHEMA.Person))
book_data.add((EX.book3, RDF.type, SCHEMA.Book))
book_data.add((EX.book3, SCHEMA.name, Literal("")))
book_data.add((EX.book3, SCHEMA.isbn, Literal("123-INVALID")))
book_data.add((EX.book4, RDF.type, SCHEMA.Book))
book_data.add((EX.book4, SCHEMA.name, Literal("Future Book")))
book_data.add((EX.book4, SCHEMA.datePublished, Literal(3000)))
book_data.add((EX.book4, SCHEMA.author, EX.robot))
# Validation
conforms_book, _, text_book = validate(
book_data,
shacl_graph=book_shapes,
inference='rdfs',
abort_on_first=False
)
print(conforms_book)
print(text_book)Forme BookShape :
@prefix ex: <http://example.org/> .
@prefix schema1: <http://schema.org/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:BookShape a sh:NodeShape ;
sh:property ex:authorShape,
ex:isbnShape,
ex:titleShape,
ex:yearShape ;
sh:targetClass schema1:Book .
ex:authorShape sh:class schema1:Person ;
sh:minCount 1 ;
sh:nodeKind sh:IRI ;
sh:path schema1:author .
ex:isbnShape sh:datatype xsd:string ;
sh:path schema1:isbn ;
sh:pattern "^97[89]-\\d-\\d{4}-\\d{4}-\\d$" .
ex:titleShape sh:datatype xsd:string ;
sh:minCount 1 ;
sh:minLength 1 ;
sh:path schema1:name .
ex:yearShape sh:datatype xsd:integer ;
sh:maxInclusive 2030 ;
sh:minInclusive 1450 ;
sh:path schema1:datePublished .
False
Validation Report
Conforms: False
Results (5):
Constraint Violation in ClassConstraintComponent (http://www.w3.org/ns/shacl#ClassConstraintComponent):
Severity: sh:Violation
Source Shape: ex:authorShape
Focus Node: <http://example.org/book4>
Value Node: <http://example.org/robot>
Result Path: schema1:author
Message: Value does not have class schema1:Person
Constraint Violation in MaxInclusiveConstraintComponent (http://www.w3.org/ns/shacl#MaxInclusiveConstraintComponent):
Severity: sh:Violation
Source Shape: ex:yearShape
Focus Node: <http://example.org/book4>
Value Node: Literal("3000", datatype=xsd:integer)
Result Path: schema1:datePublished
Message: Value is not <= Literal("2030", datatype=xsd:integer)
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: ex:authorShape
Focus Node: <http://example.org/book3>
Result Path: schema1:author
Message: Less than 1 values on <http://example.org/book3>->schema1:author
Constraint Violation in MinLengthConstraintComponent (http://www.w3.org/ns/shacl#MinLengthConstraintComponent):
Severity: sh:Violation
Source Shape: ex:titleShape
Focus Node: <http://example.org/book3>
Value Node: Literal("")
Result Path: schema1:name
Message: String length not >= Literal("1", datatype=xsd:integer)
Constraint Violation in PatternConstraintComponent (http://www.w3.org/ns/shacl#PatternConstraintComponent):
Severity: sh:Violation
Source Shape: ex:isbnShape
Focus Node: <http://example.org/book3>
Value Node: Literal("123-INVALID")
Result Path: schema1:isbn
Message: Value does not match pattern '^97[89]-\d-\d{4}-\d{4}-\d$'
Exemple guide 3 : Contraintes avec severites multiples
Creation d’une forme SHACL ex:StudentShape pour valider des etudiants (ex:Student) avec les contraintes suivantes et les severites indiquees :
| Propriete | Contrainte | Severite |
|---|---|---|
foaf:name |
Obligatoire, chaîne | sh:Violation |
ex:studentId |
Obligatoire, motif ^[A-Z]{2}\d{6}$ |
sh:Violation |
foaf:mbox |
Recommande (minCount 1) | sh:Warning |
ex:phone |
Souhaitable (minCount 1) | sh:Info |
Test avec 3 etudiants : un complet, un sans email/telephone, un sans nom.
# Exemple guide 3 : StudentShape avec severites multiples
student_shapes_ttl = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <http://example.org/> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
ex:StudentShape a sh:NodeShape ;
sh:targetClass ex:Student ;
sh:property [
sh:path foaf:name ;
sh:minCount 1 ;
sh:datatype xsd:string ;
sh:severity sh:Violation ;
] ;
sh:property [
sh:path ex:studentId ;
sh:minCount 1 ;
sh:pattern "^[A-Z]{2}\\\\d{6}$" ;
sh:severity sh:Violation ;
] ;
sh:property [
sh:path foaf:mbox ;
sh:minCount 1 ;
sh:severity sh:Warning ;
] ;
sh:property [
sh:path ex:phone ;
sh:minCount 1 ;
sh:severity sh:Info ;
] .
"""
student_shapes = Graph()
student_shapes.parse(data=student_shapes_ttl, format="turtle")
student_data = Graph()
student_data.add((EX.alice, RDF.type, EX.Student))
student_data.add((EX.alice, FOAF.name, Literal("Alice Martin")))
student_data.add((EX.alice, EX.studentId, Literal("AB123456")))
student_data.add((EX.alice, FOAF.mbox, Literal("alice@example.com")))
student_data.add((EX.alice, EX.phone, Literal("0123456789")))
student_data.add((EX.bob, RDF.type, EX.Student))
student_data.add((EX.bob, FOAF.name, Literal("Bob Dupont")))
student_data.add((EX.bob, EX.studentId, Literal("CD654321")))
student_data.add((EX.charlie, RDF.type, EX.Student))
student_data.add((EX.charlie, EX.studentId, Literal("EF111111")))
conforms, results_graph, results_text = validate(
student_data,
shacl_graph=student_shapes,
inference='rdfs',
abort_on_first=False
)
print("Conforme :", conforms)
print()
print(results_text)Conforme : False
Validation Report
Conforms: False
Results (5):
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path foaf:name ; sh:severity sh:Violation ]
Focus Node: <http://example.org/charlie>
Result Path: foaf:name
Message: Less than 1 values on <http://example.org/charlie>->foaf:name
Validation Result in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Info
Source Shape: [ sh:minCount Literal("1", datatype=xsd:integer) ; sh:path ex:phone ; sh:severity sh:Info ]
Focus Node: <http://example.org/bob>
Result Path: ex:phone
Message: Less than 1 values on <http://example.org/bob>->ex:phone
Validation Result in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Info
Source Shape: [ sh:minCount Literal("1", datatype=xsd:integer) ; sh:path ex:phone ; sh:severity sh:Info ]
Focus Node: <http://example.org/charlie>
Result Path: ex:phone
Message: Less than 1 values on <http://example.org/charlie>->ex:phone
Validation Result in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Warning
Source Shape: [ sh:minCount Literal("1", datatype=xsd:integer) ; sh:path foaf:mbox ; sh:severity sh:Warning ]
Focus Node: <http://example.org/bob>
Result Path: foaf:mbox
Message: Less than 1 values on <http://example.org/bob>->foaf:mbox
Validation Result in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Warning
Source Shape: [ sh:minCount Literal("1", datatype=xsd:integer) ; sh:path foaf:mbox ; sh:severity sh:Warning ]
Focus Node: <http://example.org/charlie>
Result Path: foaf:mbox
Message: Less than 1 values on <http://example.org/charlie>->foaf:mbox
Exemples guides (solutions proposees par @Sosolalt)
Les exemples ci-dessous ont ete resolus par @Sosolalt (EPITA-IS, promo 2028). Ils servent de modèle pour comprendre les concepts abordes dans ce notebook.
Exemple guide 4 : MovieShape - Valider et corriger un graphe de films
Solution proposee par @Sosolalt (EPITA-IS, promo 2028).
# Exercice 1 : Definir MovieShape et valider des donnees de films
# Graphe de formes MovieShape
movie_shapes = Graph()
movie_shapes.bind("sh", SH)
movie_shapes.bind("ex", EX)
movie_shapes.bind("schema", SCHEMA)
movie_shapes.bind("xsd", XSD)
movie_shape = EX.MovieShape
movie_shapes.add((movie_shape, RDF.type, SH.NodeShape))
movie_shapes.add((movie_shape, SH.targetClass, SCHEMA.Movie))
# Contrainte 1 : titre obligatoire (string, min 1 caractere)
title_ps = BNode()
movie_shapes.add((movie_shape, SH.property, title_ps))
movie_shapes.add((title_ps, SH.path, SCHEMA.name))
movie_shapes.add((title_ps, SH.minCount, Literal(1)))
movie_shapes.add((title_ps, SH.datatype, XSD.string))
movie_shapes.add((title_ps, SH.minLength, Literal(1)))
movie_shapes.add((title_ps, SH.message, Literal("Un film doit avoir un titre", lang="fr")))
# Contrainte 2 : annee de publication entre 1900 et 2030
year_ps = BNode()
movie_shapes.add((movie_shape, SH.property, year_ps))
movie_shapes.add((year_ps, SH.path, SCHEMA.datePublished))
movie_shapes.add((year_ps, SH.datatype, XSD.integer))
movie_shapes.add((year_ps, SH.minInclusive, Literal(1900)))
movie_shapes.add((year_ps, SH.maxInclusive, Literal(2030)))
movie_shapes.add((year_ps, SH.message, Literal("L'annee doit etre comprise entre 1900 et 2030", lang="fr")))
# Contrainte 3 : au moins un acteur de type schema:Person
actor_ps = BNode()
movie_shapes.add((movie_shape, SH.property, actor_ps))
movie_shapes.add((actor_ps, SH.path, SCHEMA.actor))
movie_shapes.add((actor_ps, SH.minCount, Literal(1)))
movie_shapes.add((actor_ps, SH["class"], SCHEMA.Person))
movie_shapes.add((actor_ps, SH.message, Literal("Un film doit avoir au moins un acteur (schema:Person)", lang="fr")))
# Graphe de donnees : 2 films valides + 3 invalides
movie_data = Graph()
movie_data.bind("ex", EX)
movie_data.bind("schema", SCHEMA)
# Acteurs (schema:Person)
for actor in (EX.actorA, EX.actorB, EX.actorC):
movie_data.add((actor, RDF.type, SCHEMA.Person))
# Film valide 1
movie_data.add((EX.movie1, RDF.type, SCHEMA.Movie))
movie_data.add((EX.movie1, SCHEMA.name, Literal("Inception", datatype=XSD.string)))
movie_data.add((EX.movie1, SCHEMA.datePublished, Literal(2010, datatype=XSD.integer)))
movie_data.add((EX.movie1, SCHEMA.actor, EX.actorA))
# Film valide 2
movie_data.add((EX.movie2, RDF.type, SCHEMA.Movie))
movie_data.add((EX.movie2, SCHEMA.name, Literal("The Matrix", datatype=XSD.string)))
movie_data.add((EX.movie2, SCHEMA.datePublished, Literal(1999, datatype=XSD.integer)))
movie_data.add((EX.movie2, SCHEMA.actor, EX.actorB))
# Film invalide 1 : pas de titre, pas d'acteur
movie_data.add((EX.movie3, RDF.type, SCHEMA.Movie))
movie_data.add((EX.movie3, SCHEMA.datePublished, Literal(2005, datatype=XSD.integer)))
# Film invalide 2 : annee hors bornes (1850 < 1900)
movie_data.add((EX.movie4, RDF.type, SCHEMA.Movie))
movie_data.add((EX.movie4, SCHEMA.name, Literal("Vieux Film", datatype=XSD.string)))
movie_data.add((EX.movie4, SCHEMA.datePublished, Literal(1850, datatype=XSD.integer)))
movie_data.add((EX.movie4, SCHEMA.actor, EX.actorC))
# Film invalide 3 : acteur qui n'est pas un schema:Person
movie_data.add((EX.movie5, RDF.type, SCHEMA.Movie))
movie_data.add((EX.movie5, SCHEMA.name, Literal("Film Sans Acteur Valide", datatype=XSD.string)))
movie_data.add((EX.movie5, SCHEMA.datePublished, Literal(2020, datatype=XSD.integer)))
movie_data.add((EX.movie5, SCHEMA.actor, EX.studioX))
movie_data.add((EX.studioX, RDF.type, SCHEMA.Organization))
# Validation
conforms_movie, _, text_movie = validate(
movie_data, shacl_graph=movie_shapes, inference='rdfs', abort_on_first=False
)
print(f"Conforme : {conforms_movie}")
print()
print(text_movie)Conforme : False
Validation Report
Conforms: False
Results (4):
Constraint Violation in ClassConstraintComponent (http://www.w3.org/ns/shacl#ClassConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:class schema1:Person ; sh:message Literal("Un film doit avoir au moins un acteur (schema:Person)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:actor ]
Focus Node: ex:movie5
Value Node: ex:studioX
Result Path: schema1:actor
Message: Un film doit avoir au moins un acteur (schema:Person)
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:class schema1:Person ; sh:message Literal("Un film doit avoir au moins un acteur (schema:Person)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:actor ]
Focus Node: ex:movie3
Result Path: schema1:actor
Message: Un film doit avoir au moins un acteur (schema:Person)
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:message Literal("Un film doit avoir un titre", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("1", datatype=xsd:integer) ; sh:path schema1:name ]
Focus Node: ex:movie3
Result Path: schema1:name
Message: Un film doit avoir un titre
Constraint Violation in MinInclusiveConstraintComponent (http://www.w3.org/ns/shacl#MinInclusiveConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:integer ; sh:maxInclusive Literal("2030", datatype=xsd:integer) ; sh:message Literal("L'annee doit etre comprise entre 1900 et 2030", lang=fr) ; sh:minInclusive Literal("1900", datatype=xsd:integer) ; sh:path schema1:datePublished ]
Focus Node: ex:movie4
Value Node: Literal("1850", datatype=xsd:integer)
Result Path: schema1:datePublished
Message: L'annee doit etre comprise entre 1900 et 2030
Exemple guide 5 : EventShape - Forme SHACL pour un Événement
Solution proposee par @Sosolalt (EPITA-IS, promo 2028).
# Exercice 2 : Construction de EventShape
# Graphe de formes
event_shapes = Graph()
event_shapes.bind("sh", SH)
event_shapes.bind("ex", EX)
event_shapes.bind("schema", SCHEMA)
event_shapes.bind("xsd", XSD)
event_shape = EX.EventShape
event_shapes.add((event_shape, RDF.type, SH.NodeShape))
event_shapes.add((event_shape, SH.targetClass, SCHEMA.Event))
# Contrainte 1 : nom obligatoire, chaine, min 3 caracteres
name_ps = BNode()
event_shapes.add((event_shape, SH.property, name_ps))
event_shapes.add((name_ps, SH.path, SCHEMA.name))
event_shapes.add((name_ps, SH.minCount, Literal(1)))
event_shapes.add((name_ps, SH.datatype, XSD.string))
event_shapes.add((name_ps, SH.minLength, Literal(3)))
event_shapes.add((name_ps, SH.message, Literal("Le nom est obligatoire (min 3 caracteres)", lang="fr")))
# Contrainte 2 : date de debut obligatoire, format xsd:date
start_ps = BNode()
event_shapes.add((event_shape, SH.property, start_ps))
event_shapes.add((start_ps, SH.path, SCHEMA.startDate))
event_shapes.add((start_ps, SH.minCount, Literal(1)))
event_shapes.add((start_ps, SH.datatype, XSD.date))
event_shapes.add((start_ps, SH.message, Literal("La date de debut est obligatoire (xsd:date)", lang="fr")))
# Contrainte 3 : lieu obligatoire, doit etre un schema:Place
loc_ps = BNode()
event_shapes.add((event_shape, SH.property, loc_ps))
event_shapes.add((loc_ps, SH.path, SCHEMA.location))
event_shapes.add((loc_ps, SH.minCount, Literal(1)))
event_shapes.add((loc_ps, SH["class"], SCHEMA.Place))
event_shapes.add((loc_ps, SH.message, Literal("Le lieu est obligatoire (schema:Place)", lang="fr")))
# Contrainte 4 : capacite max optionnelle, entier entre 1 et 100000
cap_ps = BNode()
event_shapes.add((event_shape, SH.property, cap_ps))
event_shapes.add((cap_ps, SH.path, SCHEMA.maximumAttendeeCapacity))
event_shapes.add((cap_ps, SH.maxCount, Literal(1)))
event_shapes.add((cap_ps, SH.datatype, XSD.integer))
event_shapes.add((cap_ps, SH.minInclusive, Literal(1)))
event_shapes.add((cap_ps, SH.maxInclusive, Literal(100000)))
event_shapes.add((cap_ps, SH.message, Literal("La capacite doit etre un entier entre 1 et 100000", lang="fr")))
# Donnees de test (1 valide, 2 invalides)
event_data = Graph()
event_data.bind("ex", EX)
event_data.bind("schema", SCHEMA)
# Lieu valide
event_data.add((EX.venue1, RDF.type, SCHEMA.Place))
event_data.add((EX.venue1, SCHEMA.name, Literal("Palais des Congres")))
# Evenement valide
event_data.add((EX.event1, RDF.type, SCHEMA.Event))
event_data.add((EX.event1, SCHEMA.name, Literal("Conference Web Semantique", datatype=XSD.string)))
event_data.add((EX.event1, SCHEMA.startDate, Literal("2024-06-15", datatype=XSD.date)))
event_data.add((EX.event1, SCHEMA.location, EX.venue1))
event_data.add((EX.event1, SCHEMA.maximumAttendeeCapacity, Literal(500, datatype=XSD.integer)))
# Evenement invalide 1 : nom trop court (< 3) et pas de lieu
event_data.add((EX.event2, RDF.type, SCHEMA.Event))
event_data.add((EX.event2, SCHEMA.name, Literal("AI", datatype=XSD.string)))
event_data.add((EX.event2, SCHEMA.startDate, Literal("2024-07-01", datatype=XSD.date)))
# Evenement invalide 2 : pas de date, lieu non Place, capacite hors bornes
event_data.add((EX.event3, RDF.type, SCHEMA.Event))
event_data.add((EX.event3, SCHEMA.name, Literal("Festival", datatype=XSD.string)))
event_data.add((EX.venue2, RDF.type, SCHEMA.Organization))
event_data.add((EX.event3, SCHEMA.location, EX.venue2))
event_data.add((EX.event3, SCHEMA.maximumAttendeeCapacity, Literal(200000, datatype=XSD.integer)))
# Validation
conforms_event, _, text_event = validate(
event_data, shacl_graph=event_shapes, inference='rdfs', abort_on_first=False
)
print(f"Conforme : {conforms_event}")
print()
print(text_event)Conforme : False
Validation Report
Conforms: False
Results (5):
Constraint Violation in ClassConstraintComponent (http://www.w3.org/ns/shacl#ClassConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:class schema1:Place ; sh:message Literal("Le lieu est obligatoire (schema:Place)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:location ]
Focus Node: ex:event3
Value Node: ex:venue2
Result Path: schema1:location
Message: Le lieu est obligatoire (schema:Place)
Constraint Violation in MaxInclusiveConstraintComponent (http://www.w3.org/ns/shacl#MaxInclusiveConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:integer ; sh:maxCount Literal("1", datatype=xsd:integer) ; sh:maxInclusive Literal("100000", datatype=xsd:integer) ; sh:message Literal("La capacite doit etre un entier entre 1 et 100000", lang=fr) ; sh:minInclusive Literal("1", datatype=xsd:integer) ; sh:path schema1:maximumAttendeeCapacity ]
Focus Node: ex:event3
Value Node: Literal("200000", datatype=xsd:integer)
Result Path: schema1:maximumAttendeeCapacity
Message: La capacite doit etre un entier entre 1 et 100000
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:class schema1:Place ; sh:message Literal("Le lieu est obligatoire (schema:Place)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:location ]
Focus Node: ex:event2
Result Path: schema1:location
Message: Le lieu est obligatoire (schema:Place)
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:date ; sh:message Literal("La date de debut est obligatoire (xsd:date)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:startDate ]
Focus Node: ex:event3
Result Path: schema1:startDate
Message: La date de debut est obligatoire (xsd:date)
Constraint Violation in MinLengthConstraintComponent (http://www.w3.org/ns/shacl#MinLengthConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:message Literal("Le nom est obligatoire (min 3 caracteres)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("3", datatype=xsd:integer) ; sh:path schema1:name ]
Focus Node: ex:event2
Value Node: Literal("AI", datatype=xsd:string)
Result Path: schema1:name
Message: Le nom est obligatoire (min 3 caracteres)
Exemple guide 6 : ProductShape - Severites et contrainte logique
Solution proposee par @Sosolalt (EPITA-IS, promo 2028).
# Exercice 3 : ProductShape avec severites et sh:or
product_shapes_ttl = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <http://example.org/> .
@prefix schema: <http://schema.org/> .
ex:ProductShape a sh:NodeShape ;
sh:targetClass ex:Product ;
# Nom : obligatoire, chaine, min 2 caracteres (Violation)
sh:property [
sh:path schema:name ;
sh:minCount 1 ;
sh:datatype xsd:string ;
sh:minLength 2 ;
sh:severity sh:Violation ;
sh:message "Le nom est obligatoire (string, min 2 caracteres)"@fr ;
] ;
# Prix : obligatoire, entre 0 et 100000 (Violation)
sh:property [
sh:path schema:price ;
sh:minCount 1 ;
sh:minInclusive 0 ;
sh:maxInclusive 100000 ;
sh:severity sh:Violation ;
sh:message "Le prix est obligatoire et doit etre entre 0 et 100000"@fr ;
] ;
# Categorie : recommandee (Warning)
sh:property [
sh:path schema:category ;
sh:minCount 1 ;
sh:severity sh:Warning ;
sh:message "La categorie est recommandee"@fr ;
] ;
# Description : suggeree, min 10 caracteres (Info)
sh:property [
sh:path schema:description ;
sh:minLength 10 ;
sh:severity sh:Info ;
sh:message "Une description d'au moins 10 caracteres est suggeree"@fr ;
] ;
# Contrainte logique : une marque OU un fabricant
sh:or (
[ sh:path schema:brand ; sh:minCount 1 ]
[ sh:path schema:manufacturer ; sh:minCount 1 ]
) ;
sh:message "Un produit doit avoir une marque (brand) ou un fabricant (manufacturer)"@fr .
"""
product_shapes = Graph()
product_shapes.parse(data=product_shapes_ttl, format="turtle")
# Donnees de test
product_data = Graph()
product_data.bind("ex", EX)
product_data.bind("schema", SCHEMA)
# Produit valide : nom, prix, categorie, description longue, et une marque
product_data.add((EX.prod1, RDF.type, EX.Product))
product_data.add((EX.prod1, SCHEMA.name, Literal("Clavier mecanique")))
product_data.add((EX.prod1, SCHEMA.price, Literal(89, datatype=XSD.integer)))
product_data.add((EX.prod1, SCHEMA.category, Literal("Peripheriques")))
product_data.add((EX.prod1, SCHEMA.description, Literal("Clavier mecanique retroeclaire avec switches bleus")))
product_data.add((EX.prod1, SCHEMA.brand, Literal("KeyCo")))
# Produit invalide :
# - nom trop court "X" (Violation)
# - prix hors bornes 200000 (Violation)
# - ni marque ni fabricant (Violation via sh:or)
# - pas de categorie (Warning)
# - description trop courte "court" (Info)
product_data.add((EX.prod2, RDF.type, EX.Product))
product_data.add((EX.prod2, SCHEMA.name, Literal("X")))
product_data.add((EX.prod2, SCHEMA.price, Literal(200000, datatype=XSD.integer)))
product_data.add((EX.prod2, SCHEMA.description, Literal("court")))
# Validation
conforms_prod, results_prod, text_prod = validate(
product_data, shacl_graph=product_shapes, inference='rdfs', abort_on_first=False
)
print(f"Conforme : {conforms_prod}")
print()
print(text_prod)Conforme : False
Validation Report
Conforms: False
Results (5):
Constraint Violation in MaxInclusiveConstraintComponent (http://www.w3.org/ns/shacl#MaxInclusiveConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:maxInclusive Literal("100000", datatype=xsd:integer) ; sh:message Literal("Le prix est obligatoire et doit etre entre 0 et 100000", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minInclusive Literal("0", datatype=xsd:integer) ; sh:path schema1:price ; sh:severity sh:Violation ]
Focus Node: ex:prod2
Value Node: Literal("200000", datatype=xsd:integer)
Result Path: schema1:price
Message: Le prix est obligatoire et doit etre entre 0 et 100000
Constraint Violation in MinLengthConstraintComponent (http://www.w3.org/ns/shacl#MinLengthConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:datatype xsd:string ; sh:message Literal("Le nom est obligatoire (string, min 2 caracteres)", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:minLength Literal("2", datatype=xsd:integer) ; sh:path schema1:name ; sh:severity sh:Violation ]
Focus Node: ex:prod2
Value Node: Literal("X")
Result Path: schema1:name
Message: Le nom est obligatoire (string, min 2 caracteres)
Constraint Violation in OrConstraintComponent (http://www.w3.org/ns/shacl#OrConstraintComponent):
Severity: sh:Violation
Source Shape: ex:ProductShape
Focus Node: ex:prod2
Value Node: ex:prod2
Message: Un produit doit avoir une marque (brand) ou un fabricant (manufacturer)
Validation Result in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Warning
Source Shape: [ sh:message Literal("La categorie est recommandee", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path schema1:category ; sh:severity sh:Warning ]
Focus Node: ex:prod2
Result Path: schema1:category
Message: La categorie est recommandee
Validation Result in MinLengthConstraintComponent (http://www.w3.org/ns/shacl#MinLengthConstraintComponent):
Severity: sh:Info
Source Shape: [ sh:message Literal("Une description d'au moins 10 caracteres est suggeree", lang=fr) ; sh:minLength Literal("10", datatype=xsd:integer) ; sh:path schema1:description ; sh:severity sh:Info ]
Focus Node: ex:prod2
Value Node: Literal("court")
Result Path: schema1:description
Message: Une description d'au moins 10 caracteres est suggeree
Interprétation : que dit ce rapport de validation à sévérités multiples ?
Le rapport renvoyé par pyshacl mélange trois niveaux de sévérité sur le produit invalide ex:prod2. Cette hiérarchie est centrale pour comprendre comment SHACL sépare le blocage de la simple remarque.
1. Pourquoi conforms = False alors qu’il y a 5 résultats ? Le booléen conforms est piloté uniquement par les sh:Violation. Ici on en compte trois : le nom trop court ("X"), le prix hors bornes (200000) et l’absence simultanée de marque et de fabricant. Les sh:Warning (catégorie manquante) et sh:Info (description trop courte) sont bien présents dans le rapport mais ne font pas basculer conforms : ils informent l’utilisateur sans invalider la donnée. C’est ce qui distingue une erreur fatale d’une alerte ou d’une suggestion.
2. Le rôle de sh:or (contrainte logique OU). La troisième Violation provient d’un sh:or : la forme exige qu’un produit ait soit une schema:brand soit un schema:manufacturer. Comme prod2 n’a ni l’un ni l’autre, aucune branche du OU n’est satisfaite → Violation. Si prod2 avait eu seulement une marque, la contrainte serait passée. sh:or (comme sh:and, sh:not, sh:xone) permet d’exprimer des combinaisons logiques que des contraintes atomiques ne sauraient capturer.
3. Le design intentionnel des trois sévérités.
| Sévérité | Sémantique | Effet sur conforms |
Cas typique |
|---|---|---|---|
sh:Violation (défaut) |
Contrainte dure violée | False |
Champ obligatoire absent, type incorrect |
sh:Warning |
Anomalie suspecte | inchangé | Donnée facultative mais recommandée absente |
sh:Info |
Remarque informative | inchangé | Convention de style non respectée |
À retenir : un
conforms = Truene signifie pas « zéro résultat dans le rapport » — il signifie « zérosh:Violation». Pour un audit complet, il faut donc lire le rapport entier (Violations + Warnings + Infos), pas seulement le booléen. Inversement, un pipeline automatisé qui rejette les données peut se contenter deconforms: les Warning/Info sont destinés à un humain, pas à un gate automatique.
Exemple guide 7 : BibliographicRecordShape - Dataset bibliothecaire
Solution proposee par @Sosolalt (EPITA-IS, promo 2028).
# Exercice final : Valider un dataset bibliotheque avec SHACL
from rdflib import Graph, Namespace, Literal
from rdflib.namespace import DC, RDF, XSD
from pyshacl import validate
SH = Namespace("http://www.w3.org/ns/shacl#")
EX = Namespace("http://example.org/")
# Forme SHACL pour une notice bibliographique
biblio_shapes_ttl = r"""
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <http://example.org/> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
ex:BibliographicRecordShape a sh:NodeShape ;
sh:targetClass ex:BibliographicRecord ;
# Titre : exactement 1, type string
sh:property [
sh:path dc:title ;
sh:minCount 1 ;
sh:maxCount 1 ;
sh:datatype xsd:string ;
sh:message "Une notice doit avoir exactement un titre (string)"@fr ;
] ;
# Createur : au moins 1
sh:property [
sh:path dc:creator ;
sh:minCount 1 ;
sh:message "Une notice doit avoir au moins un createur"@fr ;
] ;
# Date : motif YYYY-MM-DD
sh:property [
sh:path dc:date ;
sh:pattern "^\\d{4}-\\d{2}-\\d{2}$" ;
sh:message "La date doit etre au format YYYY-MM-DD"@fr ;
] ;
# Type : parmi Book, Article, Thesis
sh:property [
sh:path dc:type ;
sh:in ( "Book" "Article" "Thesis" ) ;
sh:message "Le type doit etre Book, Article ou Thesis"@fr ;
] ;
# Langue : facultative (au plus une valeur)
sh:property [
sh:path dc:language ;
sh:maxCount 1 ;
sh:message "Une seule langue au maximum"@fr ;
] .
"""
biblio_shapes = Graph()
biblio_shapes.parse(data=biblio_shapes_ttl, format="turtle")
# Donnees de test (2 valides, 1 invalide)
biblio_data = Graph()
biblio_data.bind("ex", EX)
biblio_data.bind("dc", DC)
# Notice valide 1 (complete)
biblio_data.add((EX.rec1, RDF.type, EX.BibliographicRecord))
biblio_data.add((EX.rec1, DC.title, Literal("Le Web Semantique", datatype=XSD.string)))
biblio_data.add((EX.rec1, DC.creator, Literal("Jean Dupont")))
biblio_data.add((EX.rec1, DC.date, Literal("2021-05-12")))
biblio_data.add((EX.rec1, DC.type, Literal("Book")))
biblio_data.add((EX.rec1, DC.language, Literal("fr")))
# Notice valide 2 (plusieurs createurs, sans langue -> facultative)
biblio_data.add((EX.rec2, RDF.type, EX.BibliographicRecord))
biblio_data.add((EX.rec2, DC.title, Literal("Introduction a RDF", datatype=XSD.string)))
biblio_data.add((EX.rec2, DC.creator, Literal("Alice Martin")))
biblio_data.add((EX.rec2, DC.creator, Literal("Bob Durand")))
biblio_data.add((EX.rec2, DC.date, Literal("2019-11-03")))
biblio_data.add((EX.rec2, DC.type, Literal("Article")))
# Notice invalide : pas de createur, date mal formee, type hors liste
biblio_data.add((EX.rec3, RDF.type, EX.BibliographicRecord))
biblio_data.add((EX.rec3, DC.title, Literal("Notice incomplete", datatype=XSD.string)))
biblio_data.add((EX.rec3, DC.date, Literal("12/05/2021")))
biblio_data.add((EX.rec3, DC.type, Literal("Magazine")))
# Validation
conforms_biblio, results_biblio, text_biblio = validate(
biblio_data, shacl_graph=biblio_shapes, inference='rdfs', abort_on_first=False
)
print(f"Conforme : {conforms_biblio}")
print()
print(text_biblio)Conforme : False
Validation Report
Conforms: False
Results (3):
Constraint Violation in InConstraintComponent (http://www.w3.org/ns/shacl#InConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:in ( Literal("Book") Literal("Article") Literal("Thesis") ) ; sh:message Literal("Le type doit etre Book, Article ou Thesis", lang=fr) ; sh:path dc:type ]
Focus Node: ex:rec3
Value Node: Literal("Magazine")
Result Path: dc:type
Message: Le type doit etre Book, Article ou Thesis
Constraint Violation in MinCountConstraintComponent (http://www.w3.org/ns/shacl#MinCountConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:message Literal("Une notice doit avoir au moins un createur", lang=fr) ; sh:minCount Literal("1", datatype=xsd:integer) ; sh:path dc:creator ]
Focus Node: ex:rec3
Result Path: dc:creator
Message: Une notice doit avoir au moins un createur
Constraint Violation in PatternConstraintComponent (http://www.w3.org/ns/shacl#PatternConstraintComponent):
Severity: sh:Violation
Source Shape: [ sh:message Literal("La date doit etre au format YYYY-MM-DD", lang=fr) ; sh:path dc:date ; sh:pattern Literal("^\d{4}-\d{2}-\d{2}$") ]
Focus Node: ex:rec3
Value Node: Literal("12/05/2021")
Result Path: dc:date
Message: La date doit etre au format YYYY-MM-DD
Exercices a completer
Ces exercices sont a realiser par l’etudiant. Les stubs utilisent des marqueurs TODO.
Exercice 1 : Valider et corriger un graphe de films
Le fichier data/movie-data.ttl (a créer) contient des instances de schema:Movie avec des erreurs. Definissez une forme ex:MovieShape (titre obligatoire, annee entre 1900 et 2030, au moins un acteur de type schema:Person), puis créez un graphe de données avec 2 films valides et 3 films invalides. Validez et corrigez les erreurs.
# Exercice 1 : Definir MovieShape et valider des donnees de films
# TODO etudiant : creez le graphe de formes MovieShape
movie_shapes = Graph()
# TODO etudiant : ajoutez les contraintes (titre, annee, acteur)
# TODO etudiant : creez le graphe de donnees avec 2 films valides et 3 invalides
movie_data = Graph()
# TODO etudiant : ajoutez les films
# TODO etudiant : validez et affichez les resultats
print("Exercice a completer")Exercice a completer
Exercice 2 : Forme SHACL pour un Événement
Créez une forme SHACL ex:EventShape pour valider des événements (schema:Event) avec : - Nom (schema:name) : obligatoire, chaîne, min 3 caractères - Date de debut (schema:startDate) : obligatoire, format xsd:date - Lieu (schema:location) : obligatoire, doit etre un schema:Place - Nombre max de participants (schema:maximumAttendeeCapacity) : optionnel, entier entre 1 et 100000
Créez 3 événements de test (1 valide, 2 invalides) et validez.
# Exercice 2 : Construction de EventShape
# TODO etudiant : creez le graphe de formes
event_shapes = Graph()
# TODO etudiant : definissez EventShape avec les 4 contraintes
# TODO etudiant : creez les donnees de test (1 valide, 2 invalides)
event_data = Graph()
# TODO etudiant : validez et affichez les resultats
print("Exercice a completer")Exercice a completer
Exercice 3 : Produit avec severites et contrainte logique
Créez une forme ex:ProductShape pour valider des produits (ex:Product) avec :
| Propriete | Contrainte | Severite |
|---|---|---|
schema:name |
Obligatoire, chaîne, min 2 caractères | sh:Violation |
schema:price |
Obligatoire, entre 0 et 100000 | sh:Violation |
schema:category |
Obligatoire | sh:Warning |
schema:description |
Recommande (minLength 10) | sh:Info |
Ajoutez aussi une contrainte sh:or : le produit doit avoir soit un schema:brand soit un schema:manufacturer.
# Exercice 3 : ProductShape avec severites et sh:or
# TODO etudiant : creez la forme en Turtle ou par programmation
product_shapes_ttl = """
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix ex: <http://example.org/> .
@prefix schema: <http://schema.org/> .
# TODO etudiant : completez ProductShape
"""
# TODO etudiant : parsez, creez des donnees de test, et validez
print("Exercice a completer")Exercice a completer
Exercice 4 : Valider un dataset bibliothecaire avec SHACL
Créez une forme SHACL ex:BibliographicRecordShape qui valide des notices bibliographiques. Chaque notice doit avoir : - dc:title (exactement 1, type string) - dc:creator (au moins 1) - dc:date (pattern YYYY-MM-DD) - dc:type parmi {“Book”, “Article”, “Thesis”} - dc:language (facultatif, default “fr”)
Créez ensuite 3 notices de test (2 valides, 1 invalide) et verifiez la conformite.
print("Exercice a completer")
# Exercice final : Valider un dataset bibliotheque avec SHACL
from rdflib import Graph, Namespace, Literal
from rdflib.namespace import DC, RDF, XSD
from pyshacl import validate
# TODO etudiant : Creez la forme SHACL et les donnees de testExercice a completer
Resume
Tableau recapitulatif
| Concept | Ce que nous avons appris |
|---|---|
| SHACL | Recommandation W3C pour la validation de données RDF (monde ferme) |
| NodeShape | Forme qui cible des noeuds RDF (par classe ou par URI) |
| PropertyShape | Contrainte sur une propriete spécifique (cardinalite, type, motif, etc.) |
| pySHACL | validate(data_graph, shacl_graph=..., inference=..., abort_on_first=...) |
| Rapport | Triplet (conforms, results_graph, results_text) |
| Severites | sh:Violation (bloquant), sh:Warning (avertissement), sh:Info (suggestion) |
| Opérateurs logiques | sh:or, sh:and, sh:not, sh:xone |
| Construction | Shapes créées par programmation avec rdflib (BNode, add) |
Section 8 : Exemples et Exercices
| Type | Titre | Contenu |
|---|---|---|
| Exemple guide 1 | Corriger les erreurs de person-data.ttl |
Correction des 5 violations par programmation |
| Exemple guide 2 | Forme SHACL pour un Livre | BookShape avec titre, ISBN, annee, auteur |
| Exemple guide 3 | Contraintes avec severites multiples | StudentShape avec Violation/Warning/Info |
| Exercice 1 | Valider et corriger un graphe de films | MovieShape + données a créer |
| Exercice 2 | Forme SHACL pour un Événement | EventShape avec contraintes |
| Exercice 3 | Produit avec severites et contrainte logique | ProductShape + sh:or |
| Exercice 4 | Valider un dataset bibliothecaire | BibliographicRecordShape |
Contraintes SHACL – Reference rapide
| Catégorie | Propriete SHACL | Exemple |
|---|---|---|
| Cardinalite | sh:minCount, sh:maxCount |
sh:minCount 1 |
| Type | sh:datatype, sh:class, sh:nodeKind |
sh:datatype xsd:string |
| Chaîne | sh:pattern, sh:minLength, sh:maxLength |
sh:pattern "^[A-Z]" |
| Numérique | sh:minInclusive, sh:maxInclusive |
sh:minInclusive 0 |
| Valeur | sh:hasValue, sh:in |
sh:in ("FR" "DE") |
| Logique | sh:or, sh:and, sh:not, sh:xone |
Liste de sous-formes |
Ressources supplementaires
- W3C SHACL Specification – Standard complet
- SHACL Advanced Features – Règles et fonctions
- pySHACL Documentation – Bibliotheque Python
- SHACL Playground – Testez vos formes en ligne
Navigation : << 7b-OWL | Index | 9-JSONLD >>
Conclusion
Ce notebook a permis d’explorer les aspects essentiels de sw 8 python shacl. Les points cles :
- Les concepts fondamentaux ont ete presentes et illustres
- Les Exercices proposent une mise en pratique progressive
- Les résultats obtenus permettent de valider la comprehension
Pour aller plus loin : approfondir les aspects avances du sujet et explorer les liens avec d’autres domaines.