Repository navigation
Expand file tree
/
Copy pathvalidate_diagram.py
More file actions
155 lines (123 loc) · 5.51 KB
/
Copy pathvalidate_diagram.py
File metadata and controls
155 lines (123 loc) · 5.51 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
"""
Prüft ein konkretes Diagramm (View) eines ArchiMate-Modells gegen die
SHACL-Shape einer View aus dem Meta-Modell (siehe generate_shacl_shapes.py).
Der View-Name im Meta-Modell und der View-Name im konkreten Modell müssen
NICHT übereinstimmen (z.B. andere Benennung/Nummerierung im konkreten
Projekt) - beide werden daher getrennt übergeben.
Ablauf:
1. Meta-Modell einlesen, Shapes für die Meta-View bauen
(build_shapes_graphs_per_view aus generate_shacl_shapes.py).
2. Konkretes Modell einlesen, die zu prüfende View per DisplayName finden
und ihre Knoten/Verbindungen in einen RDF-Datengraphen übersetzen -
kompatibel zu den SPARQL-Targets/Property-Shapes aus add_view_shapes():
- Knoten: rdf:type archi:<Typ> ; archi:inView <view_uri>
- Verbindungen: direkte Property archi:<Verbindungstyp> zwischen
den Knoten-URIs von Quelle und Ziel
<view_uri> wird dabei aus dem Slug des MetaModell-View-Namens gebildet,
nicht aus dem (womöglich abweichenden) Namen der konkreten View, damit
er zum sh:SPARQLTarget der Shape passt.
3. Datengraph mit pyshacl gegen die Shape validieren (sh:SPARQLTarget
braucht advanced=True).
Benötigt: pip install rdflib pyshacl
"""
from lib.model_data import ModelData
from rdflib import Graph, RDF
from pyshacl import validate
from generate_shacl_shapes import (
ARCHI,
MODEL,
SH,
build_shapes_graphs_per_view,
find_diagrams_by_name,
get_diagram_connections,
get_diagram_nodes,
make_safe_name,
)
def find_diagram_by_name(model, name):
"""Findet genau EIN Diagramm/View im Modell anhand des DisplayName."""
matches = find_diagrams_by_name(model, [name])
if not matches:
raise ValueError(f"Diagramm '{name}' wurde im Modell nicht gefunden.")
if len(matches) > 1:
raise ValueError(f"Diagramm-Name '{name}' ist im Modell nicht eindeutig.")
_uuid, diagram = matches[0]
return diagram
def node_uri(uuid):
return MODEL[f"node_{uuid}"]
def build_data_graph(model, diagram, view_slug):
"""
Übersetzt Knoten/Verbindungen einer konkreten View in einen RDF-Graph.
`view_slug` muss der Slug des MetaModell-View-Namens sein (nicht der des
Namens der konkreten View!), damit archi:inView zum sh:SPARQLTarget der
Shape passt - die beiden Namen können ja voneinander abweichen.
"""
view_name = diagram.DisplayName # nur für Warnmeldungen
view_uri = MODEL[view_slug]
g = Graph()
g.bind("archi", ARCHI)
g.bind("model", MODEL)
nodes = get_diagram_nodes(model, diagram)
connections = get_diagram_connections(model, diagram)
for uuid, node in nodes.items():
subj = node_uri(uuid)
g.add((subj, RDF.type, ARCHI[make_safe_name(node.StereotypeName)]))
g.add((subj, ARCHI.inView, view_uri))
for conn in connections.values():
src_uuid = conn.SourceNode[0]
tgt_uuid = conn.TargetNode[0]
if src_uuid not in nodes or tgt_uuid not in nodes:
label = getattr(conn, "DisplayName", conn)
print(f"WARNUNG: Connection '{label}' in View '{view_name}' "
f"referenziert einen Node außerhalb des Diagramms "
f"- wird übersprungen.")
continue
conn_type = make_safe_name(conn.StereotypeName)
g.add((node_uri(src_uuid), ARCHI[conn_type], node_uri(tgt_uuid)))
return g
def validate_diagram(meta_model, model, meta_diagram_name, model_diagram_name=None):
"""
Prüft die View `model_diagram_name` aus `model` gegen die Shape der View
`meta_diagram_name` aus `meta_model`. Die beiden Namen müssen nicht
übereinstimmen - ist `model_diagram_name` nicht angegeben, wird derselbe
Name wie im Meta-Modell verwendet.
Rückgabe: (conforms: bool, results_graph: Graph, results_text: str)
"""
if model_diagram_name is None:
model_diagram_name = meta_diagram_name
shapes_by_view = build_shapes_graphs_per_view(meta_model, [meta_diagram_name])
if meta_diagram_name not in shapes_by_view:
raise ValueError(
f"Keine Shapes für View '{meta_diagram_name}' im Meta-Modell gefunden."
)
view_slug, shapes_graph = shapes_by_view[meta_diagram_name]
diagram = find_diagram_by_name(model, model_diagram_name)
data_graph = build_data_graph(model, diagram, view_slug)
return validate(
data_graph,
shacl_graph=shapes_graph,
advanced=True,
inference=None,
)
def get_violation_messages(results_graph):
"""
Extrahiert nur die menschenlesbaren sh:resultMessage-Texte aus dem
pyshacl-Validation-Report (ohne Focus Node/Source Shape/Severity etc.),
sortiert für eine stabile Ausgabe.
"""
return sorted(str(m) for m in results_graph.objects(None, SH.resultMessage))
if __name__ == "__main__":
meta_model = ModelData("./models/eam_metamodell.json")
model = ModelData("./models/konkretes_modell.json")
META_DIAGRAM_NAME = "01) Vision"
MODEL_DIAGRAM_NAME = "Vision - Projekt XYZ"
conforms, results_graph, _results_text = validate_diagram(
meta_model, model, META_DIAGRAM_NAME, MODEL_DIAGRAM_NAME
)
if conforms:
print(f"'{MODEL_DIAGRAM_NAME}' ist konform zur Shape von '{META_DIAGRAM_NAME}'.")
else:
messages = get_violation_messages(results_graph)
print(f"'{MODEL_DIAGRAM_NAME}' verletzt die Shape von '{META_DIAGRAM_NAME}' "
f"({len(messages)} Verstoß/Verstöße):")
for msg in messages:
print(f"- {msg}")