Coverage for lintro / utils / unified_config_manager.py: 100%
56 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-04-03 18:53 +0000
1"""Unified configuration manager class for Lintro.
3This module provides the central configuration manager that coordinates
4loading, merging, and applying configurations from multiple sources.
5"""
7from __future__ import annotations
9from dataclasses import dataclass, field
10from typing import Any
12from loguru import logger
14from lintro.utils.config import load_lintro_global_config, load_lintro_tool_config
15from lintro.utils.config_constants import ToolConfigInfo
16from lintro.utils.config_priority import get_effective_line_length, get_ordered_tools
17from lintro.utils.config_validation import (
18 get_tool_config_summary,
19 is_tool_injectable, # Used in get_tool_config() fallback
20 validate_config_consistency,
21)
22from lintro.utils.native_parsers import _load_native_tool_config
25@dataclass
26class UnifiedConfigManager:
27 """Central configuration manager for Lintro.
29 This class provides a unified interface for:
30 - Loading and merging configurations from multiple sources.
31 - Computing effective configurations for each tool.
32 - Validating configuration consistency.
33 - Managing tool execution order.
35 Attributes:
36 global_config: Global Lintro configuration from [tool.lintro].
37 tool_configs: Per-tool configuration info.
38 warnings: List of configuration warnings.
39 """
41 global_config: dict[str, Any] = field(default_factory=dict)
42 tool_configs: dict[str, ToolConfigInfo] = field(default_factory=dict)
43 warnings: list[str] = field(default_factory=list)
45 def __post_init__(self) -> None:
46 """Initialize configuration manager."""
47 self.refresh()
49 def refresh(self) -> None:
50 """Reload all configuration from files."""
51 self.global_config = load_lintro_global_config()
52 self.tool_configs = get_tool_config_summary()
53 self.warnings = validate_config_consistency()
55 def get_effective_line_length(self, tool_name: str) -> int | None:
56 """Get effective line length for a tool.
58 Args:
59 tool_name: Name of the tool.
61 Returns:
62 Effective line length or None.
63 """
64 return get_effective_line_length(tool_name)
66 def get_tool_config(self, tool_name: str) -> ToolConfigInfo:
67 """Get configuration info for a specific tool.
69 Args:
70 tool_name: Name of the tool.
72 Returns:
73 Tool configuration info.
74 """
75 if tool_name not in self.tool_configs:
76 self.tool_configs[tool_name] = ToolConfigInfo(
77 tool_name=tool_name,
78 native_config=_load_native_tool_config(tool_name),
79 lintro_tool_config=load_lintro_tool_config(tool_name),
80 is_injectable=is_tool_injectable(tool_name),
81 )
82 return self.tool_configs[tool_name]
84 def get_ordered_tools(self, tool_names: list[str]) -> list[str]:
85 """Get tools in execution order.
87 Args:
88 tool_names: List of tool names.
90 Returns:
91 List of tool names in execution order.
92 """
93 return get_ordered_tools(tool_names)
95 def apply_config_to_tool(
96 self,
97 tool: Any,
98 cli_overrides: dict[str, Any] | None = None,
99 ) -> None:
100 """Apply effective configuration to a tool instance.
102 Priority order:
103 1. CLI overrides (if provided).
104 2. [tool.lintro.<tool>] config.
105 3. Global [tool.lintro] settings.
107 Args:
108 tool: Tool instance with set_options method.
109 cli_overrides: Optional CLI override options.
111 Raises:
112 TypeError: If tool configuration has type mismatches.
113 ValueError: If tool configuration has invalid values.
114 """
115 tool_name = getattr(tool, "name", "").lower()
116 if not tool_name:
117 return
119 # Start with global settings
120 effective_opts: dict[str, Any] = {}
122 # Get cached tool config to avoid repeated file reads
123 tool_config = self.get_tool_config(tool_name)
125 # Apply global line_length if tool supports it
126 if tool_config.is_injectable:
127 line_length = self.get_effective_line_length(tool_name)
128 if line_length is not None:
129 effective_opts["line_length"] = line_length
131 # Apply tool-specific lintro config from cache
132 effective_opts.update(tool_config.lintro_tool_config)
134 # Apply CLI overrides last (highest priority)
135 if cli_overrides:
136 effective_opts.update(cli_overrides)
138 # Apply to tool
139 if effective_opts:
140 try:
141 tool.set_options(**effective_opts)
142 logger.debug(f"Applied config to {tool_name}: {effective_opts}")
143 except (ValueError, TypeError) as e:
144 # Configuration errors should be visible and re-raised
145 logger.warning(
146 f"Configuration error for {tool_name}: {e}",
147 exc_info=True,
148 )
149 raise
150 except (AttributeError, KeyError, RuntimeError) as e:
151 # Other unexpected errors - log at warning but allow execution
152 logger.warning(
153 f"Failed to apply config to {tool_name}: {type(e).__name__}: {e}",
154 exc_info=True,
155 )
157 def get_report(self) -> str:
158 """Get configuration report.
160 Returns:
161 Formatted configuration report string.
162 """
163 # Late import to avoid circular dependency
164 from lintro.utils.config_reporting import get_config_report
166 return str(get_config_report())
168 def print_report(self) -> None:
169 """Print configuration report."""
170 # Late import to avoid circular dependency
171 from lintro.utils.config_reporting import print_config_report
173 print_config_report()