Coverage for lintro / config / lintro_config.py: 100%

33 statements  

« prev     ^ index     » next       coverage.py v7.13.0, created at 2026-04-03 18:53 +0000

1"""Main Lintro configuration model.""" 

2 

3from typing import Any 

4 

5from pydantic import BaseModel, ConfigDict, Field 

6 

7from lintro.ai.config import AIConfig 

8from lintro.config.enforce_config import EnforceConfig 

9from lintro.config.execution_config import ExecutionConfig 

10from lintro.config.tool_config import LintroToolConfig 

11 

12__all__ = [ 

13 "AIConfig", 

14 "EnforceConfig", 

15 "ExecutionConfig", 

16 "LintroConfig", 

17 "LintroToolConfig", 

18] 

19 

20 

21class LintroConfig(BaseModel): 

22 """Main Lintro configuration container. 

23 

24 This is the root configuration object loaded from .lintro-config.yaml. 

25 Follows the tiered model: 

26 

27 1. execution: What tools run and how 

28 2. enforce: Cross-cutting settings that override native configs 

29 3. defaults: Fallback config when no native config exists 

30 4. tools: Per-tool enable/disable and config source 

31 5. ai: Optional AI-powered issue intelligence 

32 

33 Attributes: 

34 model_config: Pydantic model configuration. 

35 execution: Execution control settings. 

36 enforce: Cross-cutting settings enforced via CLI flags. 

37 defaults: Fallback configs for tools without native configs. 

38 tools: Per-tool configuration, keyed by tool name. 

39 ai: AI-powered features configuration (optional, disabled by default). 

40 config_path: Path to the config file (set by loader). 

41 """ 

42 

43 model_config = ConfigDict(frozen=False, extra="forbid") 

44 

45 execution: ExecutionConfig = Field(default_factory=ExecutionConfig) 

46 enforce: EnforceConfig = Field(default_factory=EnforceConfig) 

47 defaults: dict[str, dict[str, Any]] = Field(default_factory=dict) 

48 tools: dict[str, LintroToolConfig] = Field(default_factory=dict) 

49 ai: AIConfig = Field(default_factory=AIConfig) 

50 config_path: str | None = None 

51 

52 def get_tool_config(self, tool_name: str) -> LintroToolConfig: 

53 """Get configuration for a specific tool. 

54 

55 Args: 

56 tool_name: Name of the tool (e.g., "ruff", "black"). 

57 

58 Returns: 

59 LintroToolConfig: Tool configuration. Returns default config if not 

60 explicitly configured. 

61 """ 

62 return self.tools.get(tool_name.lower(), LintroToolConfig()) 

63 

64 def is_tool_enabled(self, tool_name: str) -> bool: 

65 """Check if a tool is enabled. 

66 

67 A tool is enabled if: 

68 1. execution.enabled_tools is empty (all tools enabled), OR 

69 2. tool_name is in execution.enabled_tools, AND 

70 3. The tool's config has enabled=True (default) 

71 

72 Args: 

73 tool_name: Name of the tool. 

74 

75 Returns: 

76 bool: True if tool should run. 

77 """ 

78 tool_lower = tool_name.lower() 

79 

80 # Check execution.enabled_tools filter 

81 if self.execution.enabled_tools: 

82 enabled_lower = [t.lower() for t in self.execution.enabled_tools] 

83 if tool_lower not in enabled_lower: 

84 return False 

85 

86 # Check tool-specific enabled flag 

87 tool_config = self.get_tool_config(tool_lower) 

88 return bool(tool_config.enabled) 

89 

90 def get_tool_defaults(self, tool_name: str) -> dict[str, Any]: 

91 """Get default configuration for a tool. 

92 

93 Used when the tool has no native config file. 

94 

95 Args: 

96 tool_name: Name of the tool. 

97 

98 Returns: 

99 dict[str, Any]: Default configuration or empty dict. 

100 """ 

101 return self.defaults.get(tool_name.lower(), {}) 

102 

103 def get_effective_line_length(self, tool_name: str) -> int | None: 

104 """Get effective line length for a specific tool. 

105 

106 In the tiered model, this simply returns the enforce.line_length 

107 value, which will be injected via CLI flags. 

108 

109 Args: 

110 tool_name: Name of the tool (unused, kept for compatibility). 

111 

112 Returns: 

113 int | None: Enforced line length or None. 

114 """ 

115 line_length: int | None = self.enforce.line_length 

116 return line_length 

117 

118 def get_effective_target_python(self, tool_name: str) -> str | None: 

119 """Get effective Python target version for a specific tool. 

120 

121 In the tiered model, this simply returns the enforce.target_python 

122 value, which will be injected via CLI flags. 

123 

124 Args: 

125 tool_name: Name of the tool (unused, kept for compatibility). 

126 

127 Returns: 

128 str | None: Enforced target version or None. 

129 """ 

130 target_python: str | None = self.enforce.target_python 

131 return target_python