Skip to main content

assert-pytest-class-holds-state

Assert that every pytest class holds a fixture, an attribute, a helper or a base.

Why Must a Class Hold Something?

A pytest class earns its place three ways. It holds a fixture, whose scope is then the class. It holds an attribute or a helper that its methods reach through self. Or it derives from a shared suite, collecting one set of tests for many subjects.

A class that does none of those is a namespace. It costs an unused self on every signature, a level of indentation on every body, a prefix on every node ID, and it suggests a setup that is not there. The methods belong at module level, where pytest collects them just as readily.

no-self-use is the mechanical signal and the wrong instrument. It reports on a method that happens not to touch self, so it flags an autouse fixture sitting in the class that is its scope, and it flags a kept class's one stateless method. This tool asks what the class is for instead, so a class that holds anything at all is silent rather than waived.

Installation

pip install assert-pytest-class-holds-state

Usage

Command Line

# Scan specific files
assert-pytest-class-holds-state test_example.py

# Scan directories recursively
assert-pytest-class-holds-state test/

# Use glob patterns
assert-pytest-class-holds-state "test/**/test_*.py"

# Exclude patterns
assert-pytest-class-holds-state test/ --exclude "**/conftest.py"

# Verbose output
assert-pytest-class-holds-state test/ --verbose

# Fail fast (exit on first finding)
assert-pytest-class-holds-state test/ --fail-fast

# Warn only (always exit 0)
assert-pytest-class-holds-state test/ --warn-only

GitHub Actions

- uses: 10U-Labs/assert-pytest-class-holds-state@latest
  with:
    files: "test/"
    verbose: "true"

As a Python Module

python -m assert_pytest_class_holds_state test/

Output Format

Default output shows one finding per line:

path/to/test_file.py:10:TestExample: the test methods belong at module level

Format: file_path:line_number:class_name, followed by the reason. The line number is the class statement.

Exit Codes

  • 0: No findings (or --warn-only specified)
  • 1: Findings detected
  • 2: Error (missing files, syntax errors, etc.)

What Counts as Holding Something?

A class whose name begins with Test is refused only when all four of these are true:

  • it has no base and no class keyword
  • it carries no decorator
  • its body holds no assignment
  • every function in its body is a test — a name beginning with test that no pytest.fixture decorator files as a fixture

Anything else passes. An inheritance stub passes on its base, even with an empty body. A fixture holder passes on the fixture, autouse or not. A class holding a @staticmethod helper, a plain helper, or a setup_method passes on the non-test function. A class carrying @pytest.mark.integration passes on the decorator, and one carrying region = "us-east-2" passes on the assignment.

A decorator on a method is not a decorator on the class, so @pytest.mark.parametrize on every test does not save a namespace.

What Is Never Examined

A class defined inside a function is a suite the function produces, not a namespace someone wrote around loose methods. Its methods close over the function's arguments and cannot move to module level, and pytest never collects it until another module inherits from it. The tool does not descend into function bodies at all.

Only files pytest would collect are scanned: test_*.py and *_test.py.

Options

Option Description
--exclude PATTERNS Glob patterns to exclude (comma-separated)
--quiet Suppress all output (exit code only)
--count Output only the number of findings
--verbose Show detailed processing information
--fail-fast Exit after first finding
--warn-only Always exit 0, even with findings

License

Apache 2.0 - See LICENSE.txt

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file assert_pytest_class_holds_state-20260907154656.tar.gz.

File metadata

File hashes

Hashes for assert_pytest_class_holds_state-20260907154656.tar.gz
Algorithm Hash digest
SHA256 ab789107109d775e992e5e60da1200fbfc8c3c8a24e8ba3013111562bd60d10c
MD5 c35db5d6840ec87b6955fbf3059f5bda
BLAKE2b-256 266226cf73fdd573a3c88a70b0fc933f3680a5ee2b8068224ddf1e5f7bf46857

See more details on using hashes here.

Provenance

The following attestation bundles were made for assert_pytest_class_holds_state-20260907154656.tar.gz:

Publisher: release.yml on 10U-Labs/assert-pytest-class-holds-state

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file assert_pytest_class_holds_state-20260907154656-py3-none-any.whl.

File metadata

File hashes

Hashes for assert_pytest_class_holds_state-20260907154656-py3-none-any.whl
Algorithm Hash digest
SHA256 9947a24be4aac5bdf038898957fa146f5bd97ca07ec464f057b1fa4c2f2c4629
MD5 8f3149b01e9ca41ac711b06e9fbd95d2
BLAKE2b-256 33ad5549e6407411af8229944a64b5f9d4da211e87504f8092b72cc2f4688f7c

See more details on using hashes here.

Provenance

The following attestation bundles were made for assert_pytest_class_holds_state-20260907154656-py3-none-any.whl:

Publisher: release.yml on 10U-Labs/assert-pytest-class-holds-state

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

20260907154656 This release

2 files

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