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
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
"""Plugin development commands for hexDAG CLI."""
|
|
2
|
+
|
|
3
|
+
import subprocess # nosec B404 - subprocess is used safely with controlled inputs
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
app = typer.Typer()
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_plugin_dir() -> Path:
|
|
18
|
+
"""Get the hexdag_plugins directory path."""
|
|
19
|
+
# Try to find the project root
|
|
20
|
+
current = Path.cwd()
|
|
21
|
+
while current != current.parent:
|
|
22
|
+
if (current / "hexdag_plugins").exists():
|
|
23
|
+
return current / "hexdag_plugins"
|
|
24
|
+
if (current / "pyproject.toml").exists():
|
|
25
|
+
with Path.open(current / "pyproject.toml") as f:
|
|
26
|
+
if "hexdag" in f.read():
|
|
27
|
+
plugin_dir = current / "hexdag_plugins"
|
|
28
|
+
if not plugin_dir.exists():
|
|
29
|
+
plugin_dir.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
return plugin_dir
|
|
31
|
+
current = current.parent
|
|
32
|
+
|
|
33
|
+
# Default to current directory
|
|
34
|
+
plugin_dir = Path.cwd() / "hexdag_plugins"
|
|
35
|
+
if not plugin_dir.exists():
|
|
36
|
+
console.print("[yellow]Creating hexdag_plugins directory in current location[/yellow]")
|
|
37
|
+
plugin_dir.mkdir(parents=True, exist_ok=True)
|
|
38
|
+
return plugin_dir
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.command("new")
|
|
42
|
+
def create_plugin(
|
|
43
|
+
name: Annotated[str, typer.Argument(help="Name of the new plugin (e.g., redis_adapter)")],
|
|
44
|
+
port: Annotated[str, typer.Option("--port", "-p", help="Port type to implement")] = "database",
|
|
45
|
+
author: Annotated[
|
|
46
|
+
str, typer.Option("--author", "-a", help="Plugin author name")
|
|
47
|
+
] = "HexDAG Team",
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Create a new plugin from template."""
|
|
50
|
+
plugin_dir = get_plugin_dir()
|
|
51
|
+
plugin_path = plugin_dir / name
|
|
52
|
+
|
|
53
|
+
if plugin_path.exists():
|
|
54
|
+
console.print(f"[red]Error: Plugin '{name}' already exists at {plugin_path}[/red]")
|
|
55
|
+
raise typer.Exit(1)
|
|
56
|
+
|
|
57
|
+
console.print(f"[green]Creating new plugin: {name}[/green]")
|
|
58
|
+
|
|
59
|
+
plugin_path.mkdir(parents=True)
|
|
60
|
+
(plugin_path / "tests").mkdir()
|
|
61
|
+
|
|
62
|
+
class_name = name.replace("_", " ").title().replace(" ", "")
|
|
63
|
+
init_content = f'''"""${name} plugin for hexDAG."""
|
|
64
|
+
|
|
65
|
+
from .{name} import {class_name}
|
|
66
|
+
|
|
67
|
+
__all__ = ["{class_name}"]
|
|
68
|
+
'''
|
|
69
|
+
(plugin_path / "__init__.py").write_text(init_content)
|
|
70
|
+
|
|
71
|
+
class_name = name.replace("_", " ").title().replace(" ", "")
|
|
72
|
+
adapter_content = f'''"""{class_name} implementation."""
|
|
73
|
+
|
|
74
|
+
import os
|
|
75
|
+
from typing import Any
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class {class_name}:
|
|
79
|
+
"""{class_name} adapter for hexDAG.
|
|
80
|
+
|
|
81
|
+
This adapter implements the {port} port interface.
|
|
82
|
+
|
|
83
|
+
YAML Usage:
|
|
84
|
+
```yaml
|
|
85
|
+
ports:
|
|
86
|
+
{port}:
|
|
87
|
+
adapter: hexdag_plugins.{name}.{class_name}
|
|
88
|
+
config:
|
|
89
|
+
# your config here
|
|
90
|
+
```
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(
|
|
94
|
+
self,
|
|
95
|
+
# Add your configuration parameters here
|
|
96
|
+
# Example with secret:
|
|
97
|
+
# api_key: str | None = None,
|
|
98
|
+
**kwargs: Any,
|
|
99
|
+
) -> None:
|
|
100
|
+
"""Initialize {name}.
|
|
101
|
+
|
|
102
|
+
Parameters
|
|
103
|
+
----------
|
|
104
|
+
**kwargs : Any
|
|
105
|
+
Additional configuration options
|
|
106
|
+
"""
|
|
107
|
+
# Example secret resolution:
|
|
108
|
+
# self.api_key = api_key or os.getenv("MY_API_KEY")
|
|
109
|
+
# if not self.api_key:
|
|
110
|
+
# raise ValueError("api_key required (pass directly or set MY_API_KEY)")
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
# TODO: Implement the {port} port interface methods
|
|
114
|
+
# Check hexdag/core/ports/{port}.py for the interface definition
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str:
|
|
117
|
+
"""String representation."""
|
|
118
|
+
return f"{class_name}()"
|
|
119
|
+
'''
|
|
120
|
+
(plugin_path / f"{name}.py").write_text(adapter_content)
|
|
121
|
+
|
|
122
|
+
pyproject_content = f"""[project]
|
|
123
|
+
name = "hexdag-{name.replace("_", "-")}"
|
|
124
|
+
version = "0.1.0"
|
|
125
|
+
description = "{class_name} plugin for hexDAG"
|
|
126
|
+
authors = [{{ name = "{author}" }}]
|
|
127
|
+
requires-python = "~=3.12.0"
|
|
128
|
+
readme = "README.md"
|
|
129
|
+
license = "MIT"
|
|
130
|
+
keywords = ["hexdag", "plugin", "{port}", "adapter"]
|
|
131
|
+
classifiers = [
|
|
132
|
+
"Development Status :: 3 - Alpha",
|
|
133
|
+
"Intended Audience :: Developers",
|
|
134
|
+
"License :: OSI Approved :: MIT License",
|
|
135
|
+
"Programming Language :: Python :: 3",
|
|
136
|
+
"Programming Language :: Python :: 3.12",
|
|
137
|
+
]
|
|
138
|
+
dependencies = [
|
|
139
|
+
# Do not add hexdag as a dependency (it's the parent project)
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
[build-system]
|
|
143
|
+
requires = ["hatchling"]
|
|
144
|
+
build-backend = "hatchling.build"
|
|
145
|
+
|
|
146
|
+
[tool.ruff]
|
|
147
|
+
line-length = 100
|
|
148
|
+
target-version = "py312"
|
|
149
|
+
|
|
150
|
+
[tool.mypy]
|
|
151
|
+
python_version = "3.12"
|
|
152
|
+
warn_return_any = true
|
|
153
|
+
disallow_untyped_defs = false
|
|
154
|
+
check_untyped_defs = true
|
|
155
|
+
"""
|
|
156
|
+
(plugin_path / "pyproject.toml").write_text(pyproject_content)
|
|
157
|
+
|
|
158
|
+
readme_content = f"""# {class_name}
|
|
159
|
+
|
|
160
|
+
A hexDAG plugin that provides {name.replace("_", " ")} functionality.
|
|
161
|
+
|
|
162
|
+
## Installation
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# From the hexDAG root directory
|
|
166
|
+
pip install -e hexdag_plugins/{name}/
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Usage in YAML Pipelines
|
|
170
|
+
|
|
171
|
+
The {class_name} implements the `{port}` port interface.
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
apiVersion: hexdag/v1
|
|
175
|
+
kind: Pipeline
|
|
176
|
+
metadata:
|
|
177
|
+
name: my-pipeline
|
|
178
|
+
spec:
|
|
179
|
+
ports:
|
|
180
|
+
{port}:
|
|
181
|
+
adapter: hexdag_plugins.{name}.{class_name}
|
|
182
|
+
config:
|
|
183
|
+
# your config here
|
|
184
|
+
|
|
185
|
+
nodes:
|
|
186
|
+
# your nodes here
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Usage in Python
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from hexdag_plugins.{name} import {class_name}
|
|
193
|
+
|
|
194
|
+
adapter = {class_name}()
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Run tests
|
|
201
|
+
hexdag plugin test {name}
|
|
202
|
+
|
|
203
|
+
# Format code
|
|
204
|
+
hexdag plugin format {name}
|
|
205
|
+
|
|
206
|
+
# Lint code
|
|
207
|
+
hexdag plugin lint {name}
|
|
208
|
+
```
|
|
209
|
+
"""
|
|
210
|
+
(plugin_path / "README.md").write_text(readme_content)
|
|
211
|
+
|
|
212
|
+
test_content = f'''"""Tests for {name}."""
|
|
213
|
+
|
|
214
|
+
import pytest
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class Test{class_name}:
|
|
218
|
+
"""Test suite for {class_name}."""
|
|
219
|
+
|
|
220
|
+
def test_adapter_initialization(self):
|
|
221
|
+
"""{class_name} should initialize without errors."""
|
|
222
|
+
from hexdag_plugins.{name}.{name} import {class_name}
|
|
223
|
+
|
|
224
|
+
adapter = {class_name}()
|
|
225
|
+
assert adapter is not None
|
|
226
|
+
assert repr(adapter) == "{class_name}()"
|
|
227
|
+
|
|
228
|
+
# TODO: Add more tests for your adapter functionality
|
|
229
|
+
'''
|
|
230
|
+
(plugin_path / "tests" / f"test_{name}.py").write_text(test_content)
|
|
231
|
+
|
|
232
|
+
license_content = """MIT License
|
|
233
|
+
|
|
234
|
+
Copyright (c) 2024 HexDAG Team
|
|
235
|
+
|
|
236
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
237
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
238
|
+
in the Software without restriction, including without limitation the rights
|
|
239
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
240
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
241
|
+
furnished to do so, subject to the following conditions:
|
|
242
|
+
|
|
243
|
+
The above copyright notice and this permission notice shall be included in all
|
|
244
|
+
copies or substantial portions of the Software.
|
|
245
|
+
|
|
246
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
247
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
248
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
249
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
250
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
251
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
252
|
+
SOFTWARE.
|
|
253
|
+
"""
|
|
254
|
+
(plugin_path / "LICENSE").write_text(license_content)
|
|
255
|
+
|
|
256
|
+
# Display success message with next steps
|
|
257
|
+
console.print(Panel(f"[green]✓ Plugin '{name}' created successfully![/green]"))
|
|
258
|
+
|
|
259
|
+
table = Table(title="Next Steps", show_header=False, box=None)
|
|
260
|
+
table.add_row("1.", f"Edit [cyan]{plugin_path / f'{name}.py'}[/cyan] to implement your adapter")
|
|
261
|
+
table.add_row(
|
|
262
|
+
"2.", f"Update the port interface methods based on [cyan]hexdag/core/ports/{port}.py[/cyan]"
|
|
263
|
+
)
|
|
264
|
+
table.add_row("3.", f"Add dependencies to [cyan]{plugin_path / 'pyproject.toml'}[/cyan]")
|
|
265
|
+
table.add_row("4.", f"Run [yellow]hexdag plugin lint {name}[/yellow] to check your code")
|
|
266
|
+
table.add_row(
|
|
267
|
+
"5.", f"Run [yellow]hexdag plugin test {name}[/yellow] to test your implementation"
|
|
268
|
+
)
|
|
269
|
+
console.print(table)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
@app.command("list")
|
|
273
|
+
def list_plugins() -> None:
|
|
274
|
+
"""List all available plugins."""
|
|
275
|
+
plugin_dir = get_plugin_dir()
|
|
276
|
+
|
|
277
|
+
if not plugin_dir.exists():
|
|
278
|
+
console.print("[yellow]No plugins directory found[/yellow]")
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
plugins = [
|
|
282
|
+
d
|
|
283
|
+
for d in plugin_dir.iterdir()
|
|
284
|
+
if d.is_dir() and not d.name.startswith(".") and d.name != "__pycache__"
|
|
285
|
+
]
|
|
286
|
+
|
|
287
|
+
if not plugins:
|
|
288
|
+
console.print("[yellow]No plugins found[/yellow]")
|
|
289
|
+
return
|
|
290
|
+
|
|
291
|
+
table = Table(title="Available Plugins", show_header=True)
|
|
292
|
+
table.add_column("Plugin", style="cyan")
|
|
293
|
+
table.add_column("Version", style="green")
|
|
294
|
+
table.add_column("Port", style="yellow")
|
|
295
|
+
table.add_column("Description")
|
|
296
|
+
|
|
297
|
+
for plugin_path in plugins:
|
|
298
|
+
name = plugin_path.name
|
|
299
|
+
pyproject_path = plugin_path / "pyproject.toml"
|
|
300
|
+
|
|
301
|
+
if pyproject_path.exists():
|
|
302
|
+
import tomllib
|
|
303
|
+
|
|
304
|
+
with Path.open(pyproject_path, "rb") as f:
|
|
305
|
+
data = tomllib.load(f)
|
|
306
|
+
version = data.get("project", {}).get("version", "unknown")
|
|
307
|
+
plugin_info = data.get("tool", {}).get("hexdag", {}).get("plugin", {})
|
|
308
|
+
port = plugin_info.get("port", "unknown")
|
|
309
|
+
description = plugin_info.get("description", "No description")
|
|
310
|
+
else:
|
|
311
|
+
version = "unknown"
|
|
312
|
+
port = "unknown"
|
|
313
|
+
description = "No pyproject.toml found"
|
|
314
|
+
|
|
315
|
+
table.add_row(name, version, port, description)
|
|
316
|
+
|
|
317
|
+
console.print(table)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@app.command("lint")
|
|
321
|
+
def lint_plugin(
|
|
322
|
+
name: Annotated[str, typer.Argument(help="Plugin name to lint")],
|
|
323
|
+
fix: Annotated[bool, typer.Option("--fix", "-f", help="Auto-fix issues")] = True,
|
|
324
|
+
) -> None:
|
|
325
|
+
"""Lint a plugin's code."""
|
|
326
|
+
plugin_dir = get_plugin_dir()
|
|
327
|
+
plugin_path = plugin_dir / name
|
|
328
|
+
|
|
329
|
+
if not plugin_path.exists():
|
|
330
|
+
console.print(f"[red]Plugin '{name}' not found[/red]")
|
|
331
|
+
raise typer.Exit(1)
|
|
332
|
+
|
|
333
|
+
console.print(f"[yellow]Linting {name}...[/yellow]")
|
|
334
|
+
|
|
335
|
+
# Run ruff check
|
|
336
|
+
check_cmd = ["ruff", "check", str(plugin_path)]
|
|
337
|
+
if fix:
|
|
338
|
+
check_cmd.append("--fix")
|
|
339
|
+
|
|
340
|
+
result = subprocess.run(check_cmd, capture_output=True, text=True) # nosec B603
|
|
341
|
+
if result.stdout:
|
|
342
|
+
console.print(result.stdout)
|
|
343
|
+
if result.stderr:
|
|
344
|
+
console.print(f"[red]{result.stderr}[/red]")
|
|
345
|
+
|
|
346
|
+
# Run ruff format
|
|
347
|
+
format_cmd = ["ruff", "format", str(plugin_path)]
|
|
348
|
+
result = subprocess.run(format_cmd, capture_output=True, text=True) # nosec B603
|
|
349
|
+
if result.stdout:
|
|
350
|
+
console.print(result.stdout)
|
|
351
|
+
|
|
352
|
+
if result.returncode == 0:
|
|
353
|
+
console.print(f"[green]✓ Plugin '{name}' linted successfully[/green]")
|
|
354
|
+
else:
|
|
355
|
+
console.print(f"[red]✗ Linting failed for '{name}'[/red]")
|
|
356
|
+
raise typer.Exit(1)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
@app.command("format")
|
|
360
|
+
def format_plugin(
|
|
361
|
+
name: Annotated[str, typer.Argument(help="Plugin name to format")],
|
|
362
|
+
) -> None:
|
|
363
|
+
"""Format a plugin's code."""
|
|
364
|
+
plugin_dir = get_plugin_dir()
|
|
365
|
+
plugin_path = plugin_dir / name
|
|
366
|
+
|
|
367
|
+
if not plugin_path.exists():
|
|
368
|
+
console.print(f"[red]Plugin '{name}' not found[/red]")
|
|
369
|
+
raise typer.Exit(1)
|
|
370
|
+
|
|
371
|
+
console.print(f"[yellow]Formatting {name}...[/yellow]")
|
|
372
|
+
|
|
373
|
+
# Run ruff format
|
|
374
|
+
cmd = ["ruff", "format", str(plugin_path), "--line-length=100"]
|
|
375
|
+
result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
|
|
376
|
+
|
|
377
|
+
if result.stdout:
|
|
378
|
+
console.print(result.stdout)
|
|
379
|
+
|
|
380
|
+
if result.returncode == 0:
|
|
381
|
+
console.print(f"[green]✓ Plugin '{name}' formatted successfully[/green]")
|
|
382
|
+
else:
|
|
383
|
+
console.print(f"[red]✗ Formatting failed for '{name}'[/red]")
|
|
384
|
+
if result.stderr:
|
|
385
|
+
console.print(f"[red]{result.stderr}[/red]")
|
|
386
|
+
raise typer.Exit(1)
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
@app.command("test")
|
|
390
|
+
def test_plugin(
|
|
391
|
+
name: Annotated[str, typer.Argument(help="Plugin name to test")],
|
|
392
|
+
verbose: Annotated[bool, typer.Option("--verbose", "-v", help="Verbose output")] = False,
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Run tests for a plugin."""
|
|
395
|
+
plugin_dir = get_plugin_dir()
|
|
396
|
+
plugin_path = plugin_dir / name
|
|
397
|
+
|
|
398
|
+
if not plugin_path.exists():
|
|
399
|
+
console.print(f"[red]Plugin '{name}' not found[/red]")
|
|
400
|
+
raise typer.Exit(1)
|
|
401
|
+
|
|
402
|
+
test_dir = plugin_path / "tests"
|
|
403
|
+
if not test_dir.exists():
|
|
404
|
+
console.print(f"[yellow]No tests found for '{name}'[/yellow]")
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
console.print(f"[yellow]Testing {name}...[/yellow]")
|
|
408
|
+
|
|
409
|
+
# Run pytest
|
|
410
|
+
cmd = [sys.executable, "-m", "pytest", str(test_dir)]
|
|
411
|
+
if verbose:
|
|
412
|
+
cmd.append("-v")
|
|
413
|
+
|
|
414
|
+
result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
|
|
415
|
+
|
|
416
|
+
if result.stdout:
|
|
417
|
+
console.print(result.stdout)
|
|
418
|
+
|
|
419
|
+
if result.returncode == 0:
|
|
420
|
+
console.print(f"[green]✓ All tests passed for '{name}'[/green]")
|
|
421
|
+
else:
|
|
422
|
+
console.print(f"[red]✗ Tests failed for '{name}'[/red]")
|
|
423
|
+
if result.stderr:
|
|
424
|
+
console.print(f"[red]{result.stderr}[/red]")
|
|
425
|
+
raise typer.Exit(1)
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
@app.command("install")
|
|
429
|
+
def install_plugin(
|
|
430
|
+
name: Annotated[str, typer.Argument(help="Plugin name to install")],
|
|
431
|
+
editable: Annotated[
|
|
432
|
+
bool, typer.Option("--editable", "-e", help="Install in editable mode")
|
|
433
|
+
] = True,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""Install a plugin in development mode."""
|
|
436
|
+
plugin_dir = get_plugin_dir()
|
|
437
|
+
plugin_path = plugin_dir / name
|
|
438
|
+
|
|
439
|
+
if not plugin_path.exists():
|
|
440
|
+
console.print(f"[red]Plugin '{name}' not found[/red]")
|
|
441
|
+
raise typer.Exit(1)
|
|
442
|
+
|
|
443
|
+
console.print(f"[yellow]Installing {name}...[/yellow]")
|
|
444
|
+
|
|
445
|
+
# Install with pip
|
|
446
|
+
cmd = [sys.executable, "-m", "pip", "install"]
|
|
447
|
+
if editable:
|
|
448
|
+
cmd.append("-e")
|
|
449
|
+
cmd.append(str(plugin_path))
|
|
450
|
+
|
|
451
|
+
result = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
|
|
452
|
+
|
|
453
|
+
if result.stdout:
|
|
454
|
+
console.print(result.stdout)
|
|
455
|
+
|
|
456
|
+
if result.returncode == 0:
|
|
457
|
+
console.print(f"[green]✓ Plugin '{name}' installed successfully[/green]")
|
|
458
|
+
else:
|
|
459
|
+
console.print(f"[red]✗ Installation failed for '{name}'[/red]")
|
|
460
|
+
if result.stderr:
|
|
461
|
+
console.print(f"[red]{result.stderr}[/red]")
|
|
462
|
+
raise typer.Exit(1)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
@app.command("check-all")
|
|
466
|
+
def check_all_plugins() -> None:
|
|
467
|
+
"""Run lint and test for all plugins."""
|
|
468
|
+
plugin_dir = get_plugin_dir()
|
|
469
|
+
|
|
470
|
+
if not plugin_dir.exists():
|
|
471
|
+
console.print("[yellow]No plugins directory found[/yellow]")
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
plugins = [
|
|
475
|
+
d.name
|
|
476
|
+
for d in plugin_dir.iterdir()
|
|
477
|
+
if d.is_dir() and not d.name.startswith(".") and d.name != "__pycache__"
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
if not plugins:
|
|
481
|
+
console.print("[yellow]No plugins found[/yellow]")
|
|
482
|
+
return
|
|
483
|
+
|
|
484
|
+
console.print(f"[bold]Checking {len(plugins)} plugins...[/bold]\n")
|
|
485
|
+
|
|
486
|
+
results = []
|
|
487
|
+
for plugin_name in plugins:
|
|
488
|
+
console.print(f"[cyan]Checking {plugin_name}...[/cyan]")
|
|
489
|
+
|
|
490
|
+
# Lint
|
|
491
|
+
lint_success = True
|
|
492
|
+
try:
|
|
493
|
+
lint_plugin(plugin_name, fix=True)
|
|
494
|
+
except typer.Exit:
|
|
495
|
+
lint_success = False
|
|
496
|
+
|
|
497
|
+
# Test
|
|
498
|
+
test_success = True
|
|
499
|
+
try:
|
|
500
|
+
test_plugin(plugin_name)
|
|
501
|
+
except typer.Exit:
|
|
502
|
+
test_success = False
|
|
503
|
+
|
|
504
|
+
results.append((plugin_name, lint_success, test_success))
|
|
505
|
+
console.print() # Add spacing
|
|
506
|
+
|
|
507
|
+
# Summary
|
|
508
|
+
console.print("[bold]Summary:[/bold]")
|
|
509
|
+
table = Table(show_header=True)
|
|
510
|
+
table.add_column("Plugin", style="cyan")
|
|
511
|
+
table.add_column("Lint", style="green")
|
|
512
|
+
table.add_column("Test", style="green")
|
|
513
|
+
|
|
514
|
+
for plugin_name, lint_ok, test_ok in results:
|
|
515
|
+
lint_status = "[green]✓[/green]" if lint_ok else "[red]✗[/red]"
|
|
516
|
+
test_status = "[green]✓[/green]" if test_ok else "[red]✗[/red]"
|
|
517
|
+
table.add_row(plugin_name, lint_status, test_status)
|
|
518
|
+
|
|
519
|
+
console.print(table)
|
|
520
|
+
|
|
521
|
+
if all(lint_ok and test_ok for _, lint_ok, test_ok in results):
|
|
522
|
+
console.print("\n[green]✓ All plugins passed checks![/green]")
|
|
523
|
+
else:
|
|
524
|
+
console.print("\n[red]✗ Some plugins failed checks[/red]")
|
|
525
|
+
raise typer.Exit(1)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
if __name__ == "__main__":
|
|
529
|
+
app()
|