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
« 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."""
3from dataclasses import dataclass, field
4from typing import Any
6from python_commitlint.core.enums import (
7 CaseType,
8 RuleCondition,
9 Severity,
10)
13@dataclass(slots=True)
14class CommitMessage:
15 """A parsed Conventional Commit message.
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.
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 """
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
49@dataclass(slots=True)
50class RuleConfig:
51 """Per-rule configuration loaded from YAML.
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 """
59 severity: Severity
60 condition: RuleCondition
61 value: Any = None
64@dataclass(slots=True)
65class ValidationError:
66 """A single rule violation produced by the linter.
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 """
76 rule: str
77 message: str
78 severity: Severity
79 line: int = 1
80 column: int = 0
83@dataclass(slots=True)
84class LintResult:
85 """Outcome of linting a single commit message.
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 """
93 valid: bool
94 errors: list[ValidationError] = field(default_factory=list)
95 warnings: list[ValidationError] = field(default_factory=list)
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)
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)
108@dataclass(slots=True)
109class Configuration:
110 """A fully-resolved commitlint configuration.
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 """
118 rules: dict[str, RuleConfig] = field(default_factory=dict)
119 extends: list[str] = field(default_factory=list)
122@dataclass(slots=True)
123class CaseValidation:
124 """Dict-form configuration for case rules with custom delimiters.
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 """
131 cases: list[CaseType]
132 delimiters: list[str] = field(default_factory=lambda: ["/", "\\", ","])
135@dataclass(slots=True)
136class ScopeEnumValidation:
137 """Dict-form configuration for ``scope-enum`` with custom delimiters.
139 Attributes:
140 scopes: Allowed scope values.
141 delimiters: Characters to split a multi-scope value on.
142 """
144 scopes: list[str]
145 delimiters: list[str] = field(default_factory=lambda: ["/", "\\", ","])