rdf-construct 0.2.1__py3-none-any.whl → 0.4.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 (43) hide show
  1. rdf_construct/__init__.py +1 -1
  2. rdf_construct/cli.py +1794 -0
  3. rdf_construct/describe/__init__.py +93 -0
  4. rdf_construct/describe/analyzer.py +176 -0
  5. rdf_construct/describe/documentation.py +146 -0
  6. rdf_construct/describe/formatters/__init__.py +47 -0
  7. rdf_construct/describe/formatters/json.py +65 -0
  8. rdf_construct/describe/formatters/markdown.py +275 -0
  9. rdf_construct/describe/formatters/text.py +315 -0
  10. rdf_construct/describe/hierarchy.py +232 -0
  11. rdf_construct/describe/imports.py +213 -0
  12. rdf_construct/describe/metadata.py +187 -0
  13. rdf_construct/describe/metrics.py +145 -0
  14. rdf_construct/describe/models.py +552 -0
  15. rdf_construct/describe/namespaces.py +180 -0
  16. rdf_construct/describe/profiles.py +415 -0
  17. rdf_construct/localise/__init__.py +114 -0
  18. rdf_construct/localise/config.py +508 -0
  19. rdf_construct/localise/extractor.py +427 -0
  20. rdf_construct/localise/formatters/__init__.py +36 -0
  21. rdf_construct/localise/formatters/markdown.py +229 -0
  22. rdf_construct/localise/formatters/text.py +224 -0
  23. rdf_construct/localise/merger.py +346 -0
  24. rdf_construct/localise/reporter.py +356 -0
  25. rdf_construct/merge/__init__.py +165 -0
  26. rdf_construct/merge/config.py +354 -0
  27. rdf_construct/merge/conflicts.py +281 -0
  28. rdf_construct/merge/formatters.py +426 -0
  29. rdf_construct/merge/merger.py +425 -0
  30. rdf_construct/merge/migrator.py +339 -0
  31. rdf_construct/merge/rules.py +377 -0
  32. rdf_construct/merge/splitter.py +1102 -0
  33. rdf_construct/refactor/__init__.py +72 -0
  34. rdf_construct/refactor/config.py +362 -0
  35. rdf_construct/refactor/deprecator.py +328 -0
  36. rdf_construct/refactor/formatters/__init__.py +8 -0
  37. rdf_construct/refactor/formatters/text.py +311 -0
  38. rdf_construct/refactor/renamer.py +294 -0
  39. {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/METADATA +91 -6
  40. {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/RECORD +43 -7
  41. {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/WHEEL +0 -0
  42. {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/entry_points.txt +0 -0
  43. {rdf_construct-0.2.1.dist-info → rdf_construct-0.4.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,93 @@
1
+ """Describe command for RDF ontology analysis.
2
+
3
+ Provides quick orientation and understanding of ontology files,
4
+ answering: "What is this?", "How big is it?", "What does it depend on?",
5
+ and "Can I work with it?"
6
+
7
+ Usage:
8
+ from rdf_construct.describe import describe_file, format_description
9
+
10
+ description = describe_file(Path("ontology.ttl"))
11
+ print(format_description(description))
12
+
13
+ # Brief mode (metadata + metrics + profile only)
14
+ description = describe_file(Path("ontology.ttl"), brief=True)
15
+
16
+ # Skip import resolution (faster)
17
+ description = describe_file(Path("ontology.ttl"), resolve_imports=False)
18
+
19
+ # JSON output
20
+ print(format_description(description, format_name="json"))
21
+ """
22
+
23
+ from rdf_construct.describe.models import (
24
+ OntologyDescription,
25
+ OntologyMetadata,
26
+ BasicMetrics,
27
+ ProfileDetection,
28
+ OntologyProfile,
29
+ NamespaceAnalysis,
30
+ NamespaceInfo,
31
+ NamespaceCategory,
32
+ ImportAnalysis,
33
+ ImportInfo,
34
+ ImportStatus,
35
+ HierarchyAnalysis,
36
+ DocumentationCoverage,
37
+ ReasoningAnalysis,
38
+ )
39
+
40
+ from rdf_construct.describe.analyzer import (
41
+ describe_ontology,
42
+ describe_file,
43
+ )
44
+
45
+ from rdf_construct.describe.formatters import (
46
+ format_description,
47
+ format_text,
48
+ format_markdown,
49
+ format_json,
50
+ )
51
+
52
+ from rdf_construct.describe.profiles import detect_profile
53
+ from rdf_construct.describe.metrics import collect_metrics
54
+ from rdf_construct.describe.imports import analyse_imports
55
+ from rdf_construct.describe.namespaces import analyse_namespaces
56
+ from rdf_construct.describe.hierarchy import analyse_hierarchy
57
+ from rdf_construct.describe.documentation import analyse_documentation
58
+ from rdf_construct.describe.metadata import extract_metadata
59
+
60
+
61
+ __all__ = [
62
+ # Main functions
63
+ "describe_file",
64
+ "describe_ontology",
65
+ "format_description",
66
+ # Formatters
67
+ "format_text",
68
+ "format_markdown",
69
+ "format_json",
70
+ # Analysis functions (for direct use)
71
+ "detect_profile",
72
+ "collect_metrics",
73
+ "analyse_imports",
74
+ "analyse_namespaces",
75
+ "analyse_hierarchy",
76
+ "analyse_documentation",
77
+ "extract_metadata",
78
+ # Data models
79
+ "OntologyDescription",
80
+ "OntologyMetadata",
81
+ "BasicMetrics",
82
+ "ProfileDetection",
83
+ "OntologyProfile",
84
+ "NamespaceAnalysis",
85
+ "NamespaceInfo",
86
+ "NamespaceCategory",
87
+ "ImportAnalysis",
88
+ "ImportInfo",
89
+ "ImportStatus",
90
+ "HierarchyAnalysis",
91
+ "DocumentationCoverage",
92
+ "ReasoningAnalysis",
93
+ ]
@@ -0,0 +1,176 @@
1
+ """Main analyzer for ontology description.
2
+
3
+ Orchestrates all analysis components and aggregates results into
4
+ a complete OntologyDescription.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from pathlib import Path
9
+
10
+ from rdflib import Graph
11
+
12
+ from rdf_construct.describe.models import OntologyDescription
13
+ from rdf_construct.describe.metadata import extract_metadata
14
+ from rdf_construct.describe.metrics import collect_metrics
15
+ from rdf_construct.describe.profiles import detect_profile
16
+ from rdf_construct.describe.namespaces import analyse_namespaces
17
+ from rdf_construct.describe.imports import analyse_imports
18
+ from rdf_construct.describe.hierarchy import analyse_hierarchy
19
+ from rdf_construct.describe.documentation import analyse_documentation
20
+
21
+
22
+ def describe_ontology(
23
+ graph: Graph,
24
+ source: str | Path,
25
+ brief: bool = False,
26
+ resolve_imports: bool = True,
27
+ include_reasoning: bool = False,
28
+ ) -> OntologyDescription:
29
+ """Generate a complete description of an ontology.
30
+
31
+ Runs all analysis components and aggregates results.
32
+
33
+ Args:
34
+ graph: Parsed RDF graph to analyse.
35
+ source: Source file path or identifier.
36
+ brief: If True, skip detailed analysis (imports, hierarchy, etc.).
37
+ resolve_imports: Whether to check resolvability of imports.
38
+ include_reasoning: Whether to include reasoning analysis.
39
+
40
+ Returns:
41
+ OntologyDescription with all analysis results.
42
+ """
43
+ # Always perform core analysis
44
+ metadata = extract_metadata(graph)
45
+ metrics = collect_metrics(graph)
46
+ profile = detect_profile(graph)
47
+
48
+ # Create base description
49
+ description = OntologyDescription(
50
+ source=source,
51
+ timestamp=datetime.now(),
52
+ metadata=metadata,
53
+ metrics=metrics,
54
+ profile=profile,
55
+ brief=brief,
56
+ include_reasoning=include_reasoning,
57
+ )
58
+
59
+ # Skip detailed analysis if brief mode
60
+ if brief:
61
+ return description
62
+
63
+ # Full analysis
64
+ description.namespaces = analyse_namespaces(graph)
65
+ description.imports = analyse_imports(graph, resolve=resolve_imports)
66
+ description.hierarchy = analyse_hierarchy(graph)
67
+ description.documentation = analyse_documentation(graph)
68
+
69
+ # Reasoning analysis is optional and off by default
70
+ if include_reasoning:
71
+ description.reasoning = _analyse_reasoning(graph, profile)
72
+
73
+ return description
74
+
75
+
76
+ def describe_file(
77
+ file_path: Path,
78
+ brief: bool = False,
79
+ resolve_imports: bool = True,
80
+ include_reasoning: bool = False,
81
+ ) -> OntologyDescription:
82
+ """Generate a complete description of an ontology file.
83
+
84
+ Convenience function that handles file loading and format detection.
85
+
86
+ Args:
87
+ file_path: Path to RDF file.
88
+ brief: If True, skip detailed analysis.
89
+ resolve_imports: Whether to check resolvability of imports.
90
+ include_reasoning: Whether to include reasoning analysis.
91
+
92
+ Returns:
93
+ OntologyDescription with all analysis results.
94
+
95
+ Raises:
96
+ FileNotFoundError: If file does not exist.
97
+ ValueError: If file cannot be parsed.
98
+ """
99
+ if not file_path.exists():
100
+ raise FileNotFoundError(f"File not found: {file_path}")
101
+
102
+ # Detect format from extension
103
+ rdf_format = _infer_format(file_path)
104
+
105
+ # Parse the file
106
+ graph = Graph()
107
+ try:
108
+ graph.parse(str(file_path), format=rdf_format)
109
+ except Exception as e:
110
+ raise ValueError(f"Failed to parse {file_path}: {e}") from e
111
+
112
+ return describe_ontology(
113
+ graph=graph,
114
+ source=file_path,
115
+ brief=brief,
116
+ resolve_imports=resolve_imports,
117
+ include_reasoning=include_reasoning,
118
+ )
119
+
120
+
121
+ def _infer_format(path: Path) -> str:
122
+ """Infer RDF format from file extension.
123
+
124
+ Args:
125
+ path: Path to RDF file.
126
+
127
+ Returns:
128
+ Format string for rdflib.
129
+ """
130
+ suffix = path.suffix.lower()
131
+ format_map = {
132
+ ".ttl": "turtle",
133
+ ".turtle": "turtle",
134
+ ".rdf": "xml",
135
+ ".xml": "xml",
136
+ ".owl": "xml",
137
+ ".nt": "nt",
138
+ ".ntriples": "nt",
139
+ ".n3": "n3",
140
+ ".jsonld": "json-ld",
141
+ ".json": "json-ld",
142
+ }
143
+ return format_map.get(suffix, "turtle")
144
+
145
+
146
+ def _analyse_reasoning(graph: Graph, profile) -> "ReasoningAnalysis":
147
+ """Perform reasoning analysis (optional feature).
148
+
149
+ This is a placeholder for future reasoning analysis functionality.
150
+
151
+ Args:
152
+ graph: RDF graph to analyse.
153
+ profile: Detected profile.
154
+
155
+ Returns:
156
+ ReasoningAnalysis with reasoning implications.
157
+ """
158
+ from rdf_construct.describe.models import ReasoningAnalysis, OntologyProfile
159
+
160
+ # Determine entailment regime based on profile
161
+ regime_map = {
162
+ OntologyProfile.RDF: "none",
163
+ OntologyProfile.RDFS: "rdfs",
164
+ OntologyProfile.OWL_DL_SIMPLE: "owl-dl",
165
+ OntologyProfile.OWL_DL_EXPRESSIVE: "owl-dl",
166
+ OntologyProfile.OWL_FULL: "owl-full",
167
+ }
168
+
169
+ regime = regime_map.get(profile.profile, "unknown")
170
+
171
+ return ReasoningAnalysis(
172
+ entailment_regime=regime,
173
+ inferred_superclasses=[],
174
+ inferred_types=[],
175
+ consistency_notes=[],
176
+ )
@@ -0,0 +1,146 @@
1
+ """Documentation coverage analysis for ontology description.
2
+
3
+ Analyses the presence of labels and definitions for classes and properties.
4
+ """
5
+
6
+ from rdflib import Graph, URIRef, RDF, RDFS
7
+ from rdflib.namespace import OWL
8
+
9
+ from rdf_construct.describe.models import DocumentationCoverage
10
+
11
+
12
+ # Predicates considered as providing a label
13
+ LABEL_PREDICATES = {
14
+ RDFS.label,
15
+ URIRef("http://www.w3.org/2004/02/skos/core#prefLabel"),
16
+ URIRef("http://www.w3.org/2004/02/skos/core#altLabel"),
17
+ URIRef("http://purl.org/dc/elements/1.1/title"),
18
+ URIRef("http://purl.org/dc/terms/title"),
19
+ }
20
+
21
+ # Predicates considered as providing a definition/description
22
+ DEFINITION_PREDICATES = {
23
+ RDFS.comment,
24
+ URIRef("http://www.w3.org/2004/02/skos/core#definition"),
25
+ URIRef("http://purl.org/dc/elements/1.1/description"),
26
+ URIRef("http://purl.org/dc/terms/description"),
27
+ }
28
+
29
+
30
+ def analyse_documentation(graph: Graph) -> DocumentationCoverage:
31
+ """Analyse documentation coverage for classes and properties.
32
+
33
+ Args:
34
+ graph: RDF graph to analyse.
35
+
36
+ Returns:
37
+ DocumentationCoverage with coverage metrics.
38
+ """
39
+ # Get all classes
40
+ classes = _get_all_classes(graph)
41
+ classes_total = len(classes)
42
+
43
+ # Get all properties
44
+ properties = _get_all_properties(graph)
45
+ properties_total = len(properties)
46
+
47
+ # Count classes with labels
48
+ classes_with_label = sum(1 for cls in classes if _has_label(graph, cls))
49
+
50
+ # Count classes with definitions
51
+ classes_with_definition = sum(1 for cls in classes if _has_definition(graph, cls))
52
+
53
+ # Count properties with labels
54
+ properties_with_label = sum(1 for prop in properties if _has_label(graph, prop))
55
+
56
+ # Count properties with definitions
57
+ properties_with_definition = sum(
58
+ 1 for prop in properties if _has_definition(graph, prop)
59
+ )
60
+
61
+ return DocumentationCoverage(
62
+ classes_with_label=classes_with_label,
63
+ classes_total=classes_total,
64
+ classes_with_definition=classes_with_definition,
65
+ properties_with_label=properties_with_label,
66
+ properties_total=properties_total,
67
+ properties_with_definition=properties_with_definition,
68
+ )
69
+
70
+
71
+ def _get_all_classes(graph: Graph) -> set[URIRef]:
72
+ """Get all classes from the graph.
73
+
74
+ Args:
75
+ graph: RDF graph to query.
76
+
77
+ Returns:
78
+ Set of class URIRefs.
79
+ """
80
+ classes: set[URIRef] = set()
81
+
82
+ for cls in graph.subjects(RDF.type, OWL.Class):
83
+ if isinstance(cls, URIRef):
84
+ classes.add(cls)
85
+
86
+ for cls in graph.subjects(RDF.type, RDFS.Class):
87
+ if isinstance(cls, URIRef):
88
+ classes.add(cls)
89
+
90
+ return classes
91
+
92
+
93
+ def _get_all_properties(graph: Graph) -> set[URIRef]:
94
+ """Get all properties from the graph.
95
+
96
+ Args:
97
+ graph: RDF graph to query.
98
+
99
+ Returns:
100
+ Set of property URIRefs.
101
+ """
102
+ properties: set[URIRef] = set()
103
+
104
+ for prop_type in (
105
+ OWL.ObjectProperty,
106
+ OWL.DatatypeProperty,
107
+ OWL.AnnotationProperty,
108
+ RDF.Property,
109
+ ):
110
+ for prop in graph.subjects(RDF.type, prop_type):
111
+ if isinstance(prop, URIRef):
112
+ properties.add(prop)
113
+
114
+ return properties
115
+
116
+
117
+ def _has_label(graph: Graph, subject: URIRef) -> bool:
118
+ """Check if a subject has any label predicate.
119
+
120
+ Args:
121
+ graph: RDF graph to query.
122
+ subject: Subject to check.
123
+
124
+ Returns:
125
+ True if subject has at least one label.
126
+ """
127
+ for pred in LABEL_PREDICATES:
128
+ if any(graph.objects(subject, pred)):
129
+ return True
130
+ return False
131
+
132
+
133
+ def _has_definition(graph: Graph, subject: URIRef) -> bool:
134
+ """Check if a subject has any definition/description predicate.
135
+
136
+ Args:
137
+ graph: RDF graph to query.
138
+ subject: Subject to check.
139
+
140
+ Returns:
141
+ True if subject has at least one definition.
142
+ """
143
+ for pred in DEFINITION_PREDICATES:
144
+ if any(graph.objects(subject, pred)):
145
+ return True
146
+ return False
@@ -0,0 +1,47 @@
1
+ """Output formatters for ontology description."""
2
+
3
+ from typing import Optional
4
+
5
+ from rdf_construct.describe.models import OntologyDescription
6
+ from rdf_construct.describe.formatters.text import format_text
7
+ from rdf_construct.describe.formatters.markdown import format_markdown
8
+ from rdf_construct.describe.formatters.json import format_json
9
+
10
+
11
+ def format_description(
12
+ description: OntologyDescription,
13
+ format_name: str = "text",
14
+ use_colour: bool = True,
15
+ ) -> str:
16
+ """Format ontology description for output.
17
+
18
+ Args:
19
+ description: The description to format.
20
+ format_name: Output format ("text", "json", "markdown", "md").
21
+ use_colour: Whether to use ANSI colour codes (text format only).
22
+
23
+ Returns:
24
+ Formatted string representation.
25
+
26
+ Raises:
27
+ ValueError: If format_name is not recognised.
28
+ """
29
+ format_name = format_name.lower()
30
+
31
+ if format_name == "text":
32
+ return format_text(description, use_colour=use_colour)
33
+ elif format_name == "json":
34
+ return format_json(description)
35
+ elif format_name in ("markdown", "md"):
36
+ return format_markdown(description)
37
+ else:
38
+ valid = "text, json, markdown, md"
39
+ raise ValueError(f"Unknown format '{format_name}'. Valid formats: {valid}")
40
+
41
+
42
+ __all__ = [
43
+ "format_description",
44
+ "format_text",
45
+ "format_markdown",
46
+ "format_json",
47
+ ]
@@ -0,0 +1,65 @@
1
+ """JSON formatter for ontology description output.
2
+
3
+ Produces structured JSON for programmatic consumption.
4
+ """
5
+
6
+ import json
7
+ from typing import Any
8
+
9
+ from rdf_construct.describe.models import OntologyDescription
10
+
11
+
12
+ def format_json(
13
+ description: OntologyDescription,
14
+ indent: int = 2,
15
+ ensure_ascii: bool = False,
16
+ ) -> str:
17
+ """Format ontology description as JSON.
18
+
19
+ Args:
20
+ description: OntologyDescription to format.
21
+ indent: Indentation level for pretty printing.
22
+ ensure_ascii: If True, escape non-ASCII characters.
23
+
24
+ Returns:
25
+ JSON string.
26
+ """
27
+ data = description.to_dict()
28
+
29
+ return json.dumps(
30
+ data,
31
+ indent=indent,
32
+ ensure_ascii=ensure_ascii,
33
+ default=_json_serializer,
34
+ )
35
+
36
+
37
+ def _json_serializer(obj: Any) -> Any:
38
+ """Custom JSON serializer for non-standard types.
39
+
40
+ Args:
41
+ obj: Object to serialize.
42
+
43
+ Returns:
44
+ JSON-serializable representation.
45
+
46
+ Raises:
47
+ TypeError: If object cannot be serialized.
48
+ """
49
+ # Handle Path objects
50
+ if hasattr(obj, "__fspath__"):
51
+ return str(obj)
52
+
53
+ # Handle datetime
54
+ if hasattr(obj, "isoformat"):
55
+ return obj.isoformat()
56
+
57
+ # Handle enums
58
+ if hasattr(obj, "value"):
59
+ return obj.value
60
+
61
+ # Handle dataclasses with to_dict method
62
+ if hasattr(obj, "to_dict"):
63
+ return obj.to_dict()
64
+
65
+ raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")