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