Skip to main content

pytest-eo

codecov

pytest --eo is a strict quality gate in the spirit of Qulice, built from ready-made tools. The plugin runs the tools, reads their reports, and shows each tool as a separate test; its own rules cover only tests and suppressions.

Test Tool
eo::ruff ruff check, all rules (select = ["ALL"])
eo::ruff-format ruff format --check
eo::mypy mypy --strict
eo::flake8 flake8 with pyeo (Elegant Objects rules)
eo::suppressions # noqa and # ruff: ignore comments

The Elegant Objects rules enabled so far, most of them from pyeo and the rest from its sibling packages:

Code Rule Package
FIN100 every class is @final pyeo
PEO101 __init__ contains only assignments pyeo
PEO102 @classmethod contains only a cls() call pyeo
PEO200 every class is frozen (immutable) pyeo
PEO300 no class names ending in "-er" pyeo
PEO301 no public attributes pyeo
PEO400 no static methods pyeo
PEO500 no @property, a getter in disguise pyeo
PEO601 no methods starting with get pyeo
PEO602 no getter methods pyeo
PEO700 no introspection, casting, or reflection pyeo
NPM100 no private functions or methods flake8-no-private-methods
OVR100 every public method is an @override of an interface method flake8-override
FOC100 one public class per module flake8-one-class

mypy runs with explicit-override and warn_unreachable on top of --strict: an @override must override a real method, every overriding method must say so, and code that can never run is an error. Python 3.11 has no typing.override; import it from typing_extensions.

The package also brings a flake8 plugin of its own, with rules in the spirit of "Angry Tests" for the files pytest collects (test_*.py, *_test.py):

Code Rule
TST100 a test ends with its only assertion, or with pytest.raises
TST200 a test module keeps no constants, except pytest_plugins
TST300 an assertion carries a negative message, such as "x doesnt y"

More rules of its own cover every file, tests or not:

Code Rule
ENC100 a class encapsulates one to four attributes; a Protocol is exempt
BOD100 a function body holds no blank lines, except around a nested def or class
BOD200 a function body holds no comments, except suppressions

As in Qulice, which rejects plain JUnit assertions, a test asserts through a Hamcrest matcher, never through a plain assert; ruff's S101 stays on in tests too. PyHamcrest comes with the package:

from hamcrest import assert_that, equal_to


def test_checks_the_whole_directory_by_default(tmp_path: Path) -> None:
    assert_that(
        Project(EoTable(tmp_path / "pyproject.toml"), tmp_path).paths(),
        equal_to((".",)),
        "Project doesnt check the whole directory by default",
    )

A rule may be suppressed on one line only, with a reason: # noqa: PEO200 - pytest.Item is mutable by design, or # ruff: ignore[unused-import] - re-exported for plugins. eo::suppressions enforces it:

Code Rule
SUP001 a suppression gives a reason after -
SUP002 a suppression never names TID251 (banned-api), the ban on mocks
SUP003 nothing suppresses past one line: no # ruff: noqa, file-ignore or disable, no # flake8: noqa, no # mypy: comment, no # type: ignore above the code

ruff already rejects a bare # noqa (PGH004), a bare # type: ignore (PGH003) and an unused # noqa for its own codes (RUF100).

The configuration is bundled with the package (src/pytest_eo/config/) and cannot be overridden by a project. A project may only say what to check and what to skip:

[tool.eo]
paths = ["src", "tests"]    # default: src and tests, if they exist
exclude = ["generated/"]

Usage

pip install 'pytest-eo==0.5.1'
pytest --eo

Without --eo the plugin does nothing. Pin an exact version: every version of the rules is a version of the package, so pip installs new rules only when the version in the requirement changes.

Claude Code hooks

This repository runs the gate on every Claude Code session that works in it: after Claude edits a .py, .toml, .ini or .cfg file, and again before it finishes. When the gate is red, the findings go back to Claude and it has to fix them. If the gate is still red after one retry, Claude may stop, and you see a warning instead.

To get the same in another project:

  1. Install pytest-eo into the project's .venv.
  2. Copy .claude/hooks/eo-gate.sh into the same place in that project and make it executable.
  3. Copy the hooks block of .claude/settings.json into that project's .claude/settings.json, merging it with what is already there.
  4. Commit both files, so everyone who opens the project with Claude Code gets the gate, and run /hooks once in an open session to load them.

Development

python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
make check

make check runs the tests and pytest --eo on the plugin itself; Rultor merges on it. make coverage measures lines and branches, counting the pytest subprocesses of the end-to-end tests too, and writes coverage.xml, which GitHub Actions sends to Codecov on every push and pull request.

pyproject.toml on main always says version = "0.0.0"; a real version exists only in a tag. To release, comment in any issue, with the version in backticks, since Rultor reads nothing else:

@rultor release, tag is `0.3.0`

Rultor writes the version into pyproject.toml, runs make check, commits the result on a side branch and pushes only the tag. GitHub Actions builds that tag and uploads it to PyPI through Trusted Publishing, so no token is stored anywhere. Every change to the rules or the bundled configuration ships in a release: raise the minor number for a new or stricter rule and the patch number for a fix.

Metadata

Release files for pytest-eo 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pytest-eo 0.5.1
File Size Uploaded Provenance
pytest_eo-0.5.1.tar.gz 40.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-eo 0.5.1
File Interpreter ABI Platform
pytest_eo-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 82.6 kB

Release files / pytest_eo-0.5.1.tar.gz

Download URL pytest_eo-0.5.1.tar.gz
Size 40.0 kB
Tags Source
SHA-256 checksum
How to use checksums
a3701bb2628e682c898b8fbd40d77af2f5673c22d8434d08040913287acdff90
BLAKE2b-256 checksum
How to use checksums
d099c88015d9e7441515843681060f912ecb5e206b695988c5ab5d4cb6818f78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / pytest_eo-0.5.1-py3-none-any.whl

Download URL pytest_eo-0.5.1-py3-none-any.whl
Size 42.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
af20fd42c85a6df5c87243b12949a50ec3f97f7af1b2fdff02508b43d0eb8b84
BLAKE2b-256 checksum
How to use checksums
74557bb2a5b245e22fca76dd2f502e4f2ebe15addac2e3d383307cb3e7c72502
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for pytest-eo 0.5.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pytest_eo-0.5.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page