naas-abi-core 1.4.1__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 (124) hide show
  1. assets/favicon.ico +0 -0
  2. assets/logo.png +0 -0
  3. naas_abi_core/__init__.py +1 -0
  4. naas_abi_core/apps/api/api.py +245 -0
  5. naas_abi_core/apps/api/api_test.py +281 -0
  6. naas_abi_core/apps/api/openapi_doc.py +144 -0
  7. naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
  8. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  9. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  10. naas_abi_core/apps/terminal_agent/main.py +555 -0
  11. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  12. naas_abi_core/engine/Engine.py +87 -0
  13. naas_abi_core/engine/EngineProxy.py +109 -0
  14. naas_abi_core/engine/Engine_test.py +6 -0
  15. naas_abi_core/engine/IEngine.py +91 -0
  16. naas_abi_core/engine/conftest.py +45 -0
  17. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
  18. naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
  19. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  20. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
  21. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  29. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  30. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  31. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  32. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  33. naas_abi_core/integration/__init__.py +7 -0
  34. naas_abi_core/integration/integration.py +28 -0
  35. naas_abi_core/models/Model.py +198 -0
  36. naas_abi_core/models/OpenRouter.py +18 -0
  37. naas_abi_core/models/OpenRouter_test.py +36 -0
  38. naas_abi_core/module/Module.py +252 -0
  39. naas_abi_core/module/ModuleAgentLoader.py +50 -0
  40. naas_abi_core/module/ModuleUtils.py +20 -0
  41. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  42. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  43. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  44. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  46. naas_abi_core/pipeline/__init__.py +6 -0
  47. naas_abi_core/pipeline/pipeline.py +70 -0
  48. naas_abi_core/services/__init__.py +0 -0
  49. naas_abi_core/services/agent/Agent.py +1619 -0
  50. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  51. naas_abi_core/services/agent/Agent_test.py +214 -0
  52. naas_abi_core/services/agent/IntentAgent.py +1179 -0
  53. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  54. naas_abi_core/services/agent/beta/Embeddings.py +181 -0
  55. naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
  56. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  57. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  58. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  59. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  60. naas_abi_core/services/cache/CacheFactory.py +31 -0
  61. naas_abi_core/services/cache/CachePort.py +63 -0
  62. naas_abi_core/services/cache/CacheService.py +246 -0
  63. naas_abi_core/services/cache/CacheService_test.py +85 -0
  64. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  65. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  66. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  67. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  68. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  71. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  72. naas_abi_core/services/ontology/OntologyService.py +17 -0
  73. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  74. naas_abi_core/services/secret/Secret.py +138 -0
  75. naas_abi_core/services/secret/SecretPorts.py +45 -0
  76. naas_abi_core/services/secret/Secret_test.py +65 -0
  77. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  79. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  81. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
  82. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  83. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  84. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  85. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  92. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  94. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  95. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  96. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  97. naas_abi_core/services/vector_store/__init__.py +13 -0
  98. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  100. naas_abi_core/tests/test_services_imports.py +69 -0
  101. naas_abi_core/utils/Expose.py +55 -0
  102. naas_abi_core/utils/Graph.py +182 -0
  103. naas_abi_core/utils/JSON.py +49 -0
  104. naas_abi_core/utils/LazyLoader.py +44 -0
  105. naas_abi_core/utils/Logger.py +12 -0
  106. naas_abi_core/utils/OntologyReasoner.py +141 -0
  107. naas_abi_core/utils/OntologyYaml.py +681 -0
  108. naas_abi_core/utils/SPARQL.py +256 -0
  109. naas_abi_core/utils/Storage.py +33 -0
  110. naas_abi_core/utils/StorageUtils.py +398 -0
  111. naas_abi_core/utils/String.py +52 -0
  112. naas_abi_core/utils/Workers.py +114 -0
  113. naas_abi_core/utils/__init__.py +0 -0
  114. naas_abi_core/utils/onto2py/README.md +0 -0
  115. naas_abi_core/utils/onto2py/__init__.py +10 -0
  116. naas_abi_core/utils/onto2py/__main__.py +29 -0
  117. naas_abi_core/utils/onto2py/onto2py.py +611 -0
  118. naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
  119. naas_abi_core/workflow/__init__.py +5 -0
  120. naas_abi_core/workflow/workflow.py +48 -0
  121. naas_abi_core-1.4.1.dist-info/METADATA +630 -0
  122. naas_abi_core-1.4.1.dist-info/RECORD +124 -0
  123. naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
  124. naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,611 @@
1
+ import rdflib
2
+ import io
3
+ from typing import Dict, Set, List, Optional
4
+ from dataclasses import dataclass, field
5
+ import re
6
+
7
+ @dataclass
8
+ class PropertyInfo:
9
+ """Information about a property (data or object property)"""
10
+ name: str
11
+ property_type: str # 'data' or 'object'
12
+ range_class: Optional[str] = None
13
+ datatype: Optional[str] = None
14
+ cardinality: Optional[str] = None # 'single', 'multiple', 'exactly_one', etc.
15
+ required: bool = False
16
+
17
+ @dataclass
18
+ class ClassInfo:
19
+ """Information about an RDF class"""
20
+ name: str
21
+ uri: str
22
+ parent_classes: List[str]
23
+ properties: List[PropertyInfo]
24
+ description: Optional[str] = None
25
+ property_uris: Dict[str, str] = field(default_factory=dict) # Maps property name to URI
26
+
27
+ def onto2py(ttl_file: str | io.TextIOBase) -> str:
28
+ """
29
+ Convert TTL file to Python classes
30
+
31
+ Args:
32
+ ttl_file: Path to TTL file or file-like object
33
+
34
+ Returns:
35
+ Generated Python code as string
36
+ """
37
+ if isinstance(ttl_file, str):
38
+ with open(ttl_file, "r") as f:
39
+ content = f.read()
40
+ g = rdflib.Graph()
41
+ g.parse(data=content, format="turtle")
42
+ else:
43
+ content = ttl_file.read()
44
+ g = rdflib.Graph()
45
+ g.parse(data=content, format="turtle")
46
+
47
+ # Define common RDF/OWL/SHACL namespaces
48
+ RDF = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
49
+ RDFS = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#")
50
+ OWL = rdflib.Namespace("http://www.w3.org/2002/07/owl#")
51
+ SHACL = rdflib.Namespace("http://www.w3.org/ns/shacl#")
52
+
53
+ # Extract classes and their information
54
+ classes: Dict[str, ClassInfo] = {}
55
+
56
+ # Find all OWL classes
57
+ for cls in g.subjects(RDF.type, OWL.Class):
58
+ class_name = extract_class_name(cls)
59
+ if class_name:
60
+ classes[str(cls)] = ClassInfo(
61
+ name=class_name,
62
+ uri=str(cls),
63
+ parent_classes=[],
64
+ properties=[],
65
+ description=get_description(g, cls)
66
+ )
67
+
68
+ # Find all RDFS classes (if not already OWL classes)
69
+ for cls in g.subjects(RDF.type, RDFS.Class):
70
+ if str(cls) not in classes:
71
+ class_name = extract_class_name(cls)
72
+ if class_name:
73
+ classes[str(cls)] = ClassInfo(
74
+ name=class_name,
75
+ uri=str(cls),
76
+ parent_classes=[],
77
+ properties=[],
78
+ description=get_description(g, cls)
79
+ )
80
+
81
+ # Extract inheritance relationships
82
+ for cls_uri, class_info in classes.items():
83
+ for parent in g.objects(rdflib.URIRef(cls_uri), RDFS.subClassOf):
84
+ if str(parent) in classes:
85
+ parent_name = classes[str(parent)].name
86
+ class_info.parent_classes.append(parent_name)
87
+
88
+ # Extract properties
89
+ properties: Dict[str, PropertyInfo] = {}
90
+
91
+ # Object properties
92
+ for prop in g.subjects(RDF.type, OWL.ObjectProperty):
93
+ prop_name = extract_property_name(prop)
94
+ if prop_name:
95
+ properties[str(prop)] = PropertyInfo(
96
+ name=prop_name,
97
+ property_type='object',
98
+ range_class=get_property_range(g, prop, classes)
99
+ )
100
+
101
+ # Data properties
102
+ for prop in g.subjects(RDF.type, OWL.DatatypeProperty):
103
+ prop_name = extract_property_name(prop)
104
+ if prop_name:
105
+ properties[str(prop)] = PropertyInfo(
106
+ name=prop_name,
107
+ property_type='data',
108
+ datatype=get_datatype_range(g, prop)
109
+ )
110
+
111
+ # Extract SHACL shapes and constraints
112
+ extract_shacl_constraints(g, classes, properties, SHACL)
113
+
114
+ # Associate properties with classes based on domain
115
+ for prop_uri, prop_info in properties.items():
116
+ for domain in g.objects(rdflib.URIRef(prop_uri), RDFS.domain):
117
+ if str(domain) in classes:
118
+ class_info = classes[str(domain)]
119
+ # Avoid emitting duplicate property declarations when a property
120
+ # specifies the same domain multiple times in the ontology.
121
+ existing_props = {prop.name: prop for prop in class_info.properties}
122
+
123
+ if prop_info.name in existing_props:
124
+ existing_prop = existing_props[prop_info.name]
125
+ # Merge stronger constraints if the duplicate carries them.
126
+ if prop_info.required and not existing_prop.required:
127
+ existing_prop.required = True
128
+ if prop_info.cardinality and not existing_prop.cardinality:
129
+ existing_prop.cardinality = prop_info.cardinality
130
+ continue
131
+
132
+ class_info.properties.append(prop_info)
133
+ class_info.property_uris[prop_info.name] = prop_uri
134
+
135
+ # Inherit properties from parent classes
136
+ inherit_parent_properties(classes)
137
+
138
+ # Generate Python code
139
+ return generate_python_code(classes, properties)
140
+
141
+ def extract_class_name(uri) -> Optional[str]:
142
+ """Extract a clean class name from a URI"""
143
+ uri_str = str(uri)
144
+ if '#' in uri_str:
145
+ name = uri_str.split('#')[-1]
146
+ elif '/' in uri_str:
147
+ name = uri_str.split('/')[-1]
148
+ else:
149
+ name = uri_str
150
+
151
+ # Clean up the name to be a valid Python class name
152
+ name = re.sub(r'[^a-zA-Z0-9_]', '', name)
153
+ if name and name[0].islower():
154
+ name = name[0].upper() + name[1:]
155
+
156
+ return name if name and name.isidentifier() else None
157
+
158
+ def extract_property_name(uri) -> Optional[str]:
159
+ """Extract a clean property name from a URI"""
160
+ uri_str = str(uri)
161
+ if '#' in uri_str:
162
+ name = uri_str.split('#')[-1]
163
+ elif '/' in uri_str:
164
+ name = uri_str.split('/')[-1]
165
+ else:
166
+ name = uri_str
167
+
168
+ # Clean up the name to be a valid Python property name
169
+ name = re.sub(r'[^a-zA-Z0-9_]', '', name)
170
+ if name and name[0].isupper():
171
+ name = name[0].lower() + name[1:]
172
+
173
+ return name if name and name.isidentifier() else None
174
+
175
+ def get_description(g: rdflib.Graph, resource) -> Optional[str]:
176
+ """Get description/comment for a resource"""
177
+ RDFS = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#")
178
+
179
+ for comment in g.objects(resource, RDFS.comment):
180
+ return str(comment)
181
+
182
+ for label in g.objects(resource, RDFS.label):
183
+ return str(label)
184
+
185
+ return None
186
+
187
+ def get_property_range(g: rdflib.Graph, prop, classes: Dict[str, ClassInfo]) -> Optional[str]:
188
+ """Get the range class for an object property"""
189
+ RDFS = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#")
190
+
191
+ for range_cls in g.objects(prop, RDFS.range):
192
+ if str(range_cls) in classes:
193
+ return classes[str(range_cls)].name
194
+
195
+ return None
196
+
197
+ def get_datatype_range(g: rdflib.Graph, prop) -> Optional[str]:
198
+ """Get the datatype range for a data property"""
199
+ RDFS = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#")
200
+ XSD = rdflib.Namespace("http://www.w3.org/2001/XMLSchema#")
201
+
202
+ datatype_mapping = {
203
+ str(XSD.string): 'str',
204
+ str(XSD.integer): 'int',
205
+ str(XSD.int): 'int',
206
+ str(XSD.float): 'float',
207
+ str(XSD.double): 'float',
208
+ str(XSD.boolean): 'bool',
209
+ str(XSD.date): 'datetime.date',
210
+ str(XSD.dateTime): 'datetime.datetime',
211
+ }
212
+
213
+ for range_type in g.objects(prop, RDFS.range):
214
+ return datatype_mapping.get(str(range_type), 'Any')
215
+
216
+ return 'Any'
217
+
218
+ def extract_shacl_constraints(g: rdflib.Graph, classes: Dict[str, ClassInfo],
219
+ properties: Dict[str, PropertyInfo], SHACL):
220
+ """Extract SHACL constraints and apply them to properties"""
221
+
222
+ # Find SHACL shapes
223
+ for shape in g.subjects(rdflib.RDF.type, SHACL.NodeShape):
224
+ # Get target class
225
+ for target_class in g.objects(shape, SHACL.targetClass):
226
+ if str(target_class) in classes:
227
+ # Process property shapes
228
+ for prop_shape in g.objects(shape, SHACL.property):
229
+ process_property_shape(g, prop_shape, classes[str(target_class)],
230
+ properties, SHACL)
231
+
232
+ def process_property_shape(g: rdflib.Graph, prop_shape, class_info: ClassInfo,
233
+ properties: Dict[str, PropertyInfo], SHACL):
234
+ """Process a SHACL property shape"""
235
+
236
+ # Get the property path
237
+ for path in g.objects(prop_shape, SHACL.path):
238
+ if str(path) in properties:
239
+ prop_info = properties[str(path)]
240
+
241
+ # Check cardinality constraints
242
+ for min_count in g.objects(prop_shape, SHACL.minCount):
243
+ if int(str(min_count)) > 0:
244
+ prop_info.required = True
245
+
246
+ for max_count in g.objects(prop_shape, SHACL.maxCount):
247
+ if int(str(max_count)) == 1:
248
+ prop_info.cardinality = 'single'
249
+ else:
250
+ prop_info.cardinality = 'multiple'
251
+
252
+ def inherit_parent_properties(classes: Dict[str, ClassInfo]):
253
+ """
254
+ Inherit properties from parent classes to child classes.
255
+ Properties inherited from parents are made optional to represent capability links.
256
+ """
257
+ # Create a mapping from class name to class info for easier lookup
258
+ name_to_class = {class_info.name: class_info for class_info in classes.values()}
259
+
260
+ def collect_inherited_properties(class_info: ClassInfo, visited: Optional[Set[str]] = None) -> List[PropertyInfo]:
261
+ """Recursively collect properties from parent classes"""
262
+ if visited is None:
263
+ visited = set()
264
+
265
+ # Avoid infinite recursion in case of circular inheritance
266
+ if class_info.name in visited:
267
+ return []
268
+
269
+ visited.add(class_info.name)
270
+ inherited_props = []
271
+
272
+ for parent_name in class_info.parent_classes:
273
+ if parent_name in name_to_class:
274
+ parent_class = name_to_class[parent_name]
275
+
276
+ # Add direct properties from parent (preserve their required status)
277
+ for prop in parent_class.properties:
278
+ # Create a copy of the property preserving the original required status
279
+ inherited_prop = PropertyInfo(
280
+ name=prop.name,
281
+ property_type=prop.property_type,
282
+ range_class=prop.range_class,
283
+ datatype=prop.datatype,
284
+ cardinality=prop.cardinality,
285
+ required=prop.required # Preserve original required status
286
+ )
287
+ inherited_props.append(inherited_prop)
288
+
289
+ # Recursively collect from grandparents
290
+ inherited_props.extend(collect_inherited_properties(parent_class, visited.copy()))
291
+
292
+ return inherited_props
293
+
294
+ # Apply inheritance to each class
295
+ for class_info in classes.values():
296
+ inherited_props = collect_inherited_properties(class_info)
297
+
298
+ # Add inherited properties that don't already exist
299
+ existing_prop_names = {prop.name for prop in class_info.properties}
300
+
301
+ for inherited_prop in inherited_props:
302
+ if inherited_prop.name not in existing_prop_names:
303
+ class_info.properties.append(inherited_prop)
304
+ # Find the property URI from the inheritance chain
305
+ def find_property_uri(prop_name: str, current_class: ClassInfo, search_visited: Optional[Set[str]] = None) -> Optional[str]:
306
+ if search_visited is None:
307
+ search_visited = set()
308
+
309
+ if current_class.name in search_visited:
310
+ return None
311
+ search_visited.add(current_class.name)
312
+
313
+ # Check if current class has the property URI
314
+ if prop_name in current_class.property_uris:
315
+ return current_class.property_uris[prop_name]
316
+
317
+ # Search in parent classes
318
+ for parent_name in current_class.parent_classes:
319
+ if parent_name in name_to_class:
320
+ parent_class = name_to_class[parent_name]
321
+ uri = find_property_uri(prop_name, parent_class, search_visited.copy())
322
+ if uri:
323
+ return uri
324
+ return None
325
+
326
+ prop_uri = find_property_uri(inherited_prop.name, class_info)
327
+ if prop_uri:
328
+ class_info.property_uris[inherited_prop.name] = prop_uri
329
+ existing_prop_names.add(inherited_prop.name)
330
+
331
+ def generate_python_code(classes: Dict[str, ClassInfo],
332
+ properties: Dict[str, PropertyInfo]) -> str:
333
+ """Generate Python code from extracted class and property information"""
334
+
335
+ code_lines = [
336
+ "from __future__ import annotations",
337
+ "from typing import Optional, List, Any, Union, ClassVar",
338
+ "from pydantic import BaseModel, Field, PrivateAttr",
339
+ "import datetime",
340
+ "import uuid",
341
+ "import rdflib",
342
+ "from rdflib import Graph, URIRef, Literal, Namespace",
343
+ "from rdflib.namespace import RDF, RDFS, OWL, XSD",
344
+ "",
345
+ "# Generated classes from TTL file",
346
+ "",
347
+ "# Base class for all RDF entities",
348
+ "class RDFEntity(BaseModel):",
349
+ " \"\"\"Base class for all RDF entities with URI and namespace management\"\"\"",
350
+ " _namespace: ClassVar[str] = \"http://example.org/instance/\"",
351
+ " _uri: str = \"\"",
352
+ " ",
353
+ " model_config = {",
354
+ " 'arbitrary_types_allowed': True,",
355
+ " 'extra': 'forbid'",
356
+ " }",
357
+ " ",
358
+ " def __init__(self, **kwargs):",
359
+ " uri = kwargs.pop('_uri', None)",
360
+ " super().__init__(**kwargs)",
361
+ " if uri is not None:",
362
+ " self._uri = uri",
363
+ " elif not self._uri:",
364
+ " self._uri = f\"{self._namespace}{uuid.uuid4()}\"",
365
+ " ",
366
+ " @classmethod",
367
+ " def set_namespace(cls, namespace: str):",
368
+ " \"\"\"Set the namespace for generating URIs\"\"\"",
369
+ " cls._namespace = namespace",
370
+ " ",
371
+ " def rdf(self, subject_uri: str | None = None) -> Graph:",
372
+ " \"\"\"Generate RDF triples for this instance\"\"\"",
373
+ " g = Graph()",
374
+ " ",
375
+ " # Use stored URI or provided subject_uri",
376
+ " if subject_uri is None:",
377
+ " subject_uri = self._uri",
378
+ " subject = URIRef(subject_uri)",
379
+ " ",
380
+ " # Add class type",
381
+ " if hasattr(self, '_class_uri'):",
382
+ " g.add((subject, RDF.type, URIRef(self._class_uri)))",
383
+ " ",
384
+ " # Add properties",
385
+ " if hasattr(self, '_property_uris'):",
386
+ " for prop_name, prop_uri in self._property_uris.items():",
387
+ " prop_value = getattr(self, prop_name, None)",
388
+ " if prop_value is not None:",
389
+ " if isinstance(prop_value, list):",
390
+ " for item in prop_value:",
391
+ " if hasattr(item, 'rdf'):",
392
+ " # Add triples from related object",
393
+ " g += item.rdf()",
394
+ " g.add((subject, URIRef(prop_uri), URIRef(item._uri)))",
395
+ " else:",
396
+ " g.add((subject, URIRef(prop_uri), Literal(item)))",
397
+ " elif hasattr(prop_value, 'rdf'):",
398
+ " # Add triples from related object",
399
+ " g += prop_value.rdf()",
400
+ " g.add((subject, URIRef(prop_uri), URIRef(prop_value._uri)))",
401
+ " else:",
402
+ " g.add((subject, URIRef(prop_uri), Literal(prop_value)))",
403
+ " ",
404
+ " return g",
405
+ "",
406
+ ""
407
+ ]
408
+
409
+ # Sort classes to handle inheritance properly
410
+ sorted_classes = topological_sort_classes(classes)
411
+
412
+ for class_info in sorted_classes:
413
+ code_lines.extend(generate_class_code(class_info))
414
+ code_lines.append("")
415
+
416
+ # Add model_rebuild() calls for forward references
417
+ code_lines.append("# Rebuild models to resolve forward references")
418
+ for class_info in sorted_classes:
419
+ code_lines.append(f"{class_info.name}.model_rebuild()")
420
+ code_lines.append("")
421
+
422
+ return "\n".join(code_lines)
423
+
424
+ def topological_sort_classes(classes: Dict[str, ClassInfo]) -> List[ClassInfo]:
425
+ """Sort classes so that dependencies come before classes that use them"""
426
+
427
+ # More aggressive topological sort that prioritizes inheritance dependencies
428
+ # and handles circular dependencies better
429
+ sorted_classes = []
430
+ visited = set()
431
+
432
+ def get_inheritance_depth(class_info: ClassInfo, depth=0, visited_in_chain=None):
433
+ """Calculate inheritance depth, handling cycles"""
434
+ if visited_in_chain is None:
435
+ visited_in_chain = set()
436
+
437
+ if class_info.name in visited_in_chain:
438
+ return depth # Cycle detected, return current depth
439
+
440
+ if not class_info.parent_classes:
441
+ return depth
442
+
443
+ visited_in_chain.add(class_info.name)
444
+ max_parent_depth = depth
445
+
446
+ for parent_name in class_info.parent_classes:
447
+ for parent_class in classes.values():
448
+ if parent_class.name == parent_name:
449
+ parent_depth = get_inheritance_depth(parent_class, depth + 1, visited_in_chain.copy())
450
+ max_parent_depth = max(max_parent_depth, parent_depth)
451
+ break
452
+
453
+ return max_parent_depth
454
+
455
+ # Sort by inheritance depth first (deepest inheritance last)
456
+ classes_by_depth = [(get_inheritance_depth(class_info), class_info) for class_info in classes.values()]
457
+ classes_by_depth.sort(key=lambda x: x[0])
458
+
459
+ # Then do standard topological sort within each depth level
460
+ def visit(class_info: ClassInfo, temp_visited=None):
461
+ if temp_visited is None:
462
+ temp_visited = set()
463
+
464
+ if class_info.name in visited:
465
+ return
466
+ if class_info.name in temp_visited:
467
+ # Circular dependency - add the class anyway to avoid infinite loops
468
+ if class_info not in sorted_classes:
469
+ sorted_classes.append(class_info)
470
+ visited.add(class_info.name)
471
+ return
472
+
473
+ temp_visited.add(class_info.name)
474
+
475
+ # Visit parent classes first (inheritance dependencies)
476
+ for parent_name in class_info.parent_classes:
477
+ for parent_class in classes.values():
478
+ if parent_class.name == parent_name:
479
+ visit(parent_class, temp_visited.copy())
480
+ break
481
+
482
+ visited.add(class_info.name)
483
+ sorted_classes.append(class_info)
484
+
485
+ # Process classes in order of inheritance depth
486
+ for depth, class_info in classes_by_depth:
487
+ visit(class_info)
488
+
489
+ return sorted_classes
490
+
491
+ def generate_class_code(class_info: ClassInfo) -> List[str]:
492
+ """Generate Python code for a single class"""
493
+
494
+ lines = []
495
+
496
+ # Deduplicate properties by name while merging stricter constraints.
497
+ unique_props: Dict[str, PropertyInfo] = {}
498
+ for prop in class_info.properties:
499
+ if prop.name in unique_props:
500
+ existing = unique_props[prop.name]
501
+ if prop.required and not existing.required:
502
+ existing.required = True
503
+ if prop.cardinality and not existing.cardinality:
504
+ existing.cardinality = prop.cardinality
505
+ continue
506
+ unique_props[prop.name] = prop
507
+
508
+ properties_list = list(unique_props.values())
509
+
510
+ # Determine class bases
511
+ if class_info.parent_classes:
512
+ parents = list(class_info.parent_classes)
513
+ if "RDFEntity" not in parents:
514
+ parents.append("RDFEntity")
515
+ lines.append(f"class {class_info.name}({', '.join(parents)}):")
516
+ else:
517
+ lines.append(f"class {class_info.name}(RDFEntity):")
518
+
519
+ # Add class docstring if description exists
520
+ if class_info.description:
521
+ lines.append(' """')
522
+ for line in class_info.description.splitlines():
523
+ lines.append(f" {line}")
524
+ lines.append(' """')
525
+
526
+ if class_info.description:
527
+ lines.append("")
528
+
529
+ # Add class-specific metadata
530
+ lines.append(f" _class_uri: ClassVar[str] = '{class_info.uri}'")
531
+
532
+ # Add property URI mapping
533
+ if class_info.property_uris:
534
+ prop_uris_dict = ", ".join(
535
+ [
536
+ f"'{prop_name}': '{prop_uri}'"
537
+ for prop_name, prop_uri in sorted(class_info.property_uris.items())
538
+ ]
539
+ )
540
+ lines.append(f" _property_uris: ClassVar[dict] = {{{prop_uris_dict}}}")
541
+ else:
542
+ lines.append(" _property_uris: ClassVar[dict] = {}")
543
+
544
+ if class_info.property_uris:
545
+ lines.append("")
546
+
547
+ # Add properties grouped by type for readability
548
+ data_properties = sorted(
549
+ (prop for prop in properties_list if prop.property_type == 'data'),
550
+ key=lambda prop: prop.name,
551
+ )
552
+ object_properties = sorted(
553
+ (prop for prop in properties_list if prop.property_type == 'object'),
554
+ key=lambda prop: prop.name,
555
+ )
556
+ other_properties = sorted(
557
+ (
558
+ prop
559
+ for prop in properties_list
560
+ if prop.property_type not in {'data', 'object'}
561
+ ),
562
+ key=lambda prop: prop.name,
563
+ )
564
+
565
+ property_groups = [
566
+ ("Data properties", data_properties),
567
+ ("Object properties", object_properties),
568
+ ("Other properties", other_properties),
569
+ ]
570
+
571
+ emitted_property_group = False
572
+ for group_label, props in property_groups:
573
+ if not props:
574
+ continue
575
+ if emitted_property_group:
576
+ lines.append("")
577
+ lines.append(f" # {group_label}")
578
+ for prop in props:
579
+ lines.append(f" {generate_property_code(prop)}")
580
+ emitted_property_group = True
581
+
582
+ if not emitted_property_group:
583
+ lines.append(" pass")
584
+
585
+
586
+
587
+ return lines
588
+
589
+ def generate_property_code(prop: PropertyInfo) -> str:
590
+ """Generate code for a single property"""
591
+
592
+ # Determine type annotation and Pydantic Field
593
+ if prop.property_type == 'object' and prop.range_class:
594
+ if prop.cardinality == 'multiple':
595
+ type_annotation = f"List[{prop.range_class}]"
596
+ default_value = "Field(default_factory=list)"
597
+ else:
598
+ type_annotation = f"Optional[{prop.range_class}]" if not prop.required else prop.range_class
599
+ default_value = "Field(default=None)" if not prop.required else "Field(...)"
600
+ elif prop.property_type == 'data' and prop.datatype:
601
+ if prop.cardinality == 'multiple':
602
+ type_annotation = f"List[{prop.datatype}]"
603
+ default_value = "Field(default_factory=list)"
604
+ else:
605
+ type_annotation = f"Optional[{prop.datatype}]" if not prop.required else prop.datatype
606
+ default_value = "Field(default=None)" if not prop.required else "Field(...)"
607
+ else:
608
+ type_annotation = "Optional[Any]"
609
+ default_value = "Field(default=None)"
610
+
611
+ return f"{prop.name}: {type_annotation} = {default_value}"