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,216 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from io import StringIO
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
import yaml
|
|
7
|
+
from jinja2 import Template
|
|
8
|
+
from naas_abi_core import logger
|
|
9
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_Deploy import (
|
|
10
|
+
DeployConfiguration,
|
|
11
|
+
)
|
|
12
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_ObjectStorageService import (
|
|
13
|
+
ObjectStorageServiceConfiguration,
|
|
14
|
+
)
|
|
15
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_SecretService import (
|
|
16
|
+
SecretServiceConfiguration,
|
|
17
|
+
)
|
|
18
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_TripleStoreService import (
|
|
19
|
+
TripleStoreServiceConfiguration,
|
|
20
|
+
)
|
|
21
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_VectorStoreService import (
|
|
22
|
+
VectorStoreServiceConfiguration,
|
|
23
|
+
)
|
|
24
|
+
from naas_abi_core.services.secret.Secret import Secret
|
|
25
|
+
from pydantic import BaseModel, model_validator
|
|
26
|
+
from rich.prompt import Prompt
|
|
27
|
+
from typing_extensions import Literal, Self
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ServicesConfiguration(BaseModel):
|
|
31
|
+
object_storage: ObjectStorageServiceConfiguration
|
|
32
|
+
triple_store: TripleStoreServiceConfiguration
|
|
33
|
+
vector_store: VectorStoreServiceConfiguration
|
|
34
|
+
secret: SecretServiceConfiguration
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ApiConfiguration(BaseModel):
|
|
38
|
+
title: str = "ABI API"
|
|
39
|
+
description: str = "API for ABI, your Artifical Business Intelligence"
|
|
40
|
+
logo_path: str = "assets/logo.png"
|
|
41
|
+
favicon_path: str = "assets/favicon.ico"
|
|
42
|
+
cors_origins: List[str] = ["http://localhost:9879"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FirstPassConfiguration(BaseModel):
|
|
46
|
+
"""This is a first pass configuration that is used to load the secret service.
|
|
47
|
+
|
|
48
|
+
It is used to load the secret service before the other services are loaded.
|
|
49
|
+
This is because the secret service needs to be loaded before the other services
|
|
50
|
+
are loaded to be able to resolve the secrets.
|
|
51
|
+
This is a first pass configuration that is used to load the secret service.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
class FirstPassServicesConfiguration(BaseModel):
|
|
55
|
+
secret: SecretServiceConfiguration
|
|
56
|
+
|
|
57
|
+
services: FirstPassServicesConfiguration
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ModuleConfig(BaseModel):
|
|
61
|
+
path: str | None = None
|
|
62
|
+
module: str | None = None
|
|
63
|
+
enabled: bool
|
|
64
|
+
config: dict = {}
|
|
65
|
+
|
|
66
|
+
@model_validator(mode="after")
|
|
67
|
+
def validate_path_or_module(self):
|
|
68
|
+
if self.path is None and self.module is None:
|
|
69
|
+
raise ValueError("Either path or module must be provided")
|
|
70
|
+
|
|
71
|
+
if self.path is None:
|
|
72
|
+
assert self.module is not None, (
|
|
73
|
+
"module must be provided if path is not provided"
|
|
74
|
+
)
|
|
75
|
+
if self.module is None:
|
|
76
|
+
assert self.path is not None, (
|
|
77
|
+
"path must be provided if module is not provided"
|
|
78
|
+
)
|
|
79
|
+
return self
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class GlobalConfig(BaseModel):
|
|
83
|
+
ai_mode: Literal["cloud", "local", "airgap"]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class EngineConfiguration(BaseModel):
|
|
87
|
+
api: ApiConfiguration
|
|
88
|
+
|
|
89
|
+
deploy: DeployConfiguration | None = None
|
|
90
|
+
|
|
91
|
+
services: ServicesConfiguration
|
|
92
|
+
|
|
93
|
+
global_config: GlobalConfig
|
|
94
|
+
|
|
95
|
+
modules: List[ModuleConfig]
|
|
96
|
+
|
|
97
|
+
def ensure_default_modules(self) -> None:
|
|
98
|
+
if not any(
|
|
99
|
+
m.path == "naas_abi_core.modules.templatablesparqlquery"
|
|
100
|
+
or m.module == "naas_abi_core.modules.templatablesparqlquery"
|
|
101
|
+
for m in self.modules
|
|
102
|
+
):
|
|
103
|
+
self.modules.append(
|
|
104
|
+
ModuleConfig(
|
|
105
|
+
module="naas_abi_core.modules.templatablesparqlquery",
|
|
106
|
+
enabled=True,
|
|
107
|
+
config={},
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
@model_validator(mode="after")
|
|
112
|
+
def validate_modules(self) -> Self:
|
|
113
|
+
self.ensure_default_modules()
|
|
114
|
+
return self
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def from_yaml(cls, yaml_path: str) -> "EngineConfiguration":
|
|
118
|
+
with open(yaml_path, "r") as file:
|
|
119
|
+
return cls.from_yaml_content(file.read())
|
|
120
|
+
|
|
121
|
+
@classmethod
|
|
122
|
+
def from_yaml_content(cls, yaml_content: str) -> "EngineConfiguration":
|
|
123
|
+
# First we do a pass with the minimal configuration to load the secret service.
|
|
124
|
+
class SecretServiceWrapper:
|
|
125
|
+
secret_service: Secret | None = None
|
|
126
|
+
|
|
127
|
+
def __init__(self, secret_service: Secret | None = None):
|
|
128
|
+
self.secret_service = secret_service
|
|
129
|
+
|
|
130
|
+
def __getattr__(self, name):
|
|
131
|
+
if self.secret_service is None:
|
|
132
|
+
# This rule is used only when doing the first pass configuration to load the secret service.
|
|
133
|
+
|
|
134
|
+
# First priority is to check the environment variables.
|
|
135
|
+
if name in os.environ:
|
|
136
|
+
return os.environ.get(name)
|
|
137
|
+
else:
|
|
138
|
+
# If the environment variable is not found, we check the .env file. ONLY FOR THE FIRST PASS CONFIGURATION.
|
|
139
|
+
from dotenv import dotenv_values
|
|
140
|
+
|
|
141
|
+
secrets = dotenv_values()
|
|
142
|
+
if name in secrets:
|
|
143
|
+
return secrets.get(name)
|
|
144
|
+
else:
|
|
145
|
+
return f"Secret '{name}' not found while loading the secret service. Please provide it via the environment variables or .env file."
|
|
146
|
+
elif name in os.environ:
|
|
147
|
+
return os.environ.get(name)
|
|
148
|
+
secret = self.secret_service.get(name)
|
|
149
|
+
if secret is None:
|
|
150
|
+
if not sys.stdin.isatty():
|
|
151
|
+
raise ValueError(
|
|
152
|
+
f"Secret '{name}' not found and no TTY available to prompt. Please provide it via the configured secret service or environment."
|
|
153
|
+
)
|
|
154
|
+
value = Prompt.ask(
|
|
155
|
+
f"[bold yellow]Secret '{name}' not found.[/bold yellow] Please enter the value for [cyan]{name}[/cyan]",
|
|
156
|
+
password=False,
|
|
157
|
+
)
|
|
158
|
+
self.secret_service.set(name, value)
|
|
159
|
+
return value
|
|
160
|
+
return secret
|
|
161
|
+
|
|
162
|
+
first_pass_data = yaml.safe_load(
|
|
163
|
+
StringIO(Template(yaml_content).render(secret=SecretServiceWrapper()))
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
first_pass_configuration = FirstPassConfiguration(**first_pass_data)
|
|
167
|
+
secret_service = first_pass_configuration.services.secret.load()
|
|
168
|
+
|
|
169
|
+
# Here we can now template the yaml by using `yaml_content` and the secret service.
|
|
170
|
+
# Using Jinja2 template engine.
|
|
171
|
+
|
|
172
|
+
logger.debug(f"Yaml content: {yaml_content}")
|
|
173
|
+
|
|
174
|
+
template = Template(yaml_content)
|
|
175
|
+
templated_yaml = template.render(secret=SecretServiceWrapper(secret_service))
|
|
176
|
+
|
|
177
|
+
data = yaml.safe_load(StringIO(templated_yaml))
|
|
178
|
+
|
|
179
|
+
logger.debug(f"Data: {data}")
|
|
180
|
+
|
|
181
|
+
return cls(**data)
|
|
182
|
+
|
|
183
|
+
@classmethod
|
|
184
|
+
def load_configuration(
|
|
185
|
+
cls, configuration_yaml: str | None = None
|
|
186
|
+
) -> "EngineConfiguration":
|
|
187
|
+
# This is useful for testing.
|
|
188
|
+
if configuration_yaml is not None:
|
|
189
|
+
return cls.from_yaml_content(configuration_yaml)
|
|
190
|
+
|
|
191
|
+
# Get ENV value
|
|
192
|
+
from dotenv import dotenv_values
|
|
193
|
+
|
|
194
|
+
env = os.getenv("ENV")
|
|
195
|
+
if not env:
|
|
196
|
+
env = dotenv_values().get("ENV")
|
|
197
|
+
|
|
198
|
+
# First we check the environment variable.
|
|
199
|
+
if os.path.exists(f"config.{env}.yaml"):
|
|
200
|
+
config_file = f"config.{env}.yaml"
|
|
201
|
+
# If the config.{env}.yaml file is not found, we check the config.yaml file.
|
|
202
|
+
elif os.path.exists("config.yaml"):
|
|
203
|
+
config_file = "config.yaml"
|
|
204
|
+
else:
|
|
205
|
+
raise FileNotFoundError(
|
|
206
|
+
"Configuration file not found. Please create a config.yaml file or config.{env}.yaml file."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
logger.debug(f"Loading configuration from {config_file}")
|
|
210
|
+
|
|
211
|
+
return cls.from_yaml(config_file)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == "__main__":
|
|
215
|
+
config = EngineConfiguration.load_configuration()
|
|
216
|
+
print(config)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
from typing import Any, Dict
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GenericLoader(BaseModel):
|
|
8
|
+
"""
|
|
9
|
+
Generic loader for dynamically importing and instantiating Python callables.
|
|
10
|
+
|
|
11
|
+
This class enables dynamic loading of Python classes or functions by specifying
|
|
12
|
+
the module path and callable name. The callable is invoked with the provided
|
|
13
|
+
configuration dictionary unpacked as keyword arguments.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
1. Use importlib.import_module(python_module) to import the target module
|
|
17
|
+
2. Use getattr(module, module_callable) to retrieve the callable
|
|
18
|
+
3. Invoke the callable with the custom_config as **custom_config
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
loader = GenericLoader(
|
|
22
|
+
python_module="my.package.module",
|
|
23
|
+
module_callable="MyClass",
|
|
24
|
+
custom_config={"param1": "value1", "param2": 42}
|
|
25
|
+
)
|
|
26
|
+
# This would effectively do:
|
|
27
|
+
# import importlib
|
|
28
|
+
# module = importlib.import_module("my.package.module")
|
|
29
|
+
# callable_obj = getattr(module, "MyClass")
|
|
30
|
+
# instance = callable_obj(param1="value1", param2=42)
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
python_module: The fully qualified Python module path (e.g., "package.subpackage.module")
|
|
34
|
+
module_callable: The name of the callable (class or function) to retrieve from the module
|
|
35
|
+
custom_config: Dictionary of configuration parameters to pass as keyword arguments to the callable
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
python_module: str | None = None
|
|
39
|
+
module_callable: str | None = None
|
|
40
|
+
custom_config: Dict[str, Any] | None = None
|
|
41
|
+
|
|
42
|
+
def load(self) -> Any:
|
|
43
|
+
assert self.python_module is not None, "python_module is required"
|
|
44
|
+
assert self.module_callable is not None, "module_callable is required"
|
|
45
|
+
assert self.custom_config is not None, "custom_config is required"
|
|
46
|
+
|
|
47
|
+
module = importlib.import_module(self.python_module)
|
|
48
|
+
callable_obj = getattr(module, self.module_callable)
|
|
49
|
+
return callable_obj(**self.custom_config)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
from typing import Dict, Literal, Tuple, Union
|
|
2
|
+
|
|
3
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_GenericLoader import (
|
|
4
|
+
GenericLoader,
|
|
5
|
+
)
|
|
6
|
+
from naas_abi_core.engine.engine_configuration.utils.PydanticModelValidator import (
|
|
7
|
+
pydantic_model_validator,
|
|
8
|
+
)
|
|
9
|
+
from naas_abi_core.services.object_storage.ObjectStoragePort import (
|
|
10
|
+
IObjectStorageAdapter,
|
|
11
|
+
)
|
|
12
|
+
from naas_abi_core.services.object_storage.ObjectStorageService import (
|
|
13
|
+
ObjectStorageService,
|
|
14
|
+
)
|
|
15
|
+
from pydantic import BaseModel, model_validator
|
|
16
|
+
from typing_extensions import Self
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ObjectStorageAdapterFSConfiguration(BaseModel):
|
|
20
|
+
"""Object storage adapter filesystem configuration.
|
|
21
|
+
|
|
22
|
+
object_storage_adapter:
|
|
23
|
+
adapter: "fs"
|
|
24
|
+
config:
|
|
25
|
+
base_path: "storage/datastore"
|
|
26
|
+
"""
|
|
27
|
+
base_path: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ObjectStorageAdapterS3Configuration(BaseModel):
|
|
31
|
+
"""Object storage adapter S3 configuration.
|
|
32
|
+
|
|
33
|
+
object_storage_adapter:
|
|
34
|
+
adapter: "s3"
|
|
35
|
+
config:
|
|
36
|
+
bucket_name: "my-bucket"
|
|
37
|
+
base_prefix: "my-prefix"
|
|
38
|
+
access_key_id: "{{ secret.AWS_ACCESS_KEY_ID }}"
|
|
39
|
+
secret_access_key: "{{ secret.AWS_SECRET_ACCESS_KEY }}"
|
|
40
|
+
session_token: "{{ secret.AWS_SESSION_TOKEN }}"
|
|
41
|
+
"""
|
|
42
|
+
bucket_name: str
|
|
43
|
+
base_prefix: str
|
|
44
|
+
access_key_id: str
|
|
45
|
+
secret_access_key: str
|
|
46
|
+
session_token: str | None = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class ObjectStorageAdapterNaasConfiguration(BaseModel):
|
|
50
|
+
"""Object storage adapter Naas configuration.
|
|
51
|
+
|
|
52
|
+
object_storage_adapter:
|
|
53
|
+
adapter: "naas"
|
|
54
|
+
config:
|
|
55
|
+
naas_api_key: "{{ secret.NAAS_API_KEY }}"
|
|
56
|
+
workspace_id: "{{ secret.WORKSPACE_ID }}"
|
|
57
|
+
storage_name: "{{ secret.STORAGE_NAME }}"
|
|
58
|
+
base_prefix: "my-prefix"
|
|
59
|
+
"""
|
|
60
|
+
naas_api_key: str
|
|
61
|
+
workspace_id: str
|
|
62
|
+
storage_name: str
|
|
63
|
+
base_prefix: str = ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ObjectStorageAdapterConfiguration(GenericLoader):
|
|
67
|
+
adapter: Literal["fs", "s3", "naas", "custom"]
|
|
68
|
+
config: (
|
|
69
|
+
Union[
|
|
70
|
+
ObjectStorageAdapterFSConfiguration,
|
|
71
|
+
ObjectStorageAdapterS3Configuration,
|
|
72
|
+
ObjectStorageAdapterNaasConfiguration,
|
|
73
|
+
]
|
|
74
|
+
| None
|
|
75
|
+
) = None
|
|
76
|
+
|
|
77
|
+
__MAPPING: Dict[Literal["fs", "s3", "naas"], Tuple[str, str]] = {
|
|
78
|
+
"fs": (
|
|
79
|
+
"abi.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterFS",
|
|
80
|
+
"ObjectStorageSecondaryAdapterFS",
|
|
81
|
+
),
|
|
82
|
+
"s3": (
|
|
83
|
+
"abi.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterS3",
|
|
84
|
+
"ObjectStorageSecondaryAdapterS3",
|
|
85
|
+
),
|
|
86
|
+
"naas": (
|
|
87
|
+
"abi.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterNaas",
|
|
88
|
+
"ObjectStorageSecondaryAdapterNaas",
|
|
89
|
+
),
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@model_validator(mode="after")
|
|
93
|
+
def validate_adapter(self) -> Self:
|
|
94
|
+
if self.adapter != "custom":
|
|
95
|
+
assert self.config is not None, (
|
|
96
|
+
"config is required if adapter is not custom"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
if self.adapter == "fs":
|
|
100
|
+
pydantic_model_validator(
|
|
101
|
+
ObjectStorageAdapterFSConfiguration,
|
|
102
|
+
self.config,
|
|
103
|
+
"Invalid configuration for services.object_storage.object_storage_adapter 'fs' adapter",
|
|
104
|
+
)
|
|
105
|
+
if self.adapter == "s3":
|
|
106
|
+
pydantic_model_validator(
|
|
107
|
+
ObjectStorageAdapterS3Configuration,
|
|
108
|
+
self.config,
|
|
109
|
+
"Invalid configuration for services.object_storage.object_storage_adapter 's3' adapter",
|
|
110
|
+
)
|
|
111
|
+
if self.adapter == "naas":
|
|
112
|
+
pydantic_model_validator(
|
|
113
|
+
ObjectStorageAdapterNaasConfiguration,
|
|
114
|
+
self.config,
|
|
115
|
+
"Invalid configuration for services.object_storage.object_storage_adapter 'naas' adapter",
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return self
|
|
119
|
+
|
|
120
|
+
def load(self) -> IObjectStorageAdapter:
|
|
121
|
+
if self.adapter != "custom":
|
|
122
|
+
assert self.config is not None, (
|
|
123
|
+
"config is required if adapter is not custom"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if self.adapter == "fs":
|
|
127
|
+
from naas_abi_core.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterFS import (
|
|
128
|
+
ObjectStorageSecondaryAdapterFS,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
return ObjectStorageSecondaryAdapterFS(**self.config.model_dump())
|
|
132
|
+
elif self.adapter == "s3":
|
|
133
|
+
from naas_abi_core.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterS3 import (
|
|
134
|
+
ObjectStorageSecondaryAdapterS3,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return ObjectStorageSecondaryAdapterS3(**self.config.model_dump())
|
|
138
|
+
elif self.adapter == "naas":
|
|
139
|
+
from naas_abi_core.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterNaas import (
|
|
140
|
+
ObjectStorageSecondaryAdapterNaas,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return ObjectStorageSecondaryAdapterNaas(**self.config.model_dump())
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError(f"Unknown adapter: {self.adapter}")
|
|
146
|
+
# return GenericLoader(
|
|
147
|
+
# python_module=self.__MAPPING[self.adapter][0],
|
|
148
|
+
# module_callable=self.__MAPPING[self.adapter][1],
|
|
149
|
+
# custom_config=self.config.model_dump(),
|
|
150
|
+
# ).load()
|
|
151
|
+
else:
|
|
152
|
+
return super().load()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class ObjectStorageServiceConfiguration(BaseModel):
|
|
156
|
+
object_storage_adapter: ObjectStorageAdapterConfiguration
|
|
157
|
+
|
|
158
|
+
def load(self) -> ObjectStorageService:
|
|
159
|
+
return ObjectStorageService(adapter=self.object_storage_adapter.load())
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_ObjectStorageService import (
|
|
2
|
+
ObjectStorageAdapterConfiguration,
|
|
3
|
+
ObjectStorageAdapterFSConfiguration,
|
|
4
|
+
ObjectStorageServiceConfiguration,
|
|
5
|
+
)
|
|
6
|
+
from naas_abi_core.services.object_storage.adapters.secondary.ObjectStorageSecondaryAdapterFS import (
|
|
7
|
+
ObjectStorageSecondaryAdapterFS,
|
|
8
|
+
)
|
|
9
|
+
from naas_abi_core.services.object_storage.ObjectStoragePort import (
|
|
10
|
+
IObjectStorageAdapter,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_object_storage_service_configuration():
|
|
15
|
+
configuration = ObjectStorageServiceConfiguration(
|
|
16
|
+
object_storage_adapter=ObjectStorageAdapterConfiguration(
|
|
17
|
+
adapter="fs", config=ObjectStorageAdapterFSConfiguration(base_path="test")
|
|
18
|
+
)
|
|
19
|
+
)
|
|
20
|
+
assert configuration.object_storage_adapter is not None
|
|
21
|
+
|
|
22
|
+
object_storage_adapter = configuration.object_storage_adapter.load()
|
|
23
|
+
|
|
24
|
+
assert object_storage_adapter is not None
|
|
25
|
+
assert isinstance(object_storage_adapter, IObjectStorageAdapter)
|
|
26
|
+
assert isinstance(object_storage_adapter, ObjectStorageSecondaryAdapterFS)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, List, Literal, Union
|
|
2
|
+
|
|
3
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_GenericLoader import (
|
|
4
|
+
GenericLoader,
|
|
5
|
+
)
|
|
6
|
+
from naas_abi_core.engine.engine_configuration.utils.PydanticModelValidator import (
|
|
7
|
+
pydantic_model_validator,
|
|
8
|
+
)
|
|
9
|
+
from naas_abi_core.services.secret.Secret import Secret
|
|
10
|
+
from naas_abi_core.services.secret.SecretPorts import ISecretAdapter
|
|
11
|
+
from pydantic import BaseModel, model_validator
|
|
12
|
+
from typing_extensions import Self
|
|
13
|
+
|
|
14
|
+
# Only import for type checking, not at runtime
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from naas_abi_core.services.secret.adaptors.secondary.Base64Secret import (
|
|
17
|
+
Base64Secret,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DotenvSecretConfiguration(BaseModel):
|
|
22
|
+
"""Dotenv secret configuration.
|
|
23
|
+
|
|
24
|
+
secret_adapters:
|
|
25
|
+
- adapter: "dotenv"
|
|
26
|
+
config: {}
|
|
27
|
+
"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class NaasSecretConfiguration(BaseModel):
|
|
32
|
+
"""Naas secret configuration.
|
|
33
|
+
|
|
34
|
+
secret_adapters:
|
|
35
|
+
- adapter: "naas"
|
|
36
|
+
config:
|
|
37
|
+
naas_api_key: "{{ secret.NAAS_API_KEY }}"
|
|
38
|
+
naas_api_url: "https://api.naas.ai"
|
|
39
|
+
"""
|
|
40
|
+
naas_api_key: str
|
|
41
|
+
naas_api_url: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Base64SecretConfiguration(BaseModel):
|
|
45
|
+
"""Base64 secret configuration.
|
|
46
|
+
|
|
47
|
+
secret_adapters:
|
|
48
|
+
- adapter: "base64"
|
|
49
|
+
config:
|
|
50
|
+
secret_adapter: *secret_adapter
|
|
51
|
+
base64_secret_key: "{{ secret.BASE64_SECRET_KEY }}"
|
|
52
|
+
"""
|
|
53
|
+
secret_adapter: "SecretAdapterConfiguration"
|
|
54
|
+
base64_secret_key: str
|
|
55
|
+
|
|
56
|
+
def load(self) -> "Base64Secret":
|
|
57
|
+
from naas_abi_core.services.secret.adaptors.secondary.Base64Secret import (
|
|
58
|
+
Base64Secret,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return Base64Secret(self.secret_adapter.load(), self.base64_secret_key)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class SecretAdapterConfiguration(GenericLoader):
|
|
65
|
+
adapter: Literal["dotenv", "naas", "base64", "custom"]
|
|
66
|
+
config: (
|
|
67
|
+
Union[
|
|
68
|
+
DotenvSecretConfiguration,
|
|
69
|
+
NaasSecretConfiguration,
|
|
70
|
+
Base64SecretConfiguration,
|
|
71
|
+
]
|
|
72
|
+
| None
|
|
73
|
+
) = None
|
|
74
|
+
|
|
75
|
+
@model_validator(mode="after")
|
|
76
|
+
def validate_adapter(self) -> Self:
|
|
77
|
+
if self.adapter != "custom":
|
|
78
|
+
assert self.config is not None, (
|
|
79
|
+
"config is required if adapter is not custom"
|
|
80
|
+
)
|
|
81
|
+
if self.adapter == "base64":
|
|
82
|
+
pydantic_model_validator(
|
|
83
|
+
Base64SecretConfiguration,
|
|
84
|
+
self.config,
|
|
85
|
+
"Invalid configuration for services.secret.secret_adapters 'base64' adapter",
|
|
86
|
+
)
|
|
87
|
+
if self.adapter == "dotenv":
|
|
88
|
+
pydantic_model_validator(
|
|
89
|
+
DotenvSecretConfiguration,
|
|
90
|
+
self.config,
|
|
91
|
+
"Invalid configuration for services.secret.secret_adapters 'dotenv' adapter",
|
|
92
|
+
)
|
|
93
|
+
if self.adapter == "naas":
|
|
94
|
+
pydantic_model_validator(
|
|
95
|
+
NaasSecretConfiguration,
|
|
96
|
+
self.config,
|
|
97
|
+
"Invalid configuration for services.secret.secret_adapters 'naas' adapter",
|
|
98
|
+
)
|
|
99
|
+
return self
|
|
100
|
+
|
|
101
|
+
def load(self) -> ISecretAdapter:
|
|
102
|
+
if self.adapter != "custom":
|
|
103
|
+
assert self.config is not None, (
|
|
104
|
+
"config is required if adapter is not custom"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Lazy import: only import the adapter that's actually configured
|
|
108
|
+
if self.adapter == "base64":
|
|
109
|
+
assert isinstance(self.config, Base64SecretConfiguration), (
|
|
110
|
+
"config must be a Base64SecretConfiguration if adapter is base64"
|
|
111
|
+
)
|
|
112
|
+
assert hasattr(self.config, "load"), (
|
|
113
|
+
"config must have a load method if adapter is base64"
|
|
114
|
+
)
|
|
115
|
+
return self.config.load()
|
|
116
|
+
elif self.adapter == "dotenv":
|
|
117
|
+
from naas_abi_core.services.secret.adaptors.secondary.dotenv_secret_secondaryadaptor import (
|
|
118
|
+
DotenvSecretSecondaryAdaptor,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return DotenvSecretSecondaryAdaptor(**self.config.model_dump())
|
|
122
|
+
elif self.adapter == "naas":
|
|
123
|
+
from naas_abi_core.services.secret.adaptors.secondary.NaasSecret import (
|
|
124
|
+
NaasSecret,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return NaasSecret(**self.config.model_dump())
|
|
128
|
+
else:
|
|
129
|
+
raise ValueError(f"Unknown adapter: {self.adapter}")
|
|
130
|
+
else:
|
|
131
|
+
return super().load()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class SecretServiceConfiguration(BaseModel):
|
|
135
|
+
secret_adapters: List[SecretAdapterConfiguration]
|
|
136
|
+
|
|
137
|
+
def load(self) -> Secret:
|
|
138
|
+
return Secret(adapters=[adapter.load() for adapter in self.secret_adapters])
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from naas_abi_core.engine.engine_configuration.EngineConfiguration_SecretService import (
|
|
2
|
+
DotenvSecretConfiguration,
|
|
3
|
+
NaasSecretConfiguration,
|
|
4
|
+
SecretAdapterConfiguration,
|
|
5
|
+
SecretServiceConfiguration,
|
|
6
|
+
)
|
|
7
|
+
from naas_abi_core.services.secret.Secret import Secret
|
|
8
|
+
from naas_abi_core.services.secret.SecretPorts import ISecretAdapter
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_secret_service_configuration():
|
|
12
|
+
from naas_abi_core.services.secret.adaptors.secondary.dotenv_secret_secondaryadaptor import (
|
|
13
|
+
DotenvSecretSecondaryAdaptor,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
configuration = SecretServiceConfiguration(
|
|
17
|
+
secret_adapters=[
|
|
18
|
+
SecretAdapterConfiguration(
|
|
19
|
+
adapter="dotenv", config=DotenvSecretConfiguration()
|
|
20
|
+
)
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
assert configuration.secret_adapters is not None
|
|
24
|
+
assert len(configuration.secret_adapters) == 1
|
|
25
|
+
|
|
26
|
+
secret_adapter = configuration.secret_adapters[0].load()
|
|
27
|
+
|
|
28
|
+
assert secret_adapter is not None
|
|
29
|
+
assert isinstance(secret_adapter, ISecretAdapter)
|
|
30
|
+
assert isinstance(secret_adapter, DotenvSecretSecondaryAdaptor)
|
|
31
|
+
|
|
32
|
+
secret_service = configuration.load()
|
|
33
|
+
|
|
34
|
+
assert secret_service is not None
|
|
35
|
+
assert isinstance(secret_service, Secret)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_secret_service_configuration_naas():
|
|
39
|
+
import os
|
|
40
|
+
|
|
41
|
+
from dotenv import load_dotenv
|
|
42
|
+
from naas_abi_core.services.secret.adaptors.secondary.NaasSecret import (
|
|
43
|
+
NaasSecret,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
load_dotenv()
|
|
47
|
+
|
|
48
|
+
naas_api_key = os.getenv("NAAS_API_KEY")
|
|
49
|
+
if naas_api_key is None:
|
|
50
|
+
raise ValueError("NAAS_API_KEY is not set")
|
|
51
|
+
|
|
52
|
+
naas_api_url = os.getenv("NAAS_API_URL")
|
|
53
|
+
if naas_api_url is None:
|
|
54
|
+
raise ValueError("NAAS_API_URL is not set")
|
|
55
|
+
|
|
56
|
+
configuration = SecretServiceConfiguration(
|
|
57
|
+
secret_adapters=[
|
|
58
|
+
SecretAdapterConfiguration(
|
|
59
|
+
adapter="naas",
|
|
60
|
+
config=NaasSecretConfiguration(
|
|
61
|
+
naas_api_key=naas_api_key,
|
|
62
|
+
naas_api_url=naas_api_url,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
]
|
|
66
|
+
)
|
|
67
|
+
assert configuration.secret_adapters is not None
|
|
68
|
+
assert len(configuration.secret_adapters) == 1
|
|
69
|
+
|
|
70
|
+
secret_adapter = configuration.secret_adapters[0].load()
|
|
71
|
+
|
|
72
|
+
assert secret_adapter is not None
|
|
73
|
+
assert isinstance(secret_adapter, ISecretAdapter)
|
|
74
|
+
assert isinstance(secret_adapter, NaasSecret)
|