qtype 0.0.16__py3-none-any.whl → 0.1.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 (128) hide show
  1. qtype/application/commons/tools.py +1 -1
  2. qtype/application/converters/tools_from_api.py +5 -5
  3. qtype/application/converters/tools_from_module.py +2 -2
  4. qtype/application/converters/types.py +14 -43
  5. qtype/application/documentation.py +1 -1
  6. qtype/application/facade.py +94 -73
  7. qtype/base/types.py +227 -7
  8. qtype/cli.py +4 -0
  9. qtype/commands/convert.py +20 -8
  10. qtype/commands/generate.py +19 -27
  11. qtype/commands/run.py +73 -36
  12. qtype/commands/serve.py +74 -54
  13. qtype/commands/validate.py +34 -8
  14. qtype/commands/visualize.py +46 -22
  15. qtype/dsl/__init__.py +6 -5
  16. qtype/dsl/custom_types.py +1 -1
  17. qtype/dsl/domain_types.py +65 -5
  18. qtype/dsl/linker.py +384 -0
  19. qtype/dsl/loader.py +315 -0
  20. qtype/dsl/model.py +612 -363
  21. qtype/dsl/parser.py +200 -0
  22. qtype/dsl/types.py +50 -0
  23. qtype/interpreter/api.py +57 -136
  24. qtype/interpreter/auth/aws.py +19 -9
  25. qtype/interpreter/auth/generic.py +93 -16
  26. qtype/interpreter/base/base_step_executor.py +436 -0
  27. qtype/interpreter/base/batch_step_executor.py +171 -0
  28. qtype/interpreter/base/exceptions.py +50 -0
  29. qtype/interpreter/base/executor_context.py +74 -0
  30. qtype/interpreter/base/factory.py +117 -0
  31. qtype/interpreter/base/progress_tracker.py +110 -0
  32. qtype/interpreter/base/secrets.py +339 -0
  33. qtype/interpreter/base/step_cache.py +74 -0
  34. qtype/interpreter/base/stream_emitter.py +469 -0
  35. qtype/interpreter/conversions.py +462 -22
  36. qtype/interpreter/converters.py +77 -0
  37. qtype/interpreter/endpoints.py +355 -0
  38. qtype/interpreter/executors/agent_executor.py +242 -0
  39. qtype/interpreter/executors/aggregate_executor.py +93 -0
  40. qtype/interpreter/executors/decoder_executor.py +163 -0
  41. qtype/interpreter/executors/doc_to_text_executor.py +112 -0
  42. qtype/interpreter/executors/document_embedder_executor.py +107 -0
  43. qtype/interpreter/executors/document_search_executor.py +122 -0
  44. qtype/interpreter/executors/document_source_executor.py +118 -0
  45. qtype/interpreter/executors/document_splitter_executor.py +105 -0
  46. qtype/interpreter/executors/echo_executor.py +63 -0
  47. qtype/interpreter/executors/field_extractor_executor.py +160 -0
  48. qtype/interpreter/executors/file_source_executor.py +101 -0
  49. qtype/interpreter/executors/file_writer_executor.py +110 -0
  50. qtype/interpreter/executors/index_upsert_executor.py +228 -0
  51. qtype/interpreter/executors/invoke_embedding_executor.py +92 -0
  52. qtype/interpreter/executors/invoke_flow_executor.py +51 -0
  53. qtype/interpreter/executors/invoke_tool_executor.py +358 -0
  54. qtype/interpreter/executors/llm_inference_executor.py +272 -0
  55. qtype/interpreter/executors/prompt_template_executor.py +78 -0
  56. qtype/interpreter/executors/sql_source_executor.py +106 -0
  57. qtype/interpreter/executors/vector_search_executor.py +91 -0
  58. qtype/interpreter/flow.py +159 -22
  59. qtype/interpreter/metadata_api.py +115 -0
  60. qtype/interpreter/resource_cache.py +5 -4
  61. qtype/interpreter/rich_progress.py +225 -0
  62. qtype/interpreter/stream/chat/__init__.py +15 -0
  63. qtype/interpreter/stream/chat/converter.py +391 -0
  64. qtype/interpreter/{chat → stream/chat}/file_conversions.py +2 -2
  65. qtype/interpreter/stream/chat/ui_request_to_domain_type.py +140 -0
  66. qtype/interpreter/stream/chat/vercel.py +609 -0
  67. qtype/interpreter/stream/utils/__init__.py +15 -0
  68. qtype/interpreter/stream/utils/build_vercel_ai_formatter.py +74 -0
  69. qtype/interpreter/stream/utils/callback_to_stream.py +66 -0
  70. qtype/interpreter/stream/utils/create_streaming_response.py +18 -0
  71. qtype/interpreter/stream/utils/default_chat_extract_text.py +20 -0
  72. qtype/interpreter/stream/utils/error_streaming_response.py +20 -0
  73. qtype/interpreter/telemetry.py +135 -8
  74. qtype/interpreter/tools/__init__.py +5 -0
  75. qtype/interpreter/tools/function_tool_helper.py +265 -0
  76. qtype/interpreter/types.py +330 -0
  77. qtype/interpreter/typing.py +83 -89
  78. qtype/interpreter/ui/404/index.html +1 -1
  79. qtype/interpreter/ui/404.html +1 -1
  80. qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
  81. qtype/interpreter/ui/_next/static/chunks/{393-8fd474427f8e19ce.js → 434-b2112d19f25c44ff.js} +3 -3
  82. qtype/interpreter/ui/_next/static/chunks/app/page-8c67d16ac90d23cb.js +1 -0
  83. qtype/interpreter/ui/_next/static/chunks/ba12c10f-546f2714ff8abc66.js +1 -0
  84. qtype/interpreter/ui/_next/static/css/8a8d1269e362fef7.css +3 -0
  85. qtype/interpreter/ui/icon.png +0 -0
  86. qtype/interpreter/ui/index.html +1 -1
  87. qtype/interpreter/ui/index.txt +4 -4
  88. qtype/semantic/checker.py +583 -0
  89. qtype/semantic/generate.py +262 -83
  90. qtype/semantic/loader.py +95 -0
  91. qtype/semantic/model.py +436 -159
  92. qtype/semantic/resolver.py +63 -19
  93. qtype/semantic/visualize.py +28 -31
  94. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/METADATA +16 -3
  95. qtype-0.1.1.dist-info/RECORD +135 -0
  96. qtype/dsl/base_types.py +0 -38
  97. qtype/dsl/validator.py +0 -465
  98. qtype/interpreter/batch/__init__.py +0 -0
  99. qtype/interpreter/batch/file_sink_source.py +0 -162
  100. qtype/interpreter/batch/flow.py +0 -95
  101. qtype/interpreter/batch/sql_source.py +0 -92
  102. qtype/interpreter/batch/step.py +0 -74
  103. qtype/interpreter/batch/types.py +0 -41
  104. qtype/interpreter/batch/utils.py +0 -178
  105. qtype/interpreter/chat/chat_api.py +0 -237
  106. qtype/interpreter/chat/vercel.py +0 -314
  107. qtype/interpreter/exceptions.py +0 -10
  108. qtype/interpreter/step.py +0 -67
  109. qtype/interpreter/steps/__init__.py +0 -0
  110. qtype/interpreter/steps/agent.py +0 -114
  111. qtype/interpreter/steps/condition.py +0 -36
  112. qtype/interpreter/steps/decoder.py +0 -88
  113. qtype/interpreter/steps/llm_inference.py +0 -171
  114. qtype/interpreter/steps/prompt_template.py +0 -54
  115. qtype/interpreter/steps/search.py +0 -24
  116. qtype/interpreter/steps/tool.py +0 -219
  117. qtype/interpreter/streaming_helpers.py +0 -123
  118. qtype/interpreter/ui/_next/static/chunks/app/page-7e26b6156cfb55d3.js +0 -1
  119. qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
  120. qtype/interpreter/ui/_next/static/css/b40532b0db09cce3.css +0 -3
  121. qtype/interpreter/ui/favicon.ico +0 -0
  122. qtype/loader.py +0 -390
  123. qtype-0.0.16.dist-info/RECORD +0 -106
  124. /qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
  125. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/WHEEL +0 -0
  126. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/entry_points.txt +0 -0
  127. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/licenses/LICENSE +0 -0
  128. {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/top_level.txt +0 -0
qtype/dsl/parser.py ADDED
@@ -0,0 +1,200 @@
1
+ """
2
+ Parse YAML dictionaries into DSL models.
3
+
4
+ This module handles the conversion of loaded YAML data into validated
5
+ Pydantic DSL models, including custom type extraction and building.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any
11
+
12
+ from pydantic import ValidationError
13
+
14
+ from qtype.base.types import CustomTypeRegistry
15
+ from qtype.dsl import model as dsl
16
+ from qtype.dsl.custom_types import build_dynamic_types
17
+
18
+
19
+ def _extract_type_definitions(data: dict[str, Any] | list) -> list[dict]:
20
+ """
21
+ Extract all custom type definitions from document.
22
+
23
+ Recursively finds type definitions in the document and any references.
24
+
25
+ Args:
26
+ data: Parsed YAML dictionary or list
27
+
28
+ Returns:
29
+ List of custom type definition dictionaries
30
+ """
31
+ types = []
32
+
33
+ # Handle list documents (e.g., ToolList, ModelList)
34
+ if isinstance(data, list):
35
+ return types
36
+
37
+ # Add types from Application documents
38
+ if isinstance(data, dict):
39
+ types.extend(data.get("types", []))
40
+
41
+ # Handle TypeList documents (root is a list of types)
42
+ if "root" in data:
43
+ root = data["root"]
44
+ if (
45
+ isinstance(root, list)
46
+ and len(root) > 0
47
+ and "properties" in root[0]
48
+ ):
49
+ types.extend(root)
50
+
51
+ # Recursively handle references
52
+ for ref in data.get("references", []):
53
+ types.extend(_extract_type_definitions(ref))
54
+
55
+ return types
56
+
57
+
58
+ def _simplify_field_path(loc: tuple) -> str:
59
+ """
60
+ Simplify a Pydantic error location path for readability.
61
+
62
+ Removes verbose union type names and formats array indices.
63
+
64
+ Args:
65
+ loc: Error location tuple from Pydantic
66
+
67
+ Returns:
68
+ Simplified, readable field path string
69
+ """
70
+ simplified = []
71
+ for part in loc:
72
+ part_str = str(part)
73
+
74
+ # Skip union type discriminator paths (too verbose)
75
+ if "tagged-union" in part_str or "Union[" in part_str:
76
+ continue
77
+
78
+ # Skip Reference wrapper types
79
+ if part_str.startswith("Reference["):
80
+ continue
81
+
82
+ # Format numeric indices as array indices
83
+ if isinstance(part, int):
84
+ simplified.append(f"[{part}]")
85
+ else:
86
+ simplified.append(part_str)
87
+
88
+ return " -> ".join(simplified).replace(" -> [", "[")
89
+
90
+
91
+ def _is_relevant_error(error: dict | Any) -> bool:
92
+ """
93
+ Determine if a validation error is relevant to show.
94
+
95
+ Filters out noise from union type matching attempts.
96
+
97
+ Args:
98
+ error: Pydantic error dictionary
99
+
100
+ Returns:
101
+ True if error should be shown to user
102
+ """
103
+ loc_str = " -> ".join(str(loc) for loc in error["loc"])
104
+ error_type = error["type"]
105
+
106
+ # Filter out "should be a valid list" errors for document types
107
+ # These are just union matching attempts
108
+ if error_type == "list_type" and any(
109
+ doc_type in loc_str
110
+ for doc_type in [
111
+ "AuthorizationProviderList",
112
+ "ModelList",
113
+ "ToolList",
114
+ "TypeList",
115
+ "VariableList",
116
+ "AgentList",
117
+ "FlowList",
118
+ "IndexList",
119
+ ]
120
+ ):
121
+ return False
122
+
123
+ # Filter out Reference wrapper errors about $ref field
124
+ # These are duplicates of actual validation errors
125
+ if "Reference[" in loc_str and "$ref" in error["loc"][-1]:
126
+ return False
127
+
128
+ return True
129
+
130
+
131
+ def _format_validation_errors(
132
+ validation_error: ValidationError, source_name: str | None
133
+ ) -> str:
134
+ """
135
+ Format Pydantic validation errors in user-friendly way.
136
+
137
+ Args:
138
+ validation_error: ValidationError from Pydantic
139
+ source_name: Optional source file name for context
140
+
141
+ Returns:
142
+ Formatted error message string
143
+ """
144
+ # Filter and collect relevant errors
145
+ relevant_errors = [
146
+ error
147
+ for error in validation_error.errors()
148
+ if _is_relevant_error(error)
149
+ ]
150
+
151
+ if not relevant_errors:
152
+ # Fallback if all errors were filtered
153
+ error_msg = "Validation failed (see details above)"
154
+ else:
155
+ error_msg = "Validation failed:\n"
156
+ for error in relevant_errors[:5]: # Show max 5 errors
157
+ loc_path = _simplify_field_path(error["loc"])
158
+ error_msg += f" {loc_path}: {error['msg']}\n"
159
+
160
+ if len(relevant_errors) > 5:
161
+ error_msg += f" ... and {len(relevant_errors) - 5} more errors\n"
162
+
163
+ if source_name:
164
+ error_msg = f"In {source_name}:\n{error_msg}"
165
+
166
+ return error_msg
167
+
168
+
169
+ def parse_document(
170
+ yaml_data: dict[str, Any], source_name: str | None = None
171
+ ) -> tuple[dsl.DocumentType, CustomTypeRegistry]:
172
+ """
173
+ Parse validated YAML dictionary into DSL document.
174
+
175
+ Args:
176
+ yaml_data: Pre-loaded YAML dictionary
177
+ source_name: Optional source name for error messages
178
+
179
+ Returns:
180
+ Tuple of (DocumentType, CustomTypeRegistry)
181
+
182
+ Raises:
183
+ ValueError: If validation fails
184
+ """
185
+ # Extract and build custom types
186
+ type_defs = _extract_type_definitions(yaml_data)
187
+ custom_types = build_dynamic_types(type_defs)
188
+
189
+ # Validate with Pydantic
190
+ try:
191
+ document = dsl.Document.model_validate(
192
+ yaml_data, context={"custom_types": custom_types}
193
+ )
194
+ except ValidationError as e:
195
+ # Format validation errors nicely
196
+ error_msg = _format_validation_errors(e, source_name)
197
+ raise ValueError(error_msg) from e
198
+
199
+ # Extract root document from wrapper
200
+ return document.root, custom_types
qtype/dsl/types.py ADDED
@@ -0,0 +1,50 @@
1
+ """Utility types and functions for the DSL."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import date, datetime, time
6
+
7
+ from qtype.base.types import PrimitiveTypeEnum
8
+
9
+ # Mapping of QType primitive types to Python types for internal representations
10
+ PRIMITIVE_TO_PYTHON_TYPE = {
11
+ PrimitiveTypeEnum.audio: bytes,
12
+ PrimitiveTypeEnum.boolean: bool,
13
+ PrimitiveTypeEnum.bytes: bytes,
14
+ PrimitiveTypeEnum.date: date,
15
+ PrimitiveTypeEnum.datetime: datetime,
16
+ PrimitiveTypeEnum.int: int,
17
+ PrimitiveTypeEnum.file: bytes, # Use bytes for file content
18
+ PrimitiveTypeEnum.float: float,
19
+ PrimitiveTypeEnum.image: bytes, # Use bytes for image data
20
+ PrimitiveTypeEnum.text: str,
21
+ PrimitiveTypeEnum.time: time, # Use time for time representation
22
+ PrimitiveTypeEnum.video: bytes, # Use bytes for video data
23
+ }
24
+
25
+ PYTHON_TYPE_TO_PRIMITIVE_TYPE = {
26
+ bytes: PrimitiveTypeEnum.file,
27
+ bool: PrimitiveTypeEnum.boolean,
28
+ str: PrimitiveTypeEnum.text,
29
+ int: PrimitiveTypeEnum.int,
30
+ float: PrimitiveTypeEnum.float,
31
+ date: PrimitiveTypeEnum.date,
32
+ datetime: PrimitiveTypeEnum.datetime,
33
+ time: PrimitiveTypeEnum.time,
34
+ # TODO: decide on internal representation for images, video, and audio,
35
+ # or use annotation/hinting
36
+ }
37
+
38
+
39
+ def python_type_for_list(element_type: PrimitiveTypeEnum) -> type:
40
+ """
41
+ Get the Python list type for a given QType primitive element type.
42
+
43
+ Args:
44
+ element_type: The primitive type of the list elements
45
+
46
+ Returns:
47
+ The corresponding Python list type (e.g., list[str] for text elements)
48
+ """
49
+ element_python_type = PRIMITIVE_TO_PYTHON_TYPE[element_type]
50
+ return list[element_python_type]
qtype/interpreter/api.py CHANGED
@@ -1,23 +1,23 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
3
+ import asyncio
4
+ from contextlib import asynccontextmanager
4
5
  from pathlib import Path
5
6
 
6
- import pandas as pd
7
- from fastapi import FastAPI, HTTPException, Query
7
+ from fastapi import FastAPI
8
8
  from fastapi.middleware.cors import CORSMiddleware
9
9
  from fastapi.responses import RedirectResponse
10
10
  from fastapi.staticfiles import StaticFiles
11
+ from opentelemetry import trace
11
12
 
12
- from qtype.dsl.base_types import StepCardinality
13
- from qtype.interpreter.batch.flow import batch_execute_flow
14
- from qtype.interpreter.batch.types import BatchConfig, ErrorMode
15
- from qtype.interpreter.flow import execute_flow
16
- from qtype.interpreter.typing import (
17
- create_input_type_model,
18
- create_output_type_model,
13
+ from qtype.interpreter.base.executor_context import ExecutorContext
14
+ from qtype.interpreter.base.secrets import create_secret_manager
15
+ from qtype.interpreter.endpoints import (
16
+ create_rest_endpoint,
17
+ create_streaming_endpoint,
19
18
  )
20
- from qtype.semantic.model import Application, Flow
19
+ from qtype.interpreter.metadata_api import create_metadata_endpoints
20
+ from qtype.semantic.model import Application
21
21
 
22
22
 
23
23
  class APIExecutor:
@@ -51,7 +51,36 @@ class APIExecutor:
51
51
  if servers is not None:
52
52
  fast_api_args["servers"] = servers
53
53
 
54
- app = FastAPI(title=name or "QType API", **fast_api_args)
54
+ # Create secret manager if configured
55
+ secret_manager = create_secret_manager(self.definition.secret_manager)
56
+
57
+ # Create lifespan context manager for telemetry
58
+ @asynccontextmanager
59
+ async def lifespan(app: FastAPI):
60
+ """Manage telemetry lifecycle during app startup/shutdown."""
61
+ tracer_provider = None
62
+ if self.definition.telemetry:
63
+ from qtype.interpreter.telemetry import register
64
+
65
+ tracer_provider = register(
66
+ self.definition.telemetry,
67
+ project_id=name or self.definition.id,
68
+ secret_manager=secret_manager,
69
+ )
70
+ yield
71
+ # Fire off telemetry shutdown in background for fast reloads
72
+ if tracer_provider is not None:
73
+
74
+ async def shutdown_telemetry():
75
+ tracer_provider.force_flush(timeout_millis=1000)
76
+ tracer_provider.shutdown()
77
+
78
+ asyncio.create_task(shutdown_telemetry())
79
+
80
+ # Create FastAPI app with lifespan
81
+ app = FastAPI(
82
+ title=name or "QType API", lifespan=lifespan, **fast_api_args
83
+ )
55
84
 
56
85
  # Serve static UI files if they exist
57
86
  if ui_enabled:
@@ -71,132 +100,24 @@ class APIExecutor:
71
100
  StaticFiles(directory=str(ui_dir), html=True),
72
101
  name="ui",
73
102
  )
74
- app.get("/")(lambda: RedirectResponse(url="/ui"))
103
+ app.get("/", include_in_schema=False)(
104
+ lambda: RedirectResponse(url="/ui")
105
+ )
75
106
 
76
- flows = self.definition.flows if self.definition.flows else []
107
+ # Create metadata endpoints for flow discovery
108
+ create_metadata_endpoints(app, self.definition)
77
109
 
78
- # Dynamically generate POST endpoints for each flow
79
- for flow in flows:
80
- if flow.mode == "Chat":
81
- # For chat, we can create a single endpoint
82
- # that handles the chat interactions
83
- from qtype.interpreter.chat.chat_api import (
84
- create_chat_flow_endpoint,
85
- )
110
+ # Create executor context
111
+ context = ExecutorContext(
112
+ secret_manager=secret_manager,
113
+ tracer=trace.get_tracer(__name__),
114
+ )
86
115
 
87
- create_chat_flow_endpoint(app, flow)
88
- else:
89
- self._create_flow_endpoint(app, flow)
116
+ # Create unified invoke endpoints for each flow
117
+ flows = self.definition.flows if self.definition.flows else []
118
+ for flow in flows:
119
+ if flow.interface is not None:
120
+ create_streaming_endpoint(app, flow, context)
121
+ create_rest_endpoint(app, flow, context)
90
122
 
91
123
  return app
92
-
93
- def _create_flow_endpoint(self, app: FastAPI, flow: Flow) -> None:
94
- """Create a dynamic POST endpoint for a specific flow."""
95
- flow_id = flow.id
96
-
97
- # determine if this is a batch inference
98
- is_batch = flow.cardinality == StepCardinality.many
99
-
100
- # Create dynamic request and response models for this flow
101
- RequestModel = create_input_type_model(flow, is_batch)
102
- ResponseModel = create_output_type_model(flow, is_batch)
103
-
104
- # Create the endpoint function with proper model binding
105
- if is_batch:
106
-
107
- def execute_flow_endpoint( # type: ignore
108
- request: RequestModel, # type: ignore
109
- error_mode: ErrorMode = Query(
110
- default=ErrorMode.FAIL,
111
- description="Error handling mode for batch processing",
112
- ),
113
- ) -> ResponseModel: # type: ignore
114
- try:
115
- # Make a copy of the flow to avoid modifying the original
116
- # TODO: Use session to ensure memory is not used across requests.
117
- flow_copy = flow.model_copy(deep=True)
118
- # convert the inputs into a dataframe with a single row
119
- inputs = pd.DataFrame(
120
- [i.model_dump() for i in request.inputs] # type: ignore
121
- )
122
-
123
- # Execute the flow
124
- results, errors = batch_execute_flow(
125
- flow_copy,
126
- inputs,
127
- batch_config=BatchConfig(error_mode=error_mode),
128
- )
129
-
130
- response_data = {
131
- "flow_id": flow_id,
132
- "outputs": results.to_dict(orient="records"),
133
- "errors": errors.to_dict(orient="records"),
134
- "num_results": len(results),
135
- "num_errors": len(errors),
136
- "num_inputs": len(inputs),
137
- "status": "success" if len(errors) == 0 else "partial",
138
- }
139
-
140
- # Return the response using the dynamic model
141
- return ResponseModel(**response_data) # type: ignore
142
-
143
- except Exception as e:
144
- logging.error("Batch Flow Execution Failed", exc_info=e)
145
- raise HTTPException(
146
- status_code=500,
147
- detail=f"Batch flow execution failed: {str(e)}",
148
- )
149
- else:
150
-
151
- def execute_flow_endpoint(request: RequestModel) -> ResponseModel: # type: ignore
152
- try:
153
- # Make a copy of the flow to avoid modifying the original
154
- # TODO: Use session to ensure memory is not used across requests.
155
- flow_copy = flow.model_copy(deep=True)
156
- # Set input values on the flow variables
157
- if flow_copy.inputs:
158
- for var in flow_copy.inputs:
159
- # Get the value from the request using the variable ID
160
- request_dict = request.model_dump() # type: ignore
161
- if var.id in request_dict:
162
- var.value = getattr(request, var.id)
163
- elif not var.is_set():
164
- raise HTTPException(
165
- status_code=400,
166
- detail=f"Required input '{var.id}' not provided",
167
- )
168
- # Execute the flow
169
- result_vars = execute_flow(flow_copy)
170
-
171
- # Extract output values
172
- outputs = {var.id: var.value for var in result_vars}
173
-
174
- response_data = {
175
- "flow_id": flow_id,
176
- "outputs": outputs,
177
- "status": "success",
178
- }
179
-
180
- # Return the response using the dynamic model
181
- return ResponseModel(**response_data) # type: ignore
182
-
183
- except Exception as e:
184
- raise HTTPException(
185
- status_code=500,
186
- detail=f"Flow execution failed: {str(e)}",
187
- )
188
-
189
- # Set the function annotations properly for FastAPI
190
- execute_flow_endpoint.__annotations__ = {
191
- "request": RequestModel,
192
- "error_mode": ErrorMode,
193
- "return": ResponseModel,
194
- }
195
-
196
- # Add the endpoint with explicit models
197
- app.post(
198
- f"/flows/{flow_id}",
199
- tags=["flow"],
200
- description=flow.description or "Execute a flow",
201
- response_model=ResponseModel,
202
- )(execute_flow_endpoint)
@@ -17,6 +17,7 @@ from botocore.exceptions import ( # type: ignore[import-untyped]
17
17
  )
18
18
 
19
19
  from qtype.interpreter.auth.cache import cache_auth, get_cached_auth
20
+ from qtype.interpreter.base.secrets import SecretManagerBase
20
21
  from qtype.semantic.model import AWSAuthProvider
21
22
 
22
23
 
@@ -56,7 +57,10 @@ def _is_session_valid(session: boto3.Session) -> bool:
56
57
 
57
58
 
58
59
  @contextmanager
59
- def aws(aws_provider: AWSAuthProvider) -> Generator[boto3.Session, None, None]:
60
+ def aws(
61
+ aws_provider: AWSAuthProvider,
62
+ secret_manager: SecretManagerBase,
63
+ ) -> Generator[boto3.Session, None, None]:
60
64
  """
61
65
  Create a boto3 Session using AWS authentication provider configuration.
62
66
 
@@ -113,7 +117,7 @@ def aws(aws_provider: AWSAuthProvider) -> Generator[boto3.Session, None, None]:
113
117
  return
114
118
 
115
119
  # Cache miss or invalid session - create new session
116
- session = _create_session(aws_provider)
120
+ session = _create_session(aws_provider, secret_manager)
117
121
 
118
122
  # Validate the session by attempting to get credentials
119
123
  credentials = session.get_credentials()
@@ -137,12 +141,16 @@ def aws(aws_provider: AWSAuthProvider) -> Generator[boto3.Session, None, None]:
137
141
  ) from e
138
142
 
139
143
 
140
- def _create_session(aws_provider: AWSAuthProvider) -> boto3.Session:
144
+ def _create_session(
145
+ aws_provider: AWSAuthProvider,
146
+ secret_manager: SecretManagerBase,
147
+ ) -> boto3.Session:
141
148
  """
142
149
  Create a boto3 Session based on the AWS provider configuration.
143
150
 
144
151
  Args:
145
152
  aws_provider: AWSAuthProvider with authentication details
153
+ secret_manager: Secret manager for resolving SecretReferences
146
154
 
147
155
  Returns:
148
156
  boto3.Session: Configured session
@@ -162,14 +170,16 @@ def _create_session(aws_provider: AWSAuthProvider) -> boto3.Session:
162
170
  session_kwargs["profile_name"] = aws_provider.profile_name
163
171
 
164
172
  elif aws_provider.access_key_id and aws_provider.secret_access_key:
165
- # Use direct credentials
166
- session_kwargs["aws_access_key_id"] = aws_provider.access_key_id
167
- session_kwargs["aws_secret_access_key"] = (
168
- aws_provider.secret_access_key
169
- )
173
+ # Use direct credentials - resolve secrets
174
+ context = f"AWS auth provider '{aws_provider.id}'"
175
+ access_key = secret_manager(aws_provider.access_key_id, context)
176
+ secret_key = secret_manager(aws_provider.secret_access_key, context)
177
+ session_kwargs["aws_access_key_id"] = access_key
178
+ session_kwargs["aws_secret_access_key"] = secret_key
170
179
 
171
180
  if aws_provider.session_token:
172
- session_kwargs["aws_session_token"] = aws_provider.session_token
181
+ session_token = secret_manager(aws_provider.session_token, context)
182
+ session_kwargs["aws_session_token"] = session_token
173
183
 
174
184
  # Create the base session
175
185
  session = boto3.Session(**session_kwargs)