naas-abi-core 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. assets/favicon.ico +0 -0
  2. assets/logo.png +0 -0
  3. naas_abi_core/__init__.py +1 -0
  4. naas_abi_core/apps/api/api.py +245 -0
  5. naas_abi_core/apps/api/api_test.py +281 -0
  6. naas_abi_core/apps/api/openapi_doc.py +144 -0
  7. naas_abi_core/apps/mcp/Dockerfile.mcp +35 -0
  8. naas_abi_core/apps/mcp/mcp_server.py +243 -0
  9. naas_abi_core/apps/mcp/mcp_server_test.py +163 -0
  10. naas_abi_core/apps/terminal_agent/main.py +555 -0
  11. naas_abi_core/apps/terminal_agent/terminal_style.py +175 -0
  12. naas_abi_core/engine/Engine.py +87 -0
  13. naas_abi_core/engine/EngineProxy.py +109 -0
  14. naas_abi_core/engine/Engine_test.py +6 -0
  15. naas_abi_core/engine/IEngine.py +91 -0
  16. naas_abi_core/engine/conftest.py +45 -0
  17. naas_abi_core/engine/engine_configuration/EngineConfiguration.py +216 -0
  18. naas_abi_core/engine/engine_configuration/EngineConfiguration_Deploy.py +7 -0
  19. naas_abi_core/engine/engine_configuration/EngineConfiguration_GenericLoader.py +49 -0
  20. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService.py +159 -0
  21. naas_abi_core/engine/engine_configuration/EngineConfiguration_ObjectStorageService_test.py +26 -0
  22. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService.py +138 -0
  23. naas_abi_core/engine/engine_configuration/EngineConfiguration_SecretService_test.py +74 -0
  24. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService.py +224 -0
  25. naas_abi_core/engine/engine_configuration/EngineConfiguration_TripleStoreService_test.py +109 -0
  26. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService.py +76 -0
  27. naas_abi_core/engine/engine_configuration/EngineConfiguration_VectorStoreService_test.py +33 -0
  28. naas_abi_core/engine/engine_configuration/EngineConfiguration_test.py +9 -0
  29. naas_abi_core/engine/engine_configuration/utils/PydanticModelValidator.py +15 -0
  30. naas_abi_core/engine/engine_loaders/EngineModuleLoader.py +302 -0
  31. naas_abi_core/engine/engine_loaders/EngineOntologyLoader.py +16 -0
  32. naas_abi_core/engine/engine_loaders/EngineServiceLoader.py +47 -0
  33. naas_abi_core/integration/__init__.py +7 -0
  34. naas_abi_core/integration/integration.py +28 -0
  35. naas_abi_core/models/Model.py +198 -0
  36. naas_abi_core/models/OpenRouter.py +18 -0
  37. naas_abi_core/models/OpenRouter_test.py +36 -0
  38. naas_abi_core/module/Module.py +252 -0
  39. naas_abi_core/module/ModuleAgentLoader.py +50 -0
  40. naas_abi_core/module/ModuleUtils.py +20 -0
  41. naas_abi_core/modules/templatablesparqlquery/README.md +196 -0
  42. naas_abi_core/modules/templatablesparqlquery/__init__.py +39 -0
  43. naas_abi_core/modules/templatablesparqlquery/ontologies/TemplatableSparqlQueryOntology.ttl +116 -0
  44. naas_abi_core/modules/templatablesparqlquery/workflows/GenericWorkflow.py +48 -0
  45. naas_abi_core/modules/templatablesparqlquery/workflows/TemplatableSparqlQueryLoader.py +192 -0
  46. naas_abi_core/pipeline/__init__.py +6 -0
  47. naas_abi_core/pipeline/pipeline.py +70 -0
  48. naas_abi_core/services/__init__.py +0 -0
  49. naas_abi_core/services/agent/Agent.py +1619 -0
  50. naas_abi_core/services/agent/AgentMemory_test.py +28 -0
  51. naas_abi_core/services/agent/Agent_test.py +214 -0
  52. naas_abi_core/services/agent/IntentAgent.py +1179 -0
  53. naas_abi_core/services/agent/IntentAgent_test.py +139 -0
  54. naas_abi_core/services/agent/beta/Embeddings.py +181 -0
  55. naas_abi_core/services/agent/beta/IntentMapper.py +120 -0
  56. naas_abi_core/services/agent/beta/LocalModel.py +88 -0
  57. naas_abi_core/services/agent/beta/VectorStore.py +89 -0
  58. naas_abi_core/services/agent/test_agent_memory.py +278 -0
  59. naas_abi_core/services/agent/test_postgres_integration.py +145 -0
  60. naas_abi_core/services/cache/CacheFactory.py +31 -0
  61. naas_abi_core/services/cache/CachePort.py +63 -0
  62. naas_abi_core/services/cache/CacheService.py +246 -0
  63. naas_abi_core/services/cache/CacheService_test.py +85 -0
  64. naas_abi_core/services/cache/adapters/secondary/CacheFSAdapter.py +39 -0
  65. naas_abi_core/services/object_storage/ObjectStorageFactory.py +57 -0
  66. naas_abi_core/services/object_storage/ObjectStoragePort.py +47 -0
  67. naas_abi_core/services/object_storage/ObjectStorageService.py +41 -0
  68. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterFS.py +52 -0
  69. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterNaas.py +131 -0
  70. naas_abi_core/services/object_storage/adapters/secondary/ObjectStorageSecondaryAdapterS3.py +171 -0
  71. naas_abi_core/services/ontology/OntologyPorts.py +36 -0
  72. naas_abi_core/services/ontology/OntologyService.py +17 -0
  73. naas_abi_core/services/ontology/adaptors/secondary/OntologyService_SecondaryAdaptor_NERPort.py +37 -0
  74. naas_abi_core/services/secret/Secret.py +138 -0
  75. naas_abi_core/services/secret/SecretPorts.py +45 -0
  76. naas_abi_core/services/secret/Secret_test.py +65 -0
  77. naas_abi_core/services/secret/adaptors/secondary/Base64Secret.py +57 -0
  78. naas_abi_core/services/secret/adaptors/secondary/Base64Secret_test.py +39 -0
  79. naas_abi_core/services/secret/adaptors/secondary/NaasSecret.py +88 -0
  80. naas_abi_core/services/secret/adaptors/secondary/NaasSecret_test.py +25 -0
  81. naas_abi_core/services/secret/adaptors/secondary/dotenv_secret_secondaryadaptor.py +29 -0
  82. naas_abi_core/services/triple_store/TripleStoreFactory.py +116 -0
  83. naas_abi_core/services/triple_store/TripleStorePorts.py +223 -0
  84. naas_abi_core/services/triple_store/TripleStoreService.py +419 -0
  85. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune.py +1300 -0
  86. naas_abi_core/services/triple_store/adaptors/secondary/AWSNeptune_test.py +284 -0
  87. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph.py +597 -0
  88. naas_abi_core/services/triple_store/adaptors/secondary/Oxigraph_test.py +1474 -0
  89. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__Filesystem.py +223 -0
  90. naas_abi_core/services/triple_store/adaptors/secondary/TripleStoreService__SecondaryAdaptor__ObjectStorage.py +234 -0
  91. naas_abi_core/services/triple_store/adaptors/secondary/base/TripleStoreService__SecondaryAdaptor__FileBase.py +18 -0
  92. naas_abi_core/services/vector_store/IVectorStorePort.py +101 -0
  93. naas_abi_core/services/vector_store/IVectorStorePort_test.py +189 -0
  94. naas_abi_core/services/vector_store/VectorStoreFactory.py +47 -0
  95. naas_abi_core/services/vector_store/VectorStoreService.py +171 -0
  96. naas_abi_core/services/vector_store/VectorStoreService_test.py +185 -0
  97. naas_abi_core/services/vector_store/__init__.py +13 -0
  98. naas_abi_core/services/vector_store/adapters/QdrantAdapter.py +251 -0
  99. naas_abi_core/services/vector_store/adapters/QdrantAdapter_test.py +57 -0
  100. naas_abi_core/tests/test_services_imports.py +69 -0
  101. naas_abi_core/utils/Expose.py +55 -0
  102. naas_abi_core/utils/Graph.py +182 -0
  103. naas_abi_core/utils/JSON.py +49 -0
  104. naas_abi_core/utils/LazyLoader.py +44 -0
  105. naas_abi_core/utils/Logger.py +12 -0
  106. naas_abi_core/utils/OntologyReasoner.py +141 -0
  107. naas_abi_core/utils/OntologyYaml.py +681 -0
  108. naas_abi_core/utils/SPARQL.py +256 -0
  109. naas_abi_core/utils/Storage.py +33 -0
  110. naas_abi_core/utils/StorageUtils.py +398 -0
  111. naas_abi_core/utils/String.py +52 -0
  112. naas_abi_core/utils/Workers.py +114 -0
  113. naas_abi_core/utils/__init__.py +0 -0
  114. naas_abi_core/utils/onto2py/README.md +0 -0
  115. naas_abi_core/utils/onto2py/__init__.py +10 -0
  116. naas_abi_core/utils/onto2py/__main__.py +29 -0
  117. naas_abi_core/utils/onto2py/onto2py.py +611 -0
  118. naas_abi_core/utils/onto2py/tests/ttl2py_test.py +271 -0
  119. naas_abi_core/workflow/__init__.py +5 -0
  120. naas_abi_core/workflow/workflow.py +48 -0
  121. naas_abi_core-1.4.1.dist-info/METADATA +630 -0
  122. naas_abi_core-1.4.1.dist-info/RECORD +124 -0
  123. naas_abi_core-1.4.1.dist-info/WHEEL +4 -0
  124. naas_abi_core-1.4.1.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,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,7 @@
1
+ from pydantic import BaseModel
2
+
3
+ class DeployConfiguration(BaseModel):
4
+ workspace_id: str
5
+ space_name: str
6
+ naas_api_key: str
7
+ env: dict[str, str] = {}
@@ -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)