Coverage for src / python_commitlint / core / models.py: 100%

49 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-04-28 02:54 +0000

1"""Domain dataclasses — parsed commits, rule configs, and lint results.""" 

2 

3from dataclasses import dataclass, field 

4from typing import Any 

5 

6from python_commitlint.core.enums import ( 

7 CaseType, 

8 RuleCondition, 

9 Severity, 

10) 

11 

12 

13@dataclass(slots=True) 

14class CommitMessage: 

15 """A parsed Conventional Commit message. 

16 

17 For backwards compatibility with consumers that read ``commit.type`` 

18 or ``commit.subject`` directly, those fields default to empty strings 

19 rather than ``None``. Use :attr:`is_conventional` to distinguish a 

20 commit that was successfully parsed from one whose header did not 

21 match the conventional pattern. 

22 

23 Attributes: 

24 raw: The original message exactly as provided. 

25 header: The first line, stripped of trailing whitespace. 

26 type: The commit type (``feat``, ``fix``, etc.); empty if unparseable. 

27 scope: The optional scope inside parentheses; empty if unset. 

28 subject: The text after the ``:`` separator; empty if unparseable. 

29 body: The body paragraphs (everything between header and footer). 

30 footer: The footer block (BREAKING CHANGE, ``token: value``, etc.). 

31 breaking: True when the header has ``!`` or a BREAKING CHANGE footer. 

32 is_conventional: True when the header matched the conventional 

33 pattern. False indicates ``type`` / ``scope`` / ``subject`` 

34 were not populated by parsing — empty strings here mean 

35 "absent", not "explicitly empty". 

36 """ 

37 

38 raw: str 

39 header: str 

40 type: str = "" 

41 scope: str = "" 

42 subject: str = "" 

43 body: str = "" 

44 footer: str = "" 

45 breaking: bool = False 

46 is_conventional: bool = False 

47 

48 

49@dataclass(slots=True) 

50class RuleConfig: 

51 """Per-rule configuration loaded from YAML. 

52 

53 Attributes: 

54 severity: How violations are reported (error, warning, disabled). 

55 condition: Whether the rule must always or never hold. 

56 value: Rule-specific argument — string, list, int, or None. 

57 """ 

58 

59 severity: Severity 

60 condition: RuleCondition 

61 value: Any = None 

62 

63 

64@dataclass(slots=True) 

65class ValidationError: 

66 """A single rule violation produced by the linter. 

67 

68 Attributes: 

69 rule: The rule name (e.g. ``type-case``). 

70 message: Human-readable explanation of the violation. 

71 severity: Whether this is an error, warning, or disabled marker. 

72 line: 1-based line number where the violation was detected. 

73 column: 0-based column offset; reserved for future use. 

74 """ 

75 

76 rule: str 

77 message: str 

78 severity: Severity 

79 line: int = 1 

80 column: int = 0 

81 

82 

83@dataclass(slots=True) 

84class LintResult: 

85 """Outcome of linting a single commit message. 

86 

87 Attributes: 

88 valid: True when no errors were produced (warnings do not fail). 

89 errors: All error-severity violations, in detection order. 

90 warnings: All warning-severity violations, in detection order. 

91 """ 

92 

93 valid: bool 

94 errors: list[ValidationError] = field(default_factory=list) 

95 warnings: list[ValidationError] = field(default_factory=list) 

96 

97 @property 

98 def has_errors(self) -> bool: 

99 """Return True when at least one error-severity violation is present.""" 

100 return bool(self.errors) 

101 

102 @property 

103 def has_warnings(self) -> bool: 

104 """Return True when at least one warning-severity violation is present.""" 

105 return bool(self.warnings) 

106 

107 

108@dataclass(slots=True) 

109class Configuration: 

110 """A fully-resolved commitlint configuration. 

111 

112 Attributes: 

113 rules: Mapping of rule name to its :class:`RuleConfig`. 

114 extends: Names of presets the config inherits from (e.g. 

115 ``conventional``). 

116 """ 

117 

118 rules: dict[str, RuleConfig] = field(default_factory=dict) 

119 extends: list[str] = field(default_factory=list) 

120 

121 

122@dataclass(slots=True) 

123class CaseValidation: 

124 """Dict-form configuration for case rules with custom delimiters. 

125 

126 Attributes: 

127 cases: Acceptable case styles to check each split part against. 

128 delimiters: Characters to split a value on before validating each part. 

129 """ 

130 

131 cases: list[CaseType] 

132 delimiters: list[str] = field(default_factory=lambda: ["/", "\\", ","]) 

133 

134 

135@dataclass(slots=True) 

136class ScopeEnumValidation: 

137 """Dict-form configuration for ``scope-enum`` with custom delimiters. 

138 

139 Attributes: 

140 scopes: Allowed scope values. 

141 delimiters: Characters to split a multi-scope value on. 

142 """ 

143 

144 scopes: list[str] 

145 delimiters: list[str] = field(default_factory=lambda: ["/", "\\", ","])