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.
- assets/favicon.ico +0 -0
- assets/logo.png +0 -0
- naas_abi_core/__init__.py +1 -0
- naas_abi_core/apps/api/api.py +245 -0
- naas_abi_core/apps/api/api_test.py +281 -0
- naas_abi_core/apps/api/openapi_doc.py +144 -0
- naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
- naas_abi_core/apps/mcp/mcp_server.py +243 -0
- naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
- naas_abi_core/apps/terminal_agent/main.py +555 -0
- naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
- naas_abi_core/engine/Engine.py +87 -0
- naas_abi_core/engine/EngineProxy.py +109 -0
- naas_abi_core/engine/Engine_test.py +6 -0
- naas_abi_core/engine/IEngine.py +91 -0
- naas_abi_core/engine/conftest.py +45 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
- naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
- naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
- naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
- naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
- naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
- naas_abi_core/integration/__init__.py +7 -0
- naas_abi_core/integration/integration.py +28 -0
- naas_abi_core/models/Model.py +198 -0
- naas_abi_core/models/OpenRouter.py +18 -0
- naas_abi_core/models/OpenRouter_test.py +36 -0
- naas_abi_core/module/Module.py +252 -0
- naas_abi_core/module/ModuleAgentLoader.py +50 -0
- naas_abi_core/module/ModuleUtils.py +20 -0
- naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
- naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
- naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
- naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
- naas_abi_core/pipeline/__init__.py +6 -0
- naas_abi_core/pipeline/pipeline.py +70 -0
- naas_abi_core/services/__init__.py +0 -0
- naas_abi_core/services/agent/Agent.py +1619 -0
- naas_abi_core/services/agent/AgentMemory_test.py +28 -0
- naas_abi_core/services/agent/Agent_test.py +214 -0
- naas_abi_core/services/agent/IntentAgent.py +1179 -0
- naas_abi_core/services/agent/IntentAgent_test.py +139 -0
- naas_abi_core/services/agent/beta/Embeddings.py +181 -0
- naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
- naas_abi_core/services/agent/beta/LocalModel.py +88 -0
- naas_abi_core/services/agent/beta/VectorStore.py +89 -0
- naas_abi_core/services/agent/test_agent_memory.py +278 -0
- naas_abi_core/services/agent/test_postgres_integration.py +145 -0
- naas_abi_core/services/cache/CacheFactory.py +31 -0
- naas_abi_core/services/cache/CachePort.py +63 -0
- naas_abi_core/services/cache/CacheService.py +246 -0
- naas_abi_core/services/cache/CacheService_test.py +85 -0
- naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
- naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
- naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
- naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
- naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
- naas_abi_core/services/ontology/OntologyPorts.py +36 -0
- naas_abi_core/services/ontology/OntologyService.py +17 -0
- naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
- naas_abi_core/services/secret/Secret.py +138 -0
- naas_abi_core/services/secret/SecretPorts.py +45 -0
- naas_abi_core/services/secret/Secret_test.py +65 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
- naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
- naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
- naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
- naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
- naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
- naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
- naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
- naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
- naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
- naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
- naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
- naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
- naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
- naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
- naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
- naas_abi_core/services/vector_store/__init__.py +13 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
- naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
- naas_abi_core/tests/test_services_imports.py +69 -0
- naas_abi_core/utils/Expose.py +55 -0
- naas_abi_core/utils/Graph.py +182 -0
- naas_abi_core/utils/JSON.py +49 -0
- naas_abi_core/utils/LazyLoader.py +44 -0
- naas_abi_core/utils/Logger.py +12 -0
- naas_abi_core/utils/OntologyReasoner.py +141 -0
- naas_abi_core/utils/OntologyYaml.py +681 -0
- naas_abi_core/utils/SPARQL.py +256 -0
- naas_abi_core/utils/Storage.py +33 -0
- naas_abi_core/utils/StorageUtils.py +398 -0
- naas_abi_core/utils/String.py +52 -0
- naas_abi_core/utils/Workers.py +114 -0
- naas_abi_core/utils/__init__.py +0 -0
- naas_abi_core/utils/onto2py/README.md +0 -0
- naas_abi_core/utils/onto2py/__init__.py +10 -0
- naas_abi_core/utils/onto2py/__main__.py +29 -0
- naas_abi_core/utils/onto2py/onto2py.py +611 -0
- naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
- naas_abi_core/workflow/__init__.py +5 -0
- naas_abi_core/workflow/workflow.py +48 -0
- naas_abi_core-1.4.1.dist-info/METADATA +630 -0
- naas_abi_core-1.4.1.dist-info/RECORD +124 -0
- naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
- 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
|