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,273 @@
1
+ """Protocols for structural typing in hexDAG.
2
+
3
+ This module defines protocols (structural types) to reduce isinstance() usage
4
+ and enable duck typing with static type checking support.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, Protocol, runtime_checkable
10
+
11
+ # ============================================================================
12
+ # Component Protocols
13
+ # ============================================================================
14
+
15
+
16
+ @runtime_checkable
17
+ class ComponentWithExecute(Protocol):
18
+ """Protocol for components that have an execute method.
19
+
20
+ This allows duck typing for tool classes without isinstance checks.
21
+
22
+ Examples
23
+ --------
24
+ Example usage::
25
+
26
+ class MyTool:
27
+ def execute(self, **kwargs: Any) -> Any:
28
+ return "result"
29
+
30
+ def use_tool(tool: ComponentWithExecute) -> Any:
31
+ return tool.execute(param=1)
32
+ """
33
+
34
+ def execute(self, **kwargs: Any) -> Any:
35
+ """Execute the component with given parameters."""
36
+ ...
37
+
38
+
39
+ # ============================================================================
40
+ # Port Protocols
41
+ # ============================================================================
42
+
43
+
44
+ @runtime_checkable
45
+ class ConfigurablePort(Protocol):
46
+ """Protocol for ports that support configuration.
47
+
48
+ Examples
49
+ --------
50
+ Example usage::
51
+
52
+ class MyAdapter:
53
+ @classmethod
54
+ def get_config_class(cls) -> type:
55
+ return MyConfig
56
+ """
57
+
58
+ @classmethod
59
+ def get_config_class(cls) -> type[Any]:
60
+ """Return the configuration class for this port."""
61
+ ...
62
+
63
+
64
+ @runtime_checkable
65
+ class HealthCheckable(Protocol):
66
+ """Protocol for components that support health checks.
67
+
68
+ Examples
69
+ --------
70
+ Example usage::
71
+
72
+ class MyAdapter:
73
+ async def ahealth_check(self) -> dict[str, Any]:
74
+ return {"status": "healthy"}
75
+ """
76
+
77
+ async def ahealth_check(self) -> dict[str, Any]:
78
+ """Perform async health check.
79
+
80
+ Returns
81
+ -------
82
+ Health status dictionary with at least 'status' key
83
+ """
84
+ ...
85
+
86
+
87
+ # ============================================================================
88
+ # Data Conversion Protocols
89
+ # ============================================================================
90
+
91
+
92
+ @runtime_checkable
93
+ class DictConvertible(Protocol):
94
+ """Protocol for objects that can be converted to dict.
95
+
96
+ This includes Pydantic models and other dict-like objects.
97
+
98
+ Examples
99
+ --------
100
+ Example usage::
101
+
102
+ class MyModel:
103
+ def model_dump(self) -> dict[str, Any]:
104
+ return {"field": "value"}
105
+ """
106
+
107
+ def model_dump(self) -> dict[str, Any]:
108
+ """Convert to dictionary representation."""
109
+ ...
110
+
111
+
112
+ @runtime_checkable
113
+ class SchemaProvider(Protocol):
114
+ """Protocol for classes that provide schema information.
115
+
116
+ Examples
117
+ --------
118
+ Example usage::
119
+
120
+ class MyModel:
121
+ @classmethod
122
+ def model_json_schema(cls) -> dict[str, Any]:
123
+ return {"type": "object", "properties": {}}
124
+ """
125
+
126
+ @classmethod
127
+ def model_json_schema(cls) -> dict[str, Any]:
128
+ """Return JSON schema for this model."""
129
+ ...
130
+
131
+
132
+ # ============================================================================
133
+ # Helper Functions
134
+ # ============================================================================
135
+
136
+
137
+ def has_execute_method(obj: Any) -> bool:
138
+ """Check if object has an execute method (duck typing).
139
+
140
+ This is more Pythonic than isinstance checks.
141
+
142
+ Args
143
+ ----
144
+ obj: Object to check
145
+
146
+ Returns
147
+ -------
148
+ True if object has callable execute method
149
+
150
+ Examples
151
+ --------
152
+ Example usage::
153
+
154
+ class Tool:
155
+ def execute(self): pass
156
+ has_execute_method(Tool())
157
+ True
158
+ """
159
+ return isinstance(obj, ComponentWithExecute)
160
+
161
+
162
+ def is_dict_convertible(obj: Any) -> bool:
163
+ """Check if object can be converted to dict.
164
+
165
+ Args
166
+ ----
167
+ obj: Object to check
168
+
169
+ Returns
170
+ -------
171
+ True if object has model_dump method or is a dict
172
+
173
+ Examples
174
+ --------
175
+ Example usage::
176
+
177
+ from pydantic import BaseModel
178
+ class MyModel(BaseModel):
179
+ field: str
180
+ is_dict_convertible(MyModel(field="value"))
181
+ True
182
+ is_dict_convertible({"key": "value"})
183
+ True
184
+ """
185
+ return isinstance(obj, (dict, DictConvertible))
186
+
187
+
188
+ def is_schema_type(type_obj: Any) -> bool:
189
+ """Check if type is a Pydantic model class (not instance).
190
+
191
+ Args
192
+ ----
193
+ type_obj: Type to check
194
+
195
+ Returns
196
+ -------
197
+ True if type is a Pydantic BaseModel subclass
198
+
199
+ Examples
200
+ --------
201
+ Example usage::
202
+
203
+ from pydantic import BaseModel
204
+ class MyModel(BaseModel):
205
+ field: str
206
+ is_schema_type(MyModel) # Type check
207
+ True
208
+ is_schema_type(MyModel(field="val")) # Instance check
209
+ False
210
+ """
211
+ try:
212
+ return isinstance(type_obj, type) and issubclass(type_obj, SchemaProvider)
213
+ except TypeError:
214
+ return False
215
+
216
+
217
+ def to_dict(obj: Any) -> dict[str, Any]:
218
+ """Convert object to dictionary, handling multiple input types.
219
+
220
+ This centralizes the type conversion logic used throughout the codebase.
221
+
222
+ Args
223
+ ----
224
+ obj: Object to convert (dict, Pydantic model, or other)
225
+
226
+ Returns
227
+ -------
228
+ Dictionary representation of the object
229
+
230
+ Raises
231
+ ------
232
+ TypeError
233
+ If object cannot be converted to dict
234
+
235
+ Examples
236
+ --------
237
+ Example usage::
238
+
239
+ to_dict({"key": "value"})
240
+ {'key': 'value'}
241
+ from pydantic import BaseModel
242
+ class MyModel(BaseModel):
243
+ field: str
244
+ to_dict(MyModel(field="value"))
245
+ {'field': 'value'}
246
+ to_dict("not_convertible") # Raises TypeError
247
+ """
248
+ # Case 1: Already a dict
249
+ if isinstance(obj, dict):
250
+ return obj
251
+
252
+ # Case 2: Pydantic model or dict-convertible
253
+ if isinstance(obj, DictConvertible):
254
+ return obj.model_dump()
255
+
256
+ # Case 3: Cannot convert
257
+ raise TypeError(
258
+ f"Cannot convert {type(obj).__name__} to dict. "
259
+ f"Expected dict or object with model_dump() method"
260
+ )
261
+
262
+
263
+ __all__ = [
264
+ "ComponentWithExecute",
265
+ "ConfigurablePort",
266
+ "HealthCheckable",
267
+ "DictConvertible",
268
+ "SchemaProvider",
269
+ "has_execute_method",
270
+ "is_dict_convertible",
271
+ "is_schema_type",
272
+ "to_dict",
273
+ ]
@@ -0,0 +1,304 @@
1
+ """Simple module path resolver for hexDAG components.
2
+
3
+ This replaces the registry system with Python's import system.
4
+ Components are resolved by their full module path.
5
+
6
+ Examples
7
+ --------
8
+ >>> from hexdag.core.resolver import resolve
9
+ >>> LLMNode = resolve("hexdag.builtin.nodes.LLMNode")
10
+ >>> adapter = resolve("hexdag.builtin.adapters.mock.MockLLM")
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import importlib
16
+ from typing import Any
17
+
18
+
19
+ class ResolveError(Exception):
20
+ """Raised when a module path cannot be resolved."""
21
+
22
+ def __init__(self, kind: str, reason: str):
23
+ self.kind = kind
24
+ self.reason = reason
25
+ super().__init__(f"Cannot resolve '{kind}': {reason}")
26
+
27
+
28
+ # Runtime storage for dynamically created components (e.g., YAML-defined macros)
29
+ _runtime_components: dict[str, type[Any]] = {}
30
+
31
+ # User-registered aliases (separate from built-in short names)
32
+ _user_aliases: dict[str, str] = {}
33
+
34
+ # Builtin aliases registered by hexdag.builtin during bootstrap
35
+ _builtin_aliases: dict[str, str] = {}
36
+
37
+ # Flag to track if builtin has been bootstrapped
38
+ _builtin_bootstrapped: bool = False
39
+
40
+
41
+ def register_builtin_aliases(aliases: dict[str, str]) -> None:
42
+ """Register builtin node aliases (called by hexdag.builtin during bootstrap).
43
+
44
+ This allows the builtin package to register its auto-discovered aliases
45
+ without core needing to import from builtin (maintaining hexagonal architecture).
46
+
47
+ Parameters
48
+ ----------
49
+ aliases : dict[str, str]
50
+ Mapping of alias -> full module path
51
+ """
52
+ global _builtin_bootstrapped
53
+ _builtin_aliases.update(aliases)
54
+ _builtin_bootstrapped = True
55
+
56
+
57
+ def _ensure_builtin_bootstrapped() -> None:
58
+ """Ensure builtin aliases are loaded (triggers bootstrap if needed)."""
59
+ global _builtin_bootstrapped
60
+ if not _builtin_bootstrapped:
61
+ # Import builtin to trigger its bootstrap which calls register_builtin_aliases
62
+ import hexdag.builtin.nodes # noqa: F401
63
+
64
+ _builtin_bootstrapped = True
65
+
66
+
67
+ def get_builtin_aliases() -> dict[str, str]:
68
+ """Get a copy of all builtin aliases.
69
+
70
+ Returns
71
+ -------
72
+ dict[str, str]
73
+ Copy of the alias -> full_path mapping
74
+ """
75
+ _ensure_builtin_bootstrapped()
76
+ return dict(_builtin_aliases)
77
+
78
+
79
+ def register_runtime(name: str, component: type[Any]) -> None:
80
+ """Register a component created at runtime (e.g., YAML-defined macros).
81
+
82
+ Parameters
83
+ ----------
84
+ name : str
85
+ Component name (will be prefixed with 'runtime:')
86
+ component : type
87
+ The component class
88
+ """
89
+ _runtime_components[name] = component
90
+
91
+
92
+ def get_runtime(name: str) -> type[Any] | None:
93
+ """Get a runtime-registered component.
94
+
95
+ Parameters
96
+ ----------
97
+ name : str
98
+ Component name
99
+
100
+ Returns
101
+ -------
102
+ type | None
103
+ The component class or None if not found
104
+ """
105
+ return _runtime_components.get(name)
106
+
107
+
108
+ def register_alias(alias: str, full_path: str) -> None:
109
+ """Register a user-defined alias for a component path.
110
+
111
+ This allows using short names in YAML instead of full module paths.
112
+ User aliases take precedence over built-in short name aliases.
113
+
114
+ Parameters
115
+ ----------
116
+ alias : str
117
+ Short name to use in YAML (e.g., "my_processor")
118
+ full_path : str
119
+ Full module path (e.g., "myapp.nodes.ProcessorNode")
120
+
121
+ Examples
122
+ --------
123
+ >>> register_alias("my_processor", "myapp.nodes.ProcessorNode")
124
+ >>> resolve("my_processor") # doctest: +SKIP
125
+ <class 'myapp.nodes.ProcessorNode'>
126
+
127
+ >>> # Use in YAML:
128
+ >>> # spec:
129
+ >>> # aliases:
130
+ >>> # my_processor: myapp.nodes.ProcessorNode
131
+ >>> # nodes:
132
+ >>> # - kind: my_processor # Uses alias!
133
+ """
134
+ if not alias:
135
+ raise ValueError("Alias cannot be empty")
136
+ if not full_path:
137
+ raise ValueError("Full path cannot be empty")
138
+ _user_aliases[alias] = full_path
139
+
140
+
141
+ def unregister_alias(alias: str) -> bool:
142
+ """Remove a user-registered alias.
143
+
144
+ Parameters
145
+ ----------
146
+ alias : str
147
+ The alias to remove
148
+
149
+ Returns
150
+ -------
151
+ bool
152
+ True if alias was removed, False if it didn't exist
153
+ """
154
+ if alias in _user_aliases:
155
+ del _user_aliases[alias]
156
+ return True
157
+ return False
158
+
159
+
160
+ def get_registered_aliases() -> dict[str, str]:
161
+ """Get a copy of all user-registered aliases.
162
+
163
+ Returns
164
+ -------
165
+ dict[str, str]
166
+ Copy of the alias -> full_path mapping
167
+ """
168
+ return dict(_user_aliases)
169
+
170
+
171
+ def clear_aliases() -> None:
172
+ """Clear all user-registered aliases.
173
+
174
+ This is primarily useful for testing.
175
+ """
176
+ _user_aliases.clear()
177
+
178
+
179
+ def resolve(kind: str) -> type[Any]:
180
+ """Resolve a kind string to a Python class.
181
+
182
+ Parameters
183
+ ----------
184
+ kind : str
185
+ Full module path to the class (e.g., "hexdag.builtin.nodes.LLMNode")
186
+ or a runtime component name (e.g., "my_macro")
187
+ or a legacy short name (e.g., "llm_node", "core:llm_node")
188
+
189
+ Returns
190
+ -------
191
+ type
192
+ The resolved class
193
+
194
+ Raises
195
+ ------
196
+ ResolveError
197
+ If the module or class cannot be found
198
+
199
+ Examples
200
+ --------
201
+ >>> resolve("hexdag.builtin.nodes.LLMNode") # doctest: +SKIP
202
+ <class 'hexdag.builtin.nodes.llm_node.LLMNode'>
203
+
204
+ >>> resolve("myapp.nodes.MyProcessor") # doctest: +SKIP
205
+ <class 'myapp.nodes.MyProcessor'>
206
+
207
+ >>> # Legacy short name support
208
+ >>> resolve("llm_node") # doctest: +SKIP
209
+ <class 'hexdag.builtin.nodes.llm_node.LLMNode'>
210
+ """
211
+ # First check runtime components (e.g., YAML-defined macros)
212
+ if runtime_component := get_runtime(kind):
213
+ return runtime_component
214
+
215
+ # Check user-registered aliases (higher priority than built-in)
216
+ if kind in _user_aliases:
217
+ kind = _user_aliases[kind]
218
+
219
+ # Check builtin aliases (registered by hexdag.builtin during bootstrap)
220
+ _ensure_builtin_bootstrapped()
221
+ if kind in _builtin_aliases:
222
+ kind = _builtin_aliases[kind]
223
+
224
+ if "." not in kind:
225
+ raise ResolveError(
226
+ kind,
227
+ "Must be a full module path (e.g., 'hexdag.builtin.nodes.LLMNode') "
228
+ "or a registered runtime component",
229
+ )
230
+
231
+ try:
232
+ module_path, class_name = kind.rsplit(".", 1)
233
+ except ValueError as e:
234
+ raise ResolveError(kind, "Invalid format - expected 'module.path.ClassName'") from e
235
+
236
+ try:
237
+ module = importlib.import_module(module_path)
238
+ except ModuleNotFoundError as e:
239
+ raise ResolveError(kind, f"Module '{module_path}' not found: {e}") from e
240
+ except ImportError as e:
241
+ raise ResolveError(kind, f"Failed to import '{module_path}': {e}") from e
242
+
243
+ try:
244
+ cls = getattr(module, class_name)
245
+ except AttributeError as e:
246
+ available = [name for name in dir(module) if not name.startswith("_")]
247
+ raise ResolveError(
248
+ kind,
249
+ f"Class '{class_name}' not found in '{module_path}'. "
250
+ f"Available: {', '.join(available[:10])}",
251
+ ) from e
252
+
253
+ if not isinstance(cls, type):
254
+ raise ResolveError(kind, f"'{class_name}' is not a class (got {type(cls).__name__})")
255
+
256
+ return cls
257
+
258
+
259
+ def resolve_function(path: str) -> Any:
260
+ """Resolve a path to a function or callable.
261
+
262
+ Parameters
263
+ ----------
264
+ path : str
265
+ Full module path to the function (e.g., "json.loads")
266
+
267
+ Returns
268
+ -------
269
+ Callable
270
+ The resolved function
271
+
272
+ Raises
273
+ ------
274
+ ResolveError
275
+ If the module or function cannot be found
276
+ """
277
+ if "." not in path:
278
+ raise ResolveError(
279
+ path,
280
+ "Must be a full module path (e.g., 'json.loads')",
281
+ )
282
+
283
+ module_path, func_name = path.rsplit(".", 1)
284
+
285
+ try:
286
+ module = importlib.import_module(module_path)
287
+ except ModuleNotFoundError as e:
288
+ raise ResolveError(path, f"Module '{module_path}' not found: {e}") from e
289
+ except ImportError as e:
290
+ raise ResolveError(path, f"Failed to import '{module_path}': {e}") from e
291
+
292
+ try:
293
+ func = getattr(module, func_name)
294
+ except AttributeError as e:
295
+ available = [name for name in dir(module) if not name.startswith("_")]
296
+ raise ResolveError(
297
+ path,
298
+ f"'{func_name}' not found in '{module_path}'. Available: {', '.join(available[:10])}",
299
+ ) from e
300
+
301
+ if not callable(func):
302
+ raise ResolveError(path, f"'{func_name}' is not callable (got {type(func).__name__})")
303
+
304
+ return func
@@ -0,0 +1,9 @@
1
+ """Schema generation and validation for hexDAG components.
2
+
3
+ This module provides automatic schema generation from Python type hints,
4
+ enabling auto-documentation and YAML validation.
5
+ """
6
+
7
+ from hexdag.core.schema.generator import SchemaGenerator
8
+
9
+ __all__ = ["SchemaGenerator"]