rdf-construct 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. rdf_construct/__init__.py +12 -0
  2. rdf_construct/__main__.py +0 -0
  3. rdf_construct/cli.py +1762 -0
  4. rdf_construct/core/__init__.py +33 -0
  5. rdf_construct/core/config.py +116 -0
  6. rdf_construct/core/ordering.py +219 -0
  7. rdf_construct/core/predicate_order.py +212 -0
  8. rdf_construct/core/profile.py +157 -0
  9. rdf_construct/core/selector.py +64 -0
  10. rdf_construct/core/serialiser.py +232 -0
  11. rdf_construct/core/utils.py +89 -0
  12. rdf_construct/cq/__init__.py +77 -0
  13. rdf_construct/cq/expectations.py +365 -0
  14. rdf_construct/cq/formatters/__init__.py +45 -0
  15. rdf_construct/cq/formatters/json.py +104 -0
  16. rdf_construct/cq/formatters/junit.py +104 -0
  17. rdf_construct/cq/formatters/text.py +146 -0
  18. rdf_construct/cq/loader.py +300 -0
  19. rdf_construct/cq/runner.py +321 -0
  20. rdf_construct/diff/__init__.py +59 -0
  21. rdf_construct/diff/change_types.py +214 -0
  22. rdf_construct/diff/comparator.py +338 -0
  23. rdf_construct/diff/filters.py +133 -0
  24. rdf_construct/diff/formatters/__init__.py +71 -0
  25. rdf_construct/diff/formatters/json.py +192 -0
  26. rdf_construct/diff/formatters/markdown.py +210 -0
  27. rdf_construct/diff/formatters/text.py +195 -0
  28. rdf_construct/docs/__init__.py +60 -0
  29. rdf_construct/docs/config.py +238 -0
  30. rdf_construct/docs/extractors.py +603 -0
  31. rdf_construct/docs/generator.py +360 -0
  32. rdf_construct/docs/renderers/__init__.py +7 -0
  33. rdf_construct/docs/renderers/html.py +803 -0
  34. rdf_construct/docs/renderers/json.py +390 -0
  35. rdf_construct/docs/renderers/markdown.py +628 -0
  36. rdf_construct/docs/search.py +278 -0
  37. rdf_construct/docs/templates/html/base.html.jinja +44 -0
  38. rdf_construct/docs/templates/html/class.html.jinja +152 -0
  39. rdf_construct/docs/templates/html/hierarchy.html.jinja +28 -0
  40. rdf_construct/docs/templates/html/index.html.jinja +110 -0
  41. rdf_construct/docs/templates/html/instance.html.jinja +90 -0
  42. rdf_construct/docs/templates/html/namespaces.html.jinja +37 -0
  43. rdf_construct/docs/templates/html/property.html.jinja +124 -0
  44. rdf_construct/docs/templates/html/single_page.html.jinja +169 -0
  45. rdf_construct/lint/__init__.py +75 -0
  46. rdf_construct/lint/config.py +214 -0
  47. rdf_construct/lint/engine.py +396 -0
  48. rdf_construct/lint/formatters.py +327 -0
  49. rdf_construct/lint/rules.py +692 -0
  50. rdf_construct/main.py +6 -0
  51. rdf_construct/puml2rdf/__init__.py +103 -0
  52. rdf_construct/puml2rdf/config.py +230 -0
  53. rdf_construct/puml2rdf/converter.py +420 -0
  54. rdf_construct/puml2rdf/merger.py +200 -0
  55. rdf_construct/puml2rdf/model.py +202 -0
  56. rdf_construct/puml2rdf/parser.py +565 -0
  57. rdf_construct/puml2rdf/validators.py +451 -0
  58. rdf_construct/shacl/__init__.py +56 -0
  59. rdf_construct/shacl/config.py +166 -0
  60. rdf_construct/shacl/converters.py +520 -0
  61. rdf_construct/shacl/generator.py +364 -0
  62. rdf_construct/shacl/namespaces.py +93 -0
  63. rdf_construct/stats/__init__.py +29 -0
  64. rdf_construct/stats/collector.py +178 -0
  65. rdf_construct/stats/comparator.py +298 -0
  66. rdf_construct/stats/formatters/__init__.py +83 -0
  67. rdf_construct/stats/formatters/json.py +38 -0
  68. rdf_construct/stats/formatters/markdown.py +153 -0
  69. rdf_construct/stats/formatters/text.py +186 -0
  70. rdf_construct/stats/metrics/__init__.py +26 -0
  71. rdf_construct/stats/metrics/basic.py +147 -0
  72. rdf_construct/stats/metrics/complexity.py +137 -0
  73. rdf_construct/stats/metrics/connectivity.py +130 -0
  74. rdf_construct/stats/metrics/documentation.py +128 -0
  75. rdf_construct/stats/metrics/hierarchy.py +207 -0
  76. rdf_construct/stats/metrics/properties.py +88 -0
  77. rdf_construct/uml/__init__.py +22 -0
  78. rdf_construct/uml/context.py +194 -0
  79. rdf_construct/uml/mapper.py +371 -0
  80. rdf_construct/uml/odm_renderer.py +789 -0
  81. rdf_construct/uml/renderer.py +684 -0
  82. rdf_construct/uml/uml_layout.py +393 -0
  83. rdf_construct/uml/uml_style.py +613 -0
  84. rdf_construct-0.2.0.dist-info/METADATA +431 -0
  85. rdf_construct-0.2.0.dist-info/RECORD +88 -0
  86. rdf_construct-0.2.0.dist-info/WHEEL +4 -0
  87. rdf_construct-0.2.0.dist-info/entry_points.txt +3 -0
  88. rdf_construct-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,371 @@
1
+ """Map RDF ontology entities to UML diagram elements.
2
+
3
+ This module handles the selection and filtering of classes, properties,
4
+ and instances from an RDF graph based on UML context specifications.
5
+ """
6
+
7
+ from typing import Optional
8
+
9
+ from rdflib import Graph, URIRef, RDF, RDFS
10
+ from rdflib.namespace import OWL
11
+
12
+ from ..core.utils import expand_curie, qname_sort_key
13
+ from ..core.selector import select_subjects
14
+ from .context import UMLContext
15
+
16
+
17
+ def get_descendants(
18
+ graph: Graph, root: URIRef, max_depth: Optional[int] = None
19
+ ) -> set[URIRef]:
20
+ """Get all descendant classes of a root class.
21
+
22
+ Traverses rdfs:subClassOf relationships to find all classes that
23
+ inherit from the root, optionally limiting depth.
24
+
25
+ Args:
26
+ graph: RDF graph to traverse
27
+ root: Root class URI
28
+ max_depth: Maximum depth to traverse (None = unlimited)
29
+
30
+ Returns:
31
+ Set of URIRefs including root and all descendants
32
+ """
33
+ descendants = {root}
34
+ current_level = {root}
35
+ depth = 0
36
+
37
+ while current_level:
38
+ if max_depth is not None and depth >= max_depth:
39
+ break
40
+
41
+ next_level = set()
42
+ for cls in current_level:
43
+ # Find direct subclasses
44
+ for subclass in graph.subjects(RDFS.subClassOf, cls):
45
+ if isinstance(subclass, URIRef) and subclass not in descendants:
46
+ descendants.add(subclass)
47
+ next_level.add(subclass)
48
+
49
+ current_level = next_level
50
+ depth += 1
51
+
52
+ return descendants
53
+
54
+
55
+ def select_classes(
56
+ graph: Graph, context: UMLContext, selectors: dict[str, str]
57
+ ) -> set[URIRef]:
58
+ """Select classes for diagram based on context configuration.
59
+
60
+ Supports three selection modes:
61
+ 1. root_classes: Start from specific roots and optionally include descendants
62
+ 2. focus_classes: Explicit list of classes to include
63
+ 3. selector: Use selector key (e.g., "classes") to select all matching
64
+
65
+ Args:
66
+ graph: RDF graph to select from
67
+ context: UML context specifying selection criteria
68
+ selectors: Selector definitions from config
69
+
70
+ Returns:
71
+ Set of URIRefs for selected classes
72
+ """
73
+ selected: set[URIRef] = set()
74
+
75
+ # Strategy 1: Root classes with optional descendants
76
+ if context.root_classes:
77
+ for curie in context.root_classes:
78
+ root = expand_curie(graph, curie)
79
+ if root is None:
80
+ continue
81
+
82
+ if context.include_descendants:
83
+ descendants = get_descendants(graph, root, context.max_depth)
84
+ selected.update(descendants)
85
+ else:
86
+ selected.add(root)
87
+
88
+ # Strategy 2: Explicit focus classes
89
+ elif context.focus_classes:
90
+ for curie in context.focus_classes:
91
+ cls = expand_curie(graph, curie)
92
+ if cls is not None:
93
+ selected.add(cls)
94
+
95
+ # Optionally include descendants
96
+ if context.include_descendants:
97
+ descendants = get_descendants(graph, cls, context.max_depth)
98
+ selected.update(descendants)
99
+
100
+ # Strategy 3: Use selector (e.g., all classes)
101
+ elif context.selector:
102
+ selected = select_subjects(graph, context.selector, selectors)
103
+
104
+ return selected
105
+
106
+
107
+ def select_properties(
108
+ graph: Graph, context: UMLContext, selected_classes: set[URIRef]
109
+ ) -> dict[str, set[URIRef]]:
110
+ """Select properties for diagram based on context and selected classes.
111
+
112
+ Returns properties categorized by type (object/datatype/annotation).
113
+ Different modes control which properties are included:
114
+ - 'domain_based': Properties whose domain is in selected classes
115
+ - 'connected': Properties connecting selected classes
116
+ - 'explicit': Only explicitly listed properties
117
+ - 'all': All properties in the graph
118
+ - 'none': No properties
119
+
120
+ Args:
121
+ graph: RDF graph to select from
122
+ context: UML context specifying property criteria
123
+ selected_classes: Set of classes already selected for diagram
124
+
125
+ Returns:
126
+ Dictionary with keys 'object', 'datatype', 'annotation' mapping to
127
+ sets of URIRefs for each property type
128
+ """
129
+ properties = {
130
+ "object": set(),
131
+ "datatype": set(),
132
+ "annotation": set(),
133
+ }
134
+
135
+ # Get all properties by type
136
+ all_obj_props = {s for s in graph.subjects(RDF.type, OWL.ObjectProperty)}
137
+ all_data_props = {s for s in graph.subjects(RDF.type, OWL.DatatypeProperty)}
138
+ all_ann_props = {s for s in graph.subjects(RDF.type, OWL.AnnotationProperty)}
139
+
140
+ mode = context.property_mode
141
+
142
+ if mode == "none":
143
+ return properties
144
+
145
+ elif mode == "all":
146
+ properties["object"] = all_obj_props
147
+ properties["datatype"] = all_data_props
148
+ properties["annotation"] = all_ann_props
149
+
150
+ elif mode == "explicit":
151
+ # Only include explicitly listed properties
152
+ for curie in context.property_include:
153
+ prop = expand_curie(graph, curie)
154
+ if prop is None:
155
+ continue
156
+
157
+ if prop in all_obj_props:
158
+ properties["object"].add(prop)
159
+ elif prop in all_data_props:
160
+ properties["datatype"].add(prop)
161
+ elif prop in all_ann_props:
162
+ properties["annotation"].add(prop)
163
+
164
+ elif mode == "domain_based":
165
+ # Include properties whose domain is in selected classes
166
+ for prop in all_obj_props | all_data_props | all_ann_props:
167
+ domains = set(graph.objects(prop, RDFS.domain))
168
+
169
+ # Check if any domain is in selected classes
170
+ if domains & selected_classes:
171
+ if prop in all_obj_props:
172
+ properties["object"].add(prop)
173
+ elif prop in all_data_props:
174
+ properties["datatype"].add(prop)
175
+ elif prop in all_ann_props:
176
+ properties["annotation"].add(prop)
177
+
178
+ elif mode == "connected":
179
+ # Include object properties that connect selected classes
180
+ for prop in all_obj_props:
181
+ domains = set(graph.objects(prop, RDFS.domain))
182
+ ranges = set(graph.objects(prop, RDFS.range))
183
+
184
+ # Both domain and range must be in selected classes
185
+ if (domains & selected_classes) and (ranges & selected_classes):
186
+ properties["object"].add(prop)
187
+
188
+ # For datatype props, just check domain
189
+ for prop in all_data_props:
190
+ domains = set(graph.objects(prop, RDFS.domain))
191
+ if domains & selected_classes:
192
+ properties["datatype"].add(prop)
193
+
194
+ # Apply exclusions
195
+ for curie in context.property_exclude:
196
+ prop = expand_curie(graph, curie)
197
+ if prop:
198
+ properties["object"].discard(prop)
199
+ properties["datatype"].discard(prop)
200
+ properties["annotation"].discard(prop)
201
+
202
+ return properties
203
+
204
+
205
+ def select_instances(
206
+ graph: Graph, selected_classes: set[URIRef]
207
+ ) -> set[URIRef]:
208
+ """Select instances of the selected classes.
209
+
210
+ Args:
211
+ graph: RDF graph to select from
212
+ selected_classes: Classes whose instances to select
213
+
214
+ Returns:
215
+ Set of URIRefs for individuals that are instances of selected classes
216
+ """
217
+ instances = set()
218
+
219
+ for cls in selected_classes:
220
+ # Find all instances of this class
221
+ for instance in graph.subjects(RDF.type, cls):
222
+ if isinstance(instance, URIRef):
223
+ instances.add(instance)
224
+
225
+ return instances
226
+
227
+
228
+ def collect_explicit_entities(
229
+ graph: Graph, context: UMLContext
230
+ ) -> dict[str, set[URIRef]]:
231
+ """Collect entities explicitly specified in context.
232
+
233
+ In explicit mode, all entities are directly listed in the configuration
234
+ rather than selected via strategies. This provides complete control over
235
+ diagram contents.
236
+
237
+ Args:
238
+ graph: RDF graph to validate entities against
239
+ context: UML context with explicit entity lists
240
+
241
+ Returns:
242
+ Dictionary with keys:
243
+ - 'classes': Explicitly specified class URIRefs
244
+ - 'object_properties': Explicitly specified object property URIRefs
245
+ - 'datatype_properties': Explicitly specified datatype property URIRefs
246
+ - 'annotation_properties': Explicitly specified annotation property URIRefs
247
+ - 'instances': Explicitly specified instance URIRefs
248
+
249
+ Raises:
250
+ ValueError: If a CURIE cannot be expanded or entity doesn't exist
251
+ """
252
+ entities = {
253
+ "classes": set(),
254
+ "object_properties": set(),
255
+ "datatype_properties": set(),
256
+ "annotation_properties": set(),
257
+ "instances": set(),
258
+ }
259
+
260
+ # Get all properties by type for validation
261
+ all_obj_props = {s for s in graph.subjects(RDF.type, OWL.ObjectProperty)}
262
+ all_data_props = {s for s in graph.subjects(RDF.type, OWL.DatatypeProperty)}
263
+ all_ann_props = {s for s in graph.subjects(RDF.type, OWL.AnnotationProperty)}
264
+
265
+ # Expand and validate classes
266
+ for curie in context.explicit_classes:
267
+ uri = expand_curie(graph, curie)
268
+ if uri is None:
269
+ raise ValueError(f"Cannot expand CURIE: {curie}")
270
+
271
+ # Validate it's actually a class
272
+ is_class = (
273
+ (uri, RDF.type, OWL.Class) in graph or
274
+ (uri, RDF.type, RDFS.Class) in graph
275
+ )
276
+ if not is_class:
277
+ # Warning but don't fail - might be a valid use case
278
+ pass
279
+
280
+ entities["classes"].add(uri)
281
+
282
+ # Expand and validate object properties
283
+ for curie in context.explicit_object_properties:
284
+ uri = expand_curie(graph, curie)
285
+ if uri is None:
286
+ raise ValueError(f"Cannot expand CURIE: {curie}")
287
+
288
+ if uri not in all_obj_props:
289
+ # Warning but don't fail
290
+ pass
291
+
292
+ entities["object_properties"].add(uri)
293
+
294
+ # Expand and validate datatype properties
295
+ for curie in context.explicit_datatype_properties:
296
+ uri = expand_curie(graph, curie)
297
+ if uri is None:
298
+ raise ValueError(f"Cannot expand CURIE: {curie}")
299
+
300
+ if uri not in all_data_props:
301
+ pass
302
+
303
+ entities["datatype_properties"].add(uri)
304
+
305
+ # Expand and validate annotation properties
306
+ for curie in context.explicit_annotation_properties:
307
+ uri = expand_curie(graph, curie)
308
+ if uri is None:
309
+ raise ValueError(f"Cannot expand CURIE: {curie}")
310
+
311
+ if uri not in all_ann_props:
312
+ pass
313
+
314
+ entities["annotation_properties"].add(uri)
315
+
316
+ # Expand instances
317
+ for curie in context.explicit_instances:
318
+ uri = expand_curie(graph, curie)
319
+ if uri is None:
320
+ raise ValueError(f"Cannot expand CURIE: {curie}")
321
+
322
+ entities["instances"].add(uri)
323
+
324
+ return entities
325
+
326
+
327
+ def collect_diagram_entities(
328
+ graph: Graph, context: UMLContext, selectors: dict[str, str]
329
+ ) -> dict[str, set[URIRef]]:
330
+ """Collect all entities for a UML diagram based on context.
331
+
332
+ This is the main entry point for entity selection. It orchestrates
333
+ the selection of classes, properties, and instances based on the
334
+ context mode (default or explicit).
335
+
336
+ Args:
337
+ graph: RDF graph to select from
338
+ context: UML context specifying selection criteria
339
+ selectors: Selector definitions from config
340
+
341
+ Returns:
342
+ Dictionary with keys:
343
+ - 'classes': Selected class URIRefs
344
+ - 'object_properties': Selected object property URIRefs
345
+ - 'datatype_properties': Selected datatype property URIRefs
346
+ - 'annotation_properties': Selected annotation property URIRefs
347
+ - 'instances': Selected instance URIRefs
348
+ """
349
+ # Handle explicit mode (direct specification of classes, properties, attributes, & instances)
350
+ if context.mode == "explicit":
351
+ return collect_explicit_entities(graph, context)
352
+
353
+ # Default mode: use existing selection strategies
354
+ # Select classes
355
+ classes = select_classes(graph, context, selectors)
356
+
357
+ # Select properties based on classes
358
+ properties = select_properties(graph, context, classes)
359
+
360
+ # Select instances if requested
361
+ instances = set()
362
+ if context.include_instances:
363
+ instances = select_instances(graph, classes)
364
+
365
+ return {
366
+ "classes": classes,
367
+ "object_properties": properties["object"],
368
+ "datatype_properties": properties["datatype"],
369
+ "annotation_properties": properties["annotation"],
370
+ "instances": instances,
371
+ }