Skip to content

Rule

Rule definitions.

Rule

The rule base class.

Source code in src/dbt_score/rule.py
class Rule:
    """The rule base class."""

    description: str
    severity: Severity = Severity.MEDIUM
    default_config: typing.ClassVar[dict[str, Any]] = {}

    def __init__(self, rule_config: RuleConfig | None = None) -> None:
        """Initialize the rule."""
        self.config: dict[str, Any] = {}
        if rule_config:
            self.process_config(rule_config)

    def __init_subclass__(cls, **kwargs) -> None:  # type: ignore
        """Initializes the subclass."""
        super().__init_subclass__(**kwargs)
        if not hasattr(cls, "description"):
            raise AttributeError("Subclass must define class attribute `description`.")

    def process_config(self, rule_config: RuleConfig) -> None:
        """Process the rule config."""
        config = self.default_config.copy()

        # Overwrite default rule configuration
        for k, v in rule_config.config.items():
            if k in self.default_config:
                config[k] = v
            else:
                raise AttributeError(
                    f"Unknown rule parameter: {k} for rule {self.source()}."
                )

        self.set_severity(
            rule_config.severity
        ) if rule_config.severity else rule_config.severity
        self.config = config

    def evaluate(self, model: Model) -> RuleViolation | None:
        """Evaluates the rule."""
        raise NotImplementedError("Subclass must implement method `evaluate`.")

    @classmethod
    def set_severity(cls, severity: Severity) -> None:
        """Set the severity of the rule."""
        cls.severity = severity

    @classmethod
    def source(cls) -> str:
        """Return the source of the rule, i.e. a fully qualified name."""
        return f"{cls.__module__}.{cls.__name__}"

    def __hash__(self) -> int:
        """Compute a unique hash for a rule."""
        return hash(self.source())

__hash__()

Compute a unique hash for a rule.

Source code in src/dbt_score/rule.py
def __hash__(self) -> int:
    """Compute a unique hash for a rule."""
    return hash(self.source())

__init__(rule_config=None)

Initialize the rule.

Source code in src/dbt_score/rule.py
def __init__(self, rule_config: RuleConfig | None = None) -> None:
    """Initialize the rule."""
    self.config: dict[str, Any] = {}
    if rule_config:
        self.process_config(rule_config)

__init_subclass__(**kwargs)

Initializes the subclass.

Source code in src/dbt_score/rule.py
def __init_subclass__(cls, **kwargs) -> None:  # type: ignore
    """Initializes the subclass."""
    super().__init_subclass__(**kwargs)
    if not hasattr(cls, "description"):
        raise AttributeError("Subclass must define class attribute `description`.")

evaluate(model)

Evaluates the rule.

Source code in src/dbt_score/rule.py
def evaluate(self, model: Model) -> RuleViolation | None:
    """Evaluates the rule."""
    raise NotImplementedError("Subclass must implement method `evaluate`.")

process_config(rule_config)

Process the rule config.

Source code in src/dbt_score/rule.py
def process_config(self, rule_config: RuleConfig) -> None:
    """Process the rule config."""
    config = self.default_config.copy()

    # Overwrite default rule configuration
    for k, v in rule_config.config.items():
        if k in self.default_config:
            config[k] = v
        else:
            raise AttributeError(
                f"Unknown rule parameter: {k} for rule {self.source()}."
            )

    self.set_severity(
        rule_config.severity
    ) if rule_config.severity else rule_config.severity
    self.config = config

set_severity(severity) classmethod

Set the severity of the rule.

Source code in src/dbt_score/rule.py
@classmethod
def set_severity(cls, severity: Severity) -> None:
    """Set the severity of the rule."""
    cls.severity = severity

source() classmethod

Return the source of the rule, i.e. a fully qualified name.

Source code in src/dbt_score/rule.py
@classmethod
def source(cls) -> str:
    """Return the source of the rule, i.e. a fully qualified name."""
    return f"{cls.__module__}.{cls.__name__}"

RuleConfig dataclass

Configuration for a rule.

Source code in src/dbt_score/rule.py
@dataclass
class RuleConfig:
    """Configuration for a rule."""

    severity: Severity | None = None
    config: dict[str, Any] = field(default_factory=dict)

    @staticmethod
    def from_dict(rule_config: dict[str, Any]) -> "RuleConfig":
        """Create a RuleConfig from a dictionary."""
        config = rule_config.copy()
        severity = (
            Severity(config.pop("severity", None))
            if "severity" in rule_config
            else None
        )

        return RuleConfig(severity=severity, config=config)

from_dict(rule_config) staticmethod

Create a RuleConfig from a dictionary.

Source code in src/dbt_score/rule.py
@staticmethod
def from_dict(rule_config: dict[str, Any]) -> "RuleConfig":
    """Create a RuleConfig from a dictionary."""
    config = rule_config.copy()
    severity = (
        Severity(config.pop("severity", None))
        if "severity" in rule_config
        else None
    )

    return RuleConfig(severity=severity, config=config)

RuleViolation dataclass

The violation of a rule.

Source code in src/dbt_score/rule.py
@dataclass
class RuleViolation:
    """The violation of a rule."""

    message: str | None = None

Severity

Bases: Enum

The severity/weight of a rule.

Source code in src/dbt_score/rule.py
class Severity(Enum):
    """The severity/weight of a rule."""

    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4

rule(__func=None, *, description=None, severity=Severity.MEDIUM)

Rule decorator.

The rule decorator creates a rule class (subclass of Rule) and returns it.

Using arguments or not are both supported: - @rule - @rule(description="...")

Parameters:

Name Type Description Default
__func RuleEvaluationType | None

The rule evaluation function being decorated.

None
description str | None

The description of the rule.

None
severity Severity

The severity of the rule.

MEDIUM
Source code in src/dbt_score/rule.py
def rule(
    __func: RuleEvaluationType | None = None,
    *,
    description: str | None = None,
    severity: Severity = Severity.MEDIUM,
) -> Type[Rule] | Callable[[RuleEvaluationType], Type[Rule]]:
    """Rule decorator.

    The rule decorator creates a rule class (subclass of Rule) and returns it.

    Using arguments or not are both supported:
    - ``@rule``
    - ``@rule(description="...")``

    Args:
        __func: The rule evaluation function being decorated.
        description: The description of the rule.
        severity: The severity of the rule.
    """

    def decorator_rule(
        func: RuleEvaluationType,
    ) -> Type[Rule]:
        """Decorator function."""
        if func.__doc__ is None and description is None:
            raise AttributeError("Rule must define `description` or `func.__doc__`.")

        # Get description parameter, otherwise use the docstring
        rule_description = description or (
            func.__doc__.split("\n")[0] if func.__doc__ else None
        )

        def wrapped_func(self: Rule, *args: Any, **kwargs: Any) -> RuleViolation | None:
            """Wrap func to add `self`."""
            return func(*args, **kwargs)

        # Get default parameters from the rule definition
        default_config = {
            key: val.default
            for key, val in inspect.signature(func).parameters.items()
            if val.default != inspect.Parameter.empty
        }

        # Create the rule class inheriting from Rule
        rule_class = type(
            func.__name__,
            (Rule,),
            {
                "description": rule_description,
                "severity": severity,
                "default_config": default_config,
                "evaluate": wrapped_func,
                # Save provided evaluate function
                "_orig_evaluate": func,
                # Forward origin of the decorated function
                "__qualname__": func.__qualname__,  # https://peps.python.org/pep-3155/
                "__module__": func.__module__,
            },
        )

        return rule_class

    if __func is not None:
        # The syntax @rule is used
        return decorator_rule(__func)
    else:
        # The syntax @rule(...) is used
        return decorator_rule