qtype 0.0.12__py3-none-any.whl → 0.1.7__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.
- qtype/application/commons/tools.py +1 -1
- qtype/application/converters/tools_from_api.py +476 -11
- qtype/application/converters/tools_from_module.py +38 -14
- qtype/application/converters/types.py +15 -30
- qtype/application/documentation.py +1 -1
- qtype/application/facade.py +102 -85
- qtype/base/types.py +227 -7
- qtype/cli.py +5 -1
- qtype/commands/convert.py +52 -6
- qtype/commands/generate.py +44 -4
- qtype/commands/run.py +78 -36
- qtype/commands/serve.py +74 -44
- qtype/commands/validate.py +37 -14
- qtype/commands/visualize.py +46 -25
- qtype/dsl/__init__.py +6 -5
- qtype/dsl/custom_types.py +1 -1
- qtype/dsl/domain_types.py +86 -5
- qtype/dsl/linker.py +384 -0
- qtype/dsl/loader.py +315 -0
- qtype/dsl/model.py +753 -264
- qtype/dsl/parser.py +200 -0
- qtype/dsl/types.py +50 -0
- qtype/interpreter/api.py +63 -136
- qtype/interpreter/auth/aws.py +19 -9
- qtype/interpreter/auth/generic.py +93 -16
- qtype/interpreter/base/base_step_executor.py +436 -0
- qtype/interpreter/base/batch_step_executor.py +171 -0
- qtype/interpreter/base/exceptions.py +50 -0
- qtype/interpreter/base/executor_context.py +91 -0
- qtype/interpreter/base/factory.py +84 -0
- qtype/interpreter/base/progress_tracker.py +110 -0
- qtype/interpreter/base/secrets.py +339 -0
- qtype/interpreter/base/step_cache.py +74 -0
- qtype/interpreter/base/stream_emitter.py +469 -0
- qtype/interpreter/conversions.py +495 -24
- qtype/interpreter/converters.py +79 -0
- qtype/interpreter/endpoints.py +355 -0
- qtype/interpreter/executors/agent_executor.py +242 -0
- qtype/interpreter/executors/aggregate_executor.py +93 -0
- qtype/interpreter/executors/bedrock_reranker_executor.py +195 -0
- qtype/interpreter/executors/decoder_executor.py +163 -0
- qtype/interpreter/executors/doc_to_text_executor.py +112 -0
- qtype/interpreter/executors/document_embedder_executor.py +123 -0
- qtype/interpreter/executors/document_search_executor.py +113 -0
- qtype/interpreter/executors/document_source_executor.py +118 -0
- qtype/interpreter/executors/document_splitter_executor.py +105 -0
- qtype/interpreter/executors/echo_executor.py +63 -0
- qtype/interpreter/executors/field_extractor_executor.py +165 -0
- qtype/interpreter/executors/file_source_executor.py +101 -0
- qtype/interpreter/executors/file_writer_executor.py +110 -0
- qtype/interpreter/executors/index_upsert_executor.py +232 -0
- qtype/interpreter/executors/invoke_embedding_executor.py +104 -0
- qtype/interpreter/executors/invoke_flow_executor.py +51 -0
- qtype/interpreter/executors/invoke_tool_executor.py +358 -0
- qtype/interpreter/executors/llm_inference_executor.py +272 -0
- qtype/interpreter/executors/prompt_template_executor.py +78 -0
- qtype/interpreter/executors/sql_source_executor.py +106 -0
- qtype/interpreter/executors/vector_search_executor.py +91 -0
- qtype/interpreter/flow.py +172 -22
- qtype/interpreter/logging_progress.py +61 -0
- qtype/interpreter/metadata_api.py +115 -0
- qtype/interpreter/resource_cache.py +5 -4
- qtype/interpreter/rich_progress.py +225 -0
- qtype/interpreter/stream/chat/__init__.py +15 -0
- qtype/interpreter/stream/chat/converter.py +391 -0
- qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
- qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
- qtype/interpreter/stream/chat/vercel.py +609 -0
- qtype/interpreter/stream/utils/__init__.py +15 -0
- qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
- qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
- qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
- qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
- qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
- qtype/interpreter/telemetry.py +135 -8
- qtype/interpreter/tools/__init__.py +5 -0
- qtype/interpreter/tools/function_tool_helper.py +265 -0
- qtype/interpreter/types.py +330 -0
- qtype/interpreter/typing.py +83 -89
- qtype/interpreter/ui/404/index.html +1 -1
- qtype/interpreter/ui/404.html +1 -1
- qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
- qtype/interpreter/ui/_next/static/chunks/434-b2112d19f25c44ff.js +36 -0
- qtype/interpreter/ui/_next/static/chunks/{964-ed4ab073db645007.js → 964-2b041321a01cbf56.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/app/{layout-5ccbc44fd528d089.js → layout-a05273ead5de2c41.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
- qtype/interpreter/ui/_next/static/chunks/{main-6d261b6c5d6fb6c2.js → main-e26b9cb206da2cac.js} +1 -1
- qtype/interpreter/ui/_next/static/chunks/webpack-08642e441b39b6c2.js +1 -0
- qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
- qtype/interpreter/ui/_next/static/media/4cf2300e9c8272f7-s.p.woff2 +0 -0
- qtype/interpreter/ui/icon.png +0 -0
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +5 -5
- qtype/semantic/checker.py +643 -0
- qtype/semantic/generate.py +268 -85
- qtype/semantic/loader.py +95 -0
- qtype/semantic/model.py +535 -163
- qtype/semantic/resolver.py +63 -19
- qtype/semantic/visualize.py +50 -35
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/METADATA +22 -5
- qtype-0.1.7.dist-info/RECORD +137 -0
- qtype/dsl/base_types.py +0 -38
- qtype/dsl/validator.py +0 -464
- qtype/interpreter/batch/__init__.py +0 -0
- qtype/interpreter/batch/flow.py +0 -95
- qtype/interpreter/batch/sql_source.py +0 -95
- qtype/interpreter/batch/step.py +0 -63
- qtype/interpreter/batch/types.py +0 -41
- qtype/interpreter/batch/utils.py +0 -179
- qtype/interpreter/chat/chat_api.py +0 -237
- qtype/interpreter/chat/vercel.py +0 -314
- qtype/interpreter/exceptions.py +0 -10
- qtype/interpreter/step.py +0 -67
- qtype/interpreter/steps/__init__.py +0 -0
- qtype/interpreter/steps/agent.py +0 -114
- qtype/interpreter/steps/condition.py +0 -36
- qtype/interpreter/steps/decoder.py +0 -88
- qtype/interpreter/steps/llm_inference.py +0 -150
- qtype/interpreter/steps/prompt_template.py +0 -54
- qtype/interpreter/steps/search.py +0 -24
- qtype/interpreter/steps/tool.py +0 -53
- qtype/interpreter/streaming_helpers.py +0 -123
- qtype/interpreter/ui/_next/static/chunks/736-7fc606e244fedcb1.js +0 -36
- qtype/interpreter/ui/_next/static/chunks/app/page-c72e847e888e549d.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/webpack-8289c17c67827f22.js +0 -1
- qtype/interpreter/ui/_next/static/css/a262c53826df929b.css +0 -3
- qtype/interpreter/ui/_next/static/media/569ce4b8f30dc480-s.p.woff2 +0 -0
- qtype/interpreter/ui/favicon.ico +0 -0
- qtype/loader.py +0 -389
- qtype-0.0.12.dist-info/RECORD +0 -105
- /qtype/interpreter/ui/_next/static/{OT8QJQW3J70VbDWWfrEMT → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/WHEEL +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.12.dist-info → qtype-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base class for secret manager implementations.
|
|
3
|
+
|
|
4
|
+
This module provides an abstract base class for secret managers that
|
|
5
|
+
resolve SecretReference objects at runtime.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from qtype.interpreter.base.exceptions import SecretResolutionError
|
|
15
|
+
from qtype.semantic.model import AWSAuthProvider
|
|
16
|
+
from qtype.semantic.model import AWSSecretManager as AWSSecretManagerConfig
|
|
17
|
+
from qtype.semantic.model import SecretManager as SecretManagerConfig
|
|
18
|
+
from qtype.semantic.model import SecretReference
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class SecretManagerBase(ABC):
|
|
22
|
+
"""
|
|
23
|
+
Abstract base class for secret manager implementations.
|
|
24
|
+
|
|
25
|
+
Secret managers are responsible for resolving SecretReference objects
|
|
26
|
+
into actual secret values at runtime. Each implementation corresponds
|
|
27
|
+
to a specific secret management service (e.g., AWS Secrets Manager,
|
|
28
|
+
Kubernetes Secrets, HashiCorp Vault).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
@abstractmethod
|
|
32
|
+
def get_secret(self, secret_ref: SecretReference) -> str:
|
|
33
|
+
"""
|
|
34
|
+
Retrieve a secret value from the underlying secret store.
|
|
35
|
+
|
|
36
|
+
Subclasses must implement this method to interface with their
|
|
37
|
+
specific secret management service.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
secret_ref: SecretReference containing the secret identifier
|
|
41
|
+
and optional key for accessing specific fields
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
str: The resolved secret value
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
Exception: If the secret cannot be retrieved or resolved
|
|
48
|
+
"""
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
def __call__(self, value: str | SecretReference, context: str = "") -> str:
|
|
52
|
+
"""
|
|
53
|
+
Resolve a value that may be a string or a SecretReference.
|
|
54
|
+
|
|
55
|
+
This is the main entry point for secret resolution. It handles
|
|
56
|
+
plain strings (pass-through) and SecretReferences (delegates to
|
|
57
|
+
get_secret()).
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
value: Either a plain string or a SecretReference to resolve
|
|
61
|
+
context: Optional context string describing where the secret
|
|
62
|
+
is being resolved (e.g., "step 'my_step'", "model 'gpt4'").
|
|
63
|
+
This is included in error messages to aid debugging.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
The resolved string value. If value is already a string, it is
|
|
67
|
+
returned unchanged. If value is a SecretReference, it is
|
|
68
|
+
resolved using get_secret().
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
SecretResolutionError: If secret resolution fails
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
>>> # Resolve a plain string (no-op)
|
|
75
|
+
>>> secret_manager("plain-text")
|
|
76
|
+
'plain-text'
|
|
77
|
+
|
|
78
|
+
>>> # Resolve a secret reference
|
|
79
|
+
>>> ref = SecretReference(secret_name="my-app/api-key")
|
|
80
|
+
>>> secret_manager(ref, context="model 'gpt4'")
|
|
81
|
+
'sk-abc123...'
|
|
82
|
+
"""
|
|
83
|
+
if isinstance(value, str):
|
|
84
|
+
return value
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
return self.get_secret(value)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise SecretResolutionError(
|
|
90
|
+
secret_name=value.secret_name, context=context, cause=e
|
|
91
|
+
) from e
|
|
92
|
+
|
|
93
|
+
def resolve_secrets_in_dict(
|
|
94
|
+
self, args: dict[str, Any], context: str = ""
|
|
95
|
+
) -> dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Resolve any SecretReferences in a dictionary's values.
|
|
98
|
+
|
|
99
|
+
This is a convenience method that iterates over a dictionary and
|
|
100
|
+
resolves any values that might be SecretReferences. Non-secret
|
|
101
|
+
values (strings, numbers, etc.) are passed through unchanged.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
args: Dictionary with potentially secret-containing values
|
|
105
|
+
context: Optional context string describing where secrets are
|
|
106
|
+
being resolved (e.g., "step 'my_step'", "index 'my_index'").
|
|
107
|
+
This is included in error messages to aid debugging.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
A new dictionary with all SecretReferences resolved to strings.
|
|
111
|
+
Other values are copied unchanged.
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
SecretResolutionError: If resolution fails for any secret.
|
|
115
|
+
|
|
116
|
+
Examples:
|
|
117
|
+
>>> args = {
|
|
118
|
+
... "api_key": SecretReference(secret_name="my-app/key"),
|
|
119
|
+
... "host": "api.example.com",
|
|
120
|
+
... "port": 443
|
|
121
|
+
... }
|
|
122
|
+
>>> secret_manager.resolve_secrets_in_dict(
|
|
123
|
+
... args, "tool 'my_api'"
|
|
124
|
+
... )
|
|
125
|
+
{'api_key': 'sk-abc123...', 'host': 'api.example.com', 'port': 443}
|
|
126
|
+
"""
|
|
127
|
+
resolved = {}
|
|
128
|
+
for key, value in args.items():
|
|
129
|
+
# Check if value might be a SecretReference
|
|
130
|
+
if isinstance(value, str) or hasattr(value, "secret_name"):
|
|
131
|
+
resolved[key] = self(value, context)
|
|
132
|
+
else:
|
|
133
|
+
resolved[key] = value
|
|
134
|
+
return resolved
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class AWSSecretManagerError(Exception):
|
|
138
|
+
"""Raised when AWS Secrets Manager operations fail."""
|
|
139
|
+
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class NoOpSecretManager(SecretManagerBase):
|
|
144
|
+
"""
|
|
145
|
+
No-op secret manager that always raises an error.
|
|
146
|
+
|
|
147
|
+
This implementation is used when no secret manager is configured.
|
|
148
|
+
It allows code to always have a valid SecretManagerBase instance
|
|
149
|
+
instead of dealing with Optional types, following the Null Object
|
|
150
|
+
pattern.
|
|
151
|
+
|
|
152
|
+
Any attempt to resolve a secret will raise a SecretResolutionError
|
|
153
|
+
with a helpful message explaining that no secret manager is configured.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def get_secret(self, secret_ref: SecretReference) -> str:
|
|
157
|
+
"""
|
|
158
|
+
Raise an error indicating no secret manager is configured.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
secret_ref: The SecretReference that cannot be resolved
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
SecretResolutionError: Always raised with configuration help
|
|
165
|
+
"""
|
|
166
|
+
raise SecretResolutionError(
|
|
167
|
+
secret_name=secret_ref.secret_name,
|
|
168
|
+
context="no secret manager configured",
|
|
169
|
+
cause=ValueError(
|
|
170
|
+
"Please add a secret_manager to your application configuration"
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class AWSSecretManager(SecretManagerBase):
|
|
176
|
+
"""
|
|
177
|
+
AWS Secrets Manager implementation.
|
|
178
|
+
|
|
179
|
+
This class uses boto3 to retrieve secrets from AWS Secrets Manager.
|
|
180
|
+
It supports both string secrets and JSON secrets with optional key
|
|
181
|
+
extraction.
|
|
182
|
+
|
|
183
|
+
The implementation uses the existing auth library to authenticate
|
|
184
|
+
with AWS and caches the boto3 session for efficient reuse.
|
|
185
|
+
|
|
186
|
+
Example:
|
|
187
|
+
```python
|
|
188
|
+
from qtype.semantic.model import (
|
|
189
|
+
AWSSecretManager as AWSSecretManagerConfig,
|
|
190
|
+
AWSAuthProvider,
|
|
191
|
+
SecretReference
|
|
192
|
+
)
|
|
193
|
+
from qtype.interpreter.base.secrets import AWSSecretManager
|
|
194
|
+
|
|
195
|
+
# Create auth provider
|
|
196
|
+
auth = AWSAuthProvider(
|
|
197
|
+
id="my-aws-auth",
|
|
198
|
+
type="aws",
|
|
199
|
+
profile_name="default",
|
|
200
|
+
region="us-east-1"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
# Create secret manager config
|
|
204
|
+
secret_mgr_config = AWSSecretManagerConfig(
|
|
205
|
+
id="my-secret-manager",
|
|
206
|
+
type="aws_secret_manager",
|
|
207
|
+
auth=auth
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Create implementation
|
|
211
|
+
secret_mgr = AWSSecretManager(secret_mgr_config)
|
|
212
|
+
|
|
213
|
+
# Resolve a secret
|
|
214
|
+
secret_ref = SecretReference(
|
|
215
|
+
secret_name="my-app/api-key",
|
|
216
|
+
key=None
|
|
217
|
+
)
|
|
218
|
+
api_key = secret_mgr(secret_ref)
|
|
219
|
+
```
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
def __init__(self, config: AWSSecretManagerConfig):
|
|
223
|
+
"""
|
|
224
|
+
Initialize AWS Secrets Manager implementation.
|
|
225
|
+
|
|
226
|
+
Args:
|
|
227
|
+
config: AWSSecretManager configuration from semantic model
|
|
228
|
+
"""
|
|
229
|
+
if not isinstance(config.auth, AWSAuthProvider):
|
|
230
|
+
raise AWSSecretManagerError(
|
|
231
|
+
f"AWSSecretManager requires AWSAuthProvider, got "
|
|
232
|
+
f"{type(config.auth).__name__}"
|
|
233
|
+
)
|
|
234
|
+
self.config = config
|
|
235
|
+
|
|
236
|
+
def get_secret(self, secret_ref: SecretReference) -> str:
|
|
237
|
+
"""
|
|
238
|
+
Retrieve a secret from AWS Secrets Manager.
|
|
239
|
+
|
|
240
|
+
This method retrieves the secret value from AWS Secrets Manager
|
|
241
|
+
using the secret name provided in the reference. If the secret
|
|
242
|
+
is a JSON object and a key is specified, it extracts that
|
|
243
|
+
specific key's value.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
secret_ref: SecretReference containing the secret name and
|
|
247
|
+
optional key
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
str: The resolved secret value
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
AWSSecretManagerError: If auth provider is wrong type
|
|
254
|
+
ClientError: If AWS API call fails
|
|
255
|
+
json.JSONDecodeError: If secret is not valid JSON when key
|
|
256
|
+
is specified
|
|
257
|
+
"""
|
|
258
|
+
from qtype.interpreter.auth.aws import aws
|
|
259
|
+
|
|
260
|
+
with aws(self.config.auth) as session: # type: ignore
|
|
261
|
+
client = session.client("secretsmanager")
|
|
262
|
+
response = client.get_secret_value(SecretId=secret_ref.secret_name)
|
|
263
|
+
|
|
264
|
+
if "SecretString" not in response:
|
|
265
|
+
raise AWSSecretManagerError(
|
|
266
|
+
f"Secret '{secret_ref.secret_name}' contains binary "
|
|
267
|
+
"data, which is not supported"
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
secret_value: str = response["SecretString"]
|
|
271
|
+
|
|
272
|
+
if not secret_ref.key:
|
|
273
|
+
return secret_value
|
|
274
|
+
|
|
275
|
+
# Parse JSON and extract key
|
|
276
|
+
secret_dict = json.loads(secret_value)
|
|
277
|
+
if not isinstance(secret_dict, dict):
|
|
278
|
+
raise AWSSecretManagerError(
|
|
279
|
+
f"Secret '{secret_ref.secret_name}' is not a JSON "
|
|
280
|
+
f"object, cannot extract key '{secret_ref.key}'"
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if secret_ref.key not in secret_dict:
|
|
284
|
+
raise AWSSecretManagerError(
|
|
285
|
+
f"Key '{secret_ref.key}' not found in secret "
|
|
286
|
+
f"'{secret_ref.secret_name}'"
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
return str(secret_dict[secret_ref.key])
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def create_secret_manager(
|
|
293
|
+
config: SecretManagerConfig | None,
|
|
294
|
+
) -> SecretManagerBase:
|
|
295
|
+
"""
|
|
296
|
+
Factory function to create the appropriate secret manager implementation.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
config: SecretManager configuration from semantic model, or None
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
SecretManagerBase: Appropriate implementation based on config type.
|
|
303
|
+
Returns NoOpSecretManager if config is None.
|
|
304
|
+
|
|
305
|
+
Raises:
|
|
306
|
+
ValueError: If the secret manager type is not supported
|
|
307
|
+
|
|
308
|
+
Example:
|
|
309
|
+
```python
|
|
310
|
+
from qtype.semantic.model import (
|
|
311
|
+
AWSSecretManager as AWSSecretManagerConfig,
|
|
312
|
+
AWSAuthProvider
|
|
313
|
+
)
|
|
314
|
+
from qtype.interpreter.base.secrets import create_secret_manager
|
|
315
|
+
|
|
316
|
+
# Create config
|
|
317
|
+
config = AWSSecretManagerConfig(
|
|
318
|
+
id="my-secret-manager",
|
|
319
|
+
type="aws_secret_manager",
|
|
320
|
+
auth=auth_provider
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
# Create implementation
|
|
324
|
+
secret_manager = create_secret_manager(config)
|
|
325
|
+
|
|
326
|
+
# Use it directly - no None checks needed!
|
|
327
|
+
secret_value = secret_manager(secret_ref)
|
|
328
|
+
```
|
|
329
|
+
"""
|
|
330
|
+
if config is None:
|
|
331
|
+
return NoOpSecretManager()
|
|
332
|
+
|
|
333
|
+
if isinstance(config, AWSSecretManagerConfig):
|
|
334
|
+
return AWSSecretManager(config)
|
|
335
|
+
|
|
336
|
+
raise ValueError(
|
|
337
|
+
f"Unsupported secret manager type: {config.type}. "
|
|
338
|
+
f"Supported types: aws_secret_manager"
|
|
339
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import json
|
|
3
|
+
import pathlib
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import diskcache as dc
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from pydantic.json import pydantic_encoder
|
|
9
|
+
|
|
10
|
+
from qtype.base.types import CacheConfig
|
|
11
|
+
from qtype.interpreter.types import FlowMessage
|
|
12
|
+
from qtype.semantic.model import Step
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def create_cache(config: CacheConfig | None, step_id: str) -> dc.Cache | None:
|
|
16
|
+
if config is None:
|
|
17
|
+
return None
|
|
18
|
+
cache_dir = pathlib.Path(config.directory)
|
|
19
|
+
if config.namespace:
|
|
20
|
+
cache_dir = cache_dir / config.namespace
|
|
21
|
+
cache_dir = cache_dir / step_id / config.version
|
|
22
|
+
|
|
23
|
+
return dc.Cache(
|
|
24
|
+
directory=str(cache_dir),
|
|
25
|
+
size_limit=0, # 0 = unlimited
|
|
26
|
+
eviction_policy="none", # disables auto-eviction
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def cache_key(message: FlowMessage, step: Step) -> str:
|
|
31
|
+
"""Generates a cache key based on the message content."""
|
|
32
|
+
|
|
33
|
+
inputs = {}
|
|
34
|
+
for var in step.inputs:
|
|
35
|
+
if var.id in message.variables:
|
|
36
|
+
value = message.variables[var.id]
|
|
37
|
+
if isinstance(value, BaseModel):
|
|
38
|
+
inputs[var.id] = value.model_dump()
|
|
39
|
+
else:
|
|
40
|
+
inputs[var.id] = value
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
f"Input variable '{var.id}' not found in message -- caching can not be performed."
|
|
44
|
+
)
|
|
45
|
+
input_str = json.dumps(inputs, sort_keys=True, default=pydantic_encoder)
|
|
46
|
+
return hashlib.sha256(input_str.encode("utf-8")).hexdigest()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def to_cache_value(message: FlowMessage, step: Step) -> dict[str, Any]:
|
|
50
|
+
"""Converts a FlowMessage to a serializable cache value."""
|
|
51
|
+
if message.is_failed():
|
|
52
|
+
return {"FlowMessage.__error__": message.error}
|
|
53
|
+
else:
|
|
54
|
+
outputs = {}
|
|
55
|
+
for var in step.outputs:
|
|
56
|
+
if var.id in message.variables:
|
|
57
|
+
outputs[var.id] = message.variables[var.id]
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(
|
|
60
|
+
f"Output variable '{var.id}' not found in message -- caching can not be performed."
|
|
61
|
+
)
|
|
62
|
+
return outputs
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def from_cache_value(
|
|
66
|
+
cache_value: dict[str, Any], message: FlowMessage
|
|
67
|
+
) -> FlowMessage:
|
|
68
|
+
"""Reconstructs a FlowMessage from cached output values."""
|
|
69
|
+
if "FlowMessage.__error__" in cache_value:
|
|
70
|
+
msg = message.model_copy(deep=True)
|
|
71
|
+
msg.error = cache_value["FlowMessage.__error__"]
|
|
72
|
+
return msg
|
|
73
|
+
else:
|
|
74
|
+
return message.copy_with_variables(cache_value)
|