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.
- qtype/application/commons/tools.py +1 -1
- qtype/application/converters/tools_from_api.py +5 -5
- qtype/application/converters/tools_from_module.py +2 -2
- qtype/application/converters/types.py +14 -43
- qtype/application/documentation.py +1 -1
- qtype/application/facade.py +94 -73
- qtype/base/types.py +227 -7
- qtype/cli.py +4 -0
- qtype/commands/convert.py +20 -8
- qtype/commands/generate.py +19 -27
- qtype/commands/run.py +73 -36
- qtype/commands/serve.py +74 -54
- qtype/commands/validate.py +34 -8
- qtype/commands/visualize.py +46 -22
- qtype/dsl/__init__.py +6 -5
- qtype/dsl/custom_types.py +1 -1
- qtype/dsl/domain_types.py +65 -5
- qtype/dsl/linker.py +384 -0
- qtype/dsl/loader.py +315 -0
- qtype/dsl/model.py +612 -363
- qtype/dsl/parser.py +200 -0
- qtype/dsl/types.py +50 -0
- qtype/interpreter/api.py +57 -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 +74 -0
- qtype/interpreter/base/factory.py +117 -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 +462 -22
- qtype/interpreter/converters.py +77 -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/decoder_executor.py +163 -0
- qtype/interpreter/executors/doc_to_text_executor.py +112 -0
- qtype/interpreter/executors/document_embedder_executor.py +107 -0
- qtype/interpreter/executors/document_search_executor.py +122 -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 +160 -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 +228 -0
- qtype/interpreter/executors/invoke_embedding_executor.py +92 -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 +159 -22
- 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/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_buildManifest.js +1 -1
- qtype/interpreter/ui/_next/static/chunks/{393-8fd474427f8e19ce.js → 434-b2112d19f25c44ff.js} +3 -3
- 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/css/8a8d1269e362fef7.css +3 -0
- qtype/interpreter/ui/icon.png +0 -0
- qtype/interpreter/ui/index.html +1 -1
- qtype/interpreter/ui/index.txt +4 -4
- qtype/semantic/checker.py +583 -0
- qtype/semantic/generate.py +262 -83
- qtype/semantic/loader.py +95 -0
- qtype/semantic/model.py +436 -159
- qtype/semantic/resolver.py +63 -19
- qtype/semantic/visualize.py +28 -31
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/METADATA +16 -3
- qtype-0.1.1.dist-info/RECORD +135 -0
- qtype/dsl/base_types.py +0 -38
- qtype/dsl/validator.py +0 -465
- qtype/interpreter/batch/__init__.py +0 -0
- qtype/interpreter/batch/file_sink_source.py +0 -162
- qtype/interpreter/batch/flow.py +0 -95
- qtype/interpreter/batch/sql_source.py +0 -92
- qtype/interpreter/batch/step.py +0 -74
- qtype/interpreter/batch/types.py +0 -41
- qtype/interpreter/batch/utils.py +0 -178
- 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 -171
- qtype/interpreter/steps/prompt_template.py +0 -54
- qtype/interpreter/steps/search.py +0 -24
- qtype/interpreter/steps/tool.py +0 -219
- qtype/interpreter/streaming_helpers.py +0 -123
- qtype/interpreter/ui/_next/static/chunks/app/page-7e26b6156cfb55d3.js +0 -1
- qtype/interpreter/ui/_next/static/chunks/ba12c10f-22556063851a6df2.js +0 -1
- qtype/interpreter/ui/_next/static/css/b40532b0db09cce3.css +0 -3
- qtype/interpreter/ui/favicon.ico +0 -0
- qtype/loader.py +0 -390
- qtype-0.0.16.dist-info/RECORD +0 -106
- /qtype/interpreter/ui/_next/static/{nUaw6_IwRwPqkzwe5s725 → 20HoJN6otZ_LyHLHpCPE6}/_ssgManifest.js +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/WHEEL +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/entry_points.txt +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/licenses/LICENSE +0 -0
- {qtype-0.0.16.dist-info → qtype-0.1.1.dist-info}/top_level.txt +0 -0
qtype/commands/generate.py
CHANGED
|
@@ -92,6 +92,17 @@ def generate_schema(args: argparse.Namespace) -> None:
|
|
|
92
92
|
if "$defs" not in schema:
|
|
93
93
|
schema["$defs"] = {}
|
|
94
94
|
|
|
95
|
+
# Note: Custom YAML tags (!include, !include_raw) and environment variable
|
|
96
|
+
# substitution (${VAR}) are handled by the QType YAML loader at parse time,
|
|
97
|
+
# not by JSON Schema validation. We define them in $defs for documentation
|
|
98
|
+
# purposes, but we don't apply them to string fields since:
|
|
99
|
+
# 1. They would cause false positives (e.g., "localhost" matching as valid)
|
|
100
|
+
# 2. The YAML loader processes these before schema validation occurs
|
|
101
|
+
# 3. After YAML loading, the schema sees the resolved/substituted values
|
|
102
|
+
#
|
|
103
|
+
# Schema validation happens on the post-processed document structure,
|
|
104
|
+
# so we don't need to (and shouldn't) validate the raw YAML tag syntax.
|
|
105
|
+
|
|
95
106
|
# Define custom YAML tags used by QType loader
|
|
96
107
|
schema["$defs"]["qtype_include_tag"] = {
|
|
97
108
|
"type": "string",
|
|
@@ -111,29 +122,6 @@ def generate_schema(args: argparse.Namespace) -> None:
|
|
|
111
122
|
"description": "String with environment variable substitution using ${VAR_NAME} or ${VAR_NAME:default} syntax",
|
|
112
123
|
}
|
|
113
124
|
|
|
114
|
-
# Add these custom patterns to string types throughout the schema
|
|
115
|
-
def add_custom_patterns(obj):
|
|
116
|
-
if isinstance(obj, dict):
|
|
117
|
-
if obj.get("type") == "string" and "anyOf" not in obj:
|
|
118
|
-
# Add anyOf to allow either regular strings or custom tag patterns
|
|
119
|
-
original_obj = obj.copy()
|
|
120
|
-
obj.clear()
|
|
121
|
-
obj["anyOf"] = [
|
|
122
|
-
original_obj,
|
|
123
|
-
{"$ref": "#/$defs/qtype_include_tag"},
|
|
124
|
-
{"$ref": "#/$defs/qtype_include_raw_tag"},
|
|
125
|
-
{"$ref": "#/$defs/qtype_env_var"},
|
|
126
|
-
]
|
|
127
|
-
else:
|
|
128
|
-
for value in obj.values():
|
|
129
|
-
add_custom_patterns(value)
|
|
130
|
-
elif isinstance(obj, list):
|
|
131
|
-
for item in obj:
|
|
132
|
-
add_custom_patterns(item)
|
|
133
|
-
|
|
134
|
-
# Apply custom patterns to the schema
|
|
135
|
-
add_custom_patterns(schema)
|
|
136
|
-
|
|
137
125
|
output = json.dumps(schema, indent=2)
|
|
138
126
|
output_path: Optional[str] = getattr(args, "output", None)
|
|
139
127
|
if output_path:
|
|
@@ -198,6 +186,14 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
198
186
|
import networkx # noqa: F401
|
|
199
187
|
import ruff # type: ignore[import-untyped] # noqa: F401
|
|
200
188
|
|
|
189
|
+
has_semantic_deps = True
|
|
190
|
+
except ImportError:
|
|
191
|
+
logger.warning(
|
|
192
|
+
"NetworkX or Ruff is not installed. Skipping semantic model generation."
|
|
193
|
+
)
|
|
194
|
+
has_semantic_deps = False
|
|
195
|
+
|
|
196
|
+
if has_semantic_deps:
|
|
201
197
|
from qtype.semantic.generate import generate_semantic_model
|
|
202
198
|
|
|
203
199
|
semantic_parser = generate_subparsers.add_parser(
|
|
@@ -212,7 +208,3 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
212
208
|
help="Output file for the semantic model (default: stdout)",
|
|
213
209
|
)
|
|
214
210
|
semantic_parser.set_defaults(func=generate_semantic_model)
|
|
215
|
-
except ImportError:
|
|
216
|
-
logger.debug(
|
|
217
|
-
"NetworkX or Ruff is not installed. Skipping semantic model generation."
|
|
218
|
-
)
|
qtype/commands/run.py
CHANGED
|
@@ -7,11 +7,12 @@ from __future__ import annotations
|
|
|
7
7
|
import argparse
|
|
8
8
|
import json
|
|
9
9
|
import logging
|
|
10
|
+
import warnings
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import Any
|
|
12
13
|
|
|
13
|
-
import magic
|
|
14
14
|
import pandas as pd
|
|
15
|
+
from pydantic.warnings import UnsupportedFieldAttributeWarning
|
|
15
16
|
|
|
16
17
|
from qtype.application.facade import QTypeFacade
|
|
17
18
|
from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
@@ -19,14 +20,41 @@ from qtype.base.exceptions import InterpreterError, LoadError, ValidationError
|
|
|
19
20
|
logger = logging.getLogger(__name__)
|
|
20
21
|
|
|
21
22
|
|
|
23
|
+
# Supress specific pydantic warnings that llamaindex needs to fix
|
|
24
|
+
warnings.filterwarnings("ignore", category=UnsupportedFieldAttributeWarning)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# supress qdrant logging
|
|
28
|
+
for name in ["httpx", "urllib3", "qdrant_client"]:
|
|
29
|
+
logging.getLogger(name).setLevel(logging.WARNING)
|
|
30
|
+
|
|
31
|
+
|
|
22
32
|
def read_data_from_file(file_path: str) -> pd.DataFrame:
|
|
23
33
|
"""
|
|
24
34
|
Reads a file into a pandas DataFrame based on its MIME type.
|
|
25
35
|
"""
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
import magic
|
|
39
|
+
|
|
26
40
|
mime_type = magic.Magic(mime=True).from_file(file_path)
|
|
27
41
|
|
|
28
42
|
if mime_type == "text/csv":
|
|
29
43
|
return pd.read_csv(file_path)
|
|
44
|
+
elif mime_type == "text/plain":
|
|
45
|
+
# For text/plain, use file extension to determine format
|
|
46
|
+
file_ext = Path(file_path).suffix.lower()
|
|
47
|
+
if file_ext == ".csv":
|
|
48
|
+
return pd.read_csv(file_path)
|
|
49
|
+
elif file_ext == ".json":
|
|
50
|
+
return pd.read_json(file_path)
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
(
|
|
54
|
+
f"Unsupported text/plain file extension: {file_ext}. "
|
|
55
|
+
"Supported extensions: .csv, .json"
|
|
56
|
+
)
|
|
57
|
+
)
|
|
30
58
|
elif mime_type == "application/json":
|
|
31
59
|
return pd.read_json(file_path)
|
|
32
60
|
elif mime_type in [
|
|
@@ -48,6 +76,8 @@ def run_flow(args: Any) -> None:
|
|
|
48
76
|
Args:
|
|
49
77
|
args: Arguments passed from the command line or calling context.
|
|
50
78
|
"""
|
|
79
|
+
import asyncio
|
|
80
|
+
|
|
51
81
|
facade = QTypeFacade()
|
|
52
82
|
spec_path = Path(args.spec)
|
|
53
83
|
|
|
@@ -65,54 +95,56 @@ def run_flow(args: Any) -> None:
|
|
|
65
95
|
logger.error(f"❌ Invalid JSON input: {e}")
|
|
66
96
|
return
|
|
67
97
|
|
|
68
|
-
# Execute the workflow using the facade
|
|
69
|
-
|
|
70
|
-
|
|
98
|
+
# Execute the workflow using the facade (now async, returns DataFrame)
|
|
99
|
+
result_df = asyncio.run(
|
|
100
|
+
facade.execute_workflow(
|
|
101
|
+
spec_path,
|
|
102
|
+
flow_name=args.flow,
|
|
103
|
+
inputs=input,
|
|
104
|
+
show_progress=args.progress,
|
|
105
|
+
)
|
|
71
106
|
)
|
|
72
107
|
|
|
73
108
|
logger.info("✅ Flow execution completed successfully")
|
|
74
109
|
|
|
75
|
-
#
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
110
|
+
# Display results
|
|
111
|
+
if len(result_df) > 0:
|
|
112
|
+
logger.info(f"Processed {len(result_df)} em")
|
|
113
|
+
|
|
114
|
+
# Remove 'row' and 'error' columns for display if all errors are None
|
|
115
|
+
display_df = result_df.copy()
|
|
116
|
+
if (
|
|
117
|
+
"error" in display_df.columns
|
|
118
|
+
and display_df["error"].isna().all()
|
|
119
|
+
):
|
|
120
|
+
display_df = display_df.drop(columns=["error"])
|
|
121
|
+
if "row" in display_df.columns:
|
|
122
|
+
display_df = display_df.drop(columns=["row"])
|
|
123
|
+
|
|
124
|
+
if len(display_df) > 1:
|
|
125
|
+
logger.info(f"\nResults:\n{display_df[0:10].to_string()}\n...")
|
|
126
|
+
else:
|
|
127
|
+
# Print the first row with column_name: value one per line
|
|
128
|
+
fmt_str = []
|
|
129
|
+
for col, val in display_df.iloc[0].items():
|
|
130
|
+
fmt_str.append(f"{col}: {val}")
|
|
131
|
+
fmt_str = "\n".join(fmt_str)
|
|
132
|
+
logger.info(f"\nResults:\n{fmt_str}")
|
|
133
|
+
|
|
134
|
+
# Save the output
|
|
135
|
+
if args.output:
|
|
136
|
+
# Save full DataFrame with row and error columns
|
|
137
|
+
result_df.to_parquet(args.output)
|
|
138
|
+
logger.info(f"Output saved to {args.output}")
|
|
95
139
|
else:
|
|
96
140
|
logger.info("Flow completed with no output")
|
|
97
141
|
|
|
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
142
|
except LoadError as e:
|
|
108
143
|
logger.error(f"❌ Failed to load document: {e}")
|
|
109
144
|
except ValidationError as e:
|
|
110
145
|
logger.error(f"❌ Validation failed: {e}")
|
|
111
146
|
except InterpreterError as e:
|
|
112
147
|
logger.error(f"❌ Execution failed: {e}")
|
|
113
|
-
except Exception as e:
|
|
114
|
-
logger.error(f"❌ Unexpected error: {e}", exc_info=True)
|
|
115
|
-
pass
|
|
116
148
|
|
|
117
149
|
|
|
118
150
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -154,6 +186,11 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
154
186
|
default=None,
|
|
155
187
|
help="Path to save output data. If input is a DataFrame, output will be saved as parquet. If single result, saved as JSON.",
|
|
156
188
|
)
|
|
189
|
+
cmd_parser.add_argument(
|
|
190
|
+
"--progress",
|
|
191
|
+
action="store_true",
|
|
192
|
+
help="Show progress bars during flow execution.",
|
|
193
|
+
)
|
|
157
194
|
|
|
158
195
|
cmd_parser.add_argument(
|
|
159
196
|
"spec", type=str, help="Path to the QType YAML spec file."
|
qtype/commands/serve.py
CHANGED
|
@@ -6,71 +6,91 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import logging
|
|
9
|
+
import os
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
13
|
import uvicorn
|
|
13
14
|
|
|
14
|
-
from qtype.
|
|
15
|
-
from qtype.
|
|
15
|
+
from qtype.base.exceptions import ValidationError
|
|
16
|
+
from qtype.semantic.loader import load
|
|
17
|
+
from qtype.semantic.model import Application
|
|
16
18
|
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
20
|
-
def
|
|
22
|
+
def create_api_app() -> Any:
|
|
23
|
+
"""Factory function to create FastAPI app.
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
FastAPI application instance.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
RuntimeError: If QTYPE_SPEC_PATH not set in environment.
|
|
30
|
+
ValidationError: If spec is not an Application document.
|
|
31
|
+
"""
|
|
32
|
+
from qtype.interpreter.api import APIExecutor
|
|
33
|
+
|
|
34
|
+
spec_path_str = os.environ["_QTYPE_SPEC_PATH"]
|
|
35
|
+
|
|
36
|
+
spec_path = Path(spec_path_str)
|
|
37
|
+
logger.info(f"Loading spec: {spec_path}")
|
|
38
|
+
|
|
39
|
+
semantic_model, _ = load(spec_path)
|
|
40
|
+
if not isinstance(semantic_model, Application):
|
|
41
|
+
raise ValidationError("Can only serve Application documents")
|
|
42
|
+
|
|
43
|
+
logger.info(f"✅ Successfully loaded spec: {spec_path}")
|
|
44
|
+
|
|
45
|
+
# Derive name from spec filename
|
|
46
|
+
name = spec_path.name.replace(".qtype.yaml", "").replace("_", " ").title()
|
|
47
|
+
|
|
48
|
+
# Get host/port from environment (set by uvicorn)
|
|
49
|
+
host = os.environ["_QTYPE_HOST"]
|
|
50
|
+
port = int(os.environ["_QTYPE_PORT"])
|
|
51
|
+
|
|
52
|
+
# Create server info for OpenAPI spec
|
|
53
|
+
servers = [
|
|
54
|
+
{
|
|
55
|
+
"url": f"http://{host}:{port}",
|
|
56
|
+
"description": "Development server",
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
api_executor = APIExecutor(semantic_model, host, port)
|
|
61
|
+
return api_executor.create_app(
|
|
62
|
+
name=name,
|
|
63
|
+
ui_enabled=True,
|
|
64
|
+
servers=servers,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def serve(args: argparse.Namespace) -> None:
|
|
21
69
|
"""Run a QType YAML spec file as an API.
|
|
22
70
|
|
|
23
71
|
Args:
|
|
24
|
-
args: Arguments passed from the command line
|
|
72
|
+
args: Arguments passed from the command line.
|
|
25
73
|
"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
api_executor = APIExecutor(semantic_model)
|
|
47
|
-
|
|
48
|
-
# Create server info for OpenAPI spec
|
|
49
|
-
servers = [
|
|
50
|
-
{
|
|
51
|
-
"url": f"http://{args.host}:{args.port}",
|
|
52
|
-
"description": "Development server",
|
|
53
|
-
}
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
fastapi_app = api_executor.create_app(
|
|
57
|
-
name=name, ui_enabled=not args.disable_ui, servers=servers
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
# Start the server
|
|
61
|
-
uvicorn.run(
|
|
62
|
-
fastapi_app, host=args.host, port=args.port, log_level="info"
|
|
63
|
-
)
|
|
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"❌ Validation failed: {e}")
|
|
70
|
-
exit(1)
|
|
71
|
-
except Exception as e:
|
|
72
|
-
logger.error(f"❌ Unexpected error starting server: {e}")
|
|
73
|
-
exit(1)
|
|
74
|
+
# Set environment variables for factory function
|
|
75
|
+
os.environ["_QTYPE_SPEC_PATH"] = args.spec
|
|
76
|
+
os.environ["_QTYPE_HOST"] = args.host
|
|
77
|
+
os.environ["_QTYPE_PORT"] = str(args.port)
|
|
78
|
+
|
|
79
|
+
logger.info(
|
|
80
|
+
f"Starting server on {args.host}:{args.port}"
|
|
81
|
+
f"{' (reload enabled)' if args.reload else ''}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Use factory mode with import string
|
|
85
|
+
uvicorn.run(
|
|
86
|
+
"qtype.commands.serve:create_api_app",
|
|
87
|
+
factory=True,
|
|
88
|
+
host=args.host,
|
|
89
|
+
port=args.port,
|
|
90
|
+
log_level="info",
|
|
91
|
+
reload=args.reload,
|
|
92
|
+
reload_includes=[args.spec] if args.reload else None,
|
|
93
|
+
)
|
|
74
94
|
|
|
75
95
|
|
|
76
96
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -86,9 +106,9 @@ def parser(subparsers: argparse._SubParsersAction) -> None:
|
|
|
86
106
|
cmd_parser.add_argument("-p", "--port", type=int, default=8000)
|
|
87
107
|
cmd_parser.add_argument("-H", "--host", type=str, default="localhost")
|
|
88
108
|
cmd_parser.add_argument(
|
|
89
|
-
"--
|
|
109
|
+
"--reload",
|
|
90
110
|
action="store_true",
|
|
91
|
-
help="
|
|
111
|
+
help="Enable auto-reload on code changes (default: False).",
|
|
92
112
|
)
|
|
93
113
|
cmd_parser.set_defaults(func=serve)
|
|
94
114
|
|
qtype/commands/validate.py
CHANGED
|
@@ -10,9 +10,12 @@ import sys
|
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
|
-
from qtype import dsl
|
|
14
|
-
from qtype.application.facade import QTypeFacade
|
|
15
13
|
from qtype.base.exceptions import LoadError, SemanticError, ValidationError
|
|
14
|
+
from qtype.dsl.linker import DuplicateComponentError, ReferenceNotFoundError
|
|
15
|
+
from qtype.dsl.loader import YAMLLoadError
|
|
16
|
+
from qtype.dsl.model import Application as DSLApplication
|
|
17
|
+
from qtype.dsl.model import Document
|
|
18
|
+
from qtype.semantic.loader import load
|
|
16
19
|
|
|
17
20
|
logger = logging.getLogger(__name__)
|
|
18
21
|
|
|
@@ -27,19 +30,33 @@ def main(args: Any) -> None:
|
|
|
27
30
|
Exits:
|
|
28
31
|
Exits with code 1 if validation fails.
|
|
29
32
|
"""
|
|
30
|
-
facade = QTypeFacade()
|
|
31
33
|
spec_path = Path(args.spec)
|
|
32
34
|
|
|
33
35
|
try:
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
loaded_data, custom_types = facade.load_semantic_model(spec_path)
|
|
36
|
+
# Load and validate the document (works for all document types)
|
|
37
|
+
# This includes: YAML parsing, Pydantic validation, linking, and semantic checks
|
|
38
|
+
loaded_data, custom_types = load(spec_path)
|
|
38
39
|
logger.info("✅ Validation successful - document is valid.")
|
|
39
40
|
|
|
40
41
|
except LoadError as e:
|
|
41
42
|
logger.error(f"❌ Failed to load document: {e}")
|
|
42
43
|
sys.exit(1)
|
|
44
|
+
except YAMLLoadError as e:
|
|
45
|
+
# YAML syntax errors
|
|
46
|
+
logger.error(f"❌ {e}")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
except DuplicateComponentError as e:
|
|
49
|
+
# Duplicate ID errors during linking
|
|
50
|
+
logger.error(f"❌ {e}")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
except ReferenceNotFoundError as e:
|
|
53
|
+
# Reference resolution errors during linking
|
|
54
|
+
logger.error(f"❌ {e}")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
# Pydantic validation errors from parse_document
|
|
58
|
+
logger.error(f"❌ {e}")
|
|
59
|
+
sys.exit(1)
|
|
43
60
|
except ValidationError as e:
|
|
44
61
|
logger.error(f"❌ Validation failed: {e}")
|
|
45
62
|
sys.exit(1)
|
|
@@ -49,7 +66,16 @@ def main(args: Any) -> None:
|
|
|
49
66
|
|
|
50
67
|
# If printing is requested, load and print the document
|
|
51
68
|
if args.print:
|
|
52
|
-
|
|
69
|
+
from pydantic_yaml import to_yaml_str
|
|
70
|
+
|
|
71
|
+
# Wrap in Document if it's a DSL Application
|
|
72
|
+
if isinstance(loaded_data, DSLApplication):
|
|
73
|
+
wrapped = Document(root=loaded_data)
|
|
74
|
+
else:
|
|
75
|
+
wrapped = loaded_data
|
|
76
|
+
logging.info(
|
|
77
|
+
to_yaml_str(wrapped, exclude_unset=True, exclude_none=True)
|
|
78
|
+
)
|
|
53
79
|
|
|
54
80
|
|
|
55
81
|
def parser(subparsers: argparse._SubParsersAction) -> None:
|
qtype/commands/visualize.py
CHANGED
|
@@ -6,13 +6,17 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import argparse
|
|
8
8
|
import logging
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
9
11
|
import tempfile
|
|
10
12
|
import webbrowser
|
|
11
13
|
from pathlib import Path
|
|
12
14
|
from typing import Any
|
|
13
15
|
|
|
14
|
-
from qtype.application.facade import QTypeFacade
|
|
15
16
|
from qtype.base.exceptions import LoadError, ValidationError
|
|
17
|
+
from qtype.semantic.loader import load
|
|
18
|
+
from qtype.semantic.model import Application
|
|
19
|
+
from qtype.semantic.visualize import visualize_application
|
|
16
20
|
|
|
17
21
|
logger = logging.getLogger(__name__)
|
|
18
22
|
|
|
@@ -27,12 +31,13 @@ def main(args: Any) -> None:
|
|
|
27
31
|
Exits:
|
|
28
32
|
Exits with code 1 if visualization fails.
|
|
29
33
|
"""
|
|
30
|
-
facade = QTypeFacade()
|
|
31
34
|
spec_path = Path(args.spec)
|
|
32
35
|
|
|
33
36
|
try:
|
|
34
|
-
#
|
|
35
|
-
|
|
37
|
+
# Load and generate visualization
|
|
38
|
+
semantic_model, _ = load(spec_path)
|
|
39
|
+
assert isinstance(semantic_model, Application)
|
|
40
|
+
mermaid_content = visualize_application(semantic_model)
|
|
36
41
|
|
|
37
42
|
if args.output:
|
|
38
43
|
# Write to file
|
|
@@ -41,26 +46,45 @@ def main(args: Any) -> None:
|
|
|
41
46
|
logger.info(f"✅ Visualization saved to {output_path}")
|
|
42
47
|
|
|
43
48
|
if not args.no_display:
|
|
44
|
-
#
|
|
49
|
+
# Check if mmdc is available
|
|
50
|
+
if shutil.which("mmdc") is None:
|
|
51
|
+
logger.error(
|
|
52
|
+
"❌ mmdc command not found. Please install mermaid-cli."
|
|
53
|
+
)
|
|
54
|
+
logger.info(
|
|
55
|
+
"Install with: npm install -g @mermaid-js/mermaid-cli"
|
|
56
|
+
)
|
|
57
|
+
exit(1)
|
|
58
|
+
|
|
59
|
+
# Create temporary directory and run mmdc from within it
|
|
45
60
|
try:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
# Create a temporary directory
|
|
62
|
+
temp_dir = tempfile.mkdtemp()
|
|
63
|
+
temp_dir_path = Path(temp_dir)
|
|
64
|
+
|
|
65
|
+
# Write mermaid file with simple names in the temp directory
|
|
66
|
+
mmd_file_path = temp_dir_path / "diagram.mmd"
|
|
67
|
+
svg_file_path = temp_dir_path / "diagram.svg"
|
|
68
|
+
|
|
69
|
+
mmd_file_path.write_text(mermaid_content, encoding="utf-8")
|
|
70
|
+
|
|
71
|
+
# Run mmdc from within the temporary directory
|
|
72
|
+
subprocess.run(
|
|
73
|
+
["mmdc", "-i", "diagram.mmd", "-o", "diagram.svg"],
|
|
74
|
+
cwd=temp_dir,
|
|
75
|
+
capture_output=True,
|
|
76
|
+
text=True,
|
|
77
|
+
check=True,
|
|
62
78
|
)
|
|
63
|
-
|
|
79
|
+
|
|
80
|
+
logger.info(
|
|
81
|
+
f"Opening visualization in browser: {svg_file_path}"
|
|
82
|
+
)
|
|
83
|
+
webbrowser.open(f"file://{svg_file_path}")
|
|
84
|
+
|
|
85
|
+
except subprocess.CalledProcessError as e:
|
|
86
|
+
logger.error(f"❌ Failed to generate SVG: {e.stderr}")
|
|
87
|
+
exit(1)
|
|
64
88
|
|
|
65
89
|
except LoadError as e:
|
|
66
90
|
logger.error(f"❌ Failed to load document: {e}")
|
qtype/dsl/__init__.py
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
from .
|
|
5
|
+
# Import all public symbols from submodules
|
|
6
|
+
# The star import is intentional here - it's controlled by __all__ in each module
|
|
7
|
+
from qtype.base.types import Reference # noqa: F401
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
from .domain_types import * # noqa: F403, F401
|
|
10
|
+
from .loader import YAMLLoadError # noqa: F401
|
|
11
|
+
from .model import * # noqa: F403, F401
|
qtype/dsl/custom_types.py
CHANGED
|
@@ -2,7 +2,7 @@ from typing import Any, ForwardRef, Type, Union
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, create_model
|
|
4
4
|
|
|
5
|
-
from qtype.
|
|
5
|
+
from qtype.dsl.types import PRIMITIVE_TO_PYTHON_TYPE
|
|
6
6
|
|
|
7
7
|
# --- This would be in your interpreter's logic ---
|
|
8
8
|
|