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,89 @@
1
+ # Mock Adapters
2
+
3
+ Mock adapter implementations for testing and development without external dependencies.
4
+
5
+ ## Loading Mock Adapters
6
+
7
+ Mock adapters are **not loaded by default**. To use them, you have two options:
8
+
9
+ ### Option 1: Load via Configuration File
10
+
11
+ ```python
12
+ from hexdag.core.bootstrap import bootstrap_registry
13
+
14
+ # Load mock adapters explicitly
15
+ bootstrap_registry("hexdag/adapters/mock/hexdag.toml")
16
+ ```
17
+
18
+ ### Option 2: Direct Import
19
+
20
+ ```python
21
+ from hexdag.adapters.mock.mock_llm import MockLLM
22
+ from hexdag.adapters.mock.mock_database import MockDatabaseAdapter
23
+ from hexdag.adapters.mock.mock_tool_router import MockToolRouter
24
+
25
+ # Create instances directly
26
+ llm = MockLLM()
27
+ db = MockDatabaseAdapter()
28
+ router = MockToolRouter()
29
+ ```
30
+
31
+ ## Available Mock Adapters
32
+
33
+ ### MockLLM
34
+ Simulates LLM responses without API calls.
35
+
36
+ ```python
37
+ from hexdag.core.ports.llm import Message
38
+
39
+ llm = registry.get("mock_llm", namespace="plugin")
40
+ messages = [Message(role="user", content="Hello!")]
41
+ response = await llm.aresponse(messages)
42
+ ```
43
+
44
+ ### MockDatabaseAdapter
45
+ Provides sample e-commerce data for testing database operations.
46
+
47
+ ```python
48
+ db = registry.get("mock_database", namespace="plugin")
49
+ schemas = await db.aget_table_schemas()
50
+ results = await db.aexecute_query("SELECT * FROM customers LIMIT 5")
51
+ ```
52
+
53
+ ### MockToolRouter
54
+ Simulates tool execution with predefined responses.
55
+
56
+ ```python
57
+ router = registry.get("mock_tool_router", namespace="plugin")
58
+ tools = router.get_available_tools()
59
+ result = await router.acall_tool("calculate", {"expression": "2 + 2"})
60
+ ```
61
+
62
+ ## Configuration
63
+
64
+ Mock adapters can be configured via the `hexdag.toml` file:
65
+
66
+ ```toml
67
+ [settings.mock_llm]
68
+ responses = ["Custom response 1", "Custom response 2"]
69
+ delay_seconds = 0.1
70
+
71
+ [settings.mock_database]
72
+ enable_sample_data = true
73
+ delay_seconds = 0.0
74
+
75
+ [settings.mock_tool_router]
76
+ available_tools = ["search", "calculate", "get_weather"]
77
+ raise_on_unknown_tool = true
78
+ ```
79
+
80
+ ## Use Cases
81
+
82
+ - **Unit Testing**: Test workflows without external dependencies
83
+ - **Development**: Rapid prototyping without API keys or database setup
84
+ - **CI/CD**: Reliable, deterministic tests in pipelines
85
+ - **Demos**: Showcase functionality without infrastructure
86
+
87
+ ## Example
88
+
89
+ See `examples/22_mock_adapters_demo.py` for a complete demonstration of using mock adapters in workflows.
@@ -0,0 +1,15 @@
1
+ """Enhanced mock implementations for testing purposes."""
2
+
3
+ from .mock_database import MockDatabaseAdapter
4
+ from .mock_embedding import MockEmbedding
5
+ from .mock_llm import MockLLM
6
+ from .mock_tool_adapter import MockToolAdapter
7
+ from .mock_tool_router import MockToolRouter
8
+
9
+ __all__ = [
10
+ "MockLLM",
11
+ "MockEmbedding",
12
+ "MockDatabaseAdapter",
13
+ "MockToolRouter",
14
+ "MockToolAdapter",
15
+ ]
@@ -0,0 +1,50 @@
1
+ # HexDAG Configuration
2
+ # This configuration loads the mock adapters as our first plugin
3
+
4
+ # Core modules to load - these are the essential framework components
5
+ modules = [
6
+ "hexdag.core.ports", # Core port definitions (must load before adapters)
7
+ "hexdag.builtin.nodes", # Core node factories
8
+ "hexdag.builtin.adapters.mock", # Mock implementations of all ports
9
+ "hexdag.builtin.adapters.memory", # Memory adapter implementations
10
+ ]
11
+
12
+ # Development mode - allows post-bootstrap registration for testing
13
+ dev_mode = true
14
+
15
+ [settings]
16
+ # Logging configuration
17
+ log_level = "INFO"
18
+ enable_metrics = true
19
+
20
+ # Mock adapter default configurations
21
+ [settings.mock_llm]
22
+ # Default responses for the mock LLM
23
+ responses = [
24
+ "I understand your request.",
25
+ "Let me help you with that.",
26
+ "Here's what I found.",
27
+ ]
28
+ delay_seconds = 0.0
29
+
30
+ [settings.mock_database]
31
+ # Enable sample e-commerce data by default
32
+ enable_sample_data = true
33
+ delay_seconds = 0.0
34
+
35
+ [settings.mock_memory]
36
+ # In-memory storage settings
37
+ delay_seconds = 0.0
38
+ max_size = 1000 # Maximum number of items to store
39
+
40
+ [settings.mock_tool_router]
41
+ # Available mock tools
42
+ available_tools = [
43
+ "search",
44
+ "calculate",
45
+ "get_weather",
46
+ "get_time",
47
+ "translate",
48
+ ]
49
+ delay_seconds = 0.0
50
+ raise_on_unknown_tool = true
@@ -0,0 +1,225 @@
1
+ """Mock database port implementation for testing."""
2
+
3
+ import asyncio
4
+ from typing import Any
5
+
6
+ from hexdag.core.ports.database import DatabasePort
7
+
8
+
9
+ class MockDatabaseAdapter(DatabasePort):
10
+ """Mock implementation of DatabasePort for testing and demos."""
11
+
12
+ # Type annotations for attributes
13
+ enable_sample_data: bool
14
+ delay_seconds: float
15
+
16
+ def __init__(self, **kwargs: Any) -> None:
17
+ """Initialize with configuration.
18
+
19
+ Args
20
+ ----
21
+ **kwargs: Configuration options (enable_sample_data, delay_seconds)
22
+ """
23
+ self.enable_sample_data = kwargs.get("enable_sample_data", True)
24
+ self.delay_seconds = kwargs.get("delay_seconds", 0.0)
25
+
26
+ if not self.enable_sample_data:
27
+ self._table_schemas: dict[str, dict[str, Any]] = {}
28
+ self._relationships: list[dict[str, Any]] = []
29
+ self._indexes: list[dict[str, Any]] = []
30
+ self._table_statistics: dict[str, dict[str, Any]] = {}
31
+ return
32
+
33
+ self._table_schemas = {
34
+ "customers": {
35
+ "table_name": "customers",
36
+ "columns": {
37
+ "id": "INTEGER PRIMARY KEY",
38
+ "customer_name": "VARCHAR(255)",
39
+ "email": "VARCHAR(255)",
40
+ "segment": "VARCHAR(50)",
41
+ "created_date": "TIMESTAMP",
42
+ "status": "VARCHAR(20)",
43
+ },
44
+ "primary_keys": ["id"],
45
+ "foreign_keys": [],
46
+ },
47
+ "orders": {
48
+ "table_name": "orders",
49
+ "columns": {
50
+ "id": "INTEGER PRIMARY KEY",
51
+ "customer_id": "INTEGER",
52
+ "order_date": "DATE",
53
+ "order_value": "DECIMAL(10,2)",
54
+ "status": "VARCHAR(20)",
55
+ "created_at": "TIMESTAMP",
56
+ },
57
+ "primary_keys": ["id"],
58
+ "foreign_keys": [
59
+ {
60
+ "column": "customer_id",
61
+ "references_table": "customers",
62
+ "references_column": "id",
63
+ }
64
+ ],
65
+ },
66
+ "products": {
67
+ "table_name": "products",
68
+ "columns": {
69
+ "id": "INTEGER PRIMARY KEY",
70
+ "product_name": "VARCHAR(255)",
71
+ "category": "VARCHAR(100)",
72
+ "price": "DECIMAL(8,2)",
73
+ "created_at": "TIMESTAMP",
74
+ },
75
+ "primary_keys": ["id"],
76
+ "foreign_keys": [],
77
+ },
78
+ "order_items": {
79
+ "table_name": "order_items",
80
+ "columns": {
81
+ "id": "INTEGER PRIMARY KEY",
82
+ "order_id": "INTEGER",
83
+ "product_id": "INTEGER",
84
+ "quantity": "INTEGER",
85
+ "unit_price": "DECIMAL(8,2)",
86
+ },
87
+ "primary_keys": ["id"],
88
+ "foreign_keys": [
89
+ {
90
+ "column": "order_id",
91
+ "references_table": "orders",
92
+ "references_column": "id",
93
+ },
94
+ {
95
+ "column": "product_id",
96
+ "references_table": "products",
97
+ "references_column": "id",
98
+ },
99
+ ],
100
+ },
101
+ }
102
+
103
+ self._relationships = [
104
+ {
105
+ "from_table": "orders",
106
+ "from_column": "customer_id",
107
+ "to_table": "customers",
108
+ "to_column": "id",
109
+ "relationship_type": "many_to_one",
110
+ },
111
+ {
112
+ "from_table": "order_items",
113
+ "from_column": "order_id",
114
+ "to_table": "orders",
115
+ "to_column": "id",
116
+ "relationship_type": "many_to_one",
117
+ },
118
+ {
119
+ "from_table": "order_items",
120
+ "from_column": "product_id",
121
+ "to_table": "products",
122
+ "to_column": "id",
123
+ "relationship_type": "many_to_one",
124
+ },
125
+ ]
126
+
127
+ self._indexes = [
128
+ {
129
+ "index_name": "idx_customers_email",
130
+ "table_name": "customers",
131
+ "columns": ["email"],
132
+ "index_type": "btree",
133
+ "is_unique": True,
134
+ },
135
+ {
136
+ "index_name": "idx_orders_customer_id",
137
+ "table_name": "orders",
138
+ "columns": ["customer_id"],
139
+ "index_type": "btree",
140
+ "is_unique": False,
141
+ },
142
+ {
143
+ "index_name": "idx_orders_date",
144
+ "table_name": "orders",
145
+ "columns": ["order_date"],
146
+ "index_type": "btree",
147
+ "is_unique": False,
148
+ },
149
+ {
150
+ "index_name": "idx_order_items_order_id",
151
+ "table_name": "order_items",
152
+ "columns": ["order_id"],
153
+ "index_type": "btree",
154
+ "is_unique": False,
155
+ },
156
+ ]
157
+
158
+ self._table_statistics = {
159
+ "customers": {
160
+ "row_count": 10000,
161
+ "size_bytes": 2048000,
162
+ "last_updated": "2024-01-15T10:30:00Z",
163
+ },
164
+ "orders": {
165
+ "row_count": 50000,
166
+ "size_bytes": 8192000,
167
+ "last_updated": "2024-01-15T11:45:00Z",
168
+ },
169
+ "products": {
170
+ "row_count": 1000,
171
+ "size_bytes": 512000,
172
+ "last_updated": "2024-01-10T09:00:00Z",
173
+ },
174
+ "order_items": {
175
+ "row_count": 150000,
176
+ "size_bytes": 12288000,
177
+ "last_updated": "2024-01-15T11:45:00Z",
178
+ },
179
+ }
180
+
181
+ # Required methods from DatabasePort
182
+ async def aget_table_schemas(self) -> dict[str, dict[str, Any]]:
183
+ """Get schema information for all tables."""
184
+ if self.delay_seconds > 0:
185
+ await asyncio.sleep(self.delay_seconds)
186
+ return self._table_schemas.copy()
187
+
188
+ async def aexecute_query(
189
+ self, query: str, params: dict[str, Any] | None = None
190
+ ) -> list[dict[str, Any]]:
191
+ """Execute a SQL query and return results."""
192
+ if self.delay_seconds > 0:
193
+ await asyncio.sleep(self.delay_seconds)
194
+
195
+ # Mock implementation - returns sample data based on query keywords
196
+ if "customers" in query.lower():
197
+ return [
198
+ {"id": 1, "customer_name": "John Doe", "segment": "Premium"},
199
+ {"id": 2, "customer_name": "Jane Smith", "segment": "Standard"},
200
+ ]
201
+ if "orders" in query.lower():
202
+ return [
203
+ {"id": 101, "customer_id": 1, "order_value": 299.99},
204
+ {"id": 102, "customer_id": 2, "order_value": 149.50},
205
+ ]
206
+ return []
207
+
208
+ # Optional methods from DatabasePort
209
+ async def aget_relationships(self) -> list[dict[str, Any]]:
210
+ """Get foreign key relationships between tables."""
211
+ if self.delay_seconds > 0:
212
+ await asyncio.sleep(self.delay_seconds)
213
+ return self._relationships.copy()
214
+
215
+ async def aget_indexes(self) -> list[dict[str, Any]]:
216
+ """Get index information for performance optimization."""
217
+ if self.delay_seconds > 0:
218
+ await asyncio.sleep(self.delay_seconds)
219
+ return self._indexes.copy()
220
+
221
+ async def aget_table_statistics(self) -> dict[str, dict[str, Any]]:
222
+ """Get table statistics for query optimization."""
223
+ if self.delay_seconds > 0:
224
+ await asyncio.sleep(self.delay_seconds)
225
+ return self._table_statistics.copy()
@@ -0,0 +1,223 @@
1
+ """Mock Embedding implementation for testing purposes."""
2
+
3
+ import asyncio
4
+ import hashlib
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from hexdag.core.ports.embedding import ImageInput
8
+
9
+ if TYPE_CHECKING:
10
+ from hexdag.core.ports.healthcheck import HealthStatus
11
+
12
+
13
+ class MockEmbedding:
14
+ """Mock implementation of the Embedding interface for testing.
15
+
16
+ This mock generates deterministic embeddings based on the input text's hash,
17
+ making tests predictable and reproducible. It also provides utilities for
18
+ testing like call inspection and configurable delays.
19
+ """
20
+
21
+ # Type annotations for attributes
22
+ delay_seconds: float
23
+ dimensions: int
24
+ call_count: int
25
+ last_texts: list[str]
26
+ last_images: list[ImageInput]
27
+ should_raise: bool
28
+
29
+ def __init__(self, **kwargs: Any) -> None:
30
+ """Initialize with configuration.
31
+
32
+ Args
33
+ ----
34
+ **kwargs: Configuration options
35
+ - dimensions: Embedding vector dimensions (default: 1536)
36
+ - delay_seconds: Delay before returning embeddings (default: 0.0)
37
+ """
38
+ self.delay_seconds = kwargs.get("delay_seconds", 0.0)
39
+ self.dimensions = kwargs.get("dimensions", 1536)
40
+
41
+ # Non-config state
42
+ self.call_count = 0
43
+ self.last_texts: list[str] = []
44
+ self.last_images: list[ImageInput] = []
45
+ self.should_raise = False
46
+
47
+ def _generate_embedding(self, text: str) -> list[float]:
48
+ """Generate a deterministic embedding vector from text.
49
+
50
+ Uses text hash to generate consistent embeddings for the same input.
51
+ """
52
+ # Use hash to generate deterministic values (not for security)
53
+ text_hash = hashlib.md5(text.encode(), usedforsecurity=False).hexdigest() # noqa: S324
54
+
55
+ # Generate embedding values from hash
56
+ embedding = []
57
+ for i in range(self.dimensions):
58
+ # Use chunks of the hash to generate values
59
+ chunk_idx = (i * 4) % len(text_hash)
60
+ chunk = text_hash[chunk_idx : chunk_idx + 4]
61
+ # Convert to float in range [-1, 1]
62
+ value = (int(chunk, 16) / 65535.0) * 2 - 1
63
+ embedding.append(value)
64
+
65
+ return embedding
66
+
67
+ def _generate_image_embedding(self, image: ImageInput) -> list[float]:
68
+ """Generate a deterministic embedding vector from image.
69
+
70
+ Uses image data hash to generate consistent embeddings for the same input.
71
+ """
72
+ # Convert image to bytes for hashing
73
+ image_bytes = image.encode() if isinstance(image, str) else image
74
+
75
+ # Use hash to generate deterministic values (not for security)
76
+ image_hash = hashlib.md5(image_bytes, usedforsecurity=False).hexdigest() # noqa: S324
77
+
78
+ # Generate embedding values from hash
79
+ embedding = []
80
+ for i in range(self.dimensions):
81
+ # Use chunks of the hash to generate values
82
+ chunk_idx = (i * 4) % len(image_hash)
83
+ chunk = image_hash[chunk_idx : chunk_idx + 4]
84
+ # Convert to float in range [-1, 1]
85
+ value = (int(chunk, 16) / 65535.0) * 2 - 1
86
+ embedding.append(value)
87
+
88
+ return embedding
89
+
90
+ async def aembed(self, text: str) -> list[float]:
91
+ """Generate embedding vector for a single text input.
92
+
93
+ Parameters
94
+ ----------
95
+ text : str
96
+ Text string to embed
97
+
98
+ Returns
99
+ -------
100
+ list[float]
101
+ Mock embedding vector
102
+
103
+ Raises
104
+ ------
105
+ Exception
106
+ When should_raise is True for testing error conditions
107
+ """
108
+ self.last_texts = [text]
109
+
110
+ if self.delay_seconds > 0:
111
+ await asyncio.sleep(self.delay_seconds)
112
+
113
+ if self.should_raise:
114
+ raise Exception("Mock Embedding error for testing")
115
+
116
+ self.call_count += 1
117
+ return self._generate_embedding(text)
118
+
119
+ async def aembed_batch(self, texts: list[str]) -> list[list[float]]:
120
+ """Generate embeddings for multiple texts.
121
+
122
+ Parameters
123
+ ----------
124
+ texts : list[str]
125
+ List of text strings to embed
126
+
127
+ Returns
128
+ -------
129
+ list[list[float]]
130
+ List of mock embedding vectors
131
+
132
+ Raises
133
+ ------
134
+ Exception
135
+ When should_raise is True for testing error conditions
136
+ """
137
+ self.last_texts = texts
138
+
139
+ if self.delay_seconds > 0:
140
+ await asyncio.sleep(self.delay_seconds)
141
+
142
+ if self.should_raise:
143
+ raise Exception("Mock Embedding batch error for testing")
144
+
145
+ self.call_count += len(texts)
146
+ return [self._generate_embedding(text) for text in texts]
147
+
148
+ async def aembed_image(self, image: ImageInput) -> list[float]:
149
+ """Generate embedding vector for a single image input.
150
+
151
+ Parameters
152
+ ----------
153
+ image : ImageInput
154
+ Image to embed (file path, base64 string, or bytes)
155
+
156
+ Returns
157
+ -------
158
+ list[float]
159
+ Mock embedding vector
160
+
161
+ Raises
162
+ ------
163
+ Exception
164
+ When should_raise is True for testing error conditions
165
+ """
166
+ self.last_images = [image]
167
+
168
+ if self.delay_seconds > 0:
169
+ await asyncio.sleep(self.delay_seconds)
170
+
171
+ if self.should_raise:
172
+ raise Exception("Mock Image Embedding error for testing")
173
+
174
+ self.call_count += 1
175
+ return self._generate_image_embedding(image)
176
+
177
+ async def aembed_image_batch(self, images: list[ImageInput]) -> list[list[float]]:
178
+ """Generate embeddings for multiple images.
179
+
180
+ Parameters
181
+ ----------
182
+ images : list[ImageInput]
183
+ List of images to embed (file paths, base64 strings, or bytes)
184
+
185
+ Returns
186
+ -------
187
+ list[list[float]]
188
+ List of mock embedding vectors
189
+
190
+ Raises
191
+ ------
192
+ Exception
193
+ When should_raise is True for testing error conditions
194
+ """
195
+ self.last_images = images
196
+
197
+ if self.delay_seconds > 0:
198
+ await asyncio.sleep(self.delay_seconds)
199
+
200
+ if self.should_raise:
201
+ raise Exception("Mock Image Embedding batch error for testing")
202
+
203
+ self.call_count += len(images)
204
+ return [self._generate_image_embedding(image) for image in images]
205
+
206
+ async def ahealth_check(self) -> "HealthStatus":
207
+ """Health check for Mock Embedding (always healthy)."""
208
+ from hexdag.core.ports.healthcheck import HealthStatus
209
+
210
+ return HealthStatus(
211
+ status="healthy",
212
+ adapter_name="MockEmbedding",
213
+ latency_ms=0.1,
214
+ details={"dimensions": self.dimensions},
215
+ )
216
+
217
+ # Testing utilities (not part of the Embedding port interface)
218
+ def reset(self) -> None:
219
+ """Reset the mock state for testing."""
220
+ self.call_count = 0
221
+ self.last_texts = []
222
+ self.last_images = []
223
+ self.should_raise = False