Skip to content

Lintkit

Official lintkit API documentation.

General

lintkit is a Python library allowing you to quickly create custom linters, while being flexible enough to be used in a complex settings.

Warning

Start with tutorials to get a feel of the framework.

Core modules

When creating custom linter(s) you will be (likely) interested in these core modules:

and the following functionalities from lintkit:

  • Value - define rule output in a reusable manner
  • run - run all (or subset of) rules on a given set of files
Tip

Roam around the docs to get a better feel of what's available.

lintkit.__version__ module-attribute

__version__ = version('lintkit')

Current lintkit version.

lintkit.Value

Value(value=None, start_line=None, start_column=None, end_line=None, end_column=None, comment=None, **kwargs)

Bases: ObjectProxy, Generic[T]

Value used by rules for verification.

Note

Instance of this type should always be returned from lintkit.rule.Rule.values

Tip

You should use objects of this class just like you would use the value directly. as it is a "perfect proxy". Its other functionalities are used internally (e.g. Pointer)

Can be essentially anything (e.g. dict from parsed JSON, string value from that dict or some other rule created value).

It is later used by the pipeline to verify whether this value complies with the Rule itself.

Tip

Use creation static methods ( lintkit.Value.from_python, lintkit.Value.from_toml, or lintkit.Value.from_json ) when returning values from rules inheriting from lintkit.loader.Python, lintkit.loader.TOML or lintkit.loader.JSON respectively.

Caution

YAML is already wrapped by lintkit.Value during when using lintkit.loader.YAML, no need to process them within values function.

Note

This class acts as a "perfect proxy" for end users by utilising wrapt ⧉ (which means wrapped value should be usable just like the original one).

Attributes:

Name Type Description
value Any

Value to check against the rules.

comment str | None

Source code comment related to the object, if any. Used internally

start_line Pointer

Line number (represented as a Pointer). Used internally

start_column Pointer

Column number (represented as a Pointer). Used internally

end_line Pointer

End line number (represented as a Pointer). Used internally

end_column Pointer

End column number (represented as a Pointer). Used internally

Source code in src/lintkit/_value.py
def __init__(  # noqa: PLR0913
    self,
    value: T = None,
    start_line: Pointer | None = None,
    start_column: Pointer | None = None,
    end_line: Pointer | None = None,
    end_column: Pointer | None = None,
    comment: str | None = None,
    **kwargs: typing.Any,
) -> None:
    super().__init__(value)

    if start_line is None:
        start_line = Pointer()
    if start_column is None:
        start_column = Pointer()
    if end_line is None:
        end_line = Pointer()
    if end_column is None:
        end_column = Pointer()

    self._self_start_line: Pointer = start_line
    self._self_start_column: Pointer = start_column
    self._self_end_line: Pointer = end_line
    self._self_end_column: Pointer = end_column
    self._self_comment: str | None = comment
    self._self_metadata: dict[str, typing.Any] = kwargs

lintkit.Value.from_python staticmethod

from_python(value, node)

Create a Value from Python's ast.AST node.

Parameters:

Name Type Description Default
value T

Some Python plain object.

required
node AST

Python's ast ⧉ Node which corresponds to the value.

required

Returns:

Type Description
Value[T]

Provided value with its respective Python node.

Source code in src/lintkit/_value.py
@staticmethod
def from_python(value: T, node: ast.AST) -> Value[T]:
    """Create a `Value` from Python's `ast.AST` node.

    Arguments:
        value:
            Some `Python` plain object.
        node:
            Python's [`ast`](https://docs.python.org/3/library/ast.html)
            `Node` which corresponds to the `value`.

    Returns:
        Provided value with its respective Python node.

    """
    return Value(
        value=value,
        start_line=_optional_get(node, "lineno"),
        start_column=_optional_get(node, "col_offset"),
        end_line=_optional_get(node, "end_lineno"),
        end_column=_optional_get(node, "end_col_offset"),
    )

lintkit.Value.from_json staticmethod

from_json(value)

Create a Value from JSON values.

Note

As JSON does not support comments, only value is necessary.

Warning

Due to no comments, all ignore lines are currently ignored and only file exclusions are available.

Parameters:

Name Type Description Default
value T

Some object, usually plain Python after parsing JSON via standard json ⧉ library.

required

Returns:

Type Description
Value[T]

JSON parsed data as a Value

Source code in src/lintkit/_value.py
@staticmethod
def from_json(value: T) -> Value[T]:
    """Create a `Value` from `JSON` values.

    Note:
        As `JSON` does not support comments,
        only `value` is necessary.

    Warning:
        Due to no comments, all ignore lines
        are currently ignored and __only file exclusions__
        are available.

    Arguments:
        value:
            Some object, usually plain `Python` after parsing
            `JSON` via
            [standard `json`](https://docs.python.org/3/library/json.html)
            library.

    Returns:
        `JSON` parsed data as a `Value`

    """
    return Value(value=value)

lintkit.Value.from_toml staticmethod

from_toml(item)

Create a Value from tomlkit Item.

Warning

Multiline ignores or skips are not supported for TOML due to the lack of line numbers.

Warning

Value will contain no line/column info (as it is unavailable in tomlkit ⧉), but propagates comment field to other elements of the system which allows it to be used for line ignoring.

Returns:

Type Description
Value[Any]

tomlkit.Item represented as Value.

Source code in src/lintkit/_value.py
@staticmethod
def from_toml(item: typing.Any) -> Value[typing.Any]:
    """Create a `Value` from `tomlkit` `Item`.

    Warning:
        Multiline `ignore`s or skips are not supported
        for `TOML` __due to the lack of line numbers__.

    Warning:
        `Value` will contain no line/column info
        (as it is unavailable in
        [`tomlkit`](https://tomlkit.readthedocs.io)), but
        propagates `comment` field to other elements of the
        system which allows it to be used for line ignoring.

    Returns:
        `tomlkit.Item` represented as `Value`.

    """
    return Value(
        # Principially items may not have an `unwrap` method, e.g.
        # https://tomlkit.readthedocs.io/en/latest/api/#tomlkit.items.Key
        # though it is available for most of the items,
        value=item.unwrap() if hasattr(item, "unwrap") else item,
        comment=item.trivia.comment
        if hasattr(item, "trivia")
        else None,
    )

lintkit.run

run(*files, include_codes=None, exclude_codes=None, end_mode='all', output=False)

Run all the rules on a given file.

Caution

This function has two modes; one returns bool indicating whether any rule raised any error (the default), the second one returns all rules and their error codes via an iterator.

Tip

Use output=False if you create a custom linter and only want to return the appropriate exit code (most common usage)

An example of minimal linter:

Example
import sys

import lintkit

# Mini linter over two files
# Assuming appropriate rules were already defined


def linter(*files: str):
    sys.exit(lintkit.run(*files))


linter("a.py", "~/user/goo.py")

An example of iteration:

Example
import lintkit

for failed, rule in lintkit.run("file.yml", "another.yml", output=True):
    print(f"Rule {rule} returned with an exit code {failed}")
Tip

output=True (iteration mode) allows to gather general statistics from each rule and adjust the output to your liking.

Warning

exclude_codes takes precedence over include_codes!

Parameters:

Name Type Description Default
files Path | str

Files to lint.

()
include_codes Iterable[int] | None

A set of rule codes to include. If None, all rules are included.

None
exclude_codes Iterable[int] | None

A set of rule codes to ignore. If None, no rules are ignored. Warning: exclude_codes takes precedence over include_codes.

None
end_mode Literal['first', 'all']

Whether to stop after the first error or run all rules. By default runs all rules.

'all'
output bool

If True, returns an iterator over all rules and their outputs. If False, returns whether any rule raised an error.

False

Returns:

Type Description
Iterator[tuple[bool, Rule]] | bool

An iterator over all rules and their outputs OR a boolean indicating whether any rule raised an error.

Source code in src/lintkit/_run.py
def run(
    *files: pathlib.Path | str,
    include_codes: Iterable[int] | None = None,
    exclude_codes: Iterable[int] | None = None,
    end_mode: typing.Literal["first", "all"] = "all",
    output: bool = False,
) -> Iterator[tuple[bool, r.Rule]] | bool:
    """Run all the rules on a given file.

    Caution:
        This function has two modes; one returns `bool`
        indicating whether __any rule raised any error__
        (the default), the second one returns
        __all rules and their error codes__ via an `iterator`.

    Tip:
        Use `output=False` if you create a custom linter
        and __only want to return the appropriate exit code__
        (most common usage)

    An example of minimal linter:

    Example:
        ```python
        import sys

        import lintkit

        # Mini linter over two files
        # Assuming appropriate rules were already defined


        def linter(*files: str):
            sys.exit(lintkit.run(*files))


        linter("a.py", "~/user/goo.py")
        ```

    An example of iteration:

    Example:
        ```python
        import lintkit

        for failed, rule in lintkit.run("file.yml", "another.yml", output=True):
            print(f"Rule {rule} returned with an exit code {failed}")
        ```

    Tip:
        `output=True` (iteration mode) allows to gather general
        statistics from each rule and adjust the output to your
        liking.

    Warning:
        `exclude_codes` takes precedence over `include_codes`!

    Args:
        files:
            Files to lint.
        include_codes:
            A set of rule codes to include. If `None`, all rules are included.
        exclude_codes:
            A set of rule codes to ignore. If `None`, no rules are ignored.
            Warning: `exclude_codes` takes precedence over `include_codes`.
        end_mode:
            Whether to stop after the first error or run all rules.
            By default runs all rules.
        output:
            If `True`, returns an iterator over all rules and their outputs.
            If `False`, returns whether any rule raised an error.

    Returns:
        An iterator over all rules and their outputs OR a boolean indicating
            whether any rule raised an error.
    """
    generator_or_callable = _run(
        *files,
        include_codes=include_codes,
        exclude_codes=exclude_codes,
        end_mode=end_mode,
    )
    if output:
        return generator_or_callable
    # Exhaust iterator and return whether any rule raised an error
    return any(result[0] for result in generator_or_callable)