Edit on GitHub

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}"
class RuleLocation(sqlmesh.utils.pydantic.PydanticModel):
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.

file_path: str
start_line: Optional[int]
model_config = {'json_encoders': {<class 'sqlglot.expressions.core.Expr'>: <function _expression_encoder>, <class 'sqlglot.expressions.datatypes.DataType'>: <function _expression_encoder>, <class 'sqlglot.expressions.query.Tuple'>: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery]: <function _expression_encoder>, typing.Union[sqlglot.expressions.query.Query, sqlmesh.core.dialect.JinjaQuery, sqlmesh.core.dialect.MacroFunc]: <function _expression_encoder>, <class 'datetime.tzinfo'>: <function PydanticModel.<lambda>>}, 'arbitrary_types_allowed': True, 'extra': 'forbid', 'protected_namespaces': ()}

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
sqlmesh.utils.pydantic.PydanticModel
dict
json
copy
fields_set
parse_obj
parse_raw
missing_required_fields
extra_fields
all_fields
all_field_infos
required_fields
@dataclass(frozen=True)
class Position:
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.

Position(line: int, character: int)
line: int
character: int
@dataclass(frozen=True)
class Range:
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.

Range( start: Position, end: Position)
start: Position
end: Position
@dataclass(frozen=True)
class TextEdit:
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.

TextEdit( path: pathlib.Path, range: Range, new_text: str)
path: pathlib.Path
range: Range
new_text: str
@dataclass(frozen=True)
class CreateFile:
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.

CreateFile(path: pathlib.Path, text: str)
path: pathlib.Path
text: str
@dataclass(frozen=True)
class Fix:
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.

Fix( title: str, edits: List[TextEdit] = <factory>, create_files: List[CreateFile] = <factory>)
title: str
edits: List[TextEdit]
create_files: List[CreateFile]
class Rule(abc.ABC):
 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.

name = 'rule'
context
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.

summary: str
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.

def violation( self, violation_msg: Optional[str] = None, violation_range: Optional[Range] = None, fixes: Optional[List[Fix]] = None) -> RuleViolation:
 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

def get_definition_location(self) -> RuleLocation:
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.

class RuleViolation:
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}"
RuleViolation( rule: Rule, violation_msg: str, violation_range: Optional[Range] = None, fixes: Optional[List[Fix]] = None)
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 []
rule
violation_msg
violation_range
fixes