diagram-to-iac 1.3.0__py3-none-any.whl → 1.4.0__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.
@@ -16,113 +16,104 @@ logger = logging.getLogger(__name__)
16
16
  class ConfigLoader:
17
17
  """
18
18
  Central configuration management for the diagram-to-iac project.
19
- Loads configuration from YAML files and provides environment variable override capability.
19
+ Loads configuration from central config.yaml file with environment variable override capability.
20
20
  """
21
21
 
22
- def __init__(self, app_config_path: Optional[str] = None, system_config_path: Optional[str] = None):
22
+ def __init__(self, config_path: Optional[str] = None):
23
23
  """
24
- Initialize ConfigLoader with optional custom config paths.
24
+ Initialize ConfigLoader with optional custom config path.
25
25
 
26
26
  Args:
27
- app_config_path: Path to application config file (default: src/diagram_to_iac/config.yaml)
28
- system_config_path: Path to system config file (default: config/system.yaml)
27
+ config_path: Path to central config file (default: src/diagram_to_iac/config.yaml)
29
28
  """
30
29
  self.logger = logging.getLogger(self.__class__.__name__)
31
30
 
32
- # Set default paths
33
- self.base_path = Path(__file__).parent.parent.parent.parent # diagram-to-iac root
34
- self.app_config_path = Path(app_config_path) if app_config_path else self.base_path / "src" / "diagram_to_iac" / "config.yaml"
35
- self.system_config_path = Path(system_config_path) if system_config_path else self.base_path / "config" / "system.yaml"
31
+ # Set default paths (container-safe)
32
+ if config_path:
33
+ self.config_path = Path(config_path)
34
+ else:
35
+ # Try multiple locations for central config
36
+ possible_paths = [
37
+ Path.cwd() / "src" / "diagram_to_iac" / "config.yaml", # Development
38
+ Path(__file__).parent.parent / "config.yaml", # Package location
39
+ Path("/workspace/src/diagram_to_iac/config.yaml"), # Container workspace
40
+ Path("/workspace/config.yaml"), # Container root
41
+ ]
42
+ self.config_path = None
43
+ for path in possible_paths:
44
+ if path.exists():
45
+ self.config_path = path
46
+ break
47
+ # Default to package location if none found (will create defaults)
48
+ if not self.config_path:
49
+ self.config_path = Path(__file__).parent.parent / "config.yaml"
36
50
 
37
- # Cache for loaded configs
38
- self._app_config = None
39
- self._system_config = None
40
- self._merged_config = None
51
+ # Cache for loaded config
52
+ self._config = None
41
53
 
42
54
  @lru_cache(maxsize=1)
43
55
  def get_config(self) -> Dict[str, Any]:
44
56
  """
45
- Get the complete merged configuration with environment variable overrides.
57
+ Get the complete configuration with environment variable overrides.
46
58
 
47
59
  Returns:
48
- Merged configuration dictionary
60
+ Configuration dictionary
49
61
  """
50
- if self._merged_config is None:
51
- self._merged_config = self._load_and_merge_configs()
52
- return self._merged_config
62
+ if self._config is None:
63
+ self._config = self._load_config()
64
+ return self._config
53
65
 
54
- def _load_and_merge_configs(self) -> Dict[str, Any]:
66
+ def _load_config(self) -> Dict[str, Any]:
55
67
  """
56
- Load and merge all configuration sources.
68
+ Load configuration from central config file.
57
69
 
58
70
  Returns:
59
- Merged configuration dictionary
71
+ Configuration dictionary with environment overrides applied
60
72
  """
61
- # Load base configs
62
- app_config = self._load_app_config()
63
- system_config = self._load_system_config()
64
-
65
- # Start with system config as base, overlay app config
66
- merged = self._deep_merge(system_config, app_config)
73
+ # Load base config
74
+ config = self._load_config_file()
67
75
 
68
76
  # Apply environment variable overrides
69
- merged = self._apply_env_overrides(merged)
77
+ config = self._apply_env_overrides(config)
70
78
 
71
- self.logger.debug("Configuration loaded and merged successfully")
72
- return merged
73
-
74
- def _load_app_config(self) -> Dict[str, Any]:
75
- """Load application configuration from YAML file."""
76
- if self._app_config is None:
77
- try:
78
- if self.app_config_path.exists():
79
- with open(self.app_config_path, 'r') as f:
80
- self._app_config = yaml.safe_load(f) or {}
81
- self.logger.debug(f"Loaded app config from {self.app_config_path}")
82
- else:
83
- self.logger.warning(f"App config file not found: {self.app_config_path}")
84
- self._app_config = {}
85
- except Exception as e:
86
- self.logger.error(f"Failed to load app config: {e}")
87
- self._app_config = {}
88
- return self._app_config
79
+ self.logger.debug("Configuration loaded successfully")
80
+ return config
89
81
 
90
- def _load_system_config(self) -> Dict[str, Any]:
91
- """Load system configuration from YAML file."""
92
- if self._system_config is None:
93
- try:
94
- if self.system_config_path.exists():
95
- with open(self.system_config_path, 'r') as f:
96
- self._system_config = yaml.safe_load(f) or {}
97
- self.logger.debug(f"Loaded system config from {self.system_config_path}")
98
- else:
99
- self.logger.warning(f"System config file not found: {self.system_config_path}")
100
- self._system_config = {}
101
- except Exception as e:
102
- self.logger.error(f"Failed to load system config: {e}")
103
- self._system_config = {}
104
- return self._system_config
105
-
106
- def _deep_merge(self, base: Dict[str, Any], overlay: Dict[str, Any]) -> Dict[str, Any]:
107
- """
108
- Deep merge two dictionaries, with overlay taking precedence.
109
-
110
- Args:
111
- base: Base dictionary
112
- overlay: Dictionary to overlay on base
113
-
114
- Returns:
115
- Merged dictionary
116
- """
117
- result = base.copy()
118
-
119
- for key, value in overlay.items():
120
- if key in result and isinstance(result[key], dict) and isinstance(value, dict):
121
- result[key] = self._deep_merge(result[key], value)
82
+ def _load_config_file(self) -> Dict[str, Any]:
83
+ """Load configuration from central YAML file."""
84
+ try:
85
+ if self.config_path.exists():
86
+ with open(self.config_path, 'r') as f:
87
+ config = yaml.safe_load(f) or {}
88
+ self.logger.debug(f"Loaded config from {self.config_path}")
122
89
  else:
123
- result[key] = value
124
-
125
- return result
90
+ self.logger.warning(f"Config file not found at {self.config_path}, using built-in defaults")
91
+ config = self._get_default_config()
92
+ except Exception as e:
93
+ self.logger.error(f"Failed to load config: {e}")
94
+ config = self._get_default_config()
95
+ return config
96
+
97
+ def _get_default_config(self) -> Dict[str, Any]:
98
+ """Get default configuration when config file is not available."""
99
+ return {
100
+ 'system': {
101
+ 'workspace_base': '/workspace',
102
+ 'log_level': 'INFO'
103
+ },
104
+ 'network': {
105
+ 'api_timeout': 10,
106
+ 'shell_timeout': 30,
107
+ 'terraform_timeout': 300,
108
+ 'github_timeout': 15,
109
+ 'git_timeout': 300
110
+ },
111
+ 'ai': {
112
+ 'default_model': 'gpt-4o-mini',
113
+ 'default_temperature': 0.1,
114
+ 'max_tokens': 1000
115
+ }
116
+ }
126
117
 
127
118
  def _apply_env_overrides(self, config: Dict[str, Any]) -> Dict[str, Any]:
128
119
  """
@@ -246,10 +237,8 @@ class ConfigLoader:
246
237
  return default
247
238
 
248
239
  def reload(self) -> None:
249
- """Reload configuration from files (clears cache)."""
250
- self._app_config = None
251
- self._system_config = None
252
- self._merged_config = None
240
+ """Reload configuration from file (clears cache)."""
241
+ self._config = None
253
242
  self.get_config.cache_clear()
254
243
  self.logger.debug("Configuration cache cleared, will reload on next access")
255
244
 
@@ -4,6 +4,13 @@ import json
4
4
  import os
5
5
  from pathlib import Path
6
6
 
7
+ try:
8
+ from .config_loader import get_config_value
9
+ except ImportError:
10
+ # Fallback for tests or standalone usage
11
+ def get_config_value(path: str, default: Any = None) -> Any:
12
+ return default
13
+
7
14
  # Abstract base for memory implementations
8
15
  class MemoryInterface(ABC):
9
16
  """Abstract interface for agent memory systems."""
@@ -94,11 +101,21 @@ class PersistentFileMemory(MemoryInterface):
94
101
 
95
102
  def __init__(self, file_path: Optional[str] = None):
96
103
  if file_path is None:
97
- # Default to data/db directory
98
- base_dir = Path(__file__).parent.parent.parent.parent
99
- data_dir = base_dir / "data" / "db"
100
- data_dir.mkdir(parents=True, exist_ok=True)
101
- file_path = data_dir / "agent_memory.json"
104
+ # Get workspace base from config with fallback
105
+ workspace_base = get_config_value("system.workspace_base", "/workspace")
106
+
107
+ # Default to workspace or /tmp directory for container safety
108
+ try:
109
+ # Try workspace first (from config)
110
+ base_dir = Path(workspace_base) if Path(workspace_base).exists() else Path.cwd()
111
+ data_dir = base_dir / "data" / "db"
112
+ data_dir.mkdir(parents=True, exist_ok=True)
113
+ file_path = data_dir / "agent_memory.json"
114
+ except (PermissionError, OSError):
115
+ # Fallback to /tmp for container environments
116
+ data_dir = Path("/tmp") / "diagram_to_iac" / "data" / "db"
117
+ data_dir.mkdir(parents=True, exist_ok=True)
118
+ file_path = data_dir / "agent_memory.json"
102
119
 
103
120
  self.file_path = Path(file_path)
104
121
  self._state: Dict[str, Any] = {}
@@ -33,6 +33,8 @@ from enum import Enum
33
33
 
34
34
  from pydantic import BaseModel, Field, field_validator
35
35
 
36
+ from .config_loader import get_config_value
37
+
36
38
 
37
39
  class RunStatus(str, Enum):
38
40
  """Status values for deployment runs."""
@@ -126,9 +128,27 @@ class RunRegistry:
126
128
  if registry_path:
127
129
  self.registry_path = Path(registry_path)
128
130
  else:
129
- # Default to data/state/issue_registry.json
130
- base_path = Path(__file__).parent.parent.parent.parent
131
- self.registry_path = base_path / "data" / "state" / "issue_registry.json"
131
+ # Get workspace base from config (with fallback)
132
+ workspace_base = get_config_value("system.workspace_base", "/workspace")
133
+
134
+ # Try multiple locations for registry file
135
+ possible_paths = [
136
+ Path.cwd() / "data" / "state" / "issue_registry.json", # Development
137
+ Path(workspace_base) / "data" / "state" / "issue_registry.json", # Container workspace
138
+ Path("/tmp/diagram_to_iac/data/state/issue_registry.json"), # Container fallback
139
+ ]
140
+ self.registry_path = None
141
+ for path in possible_paths:
142
+ try:
143
+ path.parent.mkdir(parents=True, exist_ok=True)
144
+ self.registry_path = path
145
+ break
146
+ except (PermissionError, OSError):
147
+ continue
148
+
149
+ # Final fallback if all locations fail
150
+ if not self.registry_path:
151
+ self.registry_path = Path("/tmp/issue_registry.json")
132
152
 
133
153
  # Ensure the directory exists
134
154
  self.registry_path.parent.mkdir(parents=True, exist_ok=True)
@@ -46,9 +46,20 @@ class LLMRouter:
46
46
  def _load_model_policy(self, config_path: Optional[str] = None) -> Dict[str, Any]:
47
47
  """Load model policy from YAML configuration."""
48
48
  if config_path is None:
49
- # Default to project's model_policy.yaml
50
- base_dir = Path(__file__).parent.parent.parent.parent.parent
51
- config_path = base_dir / "config" / "model_policy.yaml"
49
+ # Try multiple locations for model policy config
50
+ possible_paths = [
51
+ Path.cwd() / "config" / "model_policy.yaml", # Development
52
+ Path("/workspace/config/model_policy.yaml"), # Container workspace
53
+ Path(__file__).parent.parent.parent.parent.parent / "config" / "model_policy.yaml", # Package fallback
54
+ ]
55
+ config_path = None
56
+ for path in possible_paths:
57
+ if path.exists():
58
+ config_path = path
59
+ break
60
+ # Default to workspace location if none found
61
+ if not config_path:
62
+ config_path = Path.cwd() / "config" / "model_policy.yaml"
52
63
 
53
64
  try:
54
65
  with open(config_path, 'r') as f:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diagram-to-iac
3
- Version: 1.3.0
3
+ Version: 1.4.0
4
4
  Summary: Convert architecture diagrams into IaC modules
5
5
  Author-email: vindpro <admin@vindpro.com>
6
6
  Description-Content-Type: text/markdown
@@ -10,7 +10,7 @@ Requires-Dist: langchain_anthropic==0.3.16
10
10
  Requires-Dist: langchain-core<1.0.0,>=0.3.62
11
11
  Requires-Dist: langchain_google_genai==2.1.5
12
12
  Requires-Dist: langchain_openai==0.3.26
13
- Requires-Dist: langgraph==0.4.10
13
+ Requires-Dist: langgraph==0.5.0
14
14
  Requires-Dist: openai==1.92.2
15
15
  Requires-Dist: protobuf>=5.27.0
16
16
  Requires-Dist: pydantic==2.11.7
@@ -33,12 +33,12 @@ diagram_to_iac/agents/terraform_langgraph/agent.py,sha256=lDaRCLcqShZiUGN25R-T94
33
33
  diagram_to_iac/agents/terraform_langgraph/parser.py,sha256=J56CPlpIEIPuDHeAOL3sz4TiIgqLi7raPlX7jwFrAms,2039
34
34
  diagram_to_iac/core/__init__.py,sha256=VjXPYLIS2SAPIDniBkeA2EDK0VHJvdaKIC8dzVneaTM,140
35
35
  diagram_to_iac/core/agent_base.py,sha256=DjZjcfzDpEhfIOki00XwQ-4lPli3OBcQ_7RNKsT7JSI,505
36
- diagram_to_iac/core/config_loader.py,sha256=5kkOJoOZVMJhlVQOUu_gWXd26lrblxXtcphKYLhx8WA,10131
37
- diagram_to_iac/core/enhanced_memory.py,sha256=Ga5wtI45zEcbwL_F1YqJaXBRpWK0iJPa69j4-V-ebvM,10951
36
+ diagram_to_iac/core/config_loader.py,sha256=6WWOp6G7_xYUhm1x62sVa-7kFlCthcthbppmeGz1YsM,9276
37
+ diagram_to_iac/core/enhanced_memory.py,sha256=fJ8r-MREZRnm6Rg01CDCicMEx-dDxDEjJgrk8rnVc5Y,11761
38
38
  diagram_to_iac/core/errors.py,sha256=gZwZocnIcBlS4YccIBdjG8XztRCtMe4Cu6KWxLzebDM,115
39
39
  diagram_to_iac/core/issue_tracker.py,sha256=0eo289hn94yCoFCkLaYiDOIJBjk33i2dk6eLeYe_9YE,1659
40
40
  diagram_to_iac/core/memory.py,sha256=P9URX8m2nab65ZPF36uf6Z9hEXQGXrjrXa8dPXG7pm8,4444
41
- diagram_to_iac/core/registry.py,sha256=AM2fv9lzrNvFfkyt7VMxQ5SWIOWhdBu4_3Aaspdokj8,25758
41
+ diagram_to_iac/core/registry.py,sha256=ibdMz68W7qkwF0dprt4ni5pekgJfAPuRgL85uRU7wHY,26632
42
42
  diagram_to_iac/services/__init__.py,sha256=I5R8g7vYX4tCldRf1Jf9vEhm5mylc-MfFicqLnY6a3E,238
43
43
  diagram_to_iac/services/commenter.py,sha256=iXvHXOeih64FbE34PuGPk6fhI4RmC62ZSVtFwmMqiOA,22146
44
44
  diagram_to_iac/services/observability.py,sha256=yxbnjMc4TO1SM8RZZMHf2E8uVOLpxFhiTjsTkymDi6Y,1856
@@ -58,12 +58,12 @@ diagram_to_iac/tools/llm_utils/base_driver.py,sha256=sDUxk6_iNn3WU_HyRz2hW3YGTn8
58
58
  diagram_to_iac/tools/llm_utils/gemini_driver.py,sha256=VO1mJ3o10oSFo5hTBs6h8TJsXyAuah4FRr6Ua-9aNYc,3794
59
59
  diagram_to_iac/tools/llm_utils/grok_driver.py,sha256=hcq4m6ZEgjVsLXaaGlW5SWHEqyjY4KUDy88xSZFUa6Y,2955
60
60
  diagram_to_iac/tools/llm_utils/openai_driver.py,sha256=ZqzXEYEutwqRw3qWx-GH85Mj2afxK4NlhCOMq_MabqQ,3962
61
- diagram_to_iac/tools/llm_utils/router.py,sha256=hl-y1CCvRoBWSpKpkDI_SSyi9YIT2ZA6y6awn7_ErkM,22117
61
+ diagram_to_iac/tools/llm_utils/router.py,sha256=ga8xfmPMl_SGINDwazeAAFYTAx9L_IQcVV5AdvqD0dQ,22643
62
62
  diagram_to_iac/tools/shell/__init__.py,sha256=6UZjBcnbPabA6Qy7t4j-dCi3S2sE6sB2bTE9PIL98bA,292
63
63
  diagram_to_iac/tools/shell/shell.py,sha256=ZOJ7Vo3l_R2Gm6Ml2FL0RX__-C_JOsUrLJVvBMwAy9E,21122
64
64
  diagram_to_iac/tools/tf/terraform.py,sha256=j1boWRo6JKpNGf1OwnWoWboO0gMYTizCOHDSxozoFZw,37343
65
- diagram_to_iac-1.3.0.dist-info/METADATA,sha256=h5UhAUbrllCL4GeK7kxjXiv578CWLUstdgN0HQoa2mI,10429
66
- diagram_to_iac-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
67
- diagram_to_iac-1.3.0.dist-info/entry_points.txt,sha256=DfGCnmgWWGHtQpqU8VqcUWs5k_be-bfO67z1vOuTitA,277
68
- diagram_to_iac-1.3.0.dist-info/top_level.txt,sha256=k1cV0YODiCUU46qlmbQaquMcbMXhNm05NZLxsinDUBA,15
69
- diagram_to_iac-1.3.0.dist-info/RECORD,,
65
+ diagram_to_iac-1.4.0.dist-info/METADATA,sha256=qdDVn1c4C91A_2FrJYoAOPUrAaqI841bMUd7eymUNsM,10428
66
+ diagram_to_iac-1.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
67
+ diagram_to_iac-1.4.0.dist-info/entry_points.txt,sha256=DfGCnmgWWGHtQpqU8VqcUWs5k_be-bfO67z1vOuTitA,277
68
+ diagram_to_iac-1.4.0.dist-info/top_level.txt,sha256=k1cV0YODiCUU46qlmbQaquMcbMXhNm05NZLxsinDUBA,15
69
+ diagram_to_iac-1.4.0.dist-info/RECORD,,