python-code-validator 0.1.1__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.
@@ -0,0 +1,25 @@
1
+ """A flexible framework for static validation of Python code.
2
+
3
+ This package provides a comprehensive toolkit for statically analyzing Python source
4
+ code based on a declarative set of rules defined in a JSON format. It allows
5
+ for checking syntax, style, structure, and constraints without executing the code.
6
+
7
+ Key components exposed by this package include:
8
+ - StaticValidator: The main orchestrator for running the validation process.
9
+ - AppConfig: A dataclass for configuring the validator's behavior.
10
+ - ExitCode: An Enum defining exit codes for CLI operations.
11
+ - Custom Exceptions: For fine-grained error handling during validation.
12
+ """
13
+
14
+ from .config import AppConfig, ExitCode
15
+ from .core import StaticValidator
16
+ from .exceptions import RuleParsingError, ValidationFailedError
17
+
18
+ __all__ = [
19
+ "StaticValidator",
20
+ "AppConfig",
21
+ "ExitCode",
22
+ "ValidationFailedError",
23
+ "RuleParsingError",
24
+ ]
25
+ __version__ = "0.1.0"
@@ -0,0 +1,11 @@
1
+ """Enables running the validator as a module.
2
+
3
+ This file allows the package to be executed directly from the command line
4
+ using `python -m code_validator`. It serves as the main entry point
5
+ that invokes the command-line interface logic.
6
+ """
7
+
8
+ from .cli import run_from_cli
9
+
10
+ if __name__ == "__main__":
11
+ run_from_cli()
code_validator/cli.py ADDED
@@ -0,0 +1,88 @@
1
+ """Defines the command-line interface for the code validator.
2
+
3
+ This module is responsible for parsing command-line arguments, setting up the
4
+ application configuration, and orchestrating the main validation workflow. It acts
5
+ as the primary entry point for user interaction.
6
+ """
7
+
8
+ import argparse
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from . import __version__
13
+ from .config import AppConfig, ExitCode, LogLevel
14
+ from .core import StaticValidator
15
+ from .exceptions import CodeValidatorError
16
+ from .output import Console, setup_logging
17
+
18
+
19
+ def setup_arg_parser() -> argparse.ArgumentParser:
20
+ """Creates and configures the argument parser for the CLI.
21
+
22
+ Returns:
23
+ An instance of argparse.ArgumentParser with all arguments defined.
24
+ """
25
+ parser = argparse.ArgumentParser(
26
+ prog="validate-code",
27
+ description="Validates a Python source file against a set of JSON rules.",
28
+ )
29
+ parser.add_argument("solution_path", type=Path, help="Path to the Python solution file to validate.")
30
+ parser.add_argument("rules_path", type=Path, help="Path to the JSON file with validation rules.")
31
+ parser.add_argument(
32
+ "-l",
33
+ "--log-level",
34
+ type=LogLevel,
35
+ choices=LogLevel,
36
+ default=LogLevel.WARNING,
37
+ help="Set the logging level (default: WARNING).",
38
+ )
39
+ parser.add_argument("--silent", action="store_true", help="Suppress stdout output, show only logs.")
40
+ parser.add_argument("--stop-on-first-fail", action="store_true", help="Stop after the first failed rule.")
41
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
42
+ return parser
43
+
44
+
45
+ def run_from_cli() -> None:
46
+ """Runs the full application lifecycle from the command line.
47
+
48
+ This function parses arguments, initializes logging and configuration,
49
+ runs the validator, and handles all top-level exceptions, exiting with an
50
+ appropriate exit code.
51
+ """
52
+ parser = setup_arg_parser()
53
+ args = parser.parse_args()
54
+
55
+ # 1. Setup environment
56
+ logger = setup_logging(args.log_level)
57
+ console = Console(logger, is_silent=args.silent)
58
+ config = AppConfig(
59
+ solution_path=args.solution_path,
60
+ rules_path=args.rules_path,
61
+ log_level=args.log_level,
62
+ is_silent=args.silent,
63
+ stop_on_first_fail=args.stop_on_first_fail,
64
+ )
65
+
66
+ # 2. Run main logic with robust error handling
67
+ try:
68
+ console.print(f"Starting validation for: {config.solution_path}", level=LogLevel.INFO)
69
+ validator = StaticValidator(config, console)
70
+ is_valid = validator.run()
71
+
72
+ if is_valid:
73
+ console.print("Validation successful.", level=LogLevel.INFO)
74
+ sys.exit(ExitCode.SUCCESS)
75
+ else:
76
+ console.print("Validation failed.", level=LogLevel.ERROR)
77
+ sys.exit(ExitCode.VALIDATION_FAILED)
78
+
79
+ except CodeValidatorError as e:
80
+ console.print(str(e), level=LogLevel.CRITICAL)
81
+ sys.exit(ExitCode.VALIDATION_FAILED)
82
+ except FileNotFoundError as e:
83
+ console.print(f"Error: File not found - {e.strerror}: {e.filename}", level=LogLevel.CRITICAL)
84
+ sys.exit(ExitCode.FILE_NOT_FOUND)
85
+ except Exception as e:
86
+ console.print(f"An unexpected error occurred: {e}", level=LogLevel.CRITICAL)
87
+ logger.exception("Traceback for unexpected error:")
88
+ sys.exit(ExitCode.UNEXPECTED_ERROR)
File without changes
@@ -0,0 +1,40 @@
1
+ """Provides utility functions for working with Python's Abstract Syntax Trees (AST)."""
2
+
3
+ import ast
4
+
5
+
6
+ def enrich_ast_with_parents(tree: ast.Module) -> None:
7
+ """Walks the AST and adds a 'parent' attribute to each node.
8
+
9
+ This function mutates the AST in-place, making it easier to traverse upwards
10
+ or determine the context of a specific node. This is a crucial preprocessing
11
+ step for many complex validation rules.
12
+
13
+ Args:
14
+ tree: The root node of the AST (typically an ast.Module object) to enrich.
15
+ """
16
+ for node in ast.walk(tree):
17
+ for child in ast.iter_child_nodes(node):
18
+ # Dynamically add a reference to the parent node.
19
+ child.parent = node
20
+
21
+
22
+ def get_full_name(node: ast.AST) -> str | None:
23
+ """A helper function to recursively build a full attribute name from an AST node.
24
+
25
+ For example, for an `ast.Attribute` node representing `foo.bar.baz`, this
26
+ function will return the string "foo.bar.baz".
27
+
28
+ Args:
29
+ node: The AST node to extract the name from.
30
+
31
+ Returns:
32
+ The full, dot-separated name as a string, or None if a name cannot be
33
+ constructed.
34
+ """
35
+ if isinstance(node, ast.Name):
36
+ return node.id
37
+ if isinstance(node, ast.Attribute):
38
+ base = get_full_name(node.value)
39
+ return f"{base}.{node.attr}" if base else node.attr
40
+ return None
@@ -0,0 +1,88 @@
1
+ """Defines the core component interfaces using Protocols.
2
+
3
+ This module establishes the fundamental "contracts" for the main architectural
4
+ components of the validator: Rules, Selectors, and Constraints. By using
5
+ Protocols, we ensure that any class conforming to these interfaces can be used
6
+ interchangeably by the system's factories and core engine, enabling a flexible
7
+ and decoupled plugin-style architecture.
8
+ """
9
+
10
+ import ast
11
+ from typing import Protocol, runtime_checkable
12
+
13
+ from ..config import FullRuleConfig, ShortRuleConfig
14
+
15
+
16
+ @runtime_checkable
17
+ class Selector(Protocol):
18
+ """An interface for objects that find and select specific nodes from an AST.
19
+
20
+ A Selector's main responsibility is to traverse the Abstract Syntax Tree (AST)
21
+ of a Python source file and return a list of nodes that match a specific
22
+ criterion (e.g., all function definitions, all import statements).
23
+ """
24
+
25
+ def select(self, tree: ast.Module) -> list[ast.AST]:
26
+ """Selects and returns a list of relevant AST nodes from the tree.
27
+
28
+ Args:
29
+ tree: The full, parsed AST of the source code to be searched.
30
+
31
+ Returns:
32
+ A list of ast.AST nodes that match the selector's criteria.
33
+ An empty list should be returned if no matching nodes are found.
34
+ """
35
+ ...
36
+
37
+
38
+ @runtime_checkable
39
+ class Constraint(Protocol):
40
+ """An interface for objects that apply a condition to a set of AST nodes.
41
+
42
+ A Constraint takes the list of nodes found by a Selector and checks if they
43
+ satisfy a specific condition (e.g., the list must not be empty, the node
44
+ must inherit from a specific class).
45
+ """
46
+
47
+ def check(self, nodes: list[ast.AST]) -> bool:
48
+ """Checks if the given list of nodes satisfies the constraint.
49
+
50
+ Args:
51
+ nodes: A list of AST nodes provided by a Selector.
52
+
53
+ Returns:
54
+ True if the constraint is satisfied, False otherwise.
55
+ """
56
+ ...
57
+
58
+
59
+ @runtime_checkable
60
+ class Rule(Protocol):
61
+ """An interface for any complete, executable validation rule.
62
+
63
+ A Rule represents a single, self-contained validation check. It can be a
64
+ "short" rule (like a linter check) or a "full" rule that internally uses
65
+ a Selector and a Constraint. The core validator engine interacts with
66
+ objects conforming to this protocol.
67
+
68
+ Attributes:
69
+ config: The dataclass object holding the configuration for this rule,
70
+ parsed from the JSON file.
71
+ """
72
+
73
+ config: FullRuleConfig | ShortRuleConfig
74
+
75
+ def execute(self, tree: ast.Module | None, source_code: str | None = None) -> bool:
76
+ """Executes the validation rule.
77
+
78
+ Depending on the rule type, this method might operate on the AST, the
79
+ raw source code, or both.
80
+
81
+ Args:
82
+ tree: The full AST of the source code (for structural checks).
83
+ source_code: The raw source code string (e.g., for linter checks).
84
+
85
+ Returns:
86
+ True if the validation check passes, False otherwise.
87
+ """
88
+ ...
@@ -0,0 +1,243 @@
1
+ """Contains factories for creating rule, selector, and constraint objects.
2
+
3
+ This module implements the Factory Method design pattern to decouple the core
4
+ validator engine from the concrete implementation of rules. Factories are
5
+ responsible for parsing raw dictionary configurations from JSON and instantiating
6
+ the appropriate handler classes.
7
+ """
8
+
9
+ import dataclasses
10
+ from typing import Any, Type, TypeVar
11
+
12
+ from ..config import ConstraintConfig, FullRuleCheck, FullRuleConfig, SelectorConfig, ShortRuleConfig
13
+ from ..exceptions import RuleParsingError
14
+ from ..output import Console
15
+ from ..rules_library.basic_rules import CheckLinterRule, CheckSyntaxRule, FullRuleHandler
16
+ from ..rules_library.constraint_logic import (
17
+ IsForbiddenConstraint,
18
+ IsRequiredConstraint,
19
+ MustBeTypeConstraint,
20
+ MustHaveArgsConstraint,
21
+ MustInheritFromConstraint,
22
+ NameMustBeInConstraint,
23
+ ValueMustBeInConstraint,
24
+ )
25
+ from ..rules_library.selector_nodes import (
26
+ AssignmentSelector,
27
+ AstNodeSelector,
28
+ ClassDefSelector,
29
+ FunctionCallSelector,
30
+ FunctionDefSelector,
31
+ ImportStatementSelector,
32
+ LiteralSelector,
33
+ UsageSelector,
34
+ )
35
+ from .definitions import Constraint, Rule, Selector
36
+
37
+ T = TypeVar("T")
38
+
39
+
40
+ def _create_dataclass_from_dict(cls: Type[T], data: dict[str, Any]) -> T:
41
+ """Safely creates a dataclass instance from a dictionary.
42
+
43
+ This helper function filters the input dictionary to include only the keys
44
+ that correspond to fields in the target dataclass, preventing `TypeError`
45
+ for unexpected arguments.
46
+
47
+ Args:
48
+ cls: The dataclass type to instantiate.
49
+ data: The dictionary with raw data.
50
+
51
+ Returns:
52
+ An instance of the specified dataclass.
53
+ """
54
+ expected_fields = {f.name for f in dataclasses.fields(cls)}
55
+ filtered_data = {k: v for k, v in data.items() if k in expected_fields}
56
+ return cls(**filtered_data)
57
+
58
+
59
+ class RuleFactory:
60
+ """Creates rule handler objects from raw dictionary configuration.
61
+
62
+ This is the main factory that acts as an entry point for parsing the
63
+ 'validation_rules' list from a JSON file. It determines whether a rule is
64
+ a "short" pre-defined type or a "full" custom rule and delegates the
65
+ creation of its components to other specialized factories.
66
+
67
+ Attributes:
68
+ _console (Console): An instance of the console for logging.
69
+ _selector_factory (SelectorFactory): A factory for creating selector objects.
70
+ _constraint_factory (ConstraintFactory): A factory for creating constraint objects.
71
+ """
72
+
73
+ def __init__(self, console: Console):
74
+ """Initializes the RuleFactory.
75
+
76
+ Args:
77
+ console: An instance of the Console for logging and output,
78
+ to be passed to rule handlers.
79
+ """
80
+ self._console = console
81
+ self._selector_factory = SelectorFactory()
82
+ self._constraint_factory = ConstraintFactory()
83
+
84
+ def create(self, rule_config: dict[str, Any]) -> Rule:
85
+ """Creates a specific rule instance based on its configuration.
86
+
87
+ This method acts as a dispatcher. It determines whether the configuration
88
+ describes a "short" pre-defined rule or a "full" custom rule with a
89
+ selector/constraint pair, and then delegates to the appropriate
90
+ creation logic.
91
+
92
+ Args:
93
+ rule_config: A dictionary parsed from the JSON rules file.
94
+
95
+ Returns:
96
+ An instance of an object that conforms to the Rule protocol.
97
+
98
+ Raises:
99
+ RuleParsingError: If the rule configuration is invalid, missing
100
+ required keys, or specifies an unknown type.
101
+ """
102
+ rule_id = rule_config.get("rule_id")
103
+ try:
104
+ if "type" in rule_config:
105
+ config = _create_dataclass_from_dict(ShortRuleConfig, rule_config)
106
+ return self._create_short_rule(config)
107
+
108
+ elif "check" in rule_config:
109
+ raw_selector_cfg = rule_config["check"]["selector"]
110
+ raw_constraint_cfg = rule_config["check"]["constraint"]
111
+
112
+ selector = self._selector_factory.create(raw_selector_cfg)
113
+ constraint = self._constraint_factory.create(raw_constraint_cfg)
114
+
115
+ selector_cfg = _create_dataclass_from_dict(SelectorConfig, raw_selector_cfg)
116
+ constraint_cfg = _create_dataclass_from_dict(ConstraintConfig, raw_constraint_cfg)
117
+ check_cfg = FullRuleCheck(selector=selector_cfg, constraint=constraint_cfg)
118
+ config = FullRuleConfig(
119
+ rule_id=rule_config["rule_id"],
120
+ message=rule_config["message"],
121
+ check=check_cfg,
122
+ is_critical=rule_config.get("is_critical", False),
123
+ )
124
+ return FullRuleHandler(config, selector, constraint, self._console)
125
+ else:
126
+ raise RuleParsingError("Rule must contain 'type' or 'check' key.", rule_id)
127
+ except (TypeError, KeyError, RuleParsingError) as e:
128
+ raise RuleParsingError(f"Invalid config for rule '{rule_id}': {e}", rule_id) from e
129
+
130
+ def _create_short_rule(self, config: ShortRuleConfig) -> Rule:
131
+ """Dispatches the creation of handlers for "short" rules.
132
+
133
+ This private helper method acts as a registry for pre-defined, common
134
+ validation checks like syntax or PEP8 linting. It maps a rule's 'type'
135
+ string to a concrete Rule handler class.
136
+
137
+ Args:
138
+ config: The dataclass object containing the configuration for the
139
+ short rule.
140
+
141
+ Returns:
142
+ An initialized instance of a concrete class that implements the
143
+ Rule protocol.
144
+
145
+ Raises:
146
+ RuleParsingError: If the 'type' specified in the config does not
147
+ correspond to any known short rule.
148
+ """
149
+ if config.type == "check_syntax":
150
+ return CheckSyntaxRule(config, self._console)
151
+ elif config.type == "check_linter_pep8":
152
+ return CheckLinterRule(config, self._console)
153
+ else:
154
+ raise RuleParsingError(f"Unknown short rule type: '{config.type}'", config.rule_id)
155
+
156
+
157
+ class SelectorFactory:
158
+ """Creates selector objects from raw dictionary configuration.
159
+
160
+ This factory is responsible for instantiating the correct Selector object
161
+ based on the 'type' field in a rule's selector configuration block. Each
162
+ concrete selector specializes in finding a specific type of AST node.
163
+ This class uses a static `create` method as it does not need to maintain
164
+ any state.
165
+ """
166
+
167
+ @staticmethod
168
+ def create(selector_config: dict[str, Any]) -> Selector:
169
+ """Creates a specific selector instance based on its type.
170
+
171
+ This method uses the 'type' field from the selector configuration
172
+ to determine which concrete Selector class to instantiate.
173
+
174
+ Args:
175
+ selector_config: The 'selector' block from a JSON rule.
176
+
177
+ Returns:
178
+ An instance of a class that conforms to the Selector protocol.
179
+ """
180
+ config = _create_dataclass_from_dict(SelectorConfig, selector_config)
181
+
182
+ match config.type:
183
+ case "function_def":
184
+ return FunctionDefSelector(name=config.name, in_scope_config=config.in_scope)
185
+ case "class_def":
186
+ return ClassDefSelector(name=config.name, in_scope_config=config.in_scope)
187
+ case "import_statement":
188
+ return ImportStatementSelector(name=config.name, in_scope_config=config.in_scope)
189
+ case "function_call":
190
+ return FunctionCallSelector(name=config.name, in_scope_config=config.in_scope)
191
+ case "assignment":
192
+ return AssignmentSelector(name=config.name, in_scope_config=config.in_scope)
193
+ case "usage":
194
+ return UsageSelector(name=config.name, in_scope_config=config.in_scope)
195
+ case "literal":
196
+ return LiteralSelector(name=config.name, in_scope_config=config.in_scope)
197
+ case "ast_node":
198
+ return AstNodeSelector(node_type=config.node_type, in_scope_config=config.in_scope)
199
+ case _:
200
+ raise RuleParsingError(f"Unknown selector type: '{config.type}'")
201
+
202
+
203
+ class ConstraintFactory:
204
+ """Creates constraint objects from raw dictionary configuration.
205
+
206
+ This factory is responsible for instantiating the correct Constraint object
207
+ based on the 'type' field in a rule's constraint configuration block.
208
+ Each concrete constraint specializes in applying a specific logical check
209
+ to a list of AST nodes. This class uses a static `create` method.
210
+ """
211
+
212
+ @staticmethod
213
+ def create(constraint_config: dict[str, Any]) -> Constraint:
214
+ """Creates a specific constraint instance based on its type.
215
+
216
+ This method uses the 'type' field from the constraint configuration
217
+ to determine which concrete Constraint class to instantiate.
218
+
219
+ Args:
220
+ constraint_config: The 'constraint' block from a JSON rule.
221
+
222
+ Returns:
223
+ An instance of a class that conforms to the Constraint protocol.
224
+ """
225
+ config = _create_dataclass_from_dict(ConstraintConfig, constraint_config)
226
+
227
+ match config.type:
228
+ case "is_required":
229
+ return IsRequiredConstraint(count=config.count)
230
+ case "is_forbidden":
231
+ return IsForbiddenConstraint()
232
+ case "must_inherit_from":
233
+ return MustInheritFromConstraint(parent_name=config.parent_name)
234
+ case "must_be_type":
235
+ return MustBeTypeConstraint(expected_type=config.expected_type)
236
+ case "must_have_args":
237
+ return MustHaveArgsConstraint(count=config.count, names=config.names, exact_match=config.exact_match)
238
+ case "name_must_be_in":
239
+ return NameMustBeInConstraint(allowed_names=config.allowed_names)
240
+ case "value_must_be_in":
241
+ return ValueMustBeInConstraint(allowed_values=config.allowed_values)
242
+ case _:
243
+ raise RuleParsingError(f"Unknown constraint type: '{config.type}'")
@@ -0,0 +1,59 @@
1
+ """Provides functionality to find and isolate specific scopes within an AST.
2
+
3
+ This module contains helper functions that are used by ScopedSelectors to narrow
4
+ down their search area from the entire module to a specific function, class,
5
+ or method, based on the `in_scope` configuration from a JSON rule.
6
+ """
7
+
8
+ import ast
9
+ from typing import Any
10
+
11
+
12
+ def find_scope_node(tree: ast.Module, scope_config: dict[str, Any]) -> ast.AST | None:
13
+ """Finds a specific scope node (class or function) within the AST.
14
+
15
+ This function traverses the AST to locate a node that matches the provided
16
+ scope configuration. It supports finding global functions, classes, and
17
+ methods within classes.
18
+
19
+ Args:
20
+ tree: The root of the AST (the module object).
21
+ scope_config: A dictionary defining the desired scope.
22
+ Expected keys:
23
+ - "function": name of a global function.
24
+ - "class": name of a class.
25
+ - "method": name of a method (must be used with "class").
26
+
27
+ Returns:
28
+ The found ast.AST node (either ast.ClassDef or ast.FunctionDef) that
29
+ represents the desired scope, or None if the scope is not found.
30
+
31
+ Example:
32
+ >>> # To find the scope of 'my_func' in 'MyClass':
33
+ >>> scope_config = {"class": "MyClass", "method": "my_func"}
34
+ >>> find_scope_node(my_ast_tree, scope_config)
35
+ <ast.FunctionDef object at ...>
36
+ """
37
+ class_name = scope_config.get("class")
38
+ if class_name:
39
+ for node in ast.walk(tree):
40
+ if isinstance(node, ast.ClassDef) and node.name == class_name:
41
+ # If only a class scope is needed, return it.
42
+ if "method" not in scope_config:
43
+ return node
44
+
45
+ # If a method is needed, search within the class body.
46
+ method_name = scope_config.get("method")
47
+ for item in node.body:
48
+ if isinstance(item, ast.FunctionDef) and item.name == method_name:
49
+ return item
50
+ return None # Class was found, but the method was not.
51
+
52
+ function_name = scope_config.get("function")
53
+ if function_name:
54
+ # For global functions, search only the top-level body of the module.
55
+ for node in tree.body:
56
+ if isinstance(node, ast.FunctionDef) and node.name == function_name:
57
+ return node
58
+
59
+ return None
@@ -0,0 +1,99 @@
1
+ """Defines all data structures and configuration models for the validator.
2
+
3
+ This module contains Enum classes for standardized codes and several frozen
4
+ dataclasses that represent the structured configuration loaded from JSON files
5
+ and command-line arguments. These models ensure type safety and provide a
6
+ clear "shape" for the application's data.
7
+ """
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import IntEnum, StrEnum
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+
15
+ class ExitCode(IntEnum):
16
+ """Defines standardized exit codes for the command-line application."""
17
+
18
+ SUCCESS = 0
19
+ VALIDATION_FAILED = 1
20
+ FILE_NOT_FOUND = 2
21
+ JSON_ERROR = 3
22
+ UNEXPECTED_ERROR = 10
23
+
24
+
25
+ class LogLevel(StrEnum):
26
+ """Defines the supported logging levels for the application."""
27
+
28
+ DEBUG = "DEBUG"
29
+ INFO = "INFO"
30
+ WARNING = "WARNING"
31
+ ERROR = "ERROR"
32
+ CRITICAL = "CRITICAL"
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class AppConfig:
37
+ """Stores the main application configuration from CLI arguments."""
38
+
39
+ solution_path: Path
40
+ rules_path: Path
41
+ log_level: LogLevel
42
+ is_silent: bool
43
+ stop_on_first_fail: bool
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class SelectorConfig:
48
+ """Represents the configuration for a Selector component from a JSON rule."""
49
+
50
+ type: str
51
+ name: str | None = None
52
+ node_type: str | list[str] | None = None
53
+ in_scope: str | dict[str, Any] | None = None
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class ConstraintConfig:
58
+ """Represents the configuration for a Constraint component from a JSON rule."""
59
+
60
+ type: str
61
+ count: int | None = None
62
+ parent_name: str | None = None
63
+ expected_type: str | None = None
64
+ allowed_names: list[str] | None = None
65
+ allowed_values: list[Any] | None = None
66
+ names: list[str] | None = None
67
+ exact_match: bool | None = None
68
+
69
+
70
+ @dataclass(frozen=True)
71
+ class FullRuleCheck:
72
+ """Represents the 'check' block within a full validation rule."""
73
+
74
+ selector: SelectorConfig
75
+ constraint: ConstraintConfig
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class ShortRuleConfig:
80
+ """Represents a 'short' (pre-defined) validation rule from JSON."""
81
+
82
+ rule_id: int
83
+ type: str
84
+ message: str
85
+ params: dict[str, Any] = field(default_factory=dict)
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class FullRuleConfig:
90
+ """Represents a 'full' (custom) validation rule with selector and constraint."""
91
+
92
+ rule_id: int
93
+ message: str
94
+ check: FullRuleCheck
95
+ is_critical: bool = False
96
+
97
+
98
+ # A type alias representing any possible rule configuration object.
99
+ ValidationRuleConfig = ShortRuleConfig | FullRuleConfig