naas-abi-core 1.0.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 (124) hide show
  1. naas_abi_core/__init__.py +1 -0
  2. naas_abi_core/apps/api/api.py +242 -0
  3. naas_abi_core/apps/api/api_test.py +281 -0
  4. naas_abi_core/apps/api/openapi_doc.py +307 -0
  5. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  6. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  7. naas_abi_core/apps/terminal_agent/main.py +555 -0
  8. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  9. naas_abi_core/cli/__init__.py +53 -0
  10. naas_abi_core/cli/agent.py +30 -0
  11. naas_abi_core/cli/chat.py +26 -0
  12. naas_abi_core/cli/config.py +49 -0
  13. naas_abi_core/cli/init.py +13 -0
  14. naas_abi_core/cli/module.py +28 -0
  15. naas_abi_core/cli/new.py +13 -0
  16. naas_abi_core/cli/secret.py +79 -0
  17. naas_abi_core/engine/Engine.py +87 -0
  18. naas_abi_core/engine/EngineProxy.py +109 -0
  19. naas_abi_core/engine/Engine_test.py +6 -0
  20. naas_abi_core/engine/IEngine.py +91 -0
  21. naas_abi_core/engine/conftest.py +45 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +160 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +131 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +116 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +171 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +65 -0
  29. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  30. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  31. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  32. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  33. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  34. naas_abi_core/integration/__init__.py +7 -0
  35. naas_abi_core/integration/integration.py +28 -0
  36. naas_abi_core/models/Model.py +198 -0
  37. naas_abi_core/models/OpenRouter.py +15 -0
  38. naas_abi_core/models/OpenRouter_test.py +36 -0
  39. naas_abi_core/module/Module.py +245 -0
  40. naas_abi_core/module/ModuleAgentLoader.py +49 -0
  41. naas_abi_core/module/ModuleUtils.py +20 -0
  42. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  43. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  44. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  46. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  47. naas_abi_core/pipeline/__init__.py +6 -0
  48. naas_abi_core/pipeline/pipeline.py +70 -0
  49. naas_abi_core/services/__init__.py +0 -0
  50. naas_abi_core/services/agent/Agent.py +1619 -0
  51. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  52. naas_abi_core/services/agent/Agent_test.py +214 -0
  53. naas_abi_core/services/agent/IntentAgent.py +1171 -0
  54. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  55. naas_abi_core/services/agent/beta/Embeddings.py +180 -0
  56. naas_abi_core/services/agent/beta/IntentMapper.py +119 -0
  57. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  58. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  59. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  60. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  61. naas_abi_core/services/cache/CacheFactory.py +31 -0
  62. naas_abi_core/services/cache/CachePort.py +63 -0
  63. naas_abi_core/services/cache/CacheService.py +246 -0
  64. naas_abi_core/services/cache/CacheService_test.py +85 -0
  65. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  66. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  67. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  68. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  71. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  72. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  73. naas_abi_core/services/ontology/OntologyService.py +17 -0
  74. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  75. naas_abi_core/services/secret/Secret.py +138 -0
  76. naas_abi_core/services/secret/SecretPorts.py +40 -0
  77. naas_abi_core/services/secret/Secret_test.py +65 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  79. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +81 -0
  81. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  82. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +26 -0
  83. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  84. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  85. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  92. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  94. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  95. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  96. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  97. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  98. naas_abi_core/services/vector_store/__init__.py +13 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  100. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  101. naas_abi_core/utils/Expose.py +53 -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.disabled.py +679 -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.0.0.dist-info/METADATA +75 -0
  122. naas_abi_core-1.0.0.dist-info/RECORD +124 -0
  123. naas_abi_core-1.0.0.dist-info/WHEEL +4 -0
  124. naas_abi_core-1.0.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,679 @@
1
+ import copy
2
+ import random
3
+
4
+ import pydash as _
5
+ from naas_abi import services
6
+ from rdflib import OWL, RDF, RDFS, Graph, URIRef
7
+
8
+ from naas_abi_core import logger
9
+
10
+
11
+ class OntologyYaml:
12
+ def __init__(self):
13
+ pass
14
+
15
+ @staticmethod
16
+ def rdf_to_yaml(
17
+ graph,
18
+ class_colors_mapping: dict = {},
19
+ top_level_class: str = "http://purl.obolibrary.org/obo/BFO_0000001",
20
+ display_relations_names: bool = True,
21
+ yaml_properties: list = [],
22
+ ):
23
+ """Translate RDF graph to YAML.
24
+
25
+ Args:
26
+ graph (Graph): RDF graph to translate.
27
+ class_colors_mapping (dict): Mapping of classes to colors.
28
+ top_level_class (str): Top level class to compute class levels.
29
+ display_relations_names (bool): Whether to display relations names.
30
+ """
31
+ translator = Translator()
32
+ return translator.translate(
33
+ graph,
34
+ class_colors_mapping=class_colors_mapping,
35
+ top_level_class=top_level_class,
36
+ display_relations_names=display_relations_names,
37
+ yaml_properties=yaml_properties,
38
+ )
39
+
40
+
41
+ class Translator:
42
+ def __init__(self):
43
+ # Dictionary to store ontology components
44
+ self.onto = {}
45
+ self.onto_tuples = {}
46
+ self.onto_prop = {}
47
+ self.onto_oprop = {}
48
+ self.onto_classes = {}
49
+ self.amount_per_level = {}
50
+ self.mapping_oprop = {}
51
+
52
+ # Init ontology schemas
53
+ consolidated = services.triple_store_service.get_schema_graph()
54
+ schema_graph = Graph()
55
+
56
+ # Filter for desired types
57
+ desired_types = {
58
+ OWL.Class,
59
+ OWL.DatatypeProperty,
60
+ OWL.ObjectProperty,
61
+ OWL.AnnotationProperty,
62
+ }
63
+
64
+ # Add all triples where subject is of desired type
65
+ for s, p, o in consolidated.triples((None, RDF.type, None)):
66
+ if o in desired_types:
67
+ # Add the type triple
68
+ schema_graph.add((s, p, o))
69
+ # Add all triples where this subject is involved
70
+ for s2, p2, o2 in consolidated.triples((s, None, None)):
71
+ schema_graph.add((s2, p2, o2))
72
+ for s2, p2, o2 in consolidated.triples((None, None, s)):
73
+ schema_graph.add((s2, p2, o2))
74
+ self.ontology_schemas = schema_graph
75
+
76
+ # Init mapping
77
+ mapping = {}
78
+ for s, p, o in self.ontology_schemas.triples((None, RDFS.label, None)):
79
+ if isinstance(s, URIRef):
80
+ mapping[str(s)] = str(o)
81
+
82
+ # Add standard RDF terms
83
+ rdf_terms = {
84
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#type": "type",
85
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#first": "first",
86
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest": "rest",
87
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil": "nil",
88
+ }
89
+
90
+ # Add RDFS terms
91
+ rdfs_terms = {
92
+ "http://www.w3.org/2000/01/rdf-schema#domain": "domain",
93
+ "http://www.w3.org/2000/01/rdf-schema#label": "label",
94
+ "http://www.w3.org/2000/01/rdf-schema#range": "range",
95
+ "http://www.w3.org/2000/01/rdf-schema#subClassOf": "subclassOf",
96
+ }
97
+
98
+ # Add OWL terms
99
+ owl_terms = {
100
+ "http://www.w3.org/2002/07/owl#complementOf": "complementOf",
101
+ "http://www.w3.org/2002/07/owl#intersectionOf": "intersectionOf",
102
+ "http://www.w3.org/2002/07/owl#inverseOf": "inverseOf",
103
+ "http://www.w3.org/2002/07/owl#unionOf": "unionOf",
104
+ }
105
+
106
+ # Add SKOS terms
107
+ skos_terms = {
108
+ "http://www.w3.org/2004/02/skos/core#altLabel": "altLabel",
109
+ "http://www.w3.org/2004/02/skos/core#definition": "definition",
110
+ "http://www.w3.org/2004/02/skos/core#example": "example",
111
+ }
112
+
113
+ # Add DC terms
114
+ dc_terms = {
115
+ "http://purl.org/dc/elements/1.1/identifier": "identifier",
116
+ "http://purl.org/dc/terms/title": "title",
117
+ "http://purl.org/dc/terms/description": "description",
118
+ "http://purl.org/dc/terms/license": "license",
119
+ "http://purl.org/dc/terms/rights": "rights",
120
+ "http://purl.org/dc/terms/contributor": "contributor",
121
+ }
122
+
123
+ # Update mapping with all terms
124
+ mapping.update(rdf_terms)
125
+ mapping.update(rdfs_terms)
126
+ mapping.update(owl_terms)
127
+ mapping.update(skos_terms)
128
+ mapping.update(dc_terms)
129
+ self.mapping = mapping
130
+
131
+ # Define logical operators mapping
132
+ self.operators = {
133
+ "unionOf": "or",
134
+ "intersectionOf": "and",
135
+ "complementOf": "not",
136
+ }
137
+
138
+ def translate(
139
+ self,
140
+ graph,
141
+ class_colors_mapping,
142
+ top_level_class,
143
+ display_relations_names,
144
+ yaml_properties,
145
+ ):
146
+ """Translate RDF graph to YAML.
147
+
148
+ Args:
149
+ graph (Graph): RDF graph to translate.
150
+ class_colors_mapping (dict): Mapping of classes to colors.
151
+ top_level_class (str): Top level class to compute class levels.
152
+ display_relations_names (bool): Whether to display relations names.
153
+ """
154
+ # Extract triples from the Graph.
155
+ self.load_triples(graph)
156
+
157
+ # Load the classes from the ontology.
158
+ self.load_classes()
159
+
160
+ # Compute class levels for the hierarchy building.
161
+ self.compute_class_levels(top_level_class)
162
+
163
+ # Got object properties.
164
+ self.load_object_properties()
165
+
166
+ # Get individuals.
167
+ self.load_individuals()
168
+
169
+ # Map object properties labels
170
+ self.map_oprop_labels()
171
+
172
+ # Create the YAML file.
173
+ return self.create_yaml(
174
+ class_colors_mapping, display_relations_names, yaml_properties
175
+ )
176
+
177
+ def __handle_onto_tuples(self, s, p, o):
178
+ """Load SPO in onto_tuples dictionary.
179
+
180
+ Args:
181
+ s (_type_): Subject
182
+ p (_type_): Predicate
183
+ o (_type_): Object
184
+ """
185
+
186
+ if str(s) not in self.onto_tuples:
187
+ self.onto_tuples[str(s)] = []
188
+
189
+ self.onto_tuples[str(s)].append((p, o))
190
+
191
+ def load_triples(self, g):
192
+ """Load the triples from the graph into the ontology dictionary.
193
+
194
+ Args:
195
+ graph (_type_): _description_
196
+ """
197
+ # Consolidates graph with ConsolidatedOntology.ttl schema
198
+ g += self.ontology_schemas
199
+
200
+ # Load the triples from the graph into the ontology dictionary.
201
+ for s, p, o in g:
202
+ self.__handle_onto_tuples(s, p, o)
203
+
204
+ # Keep only the predicates that are in the mapping.
205
+ if str(p) not in self.mapping:
206
+ # logger.debug(f"🛑 Predicate not in mapping: {str(p)}")
207
+ continue
208
+
209
+ # If the subject is not in the onto dictionary, we create a new dict for it.
210
+ # We also add the __id field to the dict with the subject as the value.
211
+ if str(s) not in self.onto:
212
+ self.onto[str(s)] = {"__id": str(s)}
213
+
214
+ # If the predicate is not in the onto dict, we create a new list for it.
215
+ # We create a list because there can be multiple values for the same predicate.
216
+ if self.mapping[str(p)] not in self.onto[str(s)]:
217
+ self.onto[str(s)][self.mapping[str(p)]] = []
218
+
219
+ # We append the object to the list of the predicate.
220
+ self.onto[str(s)][self.mapping[str(p)]].append(str(o))
221
+
222
+ def load_classes(self):
223
+ # We filter the classes from the ontology.
224
+ _onto_classes = _.filter_(
225
+ self.onto,
226
+ lambda x: "http://www.w3.org/2002/07/owl#Class" in x["type"]
227
+ if "type" in x
228
+ else None,
229
+ )
230
+
231
+ # We remove the subclassOf that are restrictions to keep it simple for now.
232
+ # TODO: Resolve the restrictions to be able to display/use them later on.
233
+ for cls in _onto_classes:
234
+ cls["subclassOf"] = _.filter_(
235
+ cls.get("subclassOf", []), lambda x: True if "http" in x else False
236
+ )
237
+
238
+ # We re build a dictionary with the __id as the key as it is easier to access the data this way.
239
+ self.onto_classes = {e["__id"]: e for e in _onto_classes}
240
+
241
+ def __compute_class_levels(self, cls_id, level=0):
242
+ if cls_id in self.onto_classes:
243
+ self.onto_classes[cls_id]["level"] = level
244
+
245
+ if level not in self.amount_per_level:
246
+ self.amount_per_level[level] = 0
247
+
248
+ self.amount_per_level[level] += 1
249
+
250
+ subclassOf = _.filter_(
251
+ self.onto_classes, lambda x: cls_id in x["subclassOf"]
252
+ )
253
+
254
+ for subclass in subclassOf:
255
+ self.__compute_class_levels(subclass["__id"], level + 1)
256
+
257
+ def compute_class_levels(self, cls_id):
258
+ # Reset amount_per_level
259
+ self.amount_per_level = {}
260
+ self.__compute_class_levels(cls_id)
261
+
262
+ # get_first_rest is used to get the values from unionOf, intersectionOf and complementOf.
263
+ def __get_first_rest(self, tpl):
264
+ first = None
265
+ rest = None
266
+ for i in tpl:
267
+ a, b = i
268
+
269
+ if str(a) == "http://www.w3.org/1999/02/22-rdf-syntax-ns#first":
270
+ first = str(b)
271
+
272
+ if (
273
+ str(a) == "http://www.w3.org/1999/02/22-rdf-syntax-ns#rest"
274
+ and str(b) != "http://www.w3.org/1999/02/22-rdf-syntax-ns#nil"
275
+ ):
276
+ rest = str(b)
277
+ return first, rest
278
+
279
+ # get_linked_classes is a recursive function used for Object Properties ranges and domains.
280
+ # It will build a tree of classes based on the unionOf, intersectionOf and complementOf.
281
+ # It is usefull to understand what are the conditions for a class to be in the range or domain of an object property.
282
+ def get_linked_classes(self, cls_id, rel_type=None):
283
+ # If it's a leaf we return a dict with the class id and the operator.
284
+ if "http" in cls_id:
285
+ if rel_type is not None and rel_type in self.operators:
286
+ return {self.operators[rel_type]: [cls_id]}
287
+ return [cls_id]
288
+
289
+ # If it's a class, we want to go over the unionOf, intersectionOf and complementOf.
290
+ if cls_id in self.onto_classes:
291
+ cls = self.onto_classes[cls_id]
292
+ res = (
293
+ [
294
+ self.get_linked_classes(e, "unionOf")
295
+ for e in _.get(cls, "unionOf", [])
296
+ ]
297
+ + [
298
+ self.get_linked_classes(e, "intersectionOf")
299
+ for e in _.get(cls, "intersectionOf", [])
300
+ ]
301
+ + [
302
+ self.get_linked_classes(e, "complementOf")
303
+ for e in _.get(cls, "complementOf", [])
304
+ ]
305
+ )
306
+ return res
307
+ else:
308
+ # If it's not a class, then we will have a 'first' and a 'rest' to handle.
309
+ first, rest = self.__get_first_rest(self.onto_tuples[cls_id])
310
+
311
+ # We grab the operator based on the rel_type.
312
+ operator = self.operators[rel_type]
313
+
314
+ # We get the left/first value.
315
+ left = self.get_linked_classes(first, rel_type)
316
+ if rest:
317
+ # We get the right/rest value.
318
+ right = self.get_linked_classes(rest, rel_type)
319
+
320
+ if operator in right and operator in left:
321
+ if (
322
+ operator in right
323
+ and type(right[operator]) is dict
324
+ and operator in right[operator]
325
+ and type(right[operator][operator]) is list
326
+ ):
327
+ right[operator] = right[operator][operator]
328
+
329
+ return {operator: _.flatten([left[operator], right[operator]])}
330
+ else:
331
+ return {operator: _.flatten([left, right])}
332
+ else:
333
+ return {operator: left}
334
+
335
+ # We map the ranges and domains to the classes by calling get_linked_classes.
336
+ def map_ranges_domains(self, x):
337
+ if "range" in x:
338
+ x["range"] = _.map_(
339
+ x["range"],
340
+ lambda x: x if "http" in x else self.get_linked_classes(x)[0],
341
+ )
342
+ if "domain" in x:
343
+ x["domain"] = _.map_(
344
+ x["domain"],
345
+ lambda x: x if "http" in x else self.get_linked_classes(x)[0],
346
+ )
347
+ return x
348
+
349
+ def load_object_properties(self):
350
+ # We filter the object properties from the ontology.
351
+ _onto_oprop = _.filter_(
352
+ self.onto,
353
+ lambda x: "http://www.w3.org/2002/07/owl#ObjectProperty" in x["type"]
354
+ if "type" in x
355
+ else None,
356
+ )
357
+
358
+ # For each Object property, we map the ranges and domains.
359
+ for i in _onto_oprop:
360
+ self.map_ranges_domains(i)
361
+
362
+ # We re build a dictionary with the __id as the key as it is easier to access the data this way.
363
+ self.onto_oprop = {e["__id"]: e for e in _onto_oprop}
364
+
365
+ def load_individuals(self):
366
+ self.onto_individuals = _.filter_(
367
+ self.onto,
368
+ lambda x: "http://www.w3.org/2002/07/owl#NamedIndividual" in x["type"]
369
+ if "type" in x
370
+ else None,
371
+ )
372
+
373
+ def map_oprop_labels(self):
374
+ # Map Object properties with label
375
+ self.mapping_oprop = {}
376
+ for o in self.onto_oprop:
377
+ if o and "label" in self.onto_oprop.get(o):
378
+ self.mapping_oprop[o] = self.onto_oprop.get(o).get("label")[0]
379
+
380
+ def create_yaml(
381
+ self,
382
+ class_color,
383
+ display_relations_names,
384
+ yaml_properties,
385
+ ):
386
+ # Init
387
+ all_classes = {}
388
+ classes = {}
389
+ entities = []
390
+ prefixes = {
391
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
392
+ "abi": "http://ontology.naas.ai/abi/",
393
+ "bfo": "http://purl.obolibrary.org/obo/",
394
+ "cco": "https://www.commoncoreontologies.org/",
395
+ }
396
+ if len(yaml_properties) == 0:
397
+ yaml_properties = [
398
+ "definition",
399
+ "example",
400
+ "description",
401
+ "download url",
402
+ "asset url",
403
+ ]
404
+
405
+ # Loop on classes
406
+ for onto_class in self.onto_classes:
407
+ if onto_class.startswith("http"):
408
+ onto_class_dict = self.onto_classes.get(onto_class)
409
+ uid = onto_class_dict.get("__id")
410
+ level = onto_class_dict.get("level", 0)
411
+ label = ""
412
+ if "label" in onto_class_dict:
413
+ label = onto_class_dict.get("label")[0]
414
+ example = onto_class_dict.get("example", [])
415
+ relations = onto_class_dict.get("relations", [])
416
+ definition = onto_class_dict.get("definition", [])
417
+ subclass = onto_class_dict.get("subclassOf", [])
418
+
419
+ # Create title
420
+ title = f"{label} (id: {uid})"
421
+ if len(definition) > 0:
422
+ title = f"{title}\nDefinition: {', '.join(definition)}"
423
+ if len(example) > 0:
424
+ title = f"{title}\nExamples: {'| '.join(example)}"
425
+ if len(relations) > 0:
426
+ print(label, relations)
427
+ title = f"{title}\nRelations: {'| '.join(relations)}"
428
+ title = f"{title}\n"
429
+
430
+ # X position
431
+ x = None
432
+ entity_id = None
433
+ color = "red"
434
+ group = "TBD"
435
+ if uid.startswith("http://purl.obolibrary.org/obo/"):
436
+ entity_id = uid.split("/obo/")[1]
437
+ color = "#97c1fb"
438
+ group = "BFO"
439
+ elif uid.startswith("https://www.commoncoreontologies.org/"):
440
+ color = "#e4c51e"
441
+ group = "CCO"
442
+ elif "/abi/" in uid:
443
+ color = "#48DD82"
444
+ group = "ABI"
445
+
446
+ # Level 0: Entity
447
+ if entity_id == "BFO_0000001":
448
+ x = 0
449
+ # Level 1: Continuant
450
+ elif entity_id == "BFO_0000002":
451
+ x = -500
452
+ # Level 2
453
+ elif entity_id == "BFO_0000004":
454
+ x = -1000
455
+ elif entity_id == "BFO_0000031":
456
+ x = -600
457
+ elif entity_id == "BFO_0000020":
458
+ x = -300
459
+ # Level 3
460
+ elif entity_id == "BFO_0000040":
461
+ x = -1100
462
+ elif entity_id == "BFO_0000141":
463
+ x = -800
464
+ elif entity_id == "BFO_0000019":
465
+ x = -400
466
+ elif entity_id == "BFO_0000017":
467
+ x = -200
468
+ # Level 4
469
+ elif entity_id == "BFO_0000027":
470
+ x = -1700
471
+ elif entity_id == "BFO_0000024":
472
+ x = -1500
473
+ elif entity_id == "BFO_0000030":
474
+ x = -1300
475
+ elif entity_id == "BFO_0000006":
476
+ x = -1100
477
+ elif entity_id == "BFO_0000140":
478
+ x = -900
479
+ elif entity_id == "BFO_0000029":
480
+ x = -700
481
+ elif entity_id == "BFO_0000145":
482
+ x = -500
483
+ elif entity_id == "BFO_0000023":
484
+ x = -300
485
+ elif entity_id == "BFO_0000016":
486
+ x = -100
487
+ # Level 5
488
+ elif entity_id == "BFO_0000018":
489
+ x = -1300
490
+ elif entity_id == "BFO_0000026":
491
+ x = -1200
492
+ elif entity_id == "BFO_0000009":
493
+ x = -1100
494
+ elif entity_id == "BFO_0000028":
495
+ x = -1000
496
+ elif entity_id == "BFO_0000142":
497
+ x = -900
498
+ elif entity_id == "BFO_0000146":
499
+ x = -800
500
+ elif entity_id == "BFO_0000147":
501
+ x = -700
502
+ elif entity_id == "BFO_0000034":
503
+ x = -100
504
+
505
+ # Level 1: Occurent
506
+ elif entity_id == "BFO_0000003":
507
+ x = 500
508
+ # Level 2
509
+ elif entity_id == "BFO_0000015":
510
+ x = 100
511
+ elif entity_id == "BFO_0000035":
512
+ x = 400
513
+ elif entity_id == "BFO_0000008":
514
+ x = 700
515
+ elif entity_id == "BFO_0000011":
516
+ x = 1000
517
+ # Level 3
518
+ elif entity_id == "BFO_0000182":
519
+ x = 100
520
+ elif entity_id == "BFO_0000038":
521
+ x = 600
522
+ elif entity_id == "BFO_0000148":
523
+ x = 900
524
+ # Level 4
525
+ elif entity_id == "BFO_0000202":
526
+ x = 600
527
+ elif entity_id == "BFO_0000203":
528
+ x = 900
529
+
530
+ # Y position
531
+ start_y = -1000
532
+ margin_y = 250
533
+ y = start_y + level * margin_y
534
+
535
+ # Concat classes
536
+ cl = {
537
+ "id": uid,
538
+ "name": label,
539
+ "definition": "| ".join(definition),
540
+ "example": "| ".join(example),
541
+ "style": {
542
+ "group": group,
543
+ "color": color,
544
+ "title": title,
545
+ },
546
+ }
547
+ if x is not None:
548
+ cl["style"]["x"] = x * 1.5
549
+ cl["style"]["y"] = y * 1.5
550
+ cl["style"]["fixed"] = True
551
+
552
+ if len(subclass) > 0:
553
+ cl["relations"] = [{"label": "is_a", "to": subclass[0]}]
554
+ all_classes[uid] = cl
555
+
556
+ # Add BFO Classes by default
557
+ if "BFO_" in uid:
558
+ classes[uid] = cl
559
+
560
+ logger.debug(f"All classes: {len(all_classes)}")
561
+ logger.debug(f"BFO classes: {len(classes)}")
562
+
563
+ # Loop on individuals
564
+ for individual in self.onto_individuals:
565
+ # Init variables
566
+ uri = individual.get("__id") # Get URI
567
+ if len(individual.get("label", [])) > 0:
568
+ label = individual.get("label")[0] # Get label
569
+ else:
570
+ label = uri.split("/")[-1]
571
+ class_uri = [
572
+ i for i in individual.get("type", []) if "NamedIndividual" not in i
573
+ ][0] # Get class
574
+ if "/abi/" in uri:
575
+ # Assign random color for a new class
576
+ if class_uri not in class_color:
577
+ random_color = "#{:06x}".format(random.randint(0, 0xFFFFFF))
578
+ class_color[class_uri] = random_color
579
+ color = class_color[class_uri]
580
+
581
+ # Create entity: individuals, classes and subclassof relations
582
+ entity = {
583
+ "id": uri,
584
+ "name": label,
585
+ "class": class_uri,
586
+ "relations": [],
587
+ "style": {
588
+ "color": color,
589
+ },
590
+ }
591
+
592
+ # Add image url
593
+ image_url = (
594
+ individual.get("picture")
595
+ or individual.get("logo")
596
+ or individual.get("avatar")
597
+ )
598
+ if image_url:
599
+ for i in image_url:
600
+ if str(i) != "None" and str(i).startswith("http"):
601
+ entity["style"]["image"] = i
602
+ entity["style"]["shape"] = "image"
603
+ break
604
+
605
+ # Add ontology group
606
+ ontology_group = individual.get("ontology group")
607
+ if ontology_group:
608
+ entity["style"]["group"] = ontology_group[0]
609
+
610
+ # Add coordinates
611
+ coordinate_x = individual.get("x")
612
+ coordinate_y = individual.get("y")
613
+ if coordinate_x and coordinate_y:
614
+ entity["style"]["x"] = coordinate_x
615
+ entity["style"]["y"] = coordinate_y
616
+ entity["style"]["fixed"] = True
617
+
618
+ # Create entity relations between individuals
619
+ entity_relations = entity.get("relations")
620
+ for r in self.mapping_oprop.values():
621
+ if r in individual:
622
+ for v in individual.get(r):
623
+ entity_relations.append(
624
+ {
625
+ "label": r if display_relations_names else None,
626
+ "to": v,
627
+ }
628
+ )
629
+
630
+ # Add data properties
631
+ for x in yaml_properties:
632
+ x_value = individual.get(x)
633
+ if x_value:
634
+ entity[x] = x_value[0]
635
+
636
+ # Concat entities with individual
637
+ entities.append(entity)
638
+
639
+ # YAML: Add class to dict entities (to be displayed)
640
+ class_x = copy.deepcopy(class_uri)
641
+ while True:
642
+ # YAML: Add class to dict classes
643
+ if class_x in classes:
644
+ break
645
+ class_dict = all_classes.get(class_x)
646
+ if class_dict is None:
647
+ logger.debug(f"🛑 Class '{class_x}' does not exist!")
648
+ break
649
+ classes[class_x] = class_dict
650
+ logger.debug(f"✅ Class '{class_x}' added to entities!", class_dict)
651
+
652
+ # Check if BFO
653
+ if "BFO_" in class_dict.get("id"):
654
+ break
655
+ class_x = _.get(class_dict, "relations[0].to")
656
+
657
+ def replace_values(data, old_value, new_value):
658
+ if isinstance(data, list):
659
+ for i, item in enumerate(data):
660
+ data[i] = replace_values(item, old_value, new_value)
661
+ elif isinstance(data, dict):
662
+ for key, value in data.items():
663
+ data[key] = replace_values(value, old_value, new_value)
664
+ elif isinstance(data, str) and old_value in data:
665
+ return data.replace(old_value, new_value)
666
+ return data
667
+
668
+ for p in prefixes:
669
+ yaml_entities = replace_values(entities, prefixes.get(p), f"{p}:")
670
+ yaml_classes = replace_values(
671
+ list(classes.values()), prefixes.get(p), f"{p}:"
672
+ )
673
+
674
+ # Init
675
+ yaml_data = {}
676
+ yaml_data["prefixes"] = prefixes
677
+ yaml_data["classes"] = yaml_classes
678
+ yaml_data["entities"] = yaml_entities
679
+ return yaml_data