Configuration

Exclude certain files

You can easily exclude certain files, for example, your tests, by using the exclude parameter from pre-commit:

- repo: https://github.com/ambient-innovation/boa-restrictor
  rev: v{{ version }}
  hooks:
    - id: boa-restrictor
      ...
      exclude: |
        (?x)^(
          /.*/tests/.*
          |.*/test_.*\.py
        )$

Globally exclude configuration rule

You can disable any rule in your pyproject.toml file as follows:

[tool.boa-restrictor]
exclude = [
    "PBR001",
    "PBR002",
]

Disable Django rules

You can disable Django-specific rules by setting enable_django_rules to false.

[tool.boa-restrictor]
enable_django_rules = false

Per-file exclusion of configuration rule

You can disable rules on a per-file-basis in your pyproject.toml file as follows:

[tool.boa-restrictor.per-file-excludes]
"*/tests/*.py" = [
    "PBR001",
    "PBR002",
]
"scripts/*.py" = [
    "DBR001",
]
"*/my_app/*.py" = [
    "PBR003",
]

Take care that the path is relative to the location of your pyproject.toml. This means that example two targets all files living in a scripts/ directory on the projects top level.

Project-specific (custom) rules

You can register your own rule classes alongside the built-in ones by listing them in your pyproject.toml:

[tool.boa-restrictor]
custom_rules = [
    "myproject.linting.NoFooBarRule",
    "myproject.linting.RequireBazRule",
]

Each entry is a dotted import path to a class that subclasses boa_restrictor.common.rule.Rule and sets a RULE_ID and RULE_LABEL. A minimal example:

import ast

from boa_restrictor.common.rule import Rule
from boa_restrictor.projections.occurrence import Occurrence


class NoFooBarRule(Rule):
    RULE_ID = "MYP001"
    RULE_LABEL = 'Functions must not be named "foo_bar".'

    def check(self) -> list[Occurrence]:
        occurrences = []
        for node in ast.walk(self.source_tree):
            if isinstance(node, ast.FunctionDef) and node.name == "foo_bar":
                occurrences.append(
                    Occurrence(
                        rule_id=self.RULE_ID,
                        rule_label=self.RULE_LABEL,
                        filename=self.filename,
                        file_path=self.file_path,
                        identifier=node.name,
                        line_number=node.lineno,
                    )
                )
        return occurrences

Custom rules participate in the same exclusion mechanisms as the built-ins (exclude, per-file-excludes, and # noqa: <rule_id>).

If you use ruff, remember to add your custom rule prefix to [tool.ruff.lint].external as well — see noqa & ruff support.

Rule ID requirements

  • The PBR and DBR prefixes are reserved for built-in rules. Pick any other prefix.

  • Every loaded rule must have a unique RULE_ID. Duplicate IDs (within your custom rules, or against a built-in) abort the run with an error naming both classes.

  • Validation is eager: a misconfigured custom_rules entry fails the run before any file is linted.

  • If a custom rule raises during check(), the linting run halts. Treat exceptions inside check() as bugs in your rule.

Running custom rules under pre-commit

Custom rules need your project’s modules to be importable when boa-restrictor runs. The standard pre-commit hook installs boa-restrictor into an isolated virtualenv that does not see your project code, so you have two options:

Option A — ``language: system`` (simplest). boa-restrictor runs in the environment you’ve installed it into (typically your project venv). You lose pre-commit’s automatic version management — pin boa-restrictor in your dev requirements instead.

- repo: local
  hooks:
    - id: boa-restrictor
      name: boa-restrictor
      entry: boa-restrictor
      language: system
      types: [python]
      args: [--config=pyproject.toml]

Option B — ``additional_dependencies`` (preferable if your project is pip-installable). Keeps pre-commit’s hermetic environment and installs your package into the hook’s venv.

- repo: https://github.com/ambient-innovation/boa-restrictor
  rev: v{{ version }}
  hooks:
    - id: boa-restrictor
      args: [--config=pyproject.toml]
      additional_dependencies: [".", "boa-restrictor"]

Trust model

Listing a path under custom_rules causes boa-restrictor to import and execute the named module at lint time. boa-restrictor does not sandbox imported rule modules. Only point this at code you trust. If you run boa-restrictor against contributors’ branches in CI (e.g. PRs from forks), assume that whoever can edit pyproject.toml can run arbitrary code in your CI environment.

Common gotcha: Django imports at module top-level

boa-restrictor does not bootstrap Django before importing your custom rule modules. If your rule needs anything from django.conf / django.db / etc., import it inside check(), not at module scope, or you will see ImproperlyConfigured errors during loading.

Python version compatibility

boa-restrictor uses Python’s built-in ast.parse() to analyze your source code. This means the Python version running boa-restrictor must support all syntax used in the files being linted.

For example, Python 3.14 introduced unparenthesized multiple exception types (except TypeError, ValueError:). If your code uses this syntax but boa-restrictor runs on Python 3.13 or earlier, parsing will fail with a SyntaxError.

Solution: Run boa-restrictor with a Python version that matches (or exceeds) the version your code targets.

In pre-commit, you can pin the Python version with language_version:

- repo: https://github.com/ambient-innovation/boa-restrictor
  rev: v{{ version }}
  hooks:
    - id: boa-restrictor
      language_version: python3.14

noqa & ruff support

As any other linter, you can disable certain rules on a per-line basis with #noqa.

def function_with_args(arg1, arg2):  # noqa: PBR001
    ...

If you are using ruff, you need to tell it about our linting rules. Otherwise, ruff will remove all # noqa statements from your codebase.

[tool.ruff.lint]
# Avoiding flagging (and removing) any codes starting with `PBR` from any
# `# noqa` directives, despite Ruff's lack of support for `boa-restrictor`.
external = ["PBR", "DBR"]

If you have registered project-specific custom rules, add their prefixes here as well — otherwise ruff will strip your # noqa: MYP001 comments:

[tool.ruff.lint]
external = ["PBR", "DBR", "MYP"]

https://docs.astral.sh/ruff/settings/#lint_extend-unsafe-fixes