hexdag 0.5.0.dev1__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 (261) hide show
  1. hexdag/__init__.py +116 -0
  2. hexdag/__main__.py +30 -0
  3. hexdag/adapters/executors/__init__.py +5 -0
  4. hexdag/adapters/executors/local_executor.py +316 -0
  5. hexdag/builtin/__init__.py +6 -0
  6. hexdag/builtin/adapters/__init__.py +51 -0
  7. hexdag/builtin/adapters/anthropic/__init__.py +5 -0
  8. hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
  9. hexdag/builtin/adapters/database/__init__.py +6 -0
  10. hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
  11. hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
  12. hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
  13. hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
  14. hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
  15. hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
  16. hexdag/builtin/adapters/local/README.md +59 -0
  17. hexdag/builtin/adapters/local/__init__.py +7 -0
  18. hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
  19. hexdag/builtin/adapters/memory/__init__.py +47 -0
  20. hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
  21. hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
  22. hexdag/builtin/adapters/memory/schemas.py +57 -0
  23. hexdag/builtin/adapters/memory/session_memory.py +178 -0
  24. hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
  25. hexdag/builtin/adapters/memory/state_memory.py +280 -0
  26. hexdag/builtin/adapters/mock/README.md +89 -0
  27. hexdag/builtin/adapters/mock/__init__.py +15 -0
  28. hexdag/builtin/adapters/mock/hexdag.toml +50 -0
  29. hexdag/builtin/adapters/mock/mock_database.py +225 -0
  30. hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
  31. hexdag/builtin/adapters/mock/mock_llm.py +177 -0
  32. hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
  33. hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
  34. hexdag/builtin/adapters/openai/__init__.py +5 -0
  35. hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
  36. hexdag/builtin/adapters/secret/__init__.py +7 -0
  37. hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
  38. hexdag/builtin/adapters/unified_tool_router.py +280 -0
  39. hexdag/builtin/macros/__init__.py +17 -0
  40. hexdag/builtin/macros/conversation_agent.py +390 -0
  41. hexdag/builtin/macros/llm_macro.py +151 -0
  42. hexdag/builtin/macros/reasoning_agent.py +423 -0
  43. hexdag/builtin/macros/tool_macro.py +380 -0
  44. hexdag/builtin/nodes/__init__.py +38 -0
  45. hexdag/builtin/nodes/_discovery.py +123 -0
  46. hexdag/builtin/nodes/agent_node.py +696 -0
  47. hexdag/builtin/nodes/base_node_factory.py +242 -0
  48. hexdag/builtin/nodes/composite_node.py +926 -0
  49. hexdag/builtin/nodes/data_node.py +201 -0
  50. hexdag/builtin/nodes/expression_node.py +487 -0
  51. hexdag/builtin/nodes/function_node.py +454 -0
  52. hexdag/builtin/nodes/llm_node.py +491 -0
  53. hexdag/builtin/nodes/loop_node.py +920 -0
  54. hexdag/builtin/nodes/mapped_input.py +518 -0
  55. hexdag/builtin/nodes/port_call_node.py +269 -0
  56. hexdag/builtin/nodes/tool_call_node.py +195 -0
  57. hexdag/builtin/nodes/tool_utils.py +390 -0
  58. hexdag/builtin/prompts/__init__.py +68 -0
  59. hexdag/builtin/prompts/base.py +422 -0
  60. hexdag/builtin/prompts/chat_prompts.py +303 -0
  61. hexdag/builtin/prompts/error_correction_prompts.py +320 -0
  62. hexdag/builtin/prompts/tool_prompts.py +160 -0
  63. hexdag/builtin/tools/builtin_tools.py +84 -0
  64. hexdag/builtin/tools/database_tools.py +164 -0
  65. hexdag/cli/__init__.py +17 -0
  66. hexdag/cli/__main__.py +7 -0
  67. hexdag/cli/commands/__init__.py +27 -0
  68. hexdag/cli/commands/build_cmd.py +812 -0
  69. hexdag/cli/commands/create_cmd.py +208 -0
  70. hexdag/cli/commands/docs_cmd.py +293 -0
  71. hexdag/cli/commands/generate_types_cmd.py +252 -0
  72. hexdag/cli/commands/init_cmd.py +188 -0
  73. hexdag/cli/commands/pipeline_cmd.py +494 -0
  74. hexdag/cli/commands/plugin_dev_cmd.py +529 -0
  75. hexdag/cli/commands/plugins_cmd.py +441 -0
  76. hexdag/cli/commands/studio_cmd.py +101 -0
  77. hexdag/cli/commands/validate_cmd.py +221 -0
  78. hexdag/cli/main.py +84 -0
  79. hexdag/core/__init__.py +83 -0
  80. hexdag/core/config/__init__.py +20 -0
  81. hexdag/core/config/loader.py +479 -0
  82. hexdag/core/config/models.py +150 -0
  83. hexdag/core/configurable.py +294 -0
  84. hexdag/core/context/__init__.py +37 -0
  85. hexdag/core/context/execution_context.py +378 -0
  86. hexdag/core/docs/__init__.py +26 -0
  87. hexdag/core/docs/extractors.py +678 -0
  88. hexdag/core/docs/generators.py +890 -0
  89. hexdag/core/docs/models.py +120 -0
  90. hexdag/core/domain/__init__.py +10 -0
  91. hexdag/core/domain/dag.py +1225 -0
  92. hexdag/core/exceptions.py +234 -0
  93. hexdag/core/expression_parser.py +569 -0
  94. hexdag/core/logging.py +449 -0
  95. hexdag/core/models/__init__.py +17 -0
  96. hexdag/core/models/base.py +138 -0
  97. hexdag/core/orchestration/__init__.py +46 -0
  98. hexdag/core/orchestration/body_executor.py +481 -0
  99. hexdag/core/orchestration/components/__init__.py +97 -0
  100. hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
  101. hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
  102. hexdag/core/orchestration/components/execution_coordinator.py +360 -0
  103. hexdag/core/orchestration/components/health_check_manager.py +176 -0
  104. hexdag/core/orchestration/components/input_mapper.py +143 -0
  105. hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
  106. hexdag/core/orchestration/components/node_executor.py +377 -0
  107. hexdag/core/orchestration/components/secret_manager.py +202 -0
  108. hexdag/core/orchestration/components/wave_executor.py +158 -0
  109. hexdag/core/orchestration/constants.py +17 -0
  110. hexdag/core/orchestration/events/README.md +312 -0
  111. hexdag/core/orchestration/events/__init__.py +104 -0
  112. hexdag/core/orchestration/events/batching.py +330 -0
  113. hexdag/core/orchestration/events/decorators.py +139 -0
  114. hexdag/core/orchestration/events/events.py +573 -0
  115. hexdag/core/orchestration/events/observers/__init__.py +30 -0
  116. hexdag/core/orchestration/events/observers/core_observers.py +690 -0
  117. hexdag/core/orchestration/events/observers/models.py +111 -0
  118. hexdag/core/orchestration/events/taxonomy.py +269 -0
  119. hexdag/core/orchestration/hook_context.py +237 -0
  120. hexdag/core/orchestration/hooks.py +437 -0
  121. hexdag/core/orchestration/models.py +418 -0
  122. hexdag/core/orchestration/orchestrator.py +910 -0
  123. hexdag/core/orchestration/orchestrator_factory.py +275 -0
  124. hexdag/core/orchestration/port_wrappers.py +327 -0
  125. hexdag/core/orchestration/prompt/__init__.py +32 -0
  126. hexdag/core/orchestration/prompt/template.py +332 -0
  127. hexdag/core/pipeline_builder/__init__.py +21 -0
  128. hexdag/core/pipeline_builder/component_instantiator.py +386 -0
  129. hexdag/core/pipeline_builder/include_tag.py +265 -0
  130. hexdag/core/pipeline_builder/pipeline_config.py +133 -0
  131. hexdag/core/pipeline_builder/py_tag.py +223 -0
  132. hexdag/core/pipeline_builder/tag_discovery.py +268 -0
  133. hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
  134. hexdag/core/pipeline_builder/yaml_validator.py +569 -0
  135. hexdag/core/ports/__init__.py +65 -0
  136. hexdag/core/ports/api_call.py +133 -0
  137. hexdag/core/ports/database.py +489 -0
  138. hexdag/core/ports/embedding.py +215 -0
  139. hexdag/core/ports/executor.py +237 -0
  140. hexdag/core/ports/file_storage.py +117 -0
  141. hexdag/core/ports/healthcheck.py +87 -0
  142. hexdag/core/ports/llm.py +551 -0
  143. hexdag/core/ports/memory.py +70 -0
  144. hexdag/core/ports/observer_manager.py +130 -0
  145. hexdag/core/ports/secret.py +145 -0
  146. hexdag/core/ports/tool_router.py +94 -0
  147. hexdag/core/ports_builder.py +623 -0
  148. hexdag/core/protocols.py +273 -0
  149. hexdag/core/resolver.py +304 -0
  150. hexdag/core/schema/__init__.py +9 -0
  151. hexdag/core/schema/generator.py +742 -0
  152. hexdag/core/secrets.py +242 -0
  153. hexdag/core/types.py +413 -0
  154. hexdag/core/utils/async_warnings.py +206 -0
  155. hexdag/core/utils/schema_conversion.py +78 -0
  156. hexdag/core/utils/sql_validation.py +86 -0
  157. hexdag/core/validation/secure_json.py +148 -0
  158. hexdag/core/yaml_macro.py +517 -0
  159. hexdag/mcp_server.py +3120 -0
  160. hexdag/studio/__init__.py +10 -0
  161. hexdag/studio/build_ui.py +92 -0
  162. hexdag/studio/server/__init__.py +1 -0
  163. hexdag/studio/server/main.py +100 -0
  164. hexdag/studio/server/routes/__init__.py +9 -0
  165. hexdag/studio/server/routes/execute.py +208 -0
  166. hexdag/studio/server/routes/export.py +558 -0
  167. hexdag/studio/server/routes/files.py +207 -0
  168. hexdag/studio/server/routes/plugins.py +419 -0
  169. hexdag/studio/server/routes/validate.py +220 -0
  170. hexdag/studio/ui/index.html +13 -0
  171. hexdag/studio/ui/package-lock.json +2992 -0
  172. hexdag/studio/ui/package.json +31 -0
  173. hexdag/studio/ui/postcss.config.js +6 -0
  174. hexdag/studio/ui/public/hexdag.svg +5 -0
  175. hexdag/studio/ui/src/App.tsx +251 -0
  176. hexdag/studio/ui/src/components/Canvas.tsx +408 -0
  177. hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
  178. hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
  179. hexdag/studio/ui/src/components/Header.tsx +181 -0
  180. hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
  181. hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
  182. hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
  183. hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
  184. hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
  185. hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
  186. hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
  187. hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
  188. hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
  189. hexdag/studio/ui/src/components/index.ts +8 -0
  190. hexdag/studio/ui/src/index.css +92 -0
  191. hexdag/studio/ui/src/main.tsx +10 -0
  192. hexdag/studio/ui/src/types/index.ts +123 -0
  193. hexdag/studio/ui/src/vite-env.d.ts +1 -0
  194. hexdag/studio/ui/tailwind.config.js +29 -0
  195. hexdag/studio/ui/tsconfig.json +37 -0
  196. hexdag/studio/ui/tsconfig.node.json +13 -0
  197. hexdag/studio/ui/vite.config.ts +35 -0
  198. hexdag/visualization/__init__.py +69 -0
  199. hexdag/visualization/dag_visualizer.py +1020 -0
  200. hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
  201. hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
  202. hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
  203. hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
  204. hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
  205. hexdag_plugins/.gitignore +43 -0
  206. hexdag_plugins/README.md +73 -0
  207. hexdag_plugins/__init__.py +1 -0
  208. hexdag_plugins/azure/LICENSE +21 -0
  209. hexdag_plugins/azure/README.md +414 -0
  210. hexdag_plugins/azure/__init__.py +21 -0
  211. hexdag_plugins/azure/azure_blob_adapter.py +450 -0
  212. hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
  213. hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
  214. hexdag_plugins/azure/azure_openai_adapter.py +415 -0
  215. hexdag_plugins/azure/pyproject.toml +107 -0
  216. hexdag_plugins/azure/tests/__init__.py +1 -0
  217. hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
  218. hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
  219. hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
  220. hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
  221. hexdag_plugins/hexdag_etl/README.md +168 -0
  222. hexdag_plugins/hexdag_etl/__init__.py +53 -0
  223. hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
  224. hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
  225. hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
  226. hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
  227. hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
  228. hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
  229. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
  230. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
  231. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
  232. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
  233. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
  234. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
  235. hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
  236. hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
  237. hexdag_plugins/hexdag_etl/test_transform.py +54 -0
  238. hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
  239. hexdag_plugins/mysql_adapter/LICENSE +21 -0
  240. hexdag_plugins/mysql_adapter/README.md +224 -0
  241. hexdag_plugins/mysql_adapter/__init__.py +6 -0
  242. hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
  243. hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
  244. hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
  245. hexdag_plugins/storage/README.md +184 -0
  246. hexdag_plugins/storage/__init__.py +19 -0
  247. hexdag_plugins/storage/file/__init__.py +5 -0
  248. hexdag_plugins/storage/file/local.py +325 -0
  249. hexdag_plugins/storage/ports/__init__.py +5 -0
  250. hexdag_plugins/storage/ports/vector_store.py +236 -0
  251. hexdag_plugins/storage/sql/__init__.py +7 -0
  252. hexdag_plugins/storage/sql/base.py +187 -0
  253. hexdag_plugins/storage/sql/mysql.py +27 -0
  254. hexdag_plugins/storage/sql/postgresql.py +27 -0
  255. hexdag_plugins/storage/tests/__init__.py +1 -0
  256. hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
  257. hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
  258. hexdag_plugins/storage/vector/__init__.py +7 -0
  259. hexdag_plugins/storage/vector/chromadb.py +223 -0
  260. hexdag_plugins/storage/vector/in_memory.py +285 -0
  261. hexdag_plugins/storage/vector/pgvector.py +502 -0
@@ -0,0 +1,252 @@
1
+ """Generate typed stubs from YAML pipelines for IDE autocomplete support."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated, Any
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.table import Table
9
+
10
+ app = typer.Typer()
11
+ console = Console()
12
+
13
+
14
+ def _python_type_to_str(annotation: Any) -> str:
15
+ """Convert a Python type annotation to a string representation.
16
+
17
+ Args
18
+ ----
19
+ annotation: The type annotation to convert
20
+
21
+ Returns
22
+ -------
23
+ String representation of the type
24
+ """
25
+ if annotation is None:
26
+ return "Any"
27
+
28
+ # Handle None type
29
+ if annotation is type(None):
30
+ return "None"
31
+
32
+ # Handle string annotations (forward references)
33
+ if isinstance(annotation, str):
34
+ return annotation
35
+
36
+ # Get the type name
37
+ origin = getattr(annotation, "__origin__", None)
38
+ args = getattr(annotation, "__args__", ())
39
+
40
+ # Handle Union types (X | Y)
41
+ is_union_type = (
42
+ hasattr(annotation, "__class__") and annotation.__class__.__name__ == "UnionType"
43
+ )
44
+ if origin is type(None) or is_union_type:
45
+ # Python 3.10+ union syntax
46
+ if hasattr(annotation, "__args__"):
47
+ arg_strs = [_python_type_to_str(arg) for arg in annotation.__args__]
48
+ return " | ".join(arg_strs)
49
+ return "Any"
50
+
51
+ # Handle typing.Union
52
+ if origin is not None:
53
+ origin_name = getattr(origin, "__name__", str(origin))
54
+ if origin_name == "Union":
55
+ arg_strs = [_python_type_to_str(arg) for arg in args]
56
+ return " | ".join(arg_strs)
57
+
58
+ # Handle generic types (list[str], dict[str, int], etc.)
59
+ if args:
60
+ arg_strs = [_python_type_to_str(arg) for arg in args]
61
+ return f"{origin_name}[{', '.join(arg_strs)}]"
62
+ return origin_name
63
+
64
+ # Handle basic types
65
+ if hasattr(annotation, "__name__"):
66
+ return annotation.__name__
67
+
68
+ # Fallback
69
+ return str(annotation).replace("typing.", "").replace("<class '", "").replace("'>", "")
70
+
71
+
72
+ def _export_model_stub(model_name: str, fields: dict[str, Any], output_path: Path) -> None:
73
+ """Generate a .pyi stub file for a Pydantic model.
74
+
75
+ Args
76
+ ----
77
+ model_name: Name of the model class
78
+ fields: Dictionary of field names to their type annotations
79
+ output_path: Path to write the stub file
80
+ """
81
+ from datetime import datetime as dt_type
82
+
83
+ # Determine which imports are needed
84
+ needs_datetime = False
85
+ for field_type in fields.values():
86
+ is_datetime = hasattr(field_type, "__name__") and field_type.__name__ == "datetime"
87
+ if field_type is dt_type or is_datetime:
88
+ needs_datetime = True
89
+ break
90
+
91
+ lines = [
92
+ "# Auto-generated type stub from hexdag pipeline",
93
+ "# Do not edit manually - regenerate with: hexdag generate-types <yaml>",
94
+ "",
95
+ ]
96
+
97
+ if needs_datetime:
98
+ lines.append("from datetime import datetime")
99
+ lines.append("from typing import Any")
100
+ lines.append("from pydantic import BaseModel")
101
+ lines.append("")
102
+ lines.append("")
103
+ lines.append(f"class {model_name}(BaseModel):")
104
+
105
+ if not fields:
106
+ lines.append(" pass")
107
+ else:
108
+ for field_name, field_type in fields.items():
109
+ type_str = _python_type_to_str(field_type)
110
+ lines.append(f" {field_name}: {type_str}")
111
+
112
+ lines.append("") # Trailing newline
113
+ output_path.write_text("\n".join(lines))
114
+
115
+
116
+ def _infer_type_from_mapping(source_path: str) -> Any:
117
+ """Infer a type from a mapping source path.
118
+
119
+ Args
120
+ ----
121
+ source_path: The source path string (e.g., "$input.name", "node.field")
122
+
123
+ Returns
124
+ -------
125
+ Inferred type (defaults to Any)
126
+ """
127
+ # Check for expression patterns that have known return types
128
+ from hexdag.core.expression_parser import ALLOWED_FUNCTIONS
129
+
130
+ for func_name in ALLOWED_FUNCTIONS:
131
+ if f"{func_name}(" in source_path:
132
+ # Infer return type from function
133
+ if func_name in {"len", "int", "abs", "round"}:
134
+ return int
135
+ if func_name in {"float", "sum"}:
136
+ return float
137
+ if func_name in {"str", "upper", "lower", "strip", "join"}:
138
+ return str
139
+ if func_name in {"bool", "all", "any"}:
140
+ return bool
141
+ if func_name in {"list", "sorted", "split"}:
142
+ return list
143
+ if func_name in {"now", "utcnow"}:
144
+ from datetime import datetime
145
+
146
+ return datetime
147
+ return Any
148
+
149
+ # Default to Any for simple field paths
150
+ return Any
151
+
152
+
153
+ @app.command()
154
+ def generate_types(
155
+ yaml_path: Annotated[
156
+ Path,
157
+ typer.Argument(
158
+ help="Path to YAML pipeline file",
159
+ exists=True,
160
+ file_okay=True,
161
+ dir_okay=False,
162
+ readable=True,
163
+ ),
164
+ ],
165
+ output_dir: Annotated[
166
+ Path,
167
+ typer.Option(
168
+ "--output-dir",
169
+ "-o",
170
+ help="Output directory for stub files (default: current directory)",
171
+ ),
172
+ ] = Path(),
173
+ prefix: Annotated[
174
+ str,
175
+ typer.Option(
176
+ "--prefix",
177
+ "-p",
178
+ help="Prefix for generated file names",
179
+ ),
180
+ ] = "",
181
+ ) -> None:
182
+ """Generate typed stubs from YAML pipeline definitions.
183
+
184
+ This command analyzes YAML pipelines and generates .pyi stub files
185
+ for nodes with input_mapping. This enables IDE autocomplete for
186
+ the input_data parameter in node functions.
187
+
188
+ Examples
189
+ --------
190
+ hexdag generate-types pipeline.yaml
191
+ hexdag generate-types pipeline.yaml -o ./types
192
+ hexdag generate-types pipeline.yaml --prefix pipeline_
193
+ """
194
+ import yaml
195
+
196
+ # Read YAML file
197
+ try:
198
+ with Path.open(yaml_path) as f:
199
+ config = yaml.safe_load(f)
200
+ except yaml.YAMLError as e:
201
+ console.print(f"[red]Error:[/red] Invalid YAML syntax: {e}")
202
+ raise typer.Exit(1) from e
203
+ except OSError as e:
204
+ console.print(f"[red]Error:[/red] Cannot read file: {e}")
205
+ raise typer.Exit(1) from e
206
+
207
+ # Ensure output directory exists
208
+ output_dir.mkdir(parents=True, exist_ok=True)
209
+
210
+ # Extract nodes with input_mapping
211
+ nodes = config.get("spec", {}).get("nodes", [])
212
+ generated_files: list[tuple[str, Path]] = []
213
+
214
+ for node in nodes:
215
+ node_name = node.get("metadata", {}).get("name", "")
216
+ spec = node.get("spec", {})
217
+ input_mapping = spec.get("input_mapping")
218
+
219
+ if not input_mapping or not node_name:
220
+ continue
221
+
222
+ # Create model name
223
+ model_name = f"{node_name.replace('-', '_').title().replace('_', '')}Input"
224
+
225
+ # Infer types from mapping
226
+ fields: dict[str, Any] = {}
227
+ for target_field, source_path in input_mapping.items():
228
+ fields[target_field] = _infer_type_from_mapping(source_path)
229
+
230
+ # Generate stub file
231
+ file_name = f"{prefix}{node_name.replace('-', '_')}_types.pyi"
232
+ stub_path = output_dir / file_name
233
+
234
+ _export_model_stub(model_name, fields, stub_path)
235
+ generated_files.append((node_name, stub_path))
236
+
237
+ # Display results
238
+ if not generated_files:
239
+ console.print("[yellow]No nodes with input_mapping found in pipeline.[/yellow]")
240
+ raise typer.Exit(0)
241
+
242
+ console.print(f"\n[green]Generated {len(generated_files)} stub file(s):[/green]\n")
243
+
244
+ table = Table(show_header=True, border_style="green")
245
+ table.add_column("Node", style="cyan")
246
+ table.add_column("Stub File", style="white")
247
+
248
+ for node_name, stub_path in generated_files:
249
+ table.add_row(node_name, str(stub_path))
250
+
251
+ console.print(table)
252
+ console.print()
@@ -0,0 +1,188 @@
1
+ """Initialize command for HexDAG CLI."""
2
+
3
+ from pathlib import Path
4
+ from typing import Annotated, Any
5
+
6
+ import typer
7
+ from rich.console import Console
8
+ from rich.prompt import Confirm
9
+
10
+ app = typer.Typer()
11
+ console = Console()
12
+
13
+
14
+ @app.callback(invoke_without_command=True)
15
+ def init(
16
+ ctx: typer.Context,
17
+ with_adapters: Annotated[
18
+ str | None,
19
+ typer.Option(
20
+ "--with",
21
+ help="Comma-separated list of adapters to include (e.g., openai,anthropic,local)",
22
+ ),
23
+ ] = None,
24
+ force: Annotated[
25
+ bool,
26
+ typer.Option(
27
+ "--force",
28
+ "-f",
29
+ help="Overwrite existing configuration",
30
+ ),
31
+ ] = False,
32
+ path: Annotated[
33
+ Path | None,
34
+ typer.Argument(
35
+ help="Directory to initialize (defaults to current directory)",
36
+ ),
37
+ ] = None,
38
+ ) -> None:
39
+ """Initialize a new HexDAG project with configuration."""
40
+ if ctx.invoked_subcommand is not None:
41
+ return
42
+
43
+ if path is None:
44
+ path = Path.cwd()
45
+
46
+ config_path = path / "hexdag.toml"
47
+
48
+ if (
49
+ config_path.exists()
50
+ and not force
51
+ and not Confirm.ask(f"[yellow]hexdag.toml already exists in {path}. Overwrite?[/yellow]")
52
+ ):
53
+ console.print("[red]Initialization cancelled.[/red]")
54
+ raise typer.Exit(1)
55
+
56
+ # Parse adapters
57
+ adapters = []
58
+ if with_adapters:
59
+ adapters = [a.strip() for a in with_adapters.split(",")]
60
+
61
+ # Generate configuration
62
+ config_content = _generate_config(adapters)
63
+
64
+ # Ensure directory exists
65
+ path.mkdir(parents=True, exist_ok=True)
66
+
67
+ # Write configuration
68
+ config_path.write_text(config_content)
69
+ console.print(f"[green]✓[/green] Created hexdag.toml in {path}")
70
+
71
+ # Show next steps
72
+ console.print("\n[bold]Next steps:[/bold]")
73
+ console.print("1. Review and customize hexdag.toml")
74
+ console.print("2. Set any required environment variables")
75
+
76
+ if adapters:
77
+ console.print("\n[bold]Adapters configured:[/bold]")
78
+ for adapter in adapters:
79
+ _print_adapter_info(adapter)
80
+
81
+
82
+ def _generate_config(adapters: list[str]) -> str:
83
+ """Generate hexdag.toml configuration."""
84
+ lines = [
85
+ "# HexDAG Configuration",
86
+ "# Generated by: hexdag init",
87
+ "",
88
+ "# Core modules to load",
89
+ "modules = [",
90
+ ' "hexdag.core.ports",',
91
+ ' "hexdag.builtin.nodes",',
92
+ "]",
93
+ "",
94
+ "# Plugins to load",
95
+ "plugins = [",
96
+ ' "hexdag.builtin.adapters.local", # Local in-process adapters',
97
+ ]
98
+
99
+ lines.append(' "hexdag.adapters.mock", # Mock adapters for testing')
100
+
101
+ if "openai" in adapters:
102
+ lines.append(' # "hexdag.adapters.openai", # OpenAI adapter (requires api key)')
103
+ if "anthropic" in adapters:
104
+ lines.append(' # "hexdag.adapters.anthropic", # Anthropic adapter (requires api key)')
105
+
106
+ lines.extend([
107
+ "]",
108
+ "",
109
+ "# Development mode",
110
+ "dev_mode = true",
111
+ "",
112
+ "[settings]",
113
+ 'log_level = "INFO"',
114
+ "enable_metrics = true",
115
+ ])
116
+
117
+ if "openai" in adapters:
118
+ lines.extend([
119
+ "",
120
+ "[adapters.openai]",
121
+ "# Set via environment variable or directly",
122
+ 'api_key = "${OPENAI_API_KEY}"',
123
+ 'model = "gpt-4"',
124
+ "temperature = 0.7",
125
+ "max_tokens = 2000",
126
+ ])
127
+
128
+ if "anthropic" in adapters:
129
+ lines.extend([
130
+ "",
131
+ "[adapters.anthropic]",
132
+ "# Set via environment variable or directly",
133
+ 'api_key = "${ANTHROPIC_API_KEY}"',
134
+ 'model = "claude-3-opus-20240229"',
135
+ "temperature = 0.7",
136
+ "max_tokens = 2000",
137
+ ])
138
+
139
+ if "local" in adapters or not adapters:
140
+ lines.extend([
141
+ "",
142
+ "[adapters.local]",
143
+ "# Local adapter settings",
144
+ "memory_max_size = 1000",
145
+ "cache_ttl = 3600",
146
+ ])
147
+
148
+ return "\n".join(lines) + "\n"
149
+
150
+
151
+ def _print_adapter_info(adapter: str) -> None:
152
+ """Print information about an adapter."""
153
+ import shutil
154
+
155
+ # Detect if uv is available
156
+ has_uv = shutil.which("uv") is not None
157
+ prefix = "uv pip install" if has_uv else "pip install"
158
+
159
+ info: dict[str, dict[str, Any]] = {
160
+ "openai": {
161
+ "name": "OpenAI",
162
+ "install": f"{prefix} hexdag[adapters-openai]",
163
+ "env": "OPENAI_API_KEY",
164
+ },
165
+ "anthropic": {
166
+ "name": "Anthropic Claude",
167
+ "install": f"{prefix} hexdag[adapters-anthropic]",
168
+ "env": "ANTHROPIC_API_KEY",
169
+ },
170
+ "mock": {
171
+ "name": "Mock Adapters",
172
+ "install": "Included in core",
173
+ "env": None,
174
+ },
175
+ "local": {
176
+ "name": "Local Adapters",
177
+ "install": "Included in core",
178
+ "env": None,
179
+ },
180
+ }
181
+
182
+ if adapter in info:
183
+ details = info[adapter]
184
+ console.print(f" • [cyan]{details['name']}[/cyan]")
185
+ if details["install"] != "Included in core":
186
+ console.print(f" Install: [yellow]{details['install']}[/yellow]")
187
+ if details["env"]:
188
+ console.print(f" Required: [yellow]{details['env']}[/yellow] environment variable")