qtype 0.0.10__py3-none-any.whl → 0.0.12__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/__init__.py +12 -0
- qtype/application/commons/__init__.py +7 -0
- qtype/{converters → application/converters}/tools_from_module.py +2 -2
- qtype/application/converters/types.py +33 -0
- qtype/{dsl/document.py → application/documentation.py} +2 -0
- qtype/application/facade.py +160 -0
- qtype/base/__init__.py +14 -0
- qtype/base/exceptions.py +49 -0
- qtype/base/logging.py +39 -0
- qtype/base/types.py +29 -0
- qtype/commands/convert.py +64 -49
- qtype/commands/generate.py +59 -4
- qtype/commands/run.py +109 -72
- qtype/commands/serve.py +42 -28
- qtype/commands/validate.py +25 -42
- qtype/commands/visualize.py +51 -37
- qtype/dsl/__init__.py +9 -0
- qtype/dsl/base_types.py +8 -0
- qtype/dsl/custom_types.py +6 -4
- qtype/dsl/model.py +185 -50
- qtype/dsl/validator.py +9 -4
- qtype/interpreter/api.py +96 -40
- qtype/interpreter/auth/__init__.py +3 -0
- qtype/interpreter/auth/aws.py +234 -0
- qtype/interpreter/auth/cache.py +67 -0
- qtype/interpreter/auth/generic.py +103 -0
- qtype/interpreter/batch/flow.py +95 -0
- qtype/interpreter/batch/sql_source.py +95 -0
- qtype/interpreter/batch/step.py +63 -0
- qtype/interpreter/batch/types.py +41 -0
- qtype/interpreter/batch/utils.py +179 -0
- qtype/interpreter/conversions.py +21 -10
- qtype/interpreter/resource_cache.py +4 -2
- qtype/interpreter/steps/decoder.py +13 -9
- qtype/interpreter/steps/llm_inference.py +7 -9
- qtype/interpreter/steps/prompt_template.py +1 -1
- qtype/interpreter/streaming_helpers.py +3 -3
- qtype/interpreter/typing.py +47 -11
- qtype/interpreter/ui/404/index.html +1 -1
- qtype/interpreter/ui/404.html +1 -1
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +1 -1
- qtype/loader.py +15 -16
- qtype/semantic/generate.py +91 -39
- qtype/semantic/model.py +183 -52
- qtype/semantic/resolver.py +4 -4
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/METADATA +5 -1
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/RECORD +58 -44
- qtype/commons/generate.py +0 -93
- qtype/converters/types.py +0 -66
- qtype/semantic/errors.py +0 -4
- /qtype/{commons → application/commons}/tools.py +0 -0
- /qtype/{commons → application/converters}/__init__.py +0 -0
- /qtype/{converters → application/converters}/tools_from_api.py +0 -0
- /qtype/{converters → interpreter/batch}/__init__.py +0 -0
- /qtype/interpreter/ui/_next/static/{Jb2murBlt2XkN6punrQbE → OT8QJQW3J70VbDWWfrEMT}/_buildManifest.js +0 -0
- /qtype/interpreter/ui/_next/static/{Jb2murBlt2XkN6punrQbE → OT8QJQW3J70VbDWWfrEMT}/_ssgManifest.js +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/WHEEL +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.10.dist-info → qtype-0.0.12.dist-info}/top_level.txt +0 -0
qtype/commands/run.py
CHANGED
|
@@ -5,46 +5,41 @@ Command-line interface for running QType YAML spec files.
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
|
+
import json
|
|
8
9
|
import logging
|
|
10
|
+
from pathlib import Path
|
|
9
11
|
from typing import Any
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from qtype.interpreter.typing import create_input_type_model
|
|
14
|
-
from qtype.loader import load
|
|
15
|
-
from qtype.semantic.model import Application, Flow, Step
|
|
16
|
-
|
|
17
|
-
logger = logging.getLogger(__name__)
|
|
13
|
+
import magic
|
|
14
|
+
import pandas as pd
|
|
18
15
|
|
|
16
|
+
from qtype.application.facade import QTypeFacade
|
|
17
|
+
from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
19
18
|
|
|
20
|
-
|
|
21
|
-
if len(app.flows) == 0:
|
|
22
|
-
raise ValueError(
|
|
23
|
-
"No flows found in the application."
|
|
24
|
-
" Please ensure the spec contains at least one flow."
|
|
25
|
-
)
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
26
20
|
|
|
27
|
-
if flow_id is not None:
|
|
28
|
-
# find the first flow in the list with the given flow_id
|
|
29
|
-
flow = next((f for f in app.flows if f.id == flow_id), None)
|
|
30
|
-
if flow is None:
|
|
31
|
-
raise ValueError(f"Flow not found: {flow_id}")
|
|
32
21
|
|
|
22
|
+
def read_data_from_file(file_path: str) -> pd.DataFrame:
|
|
23
|
+
"""
|
|
24
|
+
Reads a file into a pandas DataFrame based on its MIME type.
|
|
25
|
+
"""
|
|
26
|
+
mime_type = magic.Magic(mime=True).from_file(file_path)
|
|
27
|
+
|
|
28
|
+
if mime_type == "text/csv":
|
|
29
|
+
return pd.read_csv(file_path)
|
|
30
|
+
elif mime_type == "application/json":
|
|
31
|
+
return pd.read_json(file_path)
|
|
32
|
+
elif mime_type in [
|
|
33
|
+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
34
|
+
"application/vnd.ms-excel",
|
|
35
|
+
]:
|
|
36
|
+
return pd.read_excel(file_path)
|
|
37
|
+
elif mime_type in ["application/vnd.parquet", "application/octet-stream"]:
|
|
38
|
+
return pd.read_parquet(file_path)
|
|
33
39
|
else:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return flow
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def _telemetry(spec: Application) -> None:
|
|
40
|
-
if spec.telemetry:
|
|
41
|
-
logger.info(
|
|
42
|
-
f"Telemetry enabled with endpoint: {spec.telemetry.endpoint}"
|
|
40
|
+
raise ValueError(
|
|
41
|
+
f"Unsupported MIME type for file {file_path}: {mime_type}"
|
|
43
42
|
)
|
|
44
|
-
# Register telemetry if needed
|
|
45
|
-
from qtype.interpreter.telemetry import register
|
|
46
|
-
|
|
47
|
-
register(spec.telemetry, spec.id)
|
|
48
43
|
|
|
49
44
|
|
|
50
45
|
def run_flow(args: Any) -> None:
|
|
@@ -53,45 +48,70 @@ def run_flow(args: Any) -> None:
|
|
|
53
48
|
Args:
|
|
54
49
|
args: Arguments passed from the command line or calling context.
|
|
55
50
|
"""
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
was_streamed = True
|
|
77
|
-
if isinstance(msg, ChatMessage):
|
|
78
|
-
content = " ".join(
|
|
79
|
-
[m.content for m in msg.blocks if m.content]
|
|
80
|
-
)
|
|
81
|
-
# Note: streaming chat messages accumulate the content...
|
|
82
|
-
content = content.removeprefix(previous)
|
|
83
|
-
print(content, end="", flush=True)
|
|
84
|
-
previous += content
|
|
85
|
-
else:
|
|
86
|
-
print(msg, end="", flush=True)
|
|
87
|
-
|
|
88
|
-
result = execute_flow(flow, stream_fn=stream_fn) # type: ignore
|
|
89
|
-
if not was_streamed:
|
|
90
|
-
logger.info(
|
|
91
|
-
f"Flow execution result: {', '.join([f'{var.id}: {var.value}' for var in result])}"
|
|
51
|
+
facade = QTypeFacade()
|
|
52
|
+
spec_path = Path(args.spec)
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
logger.info(f"Running flow from {spec_path}")
|
|
56
|
+
|
|
57
|
+
if args.input_file:
|
|
58
|
+
logger.info(f"Loading input data from file: {args.input_file}")
|
|
59
|
+
input: Any = read_data_from_file(args.input_file)
|
|
60
|
+
else:
|
|
61
|
+
# Parse input JSON
|
|
62
|
+
try:
|
|
63
|
+
input = json.loads(args.input) if args.input else {}
|
|
64
|
+
except json.JSONDecodeError as e:
|
|
65
|
+
logger.error(f"❌ Invalid JSON input: {e}")
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Execute the workflow using the facade
|
|
69
|
+
result = facade.execute_workflow(
|
|
70
|
+
spec_path, flow_name=args.flow, inputs=input, batch_config=None
|
|
92
71
|
)
|
|
93
|
-
|
|
94
|
-
|
|
72
|
+
|
|
73
|
+
logger.info("✅ Flow execution completed successfully")
|
|
74
|
+
|
|
75
|
+
# Print results
|
|
76
|
+
if isinstance(result, pd.DataFrame):
|
|
77
|
+
logging.info("Output DataFrame:")
|
|
78
|
+
logging.info(result)
|
|
79
|
+
elif (
|
|
80
|
+
result
|
|
81
|
+
and hasattr(result, "__iter__")
|
|
82
|
+
and not isinstance(result, str)
|
|
83
|
+
):
|
|
84
|
+
# If result is a list of variables or similar
|
|
85
|
+
try:
|
|
86
|
+
for item in result:
|
|
87
|
+
if hasattr(item, "id") and hasattr(item, "value"):
|
|
88
|
+
logger.info(f"Output {item.id}: {item.value}")
|
|
89
|
+
else:
|
|
90
|
+
logger.info(f"Result: {item}")
|
|
91
|
+
except TypeError:
|
|
92
|
+
logger.info(f"Result: {result}")
|
|
93
|
+
elif isinstance(result, str):
|
|
94
|
+
logger.info(f"Result: {result}")
|
|
95
|
+
else:
|
|
96
|
+
logger.info("Flow completed with no output")
|
|
97
|
+
|
|
98
|
+
# save the output
|
|
99
|
+
if isinstance(result, pd.DataFrame) and args.output:
|
|
100
|
+
result.to_parquet(args.output)
|
|
101
|
+
logger.info(f"Output DataFrame saved to {args.output}")
|
|
102
|
+
elif args.output:
|
|
103
|
+
with open(args.output, "w") as f:
|
|
104
|
+
json.dump(result, f, indent=2)
|
|
105
|
+
logger.info(f"Output saved to {args.output}")
|
|
106
|
+
|
|
107
|
+
except LoadError as e:
|
|
108
|
+
logger.error(f"❌ Failed to load document: {e}")
|
|
109
|
+
except ValidationError as e:
|
|
110
|
+
logger.error(f"❌ Validation failed: {e}")
|
|
111
|
+
except InterpreterError as e:
|
|
112
|
+
logger.error(f"❌ Execution failed: {e}")
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"❌ Unexpected error: {e}", exc_info=True)
|
|
95
115
|
|
|
96
116
|
|
|
97
117
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -110,14 +130,31 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
110
130
|
default=None,
|
|
111
131
|
help="The name of the flow to run. If not specified, runs the first flow found.",
|
|
112
132
|
)
|
|
133
|
+
# Allow either a direct JSON string or an input file
|
|
134
|
+
input_group = cmd_parser.add_mutually_exclusive_group()
|
|
135
|
+
input_group.add_argument(
|
|
136
|
+
"-i",
|
|
137
|
+
"--input",
|
|
138
|
+
type=str,
|
|
139
|
+
default="{}",
|
|
140
|
+
help="JSON blob of input values for the flow (default: {}).",
|
|
141
|
+
)
|
|
142
|
+
input_group.add_argument(
|
|
143
|
+
"-I",
|
|
144
|
+
"--input-file",
|
|
145
|
+
type=str,
|
|
146
|
+
default=None,
|
|
147
|
+
help="Path to a file (e.g., CSV, JSON, Parquet) with input data for batch processing.",
|
|
148
|
+
)
|
|
113
149
|
cmd_parser.add_argument(
|
|
114
|
-
"
|
|
150
|
+
"-o",
|
|
151
|
+
"--output",
|
|
115
152
|
type=str,
|
|
116
|
-
|
|
153
|
+
default=None,
|
|
154
|
+
help="Path to save output data. If input is a DataFrame, output will be saved as parquet. If single result, saved as JSON.",
|
|
117
155
|
)
|
|
118
156
|
|
|
119
157
|
cmd_parser.add_argument(
|
|
120
158
|
"spec", type=str, help="Path to the QType YAML spec file."
|
|
121
159
|
)
|
|
122
|
-
|
|
123
160
|
cmd_parser.set_defaults(func=run_flow)
|
qtype/commands/serve.py
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Command-line interface for
|
|
2
|
+
Command-line interface for serving QType YAML spec files as web APIs.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import logging
|
|
9
|
+
from pathlib import Path
|
|
9
10
|
from typing import Any
|
|
10
11
|
|
|
11
12
|
import uvicorn
|
|
12
13
|
|
|
13
|
-
from qtype.
|
|
14
|
-
from qtype.
|
|
14
|
+
from qtype.application.facade import QTypeFacade
|
|
15
|
+
from qtype.base.exceptions import LoadError, ValidationError
|
|
15
16
|
|
|
16
17
|
logger = logging.getLogger(__name__)
|
|
17
18
|
|
|
@@ -22,31 +23,44 @@ def serve(args: Any) -> None:
|
|
|
22
23
|
Args:
|
|
23
24
|
args: Arguments passed from the command line or calling context.
|
|
24
25
|
"""
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
fastapi_app
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
26
|
+
facade = QTypeFacade()
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
# Use facade to load and validate the document
|
|
30
|
+
spec_path = Path(args.spec)
|
|
31
|
+
logger.info(f"Loading and validating spec: {spec_path}")
|
|
32
|
+
|
|
33
|
+
semantic_model, type_registry = facade.load_semantic_model(spec_path)
|
|
34
|
+
logger.info(f"✅ Successfully loaded spec: {spec_path}")
|
|
35
|
+
|
|
36
|
+
# Import APIExecutor and create the FastAPI app
|
|
37
|
+
from qtype.interpreter.api import APIExecutor
|
|
38
|
+
|
|
39
|
+
# Get the name from the spec filename
|
|
40
|
+
name = (
|
|
41
|
+
spec_path.name.replace(".qtype.yaml", "").replace("_", " ").title()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
logger.info(f"Starting server for: {name}")
|
|
45
|
+
api_executor = APIExecutor(semantic_model)
|
|
46
|
+
fastapi_app = api_executor.create_app(
|
|
47
|
+
name=name, ui_enabled=not args.disable_ui
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Start the server
|
|
51
|
+
uvicorn.run(
|
|
52
|
+
fastapi_app, host=args.host, port=args.port, log_level="info"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
except LoadError as e:
|
|
56
|
+
logger.error(f"❌ Failed to load document: {e}")
|
|
57
|
+
exit(1)
|
|
58
|
+
except ValidationError as e:
|
|
59
|
+
logger.error(f"❌ Validation failed: {e}")
|
|
60
|
+
exit(1)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
logger.error(f"❌ Unexpected error starting server: {e}")
|
|
63
|
+
exit(1)
|
|
50
64
|
|
|
51
65
|
|
|
52
66
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
qtype/commands/validate.py
CHANGED
|
@@ -2,23 +2,16 @@
|
|
|
2
2
|
Command-line interface for validating QType YAML spec files.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import argparse
|
|
6
8
|
import logging
|
|
7
9
|
import sys
|
|
10
|
+
from pathlib import Path
|
|
8
11
|
from typing import Any
|
|
9
12
|
|
|
10
|
-
from
|
|
11
|
-
|
|
12
|
-
from qtype import dsl
|
|
13
|
-
from qtype.dsl.custom_types import build_dynamic_types
|
|
14
|
-
from qtype.dsl.validator import QTypeValidationError, validate
|
|
15
|
-
from qtype.loader import (
|
|
16
|
-
_list_dynamic_types_from_document,
|
|
17
|
-
_resolve_root,
|
|
18
|
-
load_yaml,
|
|
19
|
-
)
|
|
20
|
-
from qtype.semantic.errors import SemanticResolutionError
|
|
21
|
-
from qtype.semantic.resolver import resolve
|
|
13
|
+
from qtype.application.facade import QTypeFacade
|
|
14
|
+
from qtype.base.exceptions import LoadError, SemanticError, ValidationError
|
|
22
15
|
|
|
23
16
|
logger = logging.getLogger(__name__)
|
|
24
17
|
|
|
@@ -33,42 +26,32 @@ def main(args: Any) -> None:
|
|
|
33
26
|
Exits:
|
|
34
27
|
Exits with code 1 if validation fails.
|
|
35
28
|
"""
|
|
29
|
+
facade = QTypeFacade()
|
|
30
|
+
spec_path = Path(args.spec)
|
|
31
|
+
|
|
36
32
|
try:
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
logging.info("✅ Schema validation successful.")
|
|
33
|
+
# Use the facade for validation - it will raise exceptions on errors
|
|
34
|
+
loaded_data = facade.load_and_validate(spec_path)
|
|
35
|
+
logger.info("✅ Validation successful - document is valid.")
|
|
41
36
|
|
|
42
|
-
|
|
43
|
-
yaml_data, context={"custom_types": dynamic_types_registry}
|
|
44
|
-
)
|
|
45
|
-
logging.info("✅ Model validation successful.")
|
|
46
|
-
root = _resolve_root(document)
|
|
47
|
-
if not isinstance(root, dsl.Application):
|
|
48
|
-
logging.warning(
|
|
49
|
-
"🟨 Spec is not an Application, skipping semantic resolution."
|
|
50
|
-
)
|
|
51
|
-
else:
|
|
52
|
-
root = validate(root)
|
|
53
|
-
logger.info("✅ Language validation successful")
|
|
54
|
-
app = resolve(root)
|
|
55
|
-
logger.info("✅ Semantic validation successful")
|
|
37
|
+
# If printing is requested, load and print the document
|
|
56
38
|
if args.print:
|
|
57
|
-
|
|
58
|
-
(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
)
|
|
62
|
-
)
|
|
39
|
+
try:
|
|
40
|
+
print(loaded_data.model_dump_json(indent=2, exclude_none=True)) # type: ignore
|
|
41
|
+
except Exception as e:
|
|
42
|
+
logger.warning(f"Could not print document: {e}")
|
|
63
43
|
|
|
64
|
-
except
|
|
65
|
-
logger.error("❌
|
|
44
|
+
except LoadError as e:
|
|
45
|
+
logger.error(f"❌ Failed to load document: {e}")
|
|
46
|
+
sys.exit(1)
|
|
47
|
+
except ValidationError as e:
|
|
48
|
+
logger.error(f"❌ Validation failed: {e}")
|
|
66
49
|
sys.exit(1)
|
|
67
|
-
except
|
|
68
|
-
logger.error("❌
|
|
50
|
+
except SemanticError as e:
|
|
51
|
+
logger.error(f"❌ Semantic validation failed: {e}")
|
|
69
52
|
sys.exit(1)
|
|
70
|
-
except
|
|
71
|
-
logger.error("❌
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.error(f"❌ Unexpected error during validation: {e}")
|
|
72
55
|
sys.exit(1)
|
|
73
56
|
|
|
74
57
|
|
qtype/commands/visualize.py
CHANGED
|
@@ -2,61 +2,75 @@
|
|
|
2
2
|
Command-line interface for visualizing QType YAML spec files.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
5
7
|
import argparse
|
|
6
8
|
import logging
|
|
7
9
|
import tempfile
|
|
8
10
|
import webbrowser
|
|
11
|
+
from pathlib import Path
|
|
9
12
|
from typing import Any
|
|
10
13
|
|
|
11
|
-
from qtype.
|
|
12
|
-
from qtype.
|
|
14
|
+
from qtype.application.facade import QTypeFacade
|
|
15
|
+
from qtype.base.exceptions import LoadError, ValidationError
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger(__name__)
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
def main(args: Any) -> None:
|
|
18
21
|
"""
|
|
19
|
-
|
|
22
|
+
Visualize a QType YAML spec file.
|
|
20
23
|
|
|
21
24
|
Args:
|
|
22
25
|
args: Arguments passed from the command line or calling context.
|
|
23
26
|
|
|
24
27
|
Exits:
|
|
25
|
-
Exits with code 1 if
|
|
28
|
+
Exits with code 1 if visualization fails.
|
|
26
29
|
"""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
logger.info(f"
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
30
|
+
facade = QTypeFacade()
|
|
31
|
+
spec_path = Path(args.spec)
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
# Generate visualization using the facade
|
|
35
|
+
mermaid_content = facade.visualize_application(spec_path)
|
|
36
|
+
|
|
37
|
+
if args.output:
|
|
38
|
+
# Write to file
|
|
39
|
+
output_path = Path(args.output)
|
|
40
|
+
output_path.write_text(mermaid_content, encoding="utf-8")
|
|
41
|
+
logger.info(f"✅ Visualization saved to {output_path}")
|
|
42
|
+
|
|
43
|
+
if not args.no_display:
|
|
44
|
+
# Create temporary HTML file and open in browser
|
|
45
|
+
try:
|
|
46
|
+
import mermaid as md # type: ignore[import-untyped]
|
|
47
|
+
|
|
48
|
+
mm = md.Mermaid(mermaid_content)
|
|
49
|
+
html_content = mm._repr_html_()
|
|
50
|
+
|
|
51
|
+
with tempfile.NamedTemporaryFile(
|
|
52
|
+
mode="w", suffix=".html", delete=False, encoding="utf-8"
|
|
53
|
+
) as f:
|
|
54
|
+
f.write(html_content)
|
|
55
|
+
temp_file = f.name
|
|
56
|
+
|
|
57
|
+
logger.info(f"Opening visualization in browser: {temp_file}")
|
|
58
|
+
webbrowser.open(f"file://{temp_file}")
|
|
59
|
+
except ImportError:
|
|
60
|
+
logger.warning(
|
|
61
|
+
"❌ Mermaid library not installed. Cannot display in browser."
|
|
62
|
+
)
|
|
63
|
+
logger.info("Install with: pip install mermaid")
|
|
64
|
+
|
|
65
|
+
except LoadError as e:
|
|
66
|
+
logger.error(f"❌ Failed to load document: {e}")
|
|
67
|
+
exit(1)
|
|
68
|
+
except ValidationError as e:
|
|
69
|
+
logger.error(f"❌ Visualization failed: {e}")
|
|
70
|
+
exit(1)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error(f"❌ Unexpected error: {e}")
|
|
73
|
+
exit(1)
|
|
60
74
|
|
|
61
75
|
|
|
62
76
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
qtype/dsl/__init__.py
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
+
"""DSL package - core data models only."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .base_types import * # noqa: F403
|
|
6
|
+
from .domain_types import * # noqa: F403
|
|
1
7
|
from .model import * # noqa: F403
|
|
8
|
+
|
|
9
|
+
# Note: Validation logic has been moved to qtype.semantic package
|
|
10
|
+
# to avoid circular dependencies
|
qtype/dsl/base_types.py
CHANGED
|
@@ -24,6 +24,14 @@ class PrimitiveTypeEnum(str, Enum):
|
|
|
24
24
|
video = "video"
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class StepCardinality(str, Enum):
|
|
28
|
+
"""Does this step emit 1 (one) or 0...N (many) items?"""
|
|
29
|
+
|
|
30
|
+
one = "one"
|
|
31
|
+
many = "many"
|
|
32
|
+
auto = "auto" # Let's the step determine this in semantic resolution. Currently only Flows can do this..
|
|
33
|
+
|
|
34
|
+
|
|
27
35
|
class StrictBaseModel(BaseModel):
|
|
28
36
|
"""Base model with extra fields forbidden."""
|
|
29
37
|
|
qtype/dsl/custom_types.py
CHANGED
|
@@ -2,13 +2,13 @@ from typing import Any, ForwardRef, Type, Union
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, create_model
|
|
4
4
|
|
|
5
|
-
from qtype.converters.types import PRIMITIVE_TO_PYTHON_TYPE
|
|
5
|
+
from qtype.application.converters.types import PRIMITIVE_TO_PYTHON_TYPE
|
|
6
6
|
|
|
7
7
|
# --- This would be in your interpreter's logic ---
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def build_dynamic_types(
|
|
11
|
-
type_definitions: list[dict]
|
|
11
|
+
type_definitions: list[dict],
|
|
12
12
|
) -> dict[str, Type[BaseModel]]:
|
|
13
13
|
"""
|
|
14
14
|
Parses a list of simplified type definitions and dynamically creates
|
|
@@ -32,7 +32,7 @@ def build_dynamic_types(
|
|
|
32
32
|
if type_str.startswith("list[") and type_str.endswith("]"):
|
|
33
33
|
inner_type_name = type_str[5:-1]
|
|
34
34
|
inner_type, _ = _parse_type_string(inner_type_name)
|
|
35
|
-
resolved_type = list[inner_type]
|
|
35
|
+
resolved_type: Any = list[inner_type] # type: ignore[misc, valid-type]
|
|
36
36
|
elif type_str in PRIMITIVE_MAP:
|
|
37
37
|
resolved_type = PRIMITIVE_MAP[type_str]
|
|
38
38
|
elif type_str in created_models:
|
|
@@ -60,7 +60,9 @@ def build_dynamic_types(
|
|
|
60
60
|
|
|
61
61
|
# Pass the created_models dict as the local namespace for resolution
|
|
62
62
|
DynamicModel = create_model(
|
|
63
|
-
model_name,
|
|
63
|
+
model_name,
|
|
64
|
+
**field_definitions,
|
|
65
|
+
__localns=created_models, # type: ignore[call-overload]
|
|
64
66
|
)
|
|
65
67
|
created_models[model_name] = DynamicModel
|
|
66
68
|
|