sqlmesh.core.linter.rule
1from __future__ import annotations 2 3import abc 4from dataclasses import dataclass, field 5from pathlib import Path 6 7from sqlmesh.core.model import Model 8 9from typing import Type 10 11import typing as t 12 13from sqlmesh.utils.pydantic import PydanticModel 14 15 16if t.TYPE_CHECKING: 17 from sqlmesh.core.context import GenericContext 18 19 20class RuleLocation(PydanticModel): 21 """The location of a rule in a file.""" 22 23 file_path: str 24 start_line: t.Optional[int] = None 25 26 27@dataclass(frozen=True) 28class Position: 29 """The position of a rule violation in a file, the position follows the LSP standard.""" 30 31 line: int 32 character: int 33 34 35@dataclass(frozen=True) 36class Range: 37 """The range of a rule violation in a file. The range follows the LSP standard.""" 38 39 start: Position 40 end: Position 41 42 43@dataclass(frozen=True) 44class TextEdit: 45 """A text edit to apply to a file.""" 46 47 path: Path 48 range: Range 49 new_text: str 50 51 52@dataclass(frozen=True) 53class CreateFile: 54 """Create a new file with the provided text.""" 55 56 path: Path 57 text: str 58 59 60@dataclass(frozen=True) 61class Fix: 62 """A fix that can be applied to resolve a rule violation.""" 63 64 title: str 65 edits: t.List[TextEdit] = field(default_factory=list) 66 create_files: t.List[CreateFile] = field(default_factory=list) 67 68 69class _Rule(abc.ABCMeta): 70 def __new__(cls: Type[_Rule], clsname: str, bases: t.Tuple, attrs: t.Dict) -> _Rule: 71 attrs["name"] = clsname.lower() 72 return super().__new__(cls, clsname, bases, attrs) 73 74 75class Rule(abc.ABC, metaclass=_Rule): 76 """The base class for a rule.""" 77 78 name = "rule" 79 80 def __init__(self, context: GenericContext): 81 self.context = context 82 83 @abc.abstractmethod 84 def check_model( 85 self, model: Model 86 ) -> t.Optional[t.Union[RuleViolation, t.List[RuleViolation]]]: 87 """The evaluation function that'll check for a violation of this rule.""" 88 89 @property 90 def summary(self) -> str: 91 """A summary of what this rule checks for.""" 92 return self.__doc__ or "" 93 94 def violation( 95 self, 96 violation_msg: t.Optional[str] = None, 97 violation_range: t.Optional[Range] = None, 98 fixes: t.Optional[t.List[Fix]] = None, 99 ) -> RuleViolation: 100 """Create a RuleViolation instance for this rule""" 101 return RuleViolation( 102 rule=self, 103 violation_msg=violation_msg or self.summary, 104 violation_range=violation_range, 105 fixes=fixes, 106 ) 107 108 def get_definition_location(self) -> RuleLocation: 109 """Return the file path and position information for this rule. 110 111 This method returns information about where this rule is defined, 112 which can be used in diagnostics to link to the rule's documentation. 113 114 Returns: 115 A dictionary containing file path and position information. 116 """ 117 import inspect 118 119 # Get the file where the rule class is defined 120 file_path = inspect.getfile(self.__class__) 121 122 try: 123 # Get the source code and line number 124 source_lines, start_line = inspect.getsourcelines(self.__class__) 125 return RuleLocation( 126 file_path=file_path, 127 start_line=start_line, 128 ) 129 except (IOError, TypeError): 130 # Fall back to just returning the file path if we can't get source lines 131 return RuleLocation(file_path=file_path) 132 133 def __repr__(self) -> str: 134 return self.name 135 136 137class RuleViolation: 138 def __init__( 139 self, 140 rule: Rule, 141 violation_msg: str, 142 violation_range: t.Optional[Range] = None, 143 fixes: t.Optional[t.List[Fix]] = None, 144 ) -> None: 145 self.rule = rule 146 self.violation_msg = violation_msg 147 self.violation_range = violation_range 148 self.fixes = fixes or [] 149 150 def __repr__(self) -> str: 151 return f"{self.rule.name}: {self.violation_msg}"
21class RuleLocation(PydanticModel): 22 """The location of a rule in a file.""" 23 24 file_path: str 25 start_line: t.Optional[int] = None
The location of a rule in a file.
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
Inherited Members
- pydantic.main.BaseModel
- BaseModel
- model_fields
- model_computed_fields
- model_extra
- model_fields_set
- model_construct
- model_copy
- model_dump
- model_dump_json
- model_json_schema
- model_parametrized_name
- model_post_init
- model_rebuild
- model_validate
- model_validate_json
- model_validate_strings
- parse_file
- from_orm
- construct
- schema
- schema_json
- validate
- update_forward_refs
28@dataclass(frozen=True) 29class Position: 30 """The position of a rule violation in a file, the position follows the LSP standard.""" 31 32 line: int 33 character: int
The position of a rule violation in a file, the position follows the LSP standard.
36@dataclass(frozen=True) 37class Range: 38 """The range of a rule violation in a file. The range follows the LSP standard.""" 39 40 start: Position 41 end: Position
The range of a rule violation in a file. The range follows the LSP standard.
44@dataclass(frozen=True) 45class TextEdit: 46 """A text edit to apply to a file.""" 47 48 path: Path 49 range: Range 50 new_text: str
A text edit to apply to a file.
53@dataclass(frozen=True) 54class CreateFile: 55 """Create a new file with the provided text.""" 56 57 path: Path 58 text: str
Create a new file with the provided text.
61@dataclass(frozen=True) 62class Fix: 63 """A fix that can be applied to resolve a rule violation.""" 64 65 title: str 66 edits: t.List[TextEdit] = field(default_factory=list) 67 create_files: t.List[CreateFile] = field(default_factory=list)
A fix that can be applied to resolve a rule violation.
76class Rule(abc.ABC, metaclass=_Rule): 77 """The base class for a rule.""" 78 79 name = "rule" 80 81 def __init__(self, context: GenericContext): 82 self.context = context 83 84 @abc.abstractmethod 85 def check_model( 86 self, model: Model 87 ) -> t.Optional[t.Union[RuleViolation, t.List[RuleViolation]]]: 88 """The evaluation function that'll check for a violation of this rule.""" 89 90 @property 91 def summary(self) -> str: 92 """A summary of what this rule checks for.""" 93 return self.__doc__ or "" 94 95 def violation( 96 self, 97 violation_msg: t.Optional[str] = None, 98 violation_range: t.Optional[Range] = None, 99 fixes: t.Optional[t.List[Fix]] = None, 100 ) -> RuleViolation: 101 """Create a RuleViolation instance for this rule""" 102 return RuleViolation( 103 rule=self, 104 violation_msg=violation_msg or self.summary, 105 violation_range=violation_range, 106 fixes=fixes, 107 ) 108 109 def get_definition_location(self) -> RuleLocation: 110 """Return the file path and position information for this rule. 111 112 This method returns information about where this rule is defined, 113 which can be used in diagnostics to link to the rule's documentation. 114 115 Returns: 116 A dictionary containing file path and position information. 117 """ 118 import inspect 119 120 # Get the file where the rule class is defined 121 file_path = inspect.getfile(self.__class__) 122 123 try: 124 # Get the source code and line number 125 source_lines, start_line = inspect.getsourcelines(self.__class__) 126 return RuleLocation( 127 file_path=file_path, 128 start_line=start_line, 129 ) 130 except (IOError, TypeError): 131 # Fall back to just returning the file path if we can't get source lines 132 return RuleLocation(file_path=file_path) 133 134 def __repr__(self) -> str: 135 return self.name
The base class for a rule.
84 @abc.abstractmethod 85 def check_model( 86 self, model: Model 87 ) -> t.Optional[t.Union[RuleViolation, t.List[RuleViolation]]]: 88 """The evaluation function that'll check for a violation of this rule."""
The evaluation function that'll check for a violation of this rule.
90 @property 91 def summary(self) -> str: 92 """A summary of what this rule checks for.""" 93 return self.__doc__ or ""
A summary of what this rule checks for.
95 def violation( 96 self, 97 violation_msg: t.Optional[str] = None, 98 violation_range: t.Optional[Range] = None, 99 fixes: t.Optional[t.List[Fix]] = None, 100 ) -> RuleViolation: 101 """Create a RuleViolation instance for this rule""" 102 return RuleViolation( 103 rule=self, 104 violation_msg=violation_msg or self.summary, 105 violation_range=violation_range, 106 fixes=fixes, 107 )
Create a RuleViolation instance for this rule
109 def get_definition_location(self) -> RuleLocation: 110 """Return the file path and position information for this rule. 111 112 This method returns information about where this rule is defined, 113 which can be used in diagnostics to link to the rule's documentation. 114 115 Returns: 116 A dictionary containing file path and position information. 117 """ 118 import inspect 119 120 # Get the file where the rule class is defined 121 file_path = inspect.getfile(self.__class__) 122 123 try: 124 # Get the source code and line number 125 source_lines, start_line = inspect.getsourcelines(self.__class__) 126 return RuleLocation( 127 file_path=file_path, 128 start_line=start_line, 129 ) 130 except (IOError, TypeError): 131 # Fall back to just returning the file path if we can't get source lines 132 return RuleLocation(file_path=file_path)
Return the file path and position information for this rule.
This method returns information about where this rule is defined, which can be used in diagnostics to link to the rule's documentation.
Returns:
A dictionary containing file path and position information.
138class RuleViolation: 139 def __init__( 140 self, 141 rule: Rule, 142 violation_msg: str, 143 violation_range: t.Optional[Range] = None, 144 fixes: t.Optional[t.List[Fix]] = None, 145 ) -> None: 146 self.rule = rule 147 self.violation_msg = violation_msg 148 self.violation_range = violation_range 149 self.fixes = fixes or [] 150 151 def __repr__(self) -> str: 152 return f"{self.rule.name}: {self.violation_msg}"
139 def __init__( 140 self, 141 rule: Rule, 142 violation_msg: str, 143 violation_range: t.Optional[Range] = None, 144 fixes: t.Optional[t.List[Fix]] = None, 145 ) -> None: 146 self.rule = rule 147 self.violation_msg = violation_msg 148 self.violation_range = violation_range 149 self.fixes = fixes or []