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,131 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from queue import Queue
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import pydash
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
# Load S3 secondary adapter as we will use it.
|
|
11
|
+
from naas_abi_core.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterS3 import (
|
|
12
|
+
ObjectStorageSecondaryAdapterS3,
|
|
13
|
+
)
|
|
14
|
+
from naas_abi_core.services.object_storage.ObjectStoragePort import (
|
|
15
|
+
IObjectStorageAdapter,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
NAAS_API_URL = "https://api.naas.ai/"
|
|
19
|
+
CREDENTIALS_EXPIRATION_TIME = timedelta(minutes=20)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Credentials:
|
|
24
|
+
bucket_name: str
|
|
25
|
+
bucket_prefix: str
|
|
26
|
+
access_key_id: str
|
|
27
|
+
secret_key: str
|
|
28
|
+
session_token: str
|
|
29
|
+
region_name: str
|
|
30
|
+
created_at: datetime
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ObjectStorageSecondaryAdapterNaas(IObjectStorageAdapter):
|
|
34
|
+
__naas_api_key: str
|
|
35
|
+
__workspace_id: str
|
|
36
|
+
__storage_name: str
|
|
37
|
+
__credentials: Credentials | None
|
|
38
|
+
__s3_adapter: ObjectStorageSecondaryAdapterS3 | None
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
naas_api_key: str,
|
|
43
|
+
workspace_id: str,
|
|
44
|
+
storage_name: str,
|
|
45
|
+
base_prefix: str = "",
|
|
46
|
+
):
|
|
47
|
+
self.__naas_api_key = naas_api_key
|
|
48
|
+
self.__workspace_id = workspace_id
|
|
49
|
+
self.__storage_name = storage_name
|
|
50
|
+
self.__base_prefix = base_prefix
|
|
51
|
+
self.__credentials: Credentials | None = None
|
|
52
|
+
self.__s3_adapter: ObjectStorageSecondaryAdapterS3 | None = None
|
|
53
|
+
|
|
54
|
+
def ensure_credentials(self) -> Credentials:
|
|
55
|
+
if self.__credentials is None:
|
|
56
|
+
# TODO: Handle raise_for_status.
|
|
57
|
+
self.__refresh_credentials()
|
|
58
|
+
|
|
59
|
+
assert self.__credentials is not None
|
|
60
|
+
|
|
61
|
+
# If credentials are older than 10 minutes, refresh them
|
|
62
|
+
if self.__credentials.created_at < datetime.now() - CREDENTIALS_EXPIRATION_TIME:
|
|
63
|
+
self.__refresh_credentials()
|
|
64
|
+
|
|
65
|
+
return self.__credentials
|
|
66
|
+
|
|
67
|
+
def __refresh_credentials(self) -> None:
|
|
68
|
+
response = requests.post(
|
|
69
|
+
f"{NAAS_API_URL}/workspace/{self.__workspace_id}/storage/credentials/",
|
|
70
|
+
headers={"Authorization": f"Bearer {self.__naas_api_key}"},
|
|
71
|
+
json={
|
|
72
|
+
"name": self.__storage_name,
|
|
73
|
+
},
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
response.raise_for_status()
|
|
77
|
+
|
|
78
|
+
credentials = response.json()
|
|
79
|
+
|
|
80
|
+
self.__credentials = Credentials(
|
|
81
|
+
bucket_name=pydash.get(credentials, "credentials.s3.endpoint_url").split(
|
|
82
|
+
"/"
|
|
83
|
+
)[2],
|
|
84
|
+
bucket_prefix="/".join(
|
|
85
|
+
pydash.get(credentials, "credentials.s3.endpoint_url").split("/")[3:]
|
|
86
|
+
),
|
|
87
|
+
access_key_id=pydash.get(credentials, "credentials.s3.access_key_id"),
|
|
88
|
+
secret_key=pydash.get(credentials, "credentials.s3.secret_key"),
|
|
89
|
+
session_token=pydash.get(credentials, "credentials.s3.session_token"),
|
|
90
|
+
region_name=pydash.get(credentials, "credentials.s3.region_name"),
|
|
91
|
+
created_at=datetime.now(),
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Re instantiate the S3 adapter with the new credentials
|
|
95
|
+
self.__s3_adapter = ObjectStorageSecondaryAdapterS3(
|
|
96
|
+
bucket_name=self.__credentials.bucket_name,
|
|
97
|
+
access_key_id=self.__credentials.access_key_id,
|
|
98
|
+
secret_access_key=self.__credentials.secret_key,
|
|
99
|
+
base_prefix=os.path.join(
|
|
100
|
+
self.__credentials.bucket_prefix, self.__base_prefix
|
|
101
|
+
),
|
|
102
|
+
session_token=self.__credentials.session_token,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def get_object(self, prefix: str, key: str) -> bytes:
|
|
106
|
+
self.ensure_credentials()
|
|
107
|
+
|
|
108
|
+
assert self.__s3_adapter is not None
|
|
109
|
+
|
|
110
|
+
return self.__s3_adapter.get_object(prefix, key)
|
|
111
|
+
|
|
112
|
+
def put_object(self, prefix: str, key: str, content: bytes):
|
|
113
|
+
self.ensure_credentials()
|
|
114
|
+
|
|
115
|
+
assert self.__s3_adapter is not None
|
|
116
|
+
|
|
117
|
+
return self.__s3_adapter.put_object(prefix, key, content)
|
|
118
|
+
|
|
119
|
+
def delete_object(self, prefix: str, key: str):
|
|
120
|
+
self.ensure_credentials()
|
|
121
|
+
|
|
122
|
+
assert self.__s3_adapter is not None
|
|
123
|
+
|
|
124
|
+
return self.__s3_adapter.delete_object(prefix, key)
|
|
125
|
+
|
|
126
|
+
def list_objects(self, prefix: str, queue: Optional[Queue] = None) -> list[str]:
|
|
127
|
+
self.ensure_credentials()
|
|
128
|
+
|
|
129
|
+
assert self.__s3_adapter is not None
|
|
130
|
+
|
|
131
|
+
return self.__s3_adapter.list_objects(prefix, queue)
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from queue import Queue
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import boto3
|
|
5
|
+
from botocore.exceptions import ClientError
|
|
6
|
+
from naas_abi_core.services.object_storage.ObjectStoragePort import (
|
|
7
|
+
Exceptions,
|
|
8
|
+
IObjectStorageAdapter,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ObjectStorageSecondaryAdapterS3(IObjectStorageAdapter):
|
|
13
|
+
"""S3 implementation of the Object Storage adapter using boto3."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
bucket_name: str,
|
|
18
|
+
access_key_id: str,
|
|
19
|
+
secret_access_key: str,
|
|
20
|
+
base_prefix: str = "",
|
|
21
|
+
session_token: str | None = None,
|
|
22
|
+
):
|
|
23
|
+
"""Initialize S3 adapter with bucket name and credentials.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
bucket_name (str): Name of the S3 bucket to use
|
|
27
|
+
access_key_id (str): AWS access key ID
|
|
28
|
+
secret_access_key (str): AWS secret access key
|
|
29
|
+
base_prefix (str, optional): Base prefix to prepend to all operations. Defaults to ""
|
|
30
|
+
session_token (str, optional): AWS session token. Defaults to None
|
|
31
|
+
"""
|
|
32
|
+
self.bucket_name = bucket_name
|
|
33
|
+
self.base_prefix = base_prefix.rstrip("/") # Remove trailing slash if present
|
|
34
|
+
self.s3_client = boto3.client(
|
|
35
|
+
"s3",
|
|
36
|
+
aws_access_key_id=access_key_id,
|
|
37
|
+
aws_secret_access_key=secret_access_key,
|
|
38
|
+
aws_session_token=session_token,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def __get_full_key(self, prefix: str, key: str | None = None) -> str:
|
|
42
|
+
"""Construct full key path including base prefix.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
prefix (str): Prefix/folder path
|
|
46
|
+
key (str, optional): Object key name
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
str: Full key path
|
|
50
|
+
"""
|
|
51
|
+
# if key is None:
|
|
52
|
+
# return f"{self.base_prefix}/{prefix}".lstrip('/')
|
|
53
|
+
return f"{self.base_prefix + '/' if self.base_prefix else ''}{prefix + '/' if prefix else ''}{key if key else ''}".lstrip(
|
|
54
|
+
"/"
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def __object_exists(self, prefix: str, key: str | None = None) -> bool:
|
|
58
|
+
"""Check if an object exists in S3.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
prefix (str): Prefix/folder path
|
|
62
|
+
key (str, optional): Object key name
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
bool: True if exists, raises exception if not
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
if key is None:
|
|
69
|
+
# Check if prefix exists by listing objects
|
|
70
|
+
response = self.s3_client.list_objects_v2(
|
|
71
|
+
Bucket=self.bucket_name,
|
|
72
|
+
Prefix=self.__get_full_key(prefix),
|
|
73
|
+
MaxKeys=1,
|
|
74
|
+
)
|
|
75
|
+
if "Contents" not in response:
|
|
76
|
+
raise Exceptions.ObjectNotFound(f"Prefix {prefix} not found")
|
|
77
|
+
else:
|
|
78
|
+
# Check specific object
|
|
79
|
+
self.s3_client.head_object(
|
|
80
|
+
Bucket=self.bucket_name, Key=self.__get_full_key(prefix, key)
|
|
81
|
+
)
|
|
82
|
+
return True
|
|
83
|
+
except ClientError as e:
|
|
84
|
+
if e.response["Error"]["Code"] in ["404", "NoSuchKey"]:
|
|
85
|
+
raise Exceptions.ObjectNotFound(f"Object {prefix}/{key} not found")
|
|
86
|
+
raise e
|
|
87
|
+
|
|
88
|
+
def get_object(self, prefix: str, key: str) -> bytes:
|
|
89
|
+
"""Get object from S3 bucket.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
prefix (str): Prefix/folder path
|
|
93
|
+
key (str): Object key name
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
bytes: Object content
|
|
97
|
+
"""
|
|
98
|
+
self.__object_exists(prefix, key)
|
|
99
|
+
|
|
100
|
+
response = self.s3_client.get_object(
|
|
101
|
+
Bucket=self.bucket_name, Key=self.__get_full_key(prefix, key)
|
|
102
|
+
)
|
|
103
|
+
return response["Body"].read()
|
|
104
|
+
|
|
105
|
+
def put_object(self, prefix: str, key: str, content: bytes) -> None:
|
|
106
|
+
"""Put object into S3 bucket.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
prefix (str): Prefix/folder path
|
|
110
|
+
key (str): Object key name
|
|
111
|
+
content (bytes): Content to upload
|
|
112
|
+
"""
|
|
113
|
+
self.s3_client.put_object(
|
|
114
|
+
Bucket=self.bucket_name, Key=self.__get_full_key(prefix, key), Body=content
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def delete_object(self, prefix: str, key: str) -> None:
|
|
118
|
+
"""Delete object from S3 bucket.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
prefix (str): Prefix/folder path
|
|
122
|
+
key (str): Object key name
|
|
123
|
+
"""
|
|
124
|
+
self.__object_exists(prefix, key)
|
|
125
|
+
|
|
126
|
+
self.s3_client.delete_object(
|
|
127
|
+
Bucket=self.bucket_name, Key=self.__get_full_key(prefix, key)
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def list_objects(self, prefix: str, queue: Optional[Queue] = None) -> list[str]:
|
|
131
|
+
"""List objects in S3 bucket with given prefix.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
prefix (str): Prefix/folder path to list
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
list[str]: List of object keys at depth 1 only (direct children)
|
|
138
|
+
"""
|
|
139
|
+
self.__object_exists(prefix)
|
|
140
|
+
|
|
141
|
+
objects = []
|
|
142
|
+
paginator = self.s3_client.get_paginator("list_objects_v2")
|
|
143
|
+
|
|
144
|
+
for page in paginator.paginate(
|
|
145
|
+
Bucket=self.bucket_name,
|
|
146
|
+
Prefix=self.__get_full_key(prefix),
|
|
147
|
+
Delimiter="/", # This makes it list only one level deep
|
|
148
|
+
):
|
|
149
|
+
# Get regular objects at this level
|
|
150
|
+
if "Contents" in page:
|
|
151
|
+
for obj in page["Contents"]:
|
|
152
|
+
# Remove base prefix from returned keys
|
|
153
|
+
key = obj["Key"]
|
|
154
|
+
if self.base_prefix:
|
|
155
|
+
key = key.replace(f"{self.base_prefix}/", "", 1)
|
|
156
|
+
if key != "":
|
|
157
|
+
objects.append(key)
|
|
158
|
+
if queue:
|
|
159
|
+
queue.put(key)
|
|
160
|
+
|
|
161
|
+
# Get subfolders at this level
|
|
162
|
+
if "CommonPrefixes" in page:
|
|
163
|
+
for prefix_obj in page["CommonPrefixes"]:
|
|
164
|
+
prefix_key = prefix_obj["Prefix"]
|
|
165
|
+
if self.base_prefix:
|
|
166
|
+
prefix_key = prefix_key.replace(f"{self.base_prefix}/", "", 1)
|
|
167
|
+
if prefix_key != "":
|
|
168
|
+
objects.append(prefix_key)
|
|
169
|
+
if queue:
|
|
170
|
+
queue.put(prefix_key)
|
|
171
|
+
return objects
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from rdflib import Graph
|
|
3
|
+
from langchain_core.language_models import BaseChatModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IOntologyNERPort(ABC):
|
|
7
|
+
__chat_model: BaseChatModel
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def named_entity_recognition(self, input: str, ontology_str: str) -> Graph:
|
|
11
|
+
"""Apply Named Entity Recognition (NER) on input text to map entities to ontology concepts.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
input (str): The unstructured text to process with NER
|
|
15
|
+
ontology_str (str): The ontology in Turtle format to map entities against
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
Graph: An RDFLib Graph containing the mapped entities and their relationships from the input text
|
|
19
|
+
"""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class IOntologyService(ABC):
|
|
24
|
+
__ner_adaptor: IOntologyNERPort
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def named_entity_recognition(self, input: str) -> Graph:
|
|
28
|
+
"""Apply Named Entity Recognition (NER) on input text to map entities to ontology concepts.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
input (str): The input text to process with NER
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Graph: An RDFLib Graph containing the mapped entities and their relationships
|
|
35
|
+
"""
|
|
36
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from naas_abi_core.services.ontology.OntologyPorts import (
|
|
2
|
+
IOntologyNERPort,
|
|
3
|
+
IOntologyService,
|
|
4
|
+
)
|
|
5
|
+
from rdflib import Graph
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class OntologyService(IOntologyService):
|
|
9
|
+
__ontology_str: str
|
|
10
|
+
__ner_adaptor: IOntologyNERPort
|
|
11
|
+
|
|
12
|
+
def __init__(self, ner_adaptor: IOntologyNERPort, ontology_str: str):
|
|
13
|
+
self.__ner_adaptor = ner_adaptor
|
|
14
|
+
self.__ontology_str = ontology_str
|
|
15
|
+
|
|
16
|
+
def named_entity_recognition(self, input: str) -> Graph:
|
|
17
|
+
return self.__ner_adaptor.named_entity_recognition(input, self.__ontology_str)
|
naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from langchain_core.language_models import BaseChatModel
|
|
2
|
+
from langchain_core.messages import HumanMessage, SystemMessage
|
|
3
|
+
from naas_abi_core.services.ontology.OntologyPorts import IOntologyNERPort
|
|
4
|
+
from rdflib import Graph
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class OntologyService_SecondaryAdaptor_NERPort(IOntologyNERPort):
|
|
8
|
+
__chat_model: BaseChatModel
|
|
9
|
+
|
|
10
|
+
def __init__(self, chat_model: BaseChatModel):
|
|
11
|
+
self.__chat_model = chat_model
|
|
12
|
+
|
|
13
|
+
def named_entity_recognition(self, input: str, ontology_str: str) -> Graph:
|
|
14
|
+
messages = [
|
|
15
|
+
SystemMessage(
|
|
16
|
+
content=f"""
|
|
17
|
+
You are an expert in Named Entity Recognition (NER).
|
|
18
|
+
You are given a string of unstructured text and an ontology in Turtle format.
|
|
19
|
+
Your task is to map the entities in the text to the ontology concepts.
|
|
20
|
+
|
|
21
|
+
Here is the ontology in Turtle format:
|
|
22
|
+
{ontology_str}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Only output the Turtle formated file ready to be stored in a file. You must add all prefixes.
|
|
26
|
+
"""
|
|
27
|
+
),
|
|
28
|
+
HumanMessage(content=input),
|
|
29
|
+
]
|
|
30
|
+
response = self.__chat_model.invoke(messages)
|
|
31
|
+
assert isinstance(response.content, str)
|
|
32
|
+
sanitized_response = response.content.replace("```turtle", "").replace(
|
|
33
|
+
"```", ""
|
|
34
|
+
)
|
|
35
|
+
g = Graph()
|
|
36
|
+
g.parse(data=sanitized_response, format="turtle")
|
|
37
|
+
return g
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Secret Service Module
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface for managing secrets across different storage systems.
|
|
4
|
+
The Secret service acts as a facade that coordinates multiple secret adapters, allowing applications
|
|
5
|
+
to retrieve, store, and manage secrets from various sources like environment variables, files,
|
|
6
|
+
cloud services, or other secret management systems.
|
|
7
|
+
|
|
8
|
+
The module implements the adapter pattern to support pluggable secret storage backends while
|
|
9
|
+
providing a consistent API for secret operations.
|
|
10
|
+
|
|
11
|
+
Classes:
|
|
12
|
+
Secret: Main service class implementing ISecretService interface
|
|
13
|
+
|
|
14
|
+
Example:
|
|
15
|
+
>>> from naas_abi_core.services.secret.adapters import EnvVarSecretAdapter, FileSecretAdapter
|
|
16
|
+
>>> adapters = [EnvVarSecretAdapter(), FileSecretAdapter("/path/to/secrets")]
|
|
17
|
+
>>> secret_service = Secret(adapters)
|
|
18
|
+
>>> api_key = secret_service.get("API_KEY", "default_key")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from typing import Any, Dict, List
|
|
22
|
+
|
|
23
|
+
from naas_abi_core.services.secret.SecretPorts import ISecretAdapter, ISecretService
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Secret(ISecretService):
|
|
27
|
+
"""Secret service for managing and retrieving secrets.
|
|
28
|
+
|
|
29
|
+
This service provides a unified interface for accessing secrets regardless of their storage location
|
|
30
|
+
(environment variables, files, cloud services, etc.) through the use of adapters.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
__adapter (ISecretAdapter): The adapter implementation used for retrieving secrets
|
|
34
|
+
from the underlying storage system.
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> secret_service = Secret(EnvVarSecretAdapter())
|
|
38
|
+
>>> api_key = secret_service.get("API_KEY")
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
__adapters: List[ISecretAdapter]
|
|
42
|
+
|
|
43
|
+
def __init__(self, adapters: List[ISecretAdapter]):
|
|
44
|
+
self.__adapters = adapters
|
|
45
|
+
|
|
46
|
+
def get(self, key: str, default: Any = None) -> str:
|
|
47
|
+
"""Retrieve a secret value by its key.
|
|
48
|
+
|
|
49
|
+
Searches for the secret across all configured adapters in order. Returns the value
|
|
50
|
+
from the first adapter that contains the key, or the default value if not found.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
key (str): The secret key to retrieve.
|
|
54
|
+
default (Any, optional): The value to return if the key is not found.
|
|
55
|
+
Defaults to None.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
str: The secret value if found, otherwise the default value.
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> secret_service = Secret([EnvVarSecretAdapter()])
|
|
62
|
+
>>> database_url = secret_service.get("DATABASE_URL", "sqlite:///:memory:")
|
|
63
|
+
"""
|
|
64
|
+
for adapter in self.__adapters:
|
|
65
|
+
value = adapter.get(key, None)
|
|
66
|
+
|
|
67
|
+
if value is not None:
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
return default
|
|
71
|
+
|
|
72
|
+
def set(self, key: str, value: str):
|
|
73
|
+
"""Set a secret value across all configured adapters.
|
|
74
|
+
|
|
75
|
+
Stores the secret key-value pair in all adapters. This ensures consistency
|
|
76
|
+
across different secret storage systems.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
key (str): The secret key to set.
|
|
80
|
+
value (str): The secret value to store.
|
|
81
|
+
|
|
82
|
+
Note:
|
|
83
|
+
If any adapter fails to set the value, the operation continues with
|
|
84
|
+
remaining adapters. Consider implementing error handling based on
|
|
85
|
+
your specific requirements.
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> secret_service = Secret([EnvVarSecretAdapter(), FileSecretAdapter()])
|
|
89
|
+
>>> secret_service.set("API_KEY", "your-secret-api-key")
|
|
90
|
+
"""
|
|
91
|
+
for adapter in self.__adapters:
|
|
92
|
+
adapter.set(key, value)
|
|
93
|
+
|
|
94
|
+
def remove(self, key: str):
|
|
95
|
+
"""Remove a secret key from all configured adapters.
|
|
96
|
+
|
|
97
|
+
Deletes the specified key from all adapters. This ensures the secret
|
|
98
|
+
is completely removed from all storage systems.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
key (str): The secret key to remove.
|
|
102
|
+
|
|
103
|
+
Note:
|
|
104
|
+
If any adapter fails to remove the key, the operation continues with
|
|
105
|
+
remaining adapters. The method does not raise exceptions for missing keys.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> secret_service = Secret([EnvVarSecretAdapter(), FileSecretAdapter()])
|
|
109
|
+
>>> secret_service.remove("OLD_API_KEY")
|
|
110
|
+
"""
|
|
111
|
+
for adapter in self.__adapters:
|
|
112
|
+
adapter.remove(key)
|
|
113
|
+
|
|
114
|
+
def list(self) -> Dict[str, str | None]:
|
|
115
|
+
"""Retrieve all secrets from all configured adapters.
|
|
116
|
+
|
|
117
|
+
Combines secrets from all adapters into a single dictionary. When the same
|
|
118
|
+
key exists in multiple adapters, the value from the adapter with higher
|
|
119
|
+
priority (earlier in the list) takes precedence.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Dict[str, str]: A dictionary containing all secret key-value pairs.
|
|
123
|
+
|
|
124
|
+
Note:
|
|
125
|
+
The order of adapters matters for conflict resolution. Adapters are
|
|
126
|
+
processed in reverse order, so earlier adapters override later ones.
|
|
127
|
+
|
|
128
|
+
Example:
|
|
129
|
+
>>> secret_service = Secret([EnvVarSecretAdapter(), FileSecretAdapter()])
|
|
130
|
+
>>> all_secrets = secret_service.list()
|
|
131
|
+
>>> print(f"Found {len(all_secrets)} secrets")
|
|
132
|
+
"""
|
|
133
|
+
secrets = {}
|
|
134
|
+
|
|
135
|
+
for adapter in [adapter for adapter in self.__adapters][::-1]:
|
|
136
|
+
secrets.update(adapter.list())
|
|
137
|
+
|
|
138
|
+
return secrets
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Any, Dict, List
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SecretAuthenticationError(Exception):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ISecretAdapter(ABC):
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def get(self, key: str, default: Any = None) -> str | Any | None:
|
|
12
|
+
raise NotImplementedError()
|
|
13
|
+
|
|
14
|
+
@abstractmethod
|
|
15
|
+
def set(self, key: str, value: str):
|
|
16
|
+
raise NotImplementedError()
|
|
17
|
+
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def remove(self, key: str):
|
|
20
|
+
raise NotImplementedError()
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def list(self) -> Dict[str, str | None]:
|
|
24
|
+
raise NotImplementedError()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ISecretService(ABC):
|
|
28
|
+
__adapter: List[ISecretAdapter]
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def get(self, key: str, default: Any = None) -> str | Any | None:
|
|
32
|
+
raise NotImplementedError()
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def set(self, key: str, value: str):
|
|
36
|
+
raise NotImplementedError()
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def remove(self, key: str):
|
|
40
|
+
raise NotImplementedError()
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def list(self) -> Dict[str, str | None]:
|
|
44
|
+
raise NotImplementedError()
|
|
45
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Any, Dict
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from naas_abi_core.services.secret.Secret import Secret
|
|
5
|
+
from naas_abi_core.services.secret.SecretPorts import ISecretAdapter
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@pytest.fixture
|
|
9
|
+
def TestSecretAdapter():
|
|
10
|
+
class TestSecretAdapter(ISecretAdapter):
|
|
11
|
+
def __init__(self, secrets: Dict[str, str | None]):
|
|
12
|
+
self.secrets = secrets or {}
|
|
13
|
+
|
|
14
|
+
def get(self, key: str, default: Any = None) -> str | Any | None:
|
|
15
|
+
return self.secrets.get(key, default)
|
|
16
|
+
|
|
17
|
+
def set(self, key: str, value: str):
|
|
18
|
+
self.secrets[key] = value
|
|
19
|
+
|
|
20
|
+
def remove(self, key: str):
|
|
21
|
+
self.secrets.pop(key, None)
|
|
22
|
+
|
|
23
|
+
def list(self) -> Dict[str, str | None]:
|
|
24
|
+
return self.secrets
|
|
25
|
+
|
|
26
|
+
return TestSecretAdapter
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_secret(TestSecretAdapter):
|
|
30
|
+
secret = Secret(
|
|
31
|
+
[
|
|
32
|
+
TestSecretAdapter(
|
|
33
|
+
{
|
|
34
|
+
"hello": "world",
|
|
35
|
+
}
|
|
36
|
+
),
|
|
37
|
+
TestSecretAdapter(
|
|
38
|
+
{
|
|
39
|
+
"hello": "abi",
|
|
40
|
+
"second": "second",
|
|
41
|
+
}
|
|
42
|
+
),
|
|
43
|
+
]
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
assert secret.get("hello") == "world"
|
|
47
|
+
assert secret.list() == {
|
|
48
|
+
"hello": "world",
|
|
49
|
+
"second": "second",
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
secret.remove("hello")
|
|
53
|
+
assert secret.get("hello") is None
|
|
54
|
+
assert secret.list() == {
|
|
55
|
+
"second": "second",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
secret.remove("second")
|
|
59
|
+
assert secret.list() == {}
|
|
60
|
+
|
|
61
|
+
secret.set("hello", "world")
|
|
62
|
+
assert secret.get("hello") == "world"
|
|
63
|
+
assert secret.list() == {
|
|
64
|
+
"hello": "world",
|
|
65
|
+
}
|