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,479 @@
|
|
|
1
|
+
"""TOML configuration loader for HexDAG."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import tomllib # Python 3.11+
|
|
8
|
+
from functools import lru_cache
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Literal, cast
|
|
11
|
+
|
|
12
|
+
from hexdag.core.config.models import HexDAGConfig, LoggingConfig, ManifestEntry
|
|
13
|
+
from hexdag.core.logging import get_logger
|
|
14
|
+
|
|
15
|
+
TOML_IMPORT_MESSAGE = (
|
|
16
|
+
"TOML support requires 'tomli' for Python < 3.11. Install with: pip install tomli"
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Type alias for configuration data that can be recursively substituted
|
|
21
|
+
ConfigData = str | dict[str, "ConfigData"] | list["ConfigData"] | int | float | bool | None
|
|
22
|
+
|
|
23
|
+
# Constants for boolean environment variable parsing
|
|
24
|
+
_TRUTHY_VALUES = frozenset({"true", "1", "yes", "on", "enabled"})
|
|
25
|
+
_FALSY_VALUES = frozenset({"false", "0", "no", "off", "disabled"})
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _parse_bool_env(value: str) -> bool:
|
|
31
|
+
"""Parse boolean from environment variable value.
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
value : str
|
|
36
|
+
Environment variable value
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
bool
|
|
41
|
+
Parsed boolean value
|
|
42
|
+
|
|
43
|
+
Raises
|
|
44
|
+
------
|
|
45
|
+
ValueError
|
|
46
|
+
If value is not a recognized boolean string
|
|
47
|
+
"""
|
|
48
|
+
normalized = value.lower().strip()
|
|
49
|
+
if normalized in _TRUTHY_VALUES:
|
|
50
|
+
return True
|
|
51
|
+
if normalized in _FALSY_VALUES:
|
|
52
|
+
return False
|
|
53
|
+
expected = _TRUTHY_VALUES | _FALSY_VALUES
|
|
54
|
+
raise ValueError(f"Invalid boolean value: {value!r}. Expected one of: {expected}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@lru_cache(maxsize=32)
|
|
58
|
+
def _load_and_parse_cached(path_str: str) -> HexDAGConfig:
|
|
59
|
+
"""Cached configuration loader."""
|
|
60
|
+
loader = ConfigLoader()
|
|
61
|
+
return loader._load_and_parse(Path(path_str))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class ConfigLoader:
|
|
65
|
+
"""Loads and processes HexDAG configuration from TOML files."""
|
|
66
|
+
|
|
67
|
+
ENV_VAR_PATTERN = re.compile(r"\$\{([^}]+)\}")
|
|
68
|
+
|
|
69
|
+
def __init__(self) -> None:
|
|
70
|
+
"""Initialize the config loader."""
|
|
71
|
+
|
|
72
|
+
if tomllib is None:
|
|
73
|
+
raise ImportError(TOML_IMPORT_MESSAGE)
|
|
74
|
+
|
|
75
|
+
def load_from_toml(self, path: str | Path | None = None) -> HexDAGConfig:
|
|
76
|
+
"""Load configuration from TOML file.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
path : str | Path | None
|
|
81
|
+
Path to TOML file. If None, searches for pyproject.toml or hexdag.toml
|
|
82
|
+
|
|
83
|
+
Returns
|
|
84
|
+
-------
|
|
85
|
+
HexDAGConfig
|
|
86
|
+
Parsed configuration with environment variables substituted
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
# Find config file
|
|
90
|
+
config_path = self._find_config_file(path)
|
|
91
|
+
|
|
92
|
+
# Use cached loader with file path
|
|
93
|
+
return _load_and_parse_cached(str(config_path.absolute()))
|
|
94
|
+
|
|
95
|
+
def _load_and_parse(self, config_path: Path) -> HexDAGConfig:
|
|
96
|
+
"""Load and parse configuration file."""
|
|
97
|
+
logger.info("Loading configuration from {path}", path=config_path)
|
|
98
|
+
|
|
99
|
+
# Load TOML
|
|
100
|
+
with config_path.open("rb") as f:
|
|
101
|
+
data = tomllib.load(f)
|
|
102
|
+
|
|
103
|
+
if config_path.name == "pyproject.toml":
|
|
104
|
+
# Look for [tool.hexdag] section
|
|
105
|
+
hexdag_data = data.get("tool", {}).get("hexdag", {})
|
|
106
|
+
if not hexdag_data:
|
|
107
|
+
logger.warning("No [tool.hexdag] section found in pyproject.toml, using defaults")
|
|
108
|
+
return get_default_config()
|
|
109
|
+
else:
|
|
110
|
+
# Direct hexdag.toml file - check if it has [tool.hexdag] or is flat
|
|
111
|
+
if "tool" in data and "hexdag" in data.get("tool", {}):
|
|
112
|
+
# TOML file uses [tool.hexdag] format (like pyproject.toml)
|
|
113
|
+
hexdag_data = data["tool"]["hexdag"]
|
|
114
|
+
else:
|
|
115
|
+
# Flat format (top-level keys)
|
|
116
|
+
hexdag_data = data
|
|
117
|
+
|
|
118
|
+
# Process environment variable substitution
|
|
119
|
+
hexdag_data = self._substitute_env_vars(hexdag_data)
|
|
120
|
+
|
|
121
|
+
# Parse configuration sections
|
|
122
|
+
return self._parse_config(hexdag_data)
|
|
123
|
+
|
|
124
|
+
def _find_config_file(self, path: str | Path | None) -> Path:
|
|
125
|
+
"""Find configuration file.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
path : str | Path | None
|
|
130
|
+
Explicit path or None to search
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
Path
|
|
135
|
+
Path to configuration file
|
|
136
|
+
|
|
137
|
+
Raises
|
|
138
|
+
------
|
|
139
|
+
FileNotFoundError
|
|
140
|
+
If no configuration file is found
|
|
141
|
+
"""
|
|
142
|
+
if path:
|
|
143
|
+
config_path = Path(path)
|
|
144
|
+
if not config_path.exists():
|
|
145
|
+
raise FileNotFoundError(f"Configuration file not found: {config_path}")
|
|
146
|
+
return config_path
|
|
147
|
+
|
|
148
|
+
# Check environment variable first
|
|
149
|
+
if env_path := os.getenv("HEXDAG_CONFIG_PATH"):
|
|
150
|
+
config_path = Path(env_path)
|
|
151
|
+
if config_path.exists():
|
|
152
|
+
logger.debug(f"Using config from HEXDAG_CONFIG_PATH: {config_path}")
|
|
153
|
+
return config_path
|
|
154
|
+
logger.warning(f"HEXDAG_CONFIG_PATH set but file not found: {config_path}")
|
|
155
|
+
|
|
156
|
+
# Search for config files in order of preference
|
|
157
|
+
search_paths = [
|
|
158
|
+
Path("hexdag.toml"),
|
|
159
|
+
Path("pyproject.toml"),
|
|
160
|
+
Path(".hexdag.toml"),
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
for search_path in search_paths:
|
|
164
|
+
if search_path.exists():
|
|
165
|
+
return search_path
|
|
166
|
+
|
|
167
|
+
# Also check parent directories for pyproject.toml
|
|
168
|
+
current = Path.cwd()
|
|
169
|
+
while current != current.parent:
|
|
170
|
+
pyproject = current / "pyproject.toml"
|
|
171
|
+
if pyproject.exists():
|
|
172
|
+
with pyproject.open("rb") as f:
|
|
173
|
+
data = tomllib.load(f)
|
|
174
|
+
if "tool" in data and "hexdag" in data["tool"]:
|
|
175
|
+
return pyproject
|
|
176
|
+
current = current.parent
|
|
177
|
+
|
|
178
|
+
raise FileNotFoundError(
|
|
179
|
+
"No configuration file found. Searched for: hexdag.toml, pyproject.toml, .hexdag.toml"
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
def _substitute_env_vars(self, data: Any) -> Any:
|
|
183
|
+
"""Recursively substitute environment variables in configuration.
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
data : Any
|
|
188
|
+
Configuration data (dict, list, str, etc.)
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
Any
|
|
193
|
+
Data with environment variables substituted
|
|
194
|
+
"""
|
|
195
|
+
if isinstance(data, str):
|
|
196
|
+
# Replace ${VAR} with environment variable value
|
|
197
|
+
def replacer(match: re.Match[str]) -> str:
|
|
198
|
+
var_name = match.group(1)
|
|
199
|
+
value = os.environ.get(var_name)
|
|
200
|
+
if value is None:
|
|
201
|
+
# Only log at debug level to avoid cluttering CLI output
|
|
202
|
+
logger.debug(
|
|
203
|
+
f"Environment variable ${{{var_name}}} not found, keeping placeholder"
|
|
204
|
+
)
|
|
205
|
+
return match.group(0) # Keep original placeholder
|
|
206
|
+
|
|
207
|
+
return value
|
|
208
|
+
|
|
209
|
+
return self.ENV_VAR_PATTERN.sub(replacer, data)
|
|
210
|
+
|
|
211
|
+
if isinstance(data, dict):
|
|
212
|
+
return {key: self._substitute_env_vars(value) for key, value in data.items()}
|
|
213
|
+
|
|
214
|
+
if isinstance(data, list):
|
|
215
|
+
return [self._substitute_env_vars(item) for item in data]
|
|
216
|
+
|
|
217
|
+
return data
|
|
218
|
+
|
|
219
|
+
def _parse_config(self, data: dict[str, Any]) -> HexDAGConfig:
|
|
220
|
+
"""Parse configuration data into HexDAGConfig.
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
data : dict[str, Any]
|
|
225
|
+
Raw configuration data from TOML
|
|
226
|
+
|
|
227
|
+
Returns
|
|
228
|
+
-------
|
|
229
|
+
HexDAGConfig
|
|
230
|
+
Parsed configuration object
|
|
231
|
+
"""
|
|
232
|
+
config = HexDAGConfig()
|
|
233
|
+
|
|
234
|
+
# Parse modules list
|
|
235
|
+
if "modules" in data:
|
|
236
|
+
config.modules = data["modules"]
|
|
237
|
+
logger.debug("Loaded {count} modules", count=len(config.modules))
|
|
238
|
+
|
|
239
|
+
# Parse plugins list
|
|
240
|
+
if "plugins" in data:
|
|
241
|
+
config.plugins = data["plugins"]
|
|
242
|
+
logger.debug("Loaded {count} plugins", count=len(config.plugins))
|
|
243
|
+
|
|
244
|
+
# Parse dev mode
|
|
245
|
+
config.dev_mode = data.get("dev_mode", False)
|
|
246
|
+
|
|
247
|
+
# Parse logging configuration with environment variable overrides
|
|
248
|
+
config.logging = self._parse_logging_config(data.get("logging", {}))
|
|
249
|
+
|
|
250
|
+
# Parse settings section
|
|
251
|
+
if "settings" in data:
|
|
252
|
+
config.settings = data["settings"]
|
|
253
|
+
logger.debug("Loaded {count} settings", count=len(config.settings))
|
|
254
|
+
|
|
255
|
+
return config
|
|
256
|
+
|
|
257
|
+
def _parse_logging_config(self, logging_data: dict[str, Any]) -> LoggingConfig:
|
|
258
|
+
"""Parse logging configuration with environment variable overrides.
|
|
259
|
+
|
|
260
|
+
Environment variables take precedence over TOML configuration:
|
|
261
|
+
- HEXDAG_LOG_LEVEL: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
262
|
+
- HEXDAG_LOG_FORMAT: Output format (console, json, structured, rich, dual)
|
|
263
|
+
- HEXDAG_LOG_FILE: Optional file path for log output
|
|
264
|
+
- HEXDAG_LOG_COLOR: Use color output (true/false)
|
|
265
|
+
- HEXDAG_LOG_TIMESTAMP: Include timestamp (true/false)
|
|
266
|
+
- HEXDAG_LOG_RICH: Use Rich library for enhanced output (true/false)
|
|
267
|
+
- HEXDAG_LOG_DUAL_SINK: Enable dual-sink mode (true/false)
|
|
268
|
+
- HEXDAG_LOG_STDLIB_BRIDGE: Enable stdlib logging bridge (true/false)
|
|
269
|
+
- HEXDAG_LOG_BACKTRACE: Enable backtrace in logs (true/false)
|
|
270
|
+
- HEXDAG_LOG_DIAGNOSE: Enable diagnose mode (true/false)
|
|
271
|
+
|
|
272
|
+
Parameters
|
|
273
|
+
----------
|
|
274
|
+
logging_data : dict[str, Any]
|
|
275
|
+
Logging section from TOML config
|
|
276
|
+
|
|
277
|
+
Returns
|
|
278
|
+
-------
|
|
279
|
+
LoggingConfig
|
|
280
|
+
Parsed logging configuration with env overrides applied
|
|
281
|
+
"""
|
|
282
|
+
# Start with TOML config values
|
|
283
|
+
level = logging_data.get("level", "INFO")
|
|
284
|
+
format_type = logging_data.get("format", "structured")
|
|
285
|
+
output_file = logging_data.get("output_file")
|
|
286
|
+
use_color = logging_data.get("use_color", True)
|
|
287
|
+
include_timestamp = logging_data.get("include_timestamp", True)
|
|
288
|
+
use_rich = logging_data.get("use_rich", False)
|
|
289
|
+
dual_sink = logging_data.get("dual_sink", False)
|
|
290
|
+
enable_stdlib_bridge = logging_data.get("enable_stdlib_bridge", False)
|
|
291
|
+
backtrace = logging_data.get("backtrace", True)
|
|
292
|
+
diagnose = logging_data.get("diagnose", True)
|
|
293
|
+
|
|
294
|
+
# Apply environment variable overrides
|
|
295
|
+
if env_level := os.getenv("HEXDAG_LOG_LEVEL"):
|
|
296
|
+
level = env_level.upper()
|
|
297
|
+
logger.debug(f"Overriding log level from env: {level}")
|
|
298
|
+
|
|
299
|
+
if env_format := os.getenv("HEXDAG_LOG_FORMAT"):
|
|
300
|
+
format_type = env_format.lower()
|
|
301
|
+
logger.debug(f"Overriding log format from env: {format_type}")
|
|
302
|
+
|
|
303
|
+
if env_file := os.getenv("HEXDAG_LOG_FILE"):
|
|
304
|
+
output_file = env_file
|
|
305
|
+
logger.debug(f"Overriding log file from env: {output_file}")
|
|
306
|
+
|
|
307
|
+
if env_color := os.getenv("HEXDAG_LOG_COLOR"):
|
|
308
|
+
try:
|
|
309
|
+
use_color = _parse_bool_env(env_color)
|
|
310
|
+
logger.debug(f"Overriding log color from env: {use_color}")
|
|
311
|
+
except ValueError as e:
|
|
312
|
+
logger.warning(f"Invalid HEXDAG_LOG_COLOR value: {e}")
|
|
313
|
+
|
|
314
|
+
if env_timestamp := os.getenv("HEXDAG_LOG_TIMESTAMP"):
|
|
315
|
+
try:
|
|
316
|
+
include_timestamp = _parse_bool_env(env_timestamp)
|
|
317
|
+
logger.debug(f"Overriding log timestamp from env: {include_timestamp}")
|
|
318
|
+
except ValueError as e:
|
|
319
|
+
logger.warning(f"Invalid HEXDAG_LOG_TIMESTAMP value: {e}")
|
|
320
|
+
|
|
321
|
+
if env_rich := os.getenv("HEXDAG_LOG_RICH"):
|
|
322
|
+
try:
|
|
323
|
+
use_rich = _parse_bool_env(env_rich)
|
|
324
|
+
logger.debug(f"Overriding use_rich from env: {use_rich}")
|
|
325
|
+
except ValueError as e:
|
|
326
|
+
logger.warning(f"Invalid HEXDAG_LOG_RICH value: {e}")
|
|
327
|
+
|
|
328
|
+
if env_dual_sink := os.getenv("HEXDAG_LOG_DUAL_SINK"):
|
|
329
|
+
try:
|
|
330
|
+
dual_sink = _parse_bool_env(env_dual_sink)
|
|
331
|
+
logger.debug(f"Overriding dual_sink from env: {dual_sink}")
|
|
332
|
+
except ValueError as e:
|
|
333
|
+
logger.warning(f"Invalid HEXDAG_LOG_DUAL_SINK value: {e}")
|
|
334
|
+
|
|
335
|
+
if env_stdlib_bridge := os.getenv("HEXDAG_LOG_STDLIB_BRIDGE"):
|
|
336
|
+
try:
|
|
337
|
+
enable_stdlib_bridge = _parse_bool_env(env_stdlib_bridge)
|
|
338
|
+
logger.debug(f"Overriding enable_stdlib_bridge from env: {enable_stdlib_bridge}")
|
|
339
|
+
except ValueError as e:
|
|
340
|
+
logger.warning(f"Invalid HEXDAG_LOG_STDLIB_BRIDGE value: {e}")
|
|
341
|
+
|
|
342
|
+
if env_backtrace := os.getenv("HEXDAG_LOG_BACKTRACE"):
|
|
343
|
+
try:
|
|
344
|
+
backtrace = _parse_bool_env(env_backtrace)
|
|
345
|
+
logger.debug(f"Overriding backtrace from env: {backtrace}")
|
|
346
|
+
except ValueError as e:
|
|
347
|
+
logger.warning(f"Invalid HEXDAG_LOG_BACKTRACE value: {e}")
|
|
348
|
+
|
|
349
|
+
if env_diagnose := os.getenv("HEXDAG_LOG_DIAGNOSE"):
|
|
350
|
+
try:
|
|
351
|
+
diagnose = _parse_bool_env(env_diagnose)
|
|
352
|
+
logger.debug(f"Overriding diagnose from env: {diagnose}")
|
|
353
|
+
except ValueError as e:
|
|
354
|
+
logger.warning(f"Invalid HEXDAG_LOG_DIAGNOSE value: {e}")
|
|
355
|
+
|
|
356
|
+
# Cast to proper Literal types for type safety
|
|
357
|
+
# These will be validated by Pydantic at runtime
|
|
358
|
+
return LoggingConfig(
|
|
359
|
+
level=cast("Literal['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']", level),
|
|
360
|
+
format=cast("Literal['console', 'json', 'structured', 'dual', 'rich']", format_type),
|
|
361
|
+
output_file=output_file,
|
|
362
|
+
use_color=use_color,
|
|
363
|
+
include_timestamp=include_timestamp,
|
|
364
|
+
use_rich=use_rich,
|
|
365
|
+
dual_sink=dual_sink,
|
|
366
|
+
enable_stdlib_bridge=enable_stdlib_bridge,
|
|
367
|
+
backtrace=backtrace,
|
|
368
|
+
diagnose=diagnose,
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@lru_cache(maxsize=32)
|
|
373
|
+
def _cached_load_config(path_str: str | None) -> HexDAGConfig:
|
|
374
|
+
"""Internal cached configuration loader.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
path_str : str | None
|
|
379
|
+
String representation of path for caching
|
|
380
|
+
Returns
|
|
381
|
+
-------
|
|
382
|
+
HexDAGConfig
|
|
383
|
+
Loaded configuration
|
|
384
|
+
"""
|
|
385
|
+
try:
|
|
386
|
+
loader = ConfigLoader()
|
|
387
|
+
if path_str and path_str.startswith("__auto__"):
|
|
388
|
+
return loader.load_from_toml(None)
|
|
389
|
+
return loader.load_from_toml(Path(path_str) if path_str else None)
|
|
390
|
+
except FileNotFoundError:
|
|
391
|
+
logger.info("No configuration file found, using defaults")
|
|
392
|
+
return get_default_config()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def load_config(path: str | Path | None = None) -> HexDAGConfig:
|
|
396
|
+
"""Load configuration from TOML file or return defaults.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
path : str | Path | None
|
|
401
|
+
Path to configuration file or None to search
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
HexDAGConfig
|
|
406
|
+
Loaded configuration or defaults if no file found
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
try:
|
|
410
|
+
loader = ConfigLoader()
|
|
411
|
+
return loader.load_from_toml(path)
|
|
412
|
+
except FileNotFoundError:
|
|
413
|
+
logger.info("No configuration file found, using defaults")
|
|
414
|
+
return get_default_config()
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def clear_config_cache() -> None:
|
|
418
|
+
"""Clear all configuration caches.
|
|
419
|
+
|
|
420
|
+
Useful for testing or when configuration files have been modified
|
|
421
|
+
and you need to force a reload.
|
|
422
|
+
"""
|
|
423
|
+
_cached_load_config.cache_clear()
|
|
424
|
+
_load_and_parse_cached.cache_clear()
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def get_default_config() -> HexDAGConfig:
|
|
428
|
+
"""Get default configuration.
|
|
429
|
+
|
|
430
|
+
Returns
|
|
431
|
+
-------
|
|
432
|
+
HexDAGConfig
|
|
433
|
+
Default configuration with core ports and builtin components
|
|
434
|
+
"""
|
|
435
|
+
return HexDAGConfig(
|
|
436
|
+
modules=[
|
|
437
|
+
"hexdag.core.ports",
|
|
438
|
+
"hexdag.builtin.nodes",
|
|
439
|
+
"hexdag.builtin.adapters.mock",
|
|
440
|
+
"hexdag.builtin.adapters.local",
|
|
441
|
+
"hexdag.builtin.tools.builtin_tools",
|
|
442
|
+
],
|
|
443
|
+
settings={
|
|
444
|
+
"log_level": "INFO",
|
|
445
|
+
"enable_metrics": True,
|
|
446
|
+
},
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def config_to_manifest_entries(config: HexDAGConfig) -> list[ManifestEntry]:
|
|
451
|
+
"""Convert configuration to manifest entries.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
config : HexDAGConfig
|
|
456
|
+
Configuration object
|
|
457
|
+
|
|
458
|
+
Returns
|
|
459
|
+
-------
|
|
460
|
+
list[ManifestEntry]
|
|
461
|
+
List of manifest entries for registry bootstrap
|
|
462
|
+
"""
|
|
463
|
+
# Core and builtin modules go to 'core' namespace, others to 'user'
|
|
464
|
+
module_entries = [
|
|
465
|
+
ManifestEntry(
|
|
466
|
+
namespace=(
|
|
467
|
+
"core"
|
|
468
|
+
if (module.startswith("hexdag.core") or module.startswith("hexdag.builtin"))
|
|
469
|
+
else "user"
|
|
470
|
+
),
|
|
471
|
+
module=module,
|
|
472
|
+
)
|
|
473
|
+
for module in config.modules
|
|
474
|
+
]
|
|
475
|
+
|
|
476
|
+
# Plugin modules go to 'plugin' namespace
|
|
477
|
+
plugin_entries = [ManifestEntry(namespace="plugin", module=plugin) for plugin in config.plugins]
|
|
478
|
+
|
|
479
|
+
return module_entries + plugin_entries
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Configuration data models for HexDAG."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Any, Literal
|
|
7
|
+
|
|
8
|
+
from hexdag.core.exceptions import ValidationError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True, slots=True)
|
|
12
|
+
class LoggingConfig:
|
|
13
|
+
"""Logging configuration for HexDAG.
|
|
14
|
+
|
|
15
|
+
Attributes
|
|
16
|
+
----------
|
|
17
|
+
level : str, default="INFO"
|
|
18
|
+
Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
19
|
+
format : str, default="structured"
|
|
20
|
+
Output format (console, json, structured, dual, rich)
|
|
21
|
+
output_file : str | None, default=None
|
|
22
|
+
Optional file path to write logs to
|
|
23
|
+
use_color : bool, default=True
|
|
24
|
+
Use ANSI color codes (auto-disabled for non-TTY)
|
|
25
|
+
include_timestamp : bool, default=True
|
|
26
|
+
Include timestamp in log output
|
|
27
|
+
use_rich : bool, default=False
|
|
28
|
+
Use Rich library for enhanced console output with better formatting
|
|
29
|
+
dual_sink : bool, default=False
|
|
30
|
+
Enable dual-sink mode: pretty console (Rich) + structured JSON to stdout
|
|
31
|
+
enable_stdlib_bridge : bool, default=False
|
|
32
|
+
Enable interception of stdlib logging for third-party libraries
|
|
33
|
+
backtrace : bool, default=True
|
|
34
|
+
Enable backtrace for debugging (disable in production for security)
|
|
35
|
+
diagnose : bool, default=True
|
|
36
|
+
Enable diagnose mode with variable values (disable in production for security)
|
|
37
|
+
|
|
38
|
+
Examples
|
|
39
|
+
--------
|
|
40
|
+
TOML configuration:
|
|
41
|
+
|
|
42
|
+
```toml
|
|
43
|
+
[tool.hexdag.logging]
|
|
44
|
+
level = "DEBUG"
|
|
45
|
+
format = "rich"
|
|
46
|
+
use_rich = true
|
|
47
|
+
dual_sink = false
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Dual-sink configuration (dev mode):
|
|
51
|
+
|
|
52
|
+
```toml
|
|
53
|
+
[tool.hexdag.logging]
|
|
54
|
+
level = "INFO"
|
|
55
|
+
dual_sink = true
|
|
56
|
+
use_rich = true
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Environment variable overrides:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export HEXDAG_LOG_LEVEL=DEBUG
|
|
63
|
+
export HEXDAG_LOG_FORMAT=rich
|
|
64
|
+
export HEXDAG_LOG_FILE=/var/log/hexdag/app.log
|
|
65
|
+
export HEXDAG_LOG_DUAL_SINK=true
|
|
66
|
+
export HEXDAG_LOG_RICH=true
|
|
67
|
+
```
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"
|
|
71
|
+
format: Literal["console", "json", "structured", "dual", "rich"] = "structured"
|
|
72
|
+
output_file: str | None = None
|
|
73
|
+
use_color: bool = True
|
|
74
|
+
include_timestamp: bool = True
|
|
75
|
+
use_rich: bool = False
|
|
76
|
+
dual_sink: bool = False
|
|
77
|
+
enable_stdlib_bridge: bool = False
|
|
78
|
+
backtrace: bool = True
|
|
79
|
+
diagnose: bool = True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@dataclass(frozen=True, slots=True)
|
|
83
|
+
class ManifestEntry:
|
|
84
|
+
"""Single entry defining a module to load."""
|
|
85
|
+
|
|
86
|
+
namespace: str
|
|
87
|
+
module: str
|
|
88
|
+
|
|
89
|
+
def __post_init__(self) -> None:
|
|
90
|
+
"""Validate manifest entry.
|
|
91
|
+
|
|
92
|
+
Raises
|
|
93
|
+
------
|
|
94
|
+
ValidationError
|
|
95
|
+
If namespace or module is empty or namespace contains ':'
|
|
96
|
+
"""
|
|
97
|
+
if not self.namespace:
|
|
98
|
+
raise ValidationError("namespace", "cannot be empty")
|
|
99
|
+
if not self.module:
|
|
100
|
+
raise ValidationError("module", "cannot be empty")
|
|
101
|
+
if ":" in self.namespace:
|
|
102
|
+
raise ValidationError("namespace", "cannot contain ':'", self.namespace)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass(slots=True)
|
|
106
|
+
class HexDAGConfig:
|
|
107
|
+
"""Complete HexDAG configuration.
|
|
108
|
+
|
|
109
|
+
Attributes
|
|
110
|
+
----------
|
|
111
|
+
modules : list[str]
|
|
112
|
+
List of module paths to load
|
|
113
|
+
plugins : list[str]
|
|
114
|
+
List of plugin names to load
|
|
115
|
+
dev_mode : bool
|
|
116
|
+
Enable development mode features
|
|
117
|
+
logging : LoggingConfig
|
|
118
|
+
Logging configuration
|
|
119
|
+
settings : dict[str, Any]
|
|
120
|
+
Additional custom settings
|
|
121
|
+
|
|
122
|
+
Examples
|
|
123
|
+
--------
|
|
124
|
+
TOML configuration in pyproject.toml:
|
|
125
|
+
|
|
126
|
+
```toml
|
|
127
|
+
[tool.hexdag]
|
|
128
|
+
modules = ["myapp.adapters", "myapp.nodes"]
|
|
129
|
+
plugins = ["hexdag-openai", "hexdag-postgres"]
|
|
130
|
+
dev_mode = true
|
|
131
|
+
|
|
132
|
+
[tool.hexdag.logging]
|
|
133
|
+
level = "DEBUG"
|
|
134
|
+
format = "structured"
|
|
135
|
+
use_color = true
|
|
136
|
+
```
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# Core configuration
|
|
140
|
+
modules: list[str] = field(default_factory=list)
|
|
141
|
+
plugins: list[str] = field(default_factory=list)
|
|
142
|
+
|
|
143
|
+
# Development settings
|
|
144
|
+
dev_mode: bool = False
|
|
145
|
+
|
|
146
|
+
# Logging configuration
|
|
147
|
+
logging: LoggingConfig = field(default_factory=LoggingConfig)
|
|
148
|
+
|
|
149
|
+
# Additional settings
|
|
150
|
+
settings: dict[str, Any] = field(default_factory=dict)
|