Skip to content

🔧 build(lint): adopt simplint 0.8.0 - #361

Merged
gaborbernat merged 25 commits into
tox-dev:mainfrom
gaborbernat:simplint
Nov 8, 2026
Merged

gaborbernat merged 25 commits into
tox-dev:mainfrom
gaborbernat:simplint

Conversation

@gaborbernat

Copy link
Copy Markdown
Member

simplint now checks every change in pre-commit and on pre-commit.ci, so the conventions Ruff cannot check are enforced before review: reST docstrings on the public API, Final on module constants and a fixed definition order. 🧹 The library's behavior does not change, and existing import paths and positional unpacking keep working. The code is changed so simplint 0.8.0 reports nothing, and the type-level changes a caller can notice are listed below.

Hook and configuration

The hook is pinned to simplint-pre-commit v0.8.0. Each hook also gets an explicit priority, the ordering key of prek: hooks with a lower number run first, hooks with the same number run together, and the numbers are chosen so that no two of them write the same file. Ruff lint runs before simplint and Ruff format after it, so simplint sees code Ruff has already fixed, and formatting has the last word. pre-commit ignores priority and runs hooks in file order.

Ruff's undocumented-* rules are off because they ask for a docstring on every name that looks public. simplint's undocumented-public-api (SMP903) asks only for the public API, and [tool.simplint] sets docstring-style = "rst" so the checks read the reStructuredText (reST) :param: and :returns: fields the package already uses.

Public names and signatures

No importable name is renamed or removed. The package __init__ now holds only imports and __all__, and it adds BuiltMetadata and FrontendArgs to its exports. pyproject_api.__main__ gained an __all__ listing main and main_parser, so a star import from it no longer brings in the module's imports.

Four signatures change. Frontend.create_args_from_folder and Frontend.metadata_from_built return the named tuples described below. SubprocessFrontend.send_cmd annotates its result as tuple[JsonValue, str, str] instead of tuple[Any, str, str], and BackendFailed.__reduce__ annotates its pickled state with JsonValue instead of Any. Runtime values are unchanged, but a caller that assigned a send_cmd result to a non-JSON type now needs a cast.

Result types

Two methods that returned bare tuples now return NamedTuple instances, which are tuples with named fields, so positional unpacking keeps working. create_args_from_folder returns FrontendArgs with the root, backend paths, backend module, backend object, requirements and reuse flag. metadata_from_built returns BuiltMetadata with the metadata directory, out and err.

Type annotations

Values that come from the backend's JSON use JsonValue, a recursive alias for JSON types, in place of Any, and parsed TOML values use a precise union. The private _send accepts only paths and config settings as keyword arguments, and its annotation says so.

Suppressions

Two findings keep their code and a simplint: ignore comment that gives the reason. main_parser stays in __all__ because the CLI parser is public API, and send_cmd keeps **kwargs: Any because it is the generic transport for any backend command, so its keyword arguments are not limited to the frontend's own types.

Module structure

Module constants that are never reassigned are marked Final, which type checkers enforce as no reassignment. The backend's run function is split into named helpers, one per step, and the API docs now describe CmdStatus, the fields of the result types and what metadata_from_built returns.

Ruff's undocumented-* rules conflict with simplint's function-docstring, which
removes the docstrings of internal functions. undocumented-public-api (SMP903)
covers the public API instead, in reST. The docstring style is set to reST to
match the package's docstrings.
Applied with simplint check --fix --unsafe-fixes, then prek run to let the
formatters settle.
simplint requires every suppression to say why it is there. Only the reasons
are added; the codes and the code they cover are unchanged.
The package __init__ holds no code: __version__ is re-exported from the version
module. autodata took the description of __version__ from the removed #:
comment, so the docs use a data directive that carries it.
CmdStatus is exported, so simplint's undocumented-public-api asks for a
docstring, and its abstract out_err uses the reST returns field.
run exceeded simplint's complexity limit of 20. The request parsing, command
execution and response writing are separate steps, so each becomes a helper
that run calls in order.
_send returns the value with its output and error text. A NamedTuple names
those fields without changing the unpacking that callers already do.
Sphinx and the frontend read these names by value, so the annotation only
records that they are constants for simplint's missing-final rule.
The helper receives either a description or the str type, so the union replaces
Any.
simplint wants module-level names of a module without __all__ to be private and
constants to be Final. The backend is loaded by path, so the names inside it are
only read by its own hooks.
requires-python is 3.11 or newer, so the older expected requirement spelling
never ran.
A patch without a spec accepts any call, so a wrong signature in the code under
test passes.
simplint's split-composite-assert asks for one comparison per exception, so a
failure shows all fields together.
simplint's parametrize-without-id asks for every case to be named, so failures
list the case by name.
Each conditional in a test body moves into the parameters: the case states the
expected path or cases, and the body asserts without branching.
Single-letter names are only allowed in comprehensions, and the input is built
with an f-string.
The method is public, and its returns and raises fields had no text.
simplint's rst rules want every field of a public result type documented, and an
empty :param or :ivar is flagged. The descriptions move from the #: comments into
the docstrings, which Sphinx renders as the field list.
Values the backend returns arrive as JSON, so a recursive alias replaces Any where the
protocol hands them over. The alias is exported from _frontend like ConfigSettings. The
remaining Any annotations are kwargs, the TOML table and the pickled state, which are not
JSON-only.
The autofix renamed main_parser to _main_parser, so pyproject_api.__main__.main_parser
stopped being importable. The name is exported again. simplint's unused-export still
reports it, because no module in the package imports it.
create_args_from_folder and metadata_from_built returned bare tuples whose
positions had to be remembered. Both now return public NamedTuples, so the
fields are named while positional unpacking and slicing keep working. The
test that read metadata_from_built by index uses the metadata field.
tomllib yields a fixed set of types, so the pyproject.toml reader and _str_list take a
recursive alias instead of dict[str, Any]. The alias stays private, since no public
signature uses it.
main_parser is public API of the CLI module, so it stays in __all__ and
the suppression says so. send_cmd is the generic transport for any backend
command (the tests call echo and _exit), so its kwargs are not limited to the
Frontend's own arguments.
Keeping only re-exports in __init__ dropped the version import, so
pyproject_api.version stopped being importable. It is re-exported again
next to __version__.
@gaborbernat gaborbernat added enhancement New feature or request skip news No user-facing changelog entry is needed labels Nov 8, 2026
@gaborbernat
gaborbernat merged commit c27f134 into tox-dev:main Nov 8, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request skip news No user-facing changelog entry is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant