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.
- hexdag/__init__.py +116 -0
- hexdag/__main__.py +30 -0
- hexdag/adapters/executors/__init__.py +5 -0
- hexdag/adapters/executors/local_executor.py +316 -0
- hexdag/builtin/__init__.py +6 -0
- hexdag/builtin/adapters/__init__.py +51 -0
- hexdag/builtin/adapters/anthropic/__init__.py +5 -0
- hexdag/builtin/adapters/anthropic/anthropic_adapter.py +151 -0
- hexdag/builtin/adapters/database/__init__.py +6 -0
- hexdag/builtin/adapters/database/csv/csv_adapter.py +249 -0
- hexdag/builtin/adapters/database/pgvector/__init__.py +5 -0
- hexdag/builtin/adapters/database/pgvector/pgvector_adapter.py +478 -0
- hexdag/builtin/adapters/database/sqlalchemy/sqlalchemy_adapter.py +252 -0
- hexdag/builtin/adapters/database/sqlite/__init__.py +5 -0
- hexdag/builtin/adapters/database/sqlite/sqlite_adapter.py +410 -0
- hexdag/builtin/adapters/local/README.md +59 -0
- hexdag/builtin/adapters/local/__init__.py +7 -0
- hexdag/builtin/adapters/local/local_observer_manager.py +696 -0
- hexdag/builtin/adapters/memory/__init__.py +47 -0
- hexdag/builtin/adapters/memory/file_memory_adapter.py +297 -0
- hexdag/builtin/adapters/memory/in_memory_memory.py +216 -0
- hexdag/builtin/adapters/memory/schemas.py +57 -0
- hexdag/builtin/adapters/memory/session_memory.py +178 -0
- hexdag/builtin/adapters/memory/sqlite_memory_adapter.py +215 -0
- hexdag/builtin/adapters/memory/state_memory.py +280 -0
- hexdag/builtin/adapters/mock/README.md +89 -0
- hexdag/builtin/adapters/mock/__init__.py +15 -0
- hexdag/builtin/adapters/mock/hexdag.toml +50 -0
- hexdag/builtin/adapters/mock/mock_database.py +225 -0
- hexdag/builtin/adapters/mock/mock_embedding.py +223 -0
- hexdag/builtin/adapters/mock/mock_llm.py +177 -0
- hexdag/builtin/adapters/mock/mock_tool_adapter.py +192 -0
- hexdag/builtin/adapters/mock/mock_tool_router.py +232 -0
- hexdag/builtin/adapters/openai/__init__.py +5 -0
- hexdag/builtin/adapters/openai/openai_adapter.py +634 -0
- hexdag/builtin/adapters/secret/__init__.py +7 -0
- hexdag/builtin/adapters/secret/local_secret_adapter.py +248 -0
- hexdag/builtin/adapters/unified_tool_router.py +280 -0
- hexdag/builtin/macros/__init__.py +17 -0
- hexdag/builtin/macros/conversation_agent.py +390 -0
- hexdag/builtin/macros/llm_macro.py +151 -0
- hexdag/builtin/macros/reasoning_agent.py +423 -0
- hexdag/builtin/macros/tool_macro.py +380 -0
- hexdag/builtin/nodes/__init__.py +38 -0
- hexdag/builtin/nodes/_discovery.py +123 -0
- hexdag/builtin/nodes/agent_node.py +696 -0
- hexdag/builtin/nodes/base_node_factory.py +242 -0
- hexdag/builtin/nodes/composite_node.py +926 -0
- hexdag/builtin/nodes/data_node.py +201 -0
- hexdag/builtin/nodes/expression_node.py +487 -0
- hexdag/builtin/nodes/function_node.py +454 -0
- hexdag/builtin/nodes/llm_node.py +491 -0
- hexdag/builtin/nodes/loop_node.py +920 -0
- hexdag/builtin/nodes/mapped_input.py +518 -0
- hexdag/builtin/nodes/port_call_node.py +269 -0
- hexdag/builtin/nodes/tool_call_node.py +195 -0
- hexdag/builtin/nodes/tool_utils.py +390 -0
- hexdag/builtin/prompts/__init__.py +68 -0
- hexdag/builtin/prompts/base.py +422 -0
- hexdag/builtin/prompts/chat_prompts.py +303 -0
- hexdag/builtin/prompts/error_correction_prompts.py +320 -0
- hexdag/builtin/prompts/tool_prompts.py +160 -0
- hexdag/builtin/tools/builtin_tools.py +84 -0
- hexdag/builtin/tools/database_tools.py +164 -0
- hexdag/cli/__init__.py +17 -0
- hexdag/cli/__main__.py +7 -0
- hexdag/cli/commands/__init__.py +27 -0
- hexdag/cli/commands/build_cmd.py +812 -0
- hexdag/cli/commands/create_cmd.py +208 -0
- hexdag/cli/commands/docs_cmd.py +293 -0
- hexdag/cli/commands/generate_types_cmd.py +252 -0
- hexdag/cli/commands/init_cmd.py +188 -0
- hexdag/cli/commands/pipeline_cmd.py +494 -0
- hexdag/cli/commands/plugin_dev_cmd.py +529 -0
- hexdag/cli/commands/plugins_cmd.py +441 -0
- hexdag/cli/commands/studio_cmd.py +101 -0
- hexdag/cli/commands/validate_cmd.py +221 -0
- hexdag/cli/main.py +84 -0
- hexdag/core/__init__.py +83 -0
- hexdag/core/config/__init__.py +20 -0
- hexdag/core/config/loader.py +479 -0
- hexdag/core/config/models.py +150 -0
- hexdag/core/configurable.py +294 -0
- hexdag/core/context/__init__.py +37 -0
- hexdag/core/context/execution_context.py +378 -0
- hexdag/core/docs/__init__.py +26 -0
- hexdag/core/docs/extractors.py +678 -0
- hexdag/core/docs/generators.py +890 -0
- hexdag/core/docs/models.py +120 -0
- hexdag/core/domain/__init__.py +10 -0
- hexdag/core/domain/dag.py +1225 -0
- hexdag/core/exceptions.py +234 -0
- hexdag/core/expression_parser.py +569 -0
- hexdag/core/logging.py +449 -0
- hexdag/core/models/__init__.py +17 -0
- hexdag/core/models/base.py +138 -0
- hexdag/core/orchestration/__init__.py +46 -0
- hexdag/core/orchestration/body_executor.py +481 -0
- hexdag/core/orchestration/components/__init__.py +97 -0
- hexdag/core/orchestration/components/adapter_lifecycle_manager.py +113 -0
- hexdag/core/orchestration/components/checkpoint_manager.py +134 -0
- hexdag/core/orchestration/components/execution_coordinator.py +360 -0
- hexdag/core/orchestration/components/health_check_manager.py +176 -0
- hexdag/core/orchestration/components/input_mapper.py +143 -0
- hexdag/core/orchestration/components/lifecycle_manager.py +583 -0
- hexdag/core/orchestration/components/node_executor.py +377 -0
- hexdag/core/orchestration/components/secret_manager.py +202 -0
- hexdag/core/orchestration/components/wave_executor.py +158 -0
- hexdag/core/orchestration/constants.py +17 -0
- hexdag/core/orchestration/events/README.md +312 -0
- hexdag/core/orchestration/events/__init__.py +104 -0
- hexdag/core/orchestration/events/batching.py +330 -0
- hexdag/core/orchestration/events/decorators.py +139 -0
- hexdag/core/orchestration/events/events.py +573 -0
- hexdag/core/orchestration/events/observers/__init__.py +30 -0
- hexdag/core/orchestration/events/observers/core_observers.py +690 -0
- hexdag/core/orchestration/events/observers/models.py +111 -0
- hexdag/core/orchestration/events/taxonomy.py +269 -0
- hexdag/core/orchestration/hook_context.py +237 -0
- hexdag/core/orchestration/hooks.py +437 -0
- hexdag/core/orchestration/models.py +418 -0
- hexdag/core/orchestration/orchestrator.py +910 -0
- hexdag/core/orchestration/orchestrator_factory.py +275 -0
- hexdag/core/orchestration/port_wrappers.py +327 -0
- hexdag/core/orchestration/prompt/__init__.py +32 -0
- hexdag/core/orchestration/prompt/template.py +332 -0
- hexdag/core/pipeline_builder/__init__.py +21 -0
- hexdag/core/pipeline_builder/component_instantiator.py +386 -0
- hexdag/core/pipeline_builder/include_tag.py +265 -0
- hexdag/core/pipeline_builder/pipeline_config.py +133 -0
- hexdag/core/pipeline_builder/py_tag.py +223 -0
- hexdag/core/pipeline_builder/tag_discovery.py +268 -0
- hexdag/core/pipeline_builder/yaml_builder.py +1196 -0
- hexdag/core/pipeline_builder/yaml_validator.py +569 -0
- hexdag/core/ports/__init__.py +65 -0
- hexdag/core/ports/api_call.py +133 -0
- hexdag/core/ports/database.py +489 -0
- hexdag/core/ports/embedding.py +215 -0
- hexdag/core/ports/executor.py +237 -0
- hexdag/core/ports/file_storage.py +117 -0
- hexdag/core/ports/healthcheck.py +87 -0
- hexdag/core/ports/llm.py +551 -0
- hexdag/core/ports/memory.py +70 -0
- hexdag/core/ports/observer_manager.py +130 -0
- hexdag/core/ports/secret.py +145 -0
- hexdag/core/ports/tool_router.py +94 -0
- hexdag/core/ports_builder.py +623 -0
- hexdag/core/protocols.py +273 -0
- hexdag/core/resolver.py +304 -0
- hexdag/core/schema/__init__.py +9 -0
- hexdag/core/schema/generator.py +742 -0
- hexdag/core/secrets.py +242 -0
- hexdag/core/types.py +413 -0
- hexdag/core/utils/async_warnings.py +206 -0
- hexdag/core/utils/schema_conversion.py +78 -0
- hexdag/core/utils/sql_validation.py +86 -0
- hexdag/core/validation/secure_json.py +148 -0
- hexdag/core/yaml_macro.py +517 -0
- hexdag/mcp_server.py +3120 -0
- hexdag/studio/__init__.py +10 -0
- hexdag/studio/build_ui.py +92 -0
- hexdag/studio/server/__init__.py +1 -0
- hexdag/studio/server/main.py +100 -0
- hexdag/studio/server/routes/__init__.py +9 -0
- hexdag/studio/server/routes/execute.py +208 -0
- hexdag/studio/server/routes/export.py +558 -0
- hexdag/studio/server/routes/files.py +207 -0
- hexdag/studio/server/routes/plugins.py +419 -0
- hexdag/studio/server/routes/validate.py +220 -0
- hexdag/studio/ui/index.html +13 -0
- hexdag/studio/ui/package-lock.json +2992 -0
- hexdag/studio/ui/package.json +31 -0
- hexdag/studio/ui/postcss.config.js +6 -0
- hexdag/studio/ui/public/hexdag.svg +5 -0
- hexdag/studio/ui/src/App.tsx +251 -0
- hexdag/studio/ui/src/components/Canvas.tsx +408 -0
- hexdag/studio/ui/src/components/ContextMenu.tsx +187 -0
- hexdag/studio/ui/src/components/FileBrowser.tsx +123 -0
- hexdag/studio/ui/src/components/Header.tsx +181 -0
- hexdag/studio/ui/src/components/HexdagNode.tsx +193 -0
- hexdag/studio/ui/src/components/NodeInspector.tsx +512 -0
- hexdag/studio/ui/src/components/NodePalette.tsx +262 -0
- hexdag/studio/ui/src/components/NodePortsSection.tsx +403 -0
- hexdag/studio/ui/src/components/PluginManager.tsx +347 -0
- hexdag/studio/ui/src/components/PortsEditor.tsx +481 -0
- hexdag/studio/ui/src/components/PythonEditor.tsx +195 -0
- hexdag/studio/ui/src/components/ValidationPanel.tsx +105 -0
- hexdag/studio/ui/src/components/YamlEditor.tsx +196 -0
- hexdag/studio/ui/src/components/index.ts +8 -0
- hexdag/studio/ui/src/index.css +92 -0
- hexdag/studio/ui/src/main.tsx +10 -0
- hexdag/studio/ui/src/types/index.ts +123 -0
- hexdag/studio/ui/src/vite-env.d.ts +1 -0
- hexdag/studio/ui/tailwind.config.js +29 -0
- hexdag/studio/ui/tsconfig.json +37 -0
- hexdag/studio/ui/tsconfig.node.json +13 -0
- hexdag/studio/ui/vite.config.ts +35 -0
- hexdag/visualization/__init__.py +69 -0
- hexdag/visualization/dag_visualizer.py +1020 -0
- hexdag-0.5.0.dev1.dist-info/METADATA +369 -0
- hexdag-0.5.0.dev1.dist-info/RECORD +261 -0
- hexdag-0.5.0.dev1.dist-info/WHEEL +4 -0
- hexdag-0.5.0.dev1.dist-info/entry_points.txt +4 -0
- hexdag-0.5.0.dev1.dist-info/licenses/LICENSE +190 -0
- hexdag_plugins/.gitignore +43 -0
- hexdag_plugins/README.md +73 -0
- hexdag_plugins/__init__.py +1 -0
- hexdag_plugins/azure/LICENSE +21 -0
- hexdag_plugins/azure/README.md +414 -0
- hexdag_plugins/azure/__init__.py +21 -0
- hexdag_plugins/azure/azure_blob_adapter.py +450 -0
- hexdag_plugins/azure/azure_cosmos_adapter.py +383 -0
- hexdag_plugins/azure/azure_keyvault_adapter.py +314 -0
- hexdag_plugins/azure/azure_openai_adapter.py +415 -0
- hexdag_plugins/azure/pyproject.toml +107 -0
- hexdag_plugins/azure/tests/__init__.py +1 -0
- hexdag_plugins/azure/tests/test_azure_blob_adapter.py +350 -0
- hexdag_plugins/azure/tests/test_azure_cosmos_adapter.py +323 -0
- hexdag_plugins/azure/tests/test_azure_keyvault_adapter.py +330 -0
- hexdag_plugins/azure/tests/test_azure_openai_adapter.py +329 -0
- hexdag_plugins/hexdag_etl/README.md +168 -0
- hexdag_plugins/hexdag_etl/__init__.py +53 -0
- hexdag_plugins/hexdag_etl/examples/01_simple_pandas_transform.py +270 -0
- hexdag_plugins/hexdag_etl/examples/02_simple_pandas_only.py +149 -0
- hexdag_plugins/hexdag_etl/examples/03_file_io_pipeline.py +109 -0
- hexdag_plugins/hexdag_etl/examples/test_pandas_transform.py +84 -0
- hexdag_plugins/hexdag_etl/hexdag.toml +25 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/__init__.py +48 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/__init__.py +13 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/api_extract.py +230 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/base_node_factory.py +181 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/file_io.py +415 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/outlook.py +492 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/pandas_transform.py +563 -0
- hexdag_plugins/hexdag_etl/hexdag_etl/nodes/sql_extract_load.py +112 -0
- hexdag_plugins/hexdag_etl/pyproject.toml +82 -0
- hexdag_plugins/hexdag_etl/test_transform.py +54 -0
- hexdag_plugins/hexdag_etl/tests/test_plugin_integration.py +62 -0
- hexdag_plugins/mysql_adapter/LICENSE +21 -0
- hexdag_plugins/mysql_adapter/README.md +224 -0
- hexdag_plugins/mysql_adapter/__init__.py +6 -0
- hexdag_plugins/mysql_adapter/mysql_adapter.py +408 -0
- hexdag_plugins/mysql_adapter/pyproject.toml +93 -0
- hexdag_plugins/mysql_adapter/tests/test_mysql_adapter.py +259 -0
- hexdag_plugins/storage/README.md +184 -0
- hexdag_plugins/storage/__init__.py +19 -0
- hexdag_plugins/storage/file/__init__.py +5 -0
- hexdag_plugins/storage/file/local.py +325 -0
- hexdag_plugins/storage/ports/__init__.py +5 -0
- hexdag_plugins/storage/ports/vector_store.py +236 -0
- hexdag_plugins/storage/sql/__init__.py +7 -0
- hexdag_plugins/storage/sql/base.py +187 -0
- hexdag_plugins/storage/sql/mysql.py +27 -0
- hexdag_plugins/storage/sql/postgresql.py +27 -0
- hexdag_plugins/storage/tests/__init__.py +1 -0
- hexdag_plugins/storage/tests/test_local_file_storage.py +161 -0
- hexdag_plugins/storage/tests/test_sql_adapters.py +212 -0
- hexdag_plugins/storage/vector/__init__.py +7 -0
- hexdag_plugins/storage/vector/chromadb.py +223 -0
- hexdag_plugins/storage/vector/in_memory.py +285 -0
- hexdag_plugins/storage/vector/pgvector.py +502 -0
hexdag/core/protocols.py
ADDED
|
@@ -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
|
+
]
|
hexdag/core/resolver.py
ADDED
|
@@ -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"]
|