Skip to content

API

Pytest fixture plugin for pyrig-managed projects.

Supplies a shared pool of pytest fixtures that pyrig's dependency-discovery mechanism registers automatically in every dependent project's test suite, along with the tooling to generate and extend that fixture set.

rig

Mirrored namespace through which this plugin extends pyrig's scaffolding.

Mirrors the pyrig.rig package layout so pyrig's cross-package plugin discovery finds this package's fixtures, configuration, and CLI customizations automatically.

cli

CLI commands that pyrig-fixtures adds to the mk scaffolding command group.

fixture

fixture(
    name: Annotated[
        str, Argument(help="Name of the fixture to create.")
    ],
) -> None

Scaffold a new pytest fixture stub in the project's shared fixtures module.

Appends an @pytest.fixture-decorated function stub to the shared fixtures module. The file is created if it does not already exist. If import pytest is not already present in the module, it is inserted automatically.

Parameters:

Name Type Description Default
name Annotated[str, Argument(help='Name of the fixture to create.')]

Name of the fixture to create. Accepts kebab-case or snake_case; kebab-case is normalized to snake_case to form a valid identifier (e.g. my-new-fixture becomes my_new_fixture).

required
Example
$ uv run pyrig mk fixture my-fixture
Source code in src/pyrig_fixtures/rig/cli/__init__.py
@mk.command()
def fixture(
    name: Annotated[str, typer.Argument(help="Name of the fixture to create.")],
) -> None:
    """Scaffold a new pytest fixture stub in the project's shared fixtures module.

    Appends an `@pytest.fixture`-decorated function stub to the shared fixtures
    module. The file is created if it does not already exist. If `import pytest`
    is not already present in the module, it is inserted automatically.

    Args:
        name: Name of the fixture to create. Accepts kebab-case or snake_case;
            kebab-case is normalized to snake_case to form a valid identifier
            (e.g. `my-new-fixture` becomes `my_new_fixture`).

    Example:
        ```
        $ uv run pyrig mk fixture my-fixture
        ```
    """
    from pyrig_fixtures.rig.cli.commands.make.fixture import (  # noqa: PLC0415
        make_fixture,
    )

    make_fixture(name)

commands

Backend implementations for the pyrig-fixtures CLI commands.

Each module implements one command as a plain callable, decoupled from the CLI registration layer so it can be imported lazily when the command runs.

make

Backend implementations for the pyrig-fixtures scaffolding subcommands.

fixture

Scaffolding for shared pytest fixtures in a pyrig-managed project.

make_fixture
make_fixture(name: str) -> None

Scaffold a new pytest fixture in the project's shared fixtures module.

Ensures the shared fixtures module exists, then appends a new @pytest.fixture-decorated function with the given name. If import pytest is not already present in the module, it is added before the new fixture.

The name is normalized from kebab-case to snake_case so it forms a valid Python identifier (e.g. "my-new-fixture" becomes "my_new_fixture").

Parameters:

Name Type Description Default
name str

Name of the fixture in kebab-case or snake_case.

required
Source code in src/pyrig_fixtures/rig/cli/commands/make/fixture.py
def make_fixture(name: str) -> None:
    """Scaffold a new pytest fixture in the project's shared fixtures module.

    Ensures the shared fixtures module exists, then appends a new
    `@pytest.fixture`-decorated function with the given name. If
    `import pytest` is not already present in the module, it is added
    before the new fixture.

    The name is normalized from kebab-case to snake_case so it forms a
    valid Python identifier (e.g. `"my-new-fixture"` becomes
    `"my_new_fixture"`).

    Args:
        name: Name of the fixture in kebab-case or snake_case.
    """
    config_file = CopyModuleDocstringConfigFile.generate_subclass(fixtures)()
    config_file.validate()
    content = config_file.read_content()

    name = kebab_to_snake_case(name)
    pytest_import = f"import {pytest.__name__}"
    if pytest_import not in content.splitlines():
        content += f"""
{pytest_import}
"""

    content += f'''

@{pytest.__name__}.{pytest.fixture.__name__}
def {name}() -> None:
    """This is a test fixture."""
'''

    config_file.write_content(content)

configs

Declarative definitions of configuration files this package manages.

Mirrors pyrig's own config-file package path, so definitions placed here join pyrig's discovery scope and are validated alongside its built-in config files without any explicit registration.

conftest

Configuration for the generated tests/conftest.py file.

Manages a conftest file that registers pyrig_fixtures' own conftest module as a pytest plugin, giving the target project access to it without an explicit import in each test file.

ConftestConfigFile

Bases: CopyModuleDocstringConfigFile

The tests/conftest.py config file, generated for the target project.

The generated file has two parts: the module-level docstring of pyrig_fixtures.rig.tests.conftest as its own module docstring, followed by a pytest_plugins assignment that registers that module as a pytest plugin, giving the target project automatic access to it without needing an explicit import in each test file.

content
content() -> str

Return the generated conftest.py file's content.

Returns:

Type Description
str

The module docstring of pyrig_fixtures.rig.tests.conftest followed

str

by a pytest_plugins assignment that registers that module as a

str

pytest plugin.

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def content(self) -> str:
    """Return the generated `conftest.py` file's content.

    Returns:
        The module docstring of `pyrig_fixtures.rig.tests.conftest` followed
        by a `pytest_plugins` assignment that registers that module as a
        pytest plugin.
    """
    return f"{super().content()}\n{self.plugin_definition()}\n"
copy_module
copy_module() -> ModuleType

Return the pyrig_fixtures.rig.tests.conftest module.

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def copy_module(self) -> ModuleType:
    """Return the `pyrig_fixtures.rig.tests.conftest` module."""
    return conftest
is_correct
is_correct() -> bool

Return whether the conftest module is already registered as a pytest plugin.

Returns:

Type Description
bool

True if pyrig_fixtures.rig.tests.conftest's dotted name is

bool

listed in the pytest_plugins list of the file currently on disk.

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def is_correct(self) -> bool:
    """Return whether the conftest module is already registered as a pytest plugin.

    Returns:
        `True` if `pyrig_fixtures.rig.tests.conftest`'s dotted name is
        listed in the `pytest_plugins` list of the file currently on disk.
    """
    return conftest.__name__ in getattr(self.module(), "pytest_plugins", [])
package_root
package_root() -> Path

Return the tests package root rather than the source package root.

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def package_root(self) -> Path:
    """Return the tests package root rather than the source package root."""
    return ProjectTester.I.package_root()
parent_path
parent_path() -> Path

Return the tests package root as the generated file's parent directory.

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def parent_path(self) -> Path:
    """Return the tests package root as the generated file's parent directory."""
    return self.package_root()
plugin_definition
plugin_definition() -> str

Return the pytest_plugins assignment line for the generated file.

Returns:

Type Description
str

'pytest_plugins = ["pyrig_fixtures.rig.tests.conftest"]'.

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def plugin_definition(self) -> str:
    """Return the `pytest_plugins` assignment line for the generated file.

    Returns:
        `'pytest_plugins = ["pyrig_fixtures.rig.tests.conftest"]'`.
    """
    return f'pytest_plugins = ["{conftest.__name__}"]'
stem
stem() -> str

Return the filename stem for the generated file.

Returns:

Type Description
str

'conftest'

Source code in src/pyrig_fixtures/rig/configs/conftest.py
def stem(self) -> str:
    """Return the filename stem for the generated file.

    Returns:
        `'conftest'`
    """
    return "conftest"

tests

Pytest configuration root pooling fixtures across this package's dependents.

Fixtures defined in this package, and in the equivalent package of every installed package depending on it, are collected and made available to test suites without explicit imports.

conftest

Pytest configuration for automatic fixture discovery across dependent packages.

Registers every fixture module in this package's fixtures package, and in the equivalent fixtures package of every installed package that depends on this package, as a pytest plugin. This makes all discovered fixtures available in every test module without explicit imports.

fixtures

Shared pytest fixtures for pyrig and pyrig-based projects.

Each submodule groups fixtures for a single testing concern.

cli

Shared pytest fixtures for testing the project's CLI commands.

Provides helpers that check whether a CLI command is registered and reachable, and whether a command delegates to its expected implementation function.

command_calls_function
command_calls_function(
    mocker: MockerFixture,
) -> Callable[
    [FunctionType, FunctionType, Iterable[str]], bool
]

Return a callable that verifies a CLI command delegates to a function.

The returned callable registers cmd on a freshly built CLI app, patches function where it is defined, invokes cmd through the CLI with args, and reports whether the patch was called exactly once. Whether the invocation itself succeeds is not checked. Adds a second dummy command to prevent the only one command from being treated as the default command.

Parameters:

Name Type Description Default
mocker MockerFixture

pytest-mock fixture used to patch function.

required

Returns:

Type Description
Callable[[FunctionType, FunctionType, Iterable[str]], bool]

A callable (cmd, function, args) -> bool that returns True if

Callable[[FunctionType, FunctionType, Iterable[str]], bool]

function is called exactly once while cmd runs with args,

Callable[[FunctionType, FunctionType, Iterable[str]], bool]

False otherwise.

Source code in src/pyrig_fixtures/rig/tests/fixtures/cli.py
@pytest.fixture
def command_calls_function(
    mocker: MockerFixture,
) -> Callable[[FunctionType, FunctionType, Iterable[str]], bool]:
    """Return a callable that verifies a CLI command delegates to a function.

    The returned callable registers `cmd` on a freshly built CLI app, patches
    `function` where it is defined, invokes `cmd` through the CLI with
    `args`, and reports whether the patch was called exactly once. Whether
    the invocation itself succeeds is not checked.
    Adds a second dummy command to prevent the only one command from being treated
    as the default command.

    Args:
        mocker: pytest-mock fixture used to patch `function`.

    Returns:
        A callable `(cmd, function, args) -> bool` that returns `True` if
        `function` is called exactly once while `cmd` runs with `args`,
        `False` otherwise.
    """

    def check(
        cmd: FunctionType,
        function: FunctionType,
        args: Iterable[str],
    ) -> bool:
        """Run `cmd` with `args`; return whether `function` was called exactly once."""
        mock = mocker.patch(function.__module__ + "." + function.__name__)
        app = typer.Typer(name="some-app", no_args_is_help=True)
        app.command()(lambda: None)
        app.command()(cmd)
        CliRunner().invoke(app, [snake_to_kebab_case(cmd.__name__), *(args or [])])
        return mock.call_count == 1

    return check
command_works
command_works() -> Callable[[FunctionType], bool]

Return a callable that verifies a CLI command is registered and reachable.

The returned callable runs cmd as a subcommand of the project's CLI with --help and checks whether its kebab-case name appears in stdout.

Returns:

Type Description
Callable[[FunctionType], bool]

A callable (cmd) -> bool that returns True if cmd's kebab-case

Callable[[FunctionType], bool]

name appears in the --help output, False otherwise.

Raises:

Type Description
CalledProcessError

If invoking cmd with --help exits with a non-zero status, for example because cmd is not registered as a subcommand of the project's CLI.

Source code in src/pyrig_fixtures/rig/tests/fixtures/cli.py
@pytest.fixture
def command_works() -> Callable[[FunctionType], bool]:
    """Return a callable that verifies a CLI command is registered and reachable.

    The returned callable runs `cmd` as a subcommand of the project's CLI
    with `--help` and checks whether its kebab-case name appears in stdout.

    Returns:
        A callable `(cmd) -> bool` that returns `True` if `cmd`'s kebab-case
        name appears in the `--help` output, `False` otherwise.

    Raises:
        subprocess.CalledProcessError: If invoking `cmd` with `--help` exits
            with a non-zero status, for example because `cmd` is not
            registered as a subcommand of the project's CLI.
    """

    def check(cmd: FunctionType) -> bool:
        """Run `cmd` with `--help` and return whether its name appears in stdout."""
        args = Args(
            PackageManager.I.project_name(),
            snake_to_kebab_case(cmd.__name__),
            "--help",
        )
        completed_process = args.run()
        stdout = completed_process.stdout
        name = cmd.__name__.replace("_", "-")
        return name in stdout

    return check
configs

Shared pytest fixtures for testing ConfigFile subclasses in isolation.

Provides a factory that redirects a ConfigFile subclass's file operations to pytest's tmp_path so tests never touch real project files.

config_file_factory
config_file_factory[
    T: ConfigFile[dict[str, Any] | list[Any]]
](tmp_path: Path) -> Callable[[type[T]], type[T]]

Return a factory that wraps a ConfigFile subclass for isolated testing.

Each call to the factory dynamically creates a new wrapping subclass, so it may be invoked with a different ConfigFile subclass any number of times within the same test.

Parameters:

Name Type Description Default
tmp_path Path

Pytest's per-test temporary directory.

required

Returns:

Type Description
Callable[[type[T]], type[T]]

A callable (base_class) -> type[T] that wraps base_class in a

Callable[[type[T]], type[T]]

subclass whose file operations are redirected to tmp_path.

Source code in src/pyrig_fixtures/rig/tests/fixtures/configs.py
@pytest.fixture
def config_file_factory[T: ConfigFile[dict[str, Any] | list[Any]]](
    tmp_path: Path,
) -> Callable[[type[T]], type[T]]:
    """Return a factory that wraps a `ConfigFile` subclass for isolated testing.

    Each call to the factory dynamically creates a new wrapping subclass, so
    it may be invoked with a different `ConfigFile` subclass any number of
    times within the same test.

    Args:
        tmp_path: Pytest's per-test temporary directory.

    Returns:
        A callable `(base_class) -> type[T]` that wraps `base_class` in a
        subclass whose file operations are redirected to `tmp_path`.
    """

    def _make_test_config(
        base_class: type[T],
    ) -> type[T]:
        """Wrap `base_class` with `tmp_path`-redirected file operations.

        Args:
            base_class: The `ConfigFile` subclass to wrap.

        Returns:
            A subclass of `base_class` with all file paths redirected to
            `tmp_path`.
        """

        class TestConfigFile(base_class):  # ty: ignore[unsupported-base]
            """Subclass of `base_class` with every file operation under `tmp_path`."""

            def _dump(self, configs: dict[str, Any] | list[Any]) -> None:
                """Write the config from within `tmp_path`, isolating real files."""
                with chdir(tmp_path):
                    super()._dump(configs)

            def _load(self) -> dict[str, Any] | list[Any]:
                """Load the config from within `tmp_path`, isolating real files."""
                with chdir(tmp_path):
                    return super()._load()

            def create_file(self) -> None:
                """Create the file from within `tmp_path`, isolating real files."""
                with chdir(tmp_path):
                    super().create_file()

            def path(self) -> Path:
                """Return the config file path, rooted under `tmp_path`.

                Returns:
                    The parent implementation's path unchanged if it already
                    resolves under `tmp_path` (as when called while the
                    working directory is `tmp_path`); otherwise that path
                    joined onto `tmp_path`.
                """
                path = super().path()
                if not (path.is_relative_to(tmp_path) or Path.cwd() == tmp_path):
                    path = tmp_path / path
                return path

        return TestConfigFile  # ty:ignore[invalid-return-type]

    return _make_test_config
environment

Shared pytest fixtures for gating tests by platform and Python version.

Provides session-scoped predicates for the current OS and interpreter version, used to restrict environment-sensitive tests to a canonical CI environment while still running them locally.

on_latest_python_version
on_latest_python_version(
    on_python_version: Callable[[str], bool],
) -> bool

Return whether the running Python version matches the latest stable release.

Parameters:

Name Type Description Default
on_python_version Callable[[str], bool]

Callable that checks whether a given version string exactly matches the running Python version.

required

Returns:

Type Description
bool

True if the current Python micro version matches the latest stable

bool

release.

Source code in src/pyrig_fixtures/rig/tests/fixtures/environment.py
@pytest.fixture(scope="session")
def on_latest_python_version(on_python_version: Callable[[str], bool]) -> bool:
    """Return whether the running Python version matches the latest stable release.

    Args:
        on_python_version: Callable that checks whether a given version string
            exactly matches the running Python version.

    Returns:
        True if the current Python micro version matches the latest stable
        release.
    """
    latest_version = PyprojectConfigFile.I.latest_python_version("micro")
    return on_python_version(str(latest_version))
on_linux
on_linux(on_platform: Callable[[str], bool]) -> bool

Return whether the current system is Linux.

Parameters:

Name Type Description Default
on_platform Callable[[str], bool]

Callable that checks whether a given platform name exactly matches the current system platform.

required

Returns:

Type Description
bool

True if the system is Linux.

Source code in src/pyrig_fixtures/rig/tests/fixtures/environment.py
@pytest.fixture(scope="session")
def on_linux(on_platform: Callable[[str], bool]) -> bool:
    """Return whether the current system is Linux.

    Args:
        on_platform: Callable that checks whether a given platform name
            exactly matches the current system platform.

    Returns:
        True if the system is Linux.
    """
    return on_platform("Linux")
on_linux_and_latest_python_version
on_linux_and_latest_python_version(
    *, on_linux: bool, on_latest_python_version: bool
) -> bool

Return whether the current environment is Linux with the latest Python version.

Parameters:

Name Type Description Default
on_linux bool

Whether the current system is Linux.

required
on_latest_python_version bool

Whether the running Python version matches the latest stable release.

required

Returns:

Type Description
bool

True if both conditions are met.

Source code in src/pyrig_fixtures/rig/tests/fixtures/environment.py
@pytest.fixture(scope="session")
def on_linux_and_latest_python_version(
    *,
    on_linux: bool,
    on_latest_python_version: bool,
) -> bool:
    """Return whether the current environment is Linux with the latest Python version.

    Args:
        on_linux: Whether the current system is Linux.
        on_latest_python_version: Whether the running Python version matches
            the latest stable release.

    Returns:
        True if both conditions are met.
    """
    return on_linux and on_latest_python_version
on_linux_and_latest_python_version_or_not_in_ci
on_linux_and_latest_python_version_or_not_in_ci(
    *, on_linux_and_latest_python_version: bool
) -> bool

Return whether tests that require a canonical environment should run.

Parameters:

Name Type Description Default
on_linux_and_latest_python_version bool

Whether the environment is Linux with the latest Python version.

required

Returns:

Type Description
bool

True if the environment is Linux with the latest Python version, or

bool

if not currently running inside GitHub Actions.

Source code in src/pyrig_fixtures/rig/tests/fixtures/environment.py
@pytest.fixture(scope="session")
def on_linux_and_latest_python_version_or_not_in_ci(
    *,
    on_linux_and_latest_python_version: bool,
) -> bool:
    """Return whether tests that require a canonical environment should run.

    Args:
        on_linux_and_latest_python_version: Whether the environment is Linux
            with the latest Python version.

    Returns:
        True if the environment is Linux with the latest Python version, or
        if not currently running inside GitHub Actions.
    """
    return (
        on_linux_and_latest_python_version
    ) or not RemoteVersionController.I.running_in_ci()
on_platform
on_platform() -> Callable[[str], bool]

Check whether the current system platform exactly matches a given name.

Returns:

Type Description
Callable[[str], bool]

A callable (platform_name) -> bool that returns True only when

Callable[[str], bool]

platform_name exactly equals platform.system() (e.g., "Linux",

Callable[[str], bool]

"Windows", "Darwin").

Source code in src/pyrig_fixtures/rig/tests/fixtures/environment.py
@pytest.fixture(scope="session")
def on_platform() -> Callable[[str], bool]:
    """Check whether the current system platform exactly matches a given name.

    Returns:
        A callable `(platform_name) -> bool` that returns `True` only when
        `platform_name` exactly equals `platform.system()` (e.g., `"Linux"`,
        `"Windows"`, `"Darwin"`).
    """

    def check(platform_name: str) -> bool:
        return platform.system() == platform_name

    return check
on_python_version
on_python_version() -> Callable[[str], bool]

Check whether the current Python version exactly matches a given version string.

Returns:

Type Description
Callable[[str], bool]

A callable (version) -> bool that returns True only when version

Callable[[str], bool]

exactly equals platform.python_version() (e.g., "3.13.2").

Source code in src/pyrig_fixtures/rig/tests/fixtures/environment.py
@pytest.fixture(scope="session")
def on_python_version() -> Callable[[str], bool]:
    """Check whether the current Python version exactly matches a given version string.

    Returns:
        A callable `(version) -> bool` that returns `True` only when `version`
        exactly equals `platform.python_version()` (e.g., `"3.13.2"`).
    """

    def check(version: str) -> bool:
        return platform.python_version() == version

    return check
fixtures

Catch-all module for shared pytest fixtures with no more specific home.

Fixtures scaffolded without a dedicated topic are appended here rather than sorted into one of the other themed fixture modules.

claim_file
claim_file(
    tmp_path_factory: TempPathFactory, stem: str
) -> bool

Try to exclusively claim a <stem>.claimed marker file.

Every pytest-xdist worker's own base temp dir is <root>/<worker_id>, so <root> is the same shared path in every worker regardless of how many exist or how tests get distributed among them. Creating the marker there via Path.touch(exist_ok=False) is atomic on every platform pytest supports, so exactly one caller across all workers ever wins the race.

Parameters:

Name Type Description Default
tmp_path_factory TempPathFactory

Used to locate the shared <root> dir.

required
stem str

Distinguishes this claim from any other's marker file.

required

Returns:

Type Description
bool

True for the one caller that wins the race, False for every

bool

other caller.

Source code in src/pyrig_fixtures/rig/tests/fixtures/fixtures.py
def claim_file(tmp_path_factory: pytest.TempPathFactory, stem: str) -> bool:
    """Try to exclusively claim a `<stem>.claimed` marker file.

    Every pytest-xdist worker's own base temp dir is `<root>/<worker_id>`,
    so `<root>` is the same shared path in every worker regardless of how
    many exist or how tests get distributed among them. Creating the marker
    there via `Path.touch(exist_ok=False)` is atomic on every platform
    pytest supports, so exactly one caller across all workers ever wins the
    race.

    Args:
        tmp_path_factory: Used to locate the shared `<root>` dir.
        stem: Distinguishes this claim from any other's marker file.

    Returns:
        `True` for the one caller that wins the race, `False` for every
        other caller.
    """
    marker = tmp_path_factory.getbasetemp().parent / f"{stem}.claimed"
    try:
        marker.touch(exist_ok=False)
    except FileExistsError:
        return False
    return True
init_pyrig_project
init_pyrig_project(
    request: FixtureRequest,
    tmp_path_factory: TempPathFactory,
) -> tuple[bool, str]

Verify that this project can be built and adopted by a fresh consumer project.

Delegates the actual build, install, and verification flow to run_init_pyrig_project, once per test session, in an isolated temporary directory. Skipped when --skip-init-pyrig-project is passed.

Under pytest-xdist, every worker process would otherwise repeat this expensive flow independently. claim_file elects a single winner, across however many worker processes exist, to run it for real; outside of pytest-xdist there's only one process, so it always wins trivially.

Parameters:

Name Type Description Default
request FixtureRequest

Used to read the --skip-init-pyrig-project option.

required
tmp_path_factory TempPathFactory

Used to locate the shared dir to race for the claim in.

required

Returns:

Type Description
bool

A tuple of (success, message), where success is always True

str

since a failed check raises instead of returning. message

tuple[bool, str]

explains why the run was skipped, or is empty when it ran and

tuple[bool, str]

succeeded.

Raises:

Type Description
Exception

If the check does not succeed.

Note

Being autouse and session-scoped, a failed check reports a setup error for every test in the session, not just one. Under pytest-xdist, that only holds for the winning worker's own tests, since it's the only one that actually runs the check — the other workers' tests are unaffected either way, but the run as a whole still fails since the winner's tests do.

Source code in src/pyrig_fixtures/rig/tests/fixtures/fixtures.py
@pytest.fixture(scope="session", autouse=True)
def init_pyrig_project(
    request: pytest.FixtureRequest,
    tmp_path_factory: pytest.TempPathFactory,
) -> tuple[bool, str]:
    """Verify that this project can be built and adopted by a fresh consumer project.

    Delegates the actual build, install, and verification flow to
    [run_init_pyrig_project][], once per test session, in an isolated
    temporary directory. Skipped when `--skip-init-pyrig-project` is
    passed.

    Under pytest-xdist, every worker process would otherwise repeat this
    expensive flow independently. [claim_file][] elects a single winner,
    across however many worker processes exist, to run it for real; outside
    of pytest-xdist there's only one process, so it always wins trivially.

    Args:
        request: Used to read the `--skip-init-pyrig-project` option.
        tmp_path_factory: Used to locate the shared dir to race for the
            claim in.

    Returns:
        A tuple of `(success, message)`, where `success` is always `True`
        since a failed check raises instead of returning. `message`
        explains why the run was skipped, or is empty when it ran and
        succeeded.

    Raises:
        pytest.fail.Exception: If the check does not succeed.

    Note:
        Being autouse and session-scoped, a failed check reports a setup
        error for every test in the session, not just one. Under
        pytest-xdist, that only holds for the winning worker's own tests,
        since it's the only one that actually runs the check — the other
        workers' tests are unaffected either way, but the run as a whole
        still fails since the winner's tests do.
    """
    if request.config.getoption(SKIP_INIT_PYRIG_PROJECT_FLAG):
        return True, f"Skipped via {SKIP_INIT_PYRIG_PROJECT_FLAG}"

    if not claim_file(tmp_path_factory, init_pyrig_project.__name__):
        return True, "Skipped: another worker already claimed this check"

    with (
        TemporaryDirectory() as tmp_dir,
        pytest.MonkeyPatch.context() as monkeypatch,
    ):
        success, msg = run_init_pyrig_project(Path(tmp_dir), monkeypatch)

    if not success:
        pytest.fail(f"Failed to initialize pyrig project: {msg}")
    return success, msg
pytest_addoption
pytest_addoption(parser: Parser) -> None

Register the --skip-init-pyrig-project command-line flag.

The flag lets a run opt out of the expensive init_pyrig_project fixture that every project depending on pyrig-fixtures otherwise runs once per test session, e.g. for a fast local feedback loop.

A true single-dash short form (e.g. -sipp) isn't possible: pytest reserves lowercase single-dash options for its own core and rejects them from plugins, so --sipp is offered as the short alias instead.

Parameters:

Name Type Description Default
parser Parser

Pytest's parser to register the command-line option on.

required
Source code in src/pyrig_fixtures/rig/tests/fixtures/fixtures.py
def pytest_addoption(parser: pytest.Parser) -> None:
    """Register the `--skip-init-pyrig-project` command-line flag.

    The flag lets a run opt out of the expensive [init_pyrig_project][]
    fixture that every project depending on pyrig-fixtures otherwise runs
    once per test session, e.g. for a fast local feedback loop.

    A true single-dash short form (e.g. `-sipp`) isn't possible: pytest
    reserves lowercase single-dash options for its own core and rejects
    them from plugins, so `--sipp` is offered as the short alias instead.

    Args:
        parser: Pytest's parser to register the command-line option on.
    """
    parser.addoption(
        SKIP_INIT_PYRIG_PROJECT_FLAG,
        SKIP_INIT_PYRIG_PROJECT_SHORT_FLAG,
        action="store_true",
        default=False,
        help="Skip the slow `init_pyrig_project` end-to-end fixture.",
    )
run_init_pyrig_project
run_init_pyrig_project(
    tmp_path: Path, monkeypatch: MonkeyPatch
) -> tuple[bool, str]

Build this project and verify a fresh consumer project can adopt it.

Packages the current project as a wheel and scaffolds a brand-new project under tmp_path, adding the wheel plus every other currently-installed plugin that depends on pyrig-runtime as dev dependencies. Runs pyrig init in the new project, then checks that its own test suite fails as expected, that its CLI and version command produce the expected output, that the expected package directory was generated, and that every ConfigFile subclass produced its file. Finally runs pyrigger --help as a last sanity check.

Kept as a standalone function, separate from init_pyrig_project, so tests can call it directly with mocked subprocess results to exercise each failure branch independently.

Parameters:

Name Type Description Default
tmp_path Path

Scratch directory to scaffold the wheel-build copy of this project and the new consumer project under; must not already contain directories with their names.

required
monkeypatch MonkeyPatch

Used to remove the current virtual environment from the environment for the duration of the run, so subprocess commands create and use their own fresh environment instead of reusing the caller's.

required

Returns:

Type Description
bool

A tuple of (success, message). success is True only if

str

every check above passes; message describes the first check

tuple[bool, str]

that failed, or is empty when success is True.

Raises:

Type Description
CalledProcessError

If any underlying command fails, other than the new project's own test suite exiting as expected.

Source code in src/pyrig_fixtures/rig/tests/fixtures/fixtures.py
def run_init_pyrig_project(  # noqa: PLR0915
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> tuple[bool, str]:
    """Build this project and verify a fresh consumer project can adopt it.

    Packages the current project as a wheel and scaffolds a brand-new
    project under `tmp_path`, adding the wheel plus every other
    currently-installed plugin that depends on pyrig-runtime as dev
    dependencies. Runs `pyrig init` in the new project, then checks that
    its own test suite fails as expected, that its CLI and `version`
    command produce the expected output, that the expected package
    directory was generated, and that every `ConfigFile` subclass
    produced its file. Finally runs `pyrigger --help` as a last sanity
    check.

    Kept as a standalone function, separate from [init_pyrig_project][],
    so tests can call it directly with mocked subprocess results to
    exercise each failure branch independently.

    Args:
        tmp_path: Scratch directory to scaffold the wheel-build copy of
            this project and the new consumer project under; must not
            already contain directories with their names.
        monkeypatch: Used to remove the current virtual environment from
            the environment for the duration of the run, so subprocess
            commands create and use their own fresh environment instead
            of reusing the caller's.

    Returns:
        A tuple of `(success, message)`. `success` is `True` only if
        every check above passes; `message` describes the first check
        that failed, or is empty when `success` is `True`.

    Raises:
        subprocess.CalledProcessError: If any underlying command fails,
            other than the new project's own test suite exiting as
            expected.
    """
    src_project_name = "src-project"

    pyrig_project_tmp_path = tmp_path / PackageManager.I.project_name()
    shutil.copytree(
        Path(),
        pyrig_project_tmp_path,
    )
    with chdir(pyrig_project_tmp_path):
        # remove a potential dist dir from a previous build
        dist_dir = pyrig_project_tmp_path / "dist"
        with suppress(FileNotFoundError):
            shutil.rmtree(dist_dir)
        # build the package
        args = PackageManager.I.build_args()
        args.run()

    dist_files = list((pyrig_project_tmp_path / "dist").glob("*.whl"))
    wheel_path = dist_files[-1].resolve().as_posix()

    src_project_dir = tmp_path / src_project_name
    src_project_dir.mkdir()

    # Get the current Python version in major.minor format
    python_version = str(PyprojectConfigFile.I.first_supported_python_version())

    with chdir(src_project_dir):
        # Strip VIRTUAL_ENV and the outer venv's bin dir from PATH so
        # subprocesses create a new virtual environment instead of reusing
        # the current one, and commands like `pyrig` from the dev environment
        # aren't found when testing that they're absent.
        venv = os.environ.get("VIRTUAL_ENV")
        monkeypatch.delenv("VIRTUAL_ENV", raising=False)
        if venv:
            path_entries = os.environ.get("PATH", "").split(os.pathsep)
            monkeypatch.setenv(
                "PATH",
                os.pathsep.join(
                    p for p in path_entries if not p.lower().startswith(venv.lower())
                ),
            )

        # Initialize git repo in the test project directory
        VersionController.I.init_args().run()
        VersionController.I.config_args(
            "--local",
            "user.email",
            "test@example.com",
        ).run()
        VersionController.I.config_args("--local", "user.name", "Test User").run()

        args = PackageManager.I.args("init", "--python", python_version)
        args.run()

        # Add pyrig wheel as a dev dependency and plugins
        plugins = tuple(
            snake_to_kebab_case(dep.__name__)
            for dep in dependent_packages(pyrig_runtime)
            # wheel path is the package name, so don't add it as a dependency twice
            if dep.__name__ != PackageManager.I.package_name()
        )

        # add plugins
        PackageManager.I.add_group_dev_args(wheel_path, *plugins).run()

        # uv add converts absolute paths to relative paths, which breaks when
        # the project is copied to a different location. We need to replace the
        # relative path with an absolute path.
        pyproject_toml = src_project_dir / "pyproject.toml"
        pyproject_content = pyproject_toml.read_text(encoding="utf-8")
        # Replace relative path with absolute path in tool.uv.sources
        # e.g., { path = "../pyrig/dist/..." }
        # -> { path = "/tmp/.../pyrig/dist/..." }
        pyproject_content = re.sub(
            r'pyrig = \{ path = "[^"]*" \}',
            f'pyrig = {{ path = "{wheel_path}" }}',
            pyproject_content,
        )
        pyproject_toml.write_text(pyproject_content, encoding="utf-8")

        # Sync to update the lock file with the new absolute path
        args = PackageManager.I.install_dependencies_args()
        args.run()

        # Verify pyrig was installed correctly
        # also checks if the init process works
        PackageManager.I.run_args(*Pyrigger.I.cmd_args(cmd=init)).run()

        # with cov
        args = PackageManager.I.run_args(*ProjectTester.I.test_args())
        res = args.run(check=False)
        if res.returncode != pytest.ExitCode.TESTS_FAILED:
            return False, f"Expected tests to fail, got return code {res.returncode}"

        # assert the packages own cli is available
        args = PackageManager.I.run_args(src_project_name, "--help")
        res = args.run()
        stdout = res.stdout
        expected = src_project_name
        if expected not in stdout.lower():
            return (
                False,
                "Expected the projects CLI to work and find the project name in stdout",
            )

        # assert calling version works
        args = PackageManager.I.run_args(src_project_name, version.__name__)
        res = args.run()
        stdout = res.stdout
        expected = f"{src_project_name} 0.1.0"
        if expected not in stdout:
            return (
                False,
                f"Expected the projects version command to output '{expected}'",
            )

        package_dir = src_project_dir / "src" / kebab_to_snake_case(src_project_name)
        if not package_dir.exists():
            return (
                False,
                f"Expected package directory {package_dir} to exist after init",
            )

        for cf in ConfigFile.concrete_leaves():
            if not cf().path().exists():
                return (
                    False,
                    f"Expected config file {cf().path()} to exist after init",
                )

        PackageManager.I.run_args(*Pyrigger.I.args("--help")).run()

    return True, ""
modules

Shared pytest fixtures for creating temporary modules and packages.

Provides callables that build real Python modules and packages on disk (with the appropriate __init__.py hierarchy) and import them, for tests that need live module objects to introspect.

create_module
create_module() -> Callable[[Path], ModuleType]

Return a callable that creates and imports an empty Python module.

Returns:

Type Description
Callable[[Path], ModuleType]

A callable that creates an empty module file and imports it,

Callable[[Path], ModuleType]

initializing any missing parent directories as a package hierarchy

Callable[[Path], ModuleType]

first.

Source code in src/pyrig_fixtures/rig/tests/fixtures/modules.py
@pytest.fixture
def create_module() -> Callable[[Path], ModuleType]:
    """Return a callable that creates and imports an empty Python module.

    Returns:
        A callable that creates an empty module file and imports it,
        initializing any missing parent directories as a package hierarchy
        first.
    """

    def create(path: Path) -> ModuleType:
        """Create an empty module file at `path` and import it.

        Args:
            path: Path to the module file, relative to the current working
                directory. Missing parent directories are created and given
                `__init__.py` files up to the current working directory.
                The imported module's dotted name is derived from this path.

        Returns:
            The imported module, empty of any definitions.
        """
        make_package_dir(path.parent, root=Path(), content="")
        path.touch()
        return import_module_with_file_fallback(path, name=path_as_module_name(path))

    return create
create_package
create_package() -> Callable[[Path], ModuleType]

Return a callable that creates and imports an empty Python package.

Returns:

Type Description
Callable[[Path], ModuleType]

A callable that creates a directory tree as an empty package

Callable[[Path], ModuleType]

hierarchy and imports the deepest package.

Source code in src/pyrig_fixtures/rig/tests/fixtures/modules.py
@pytest.fixture
def create_package() -> Callable[[Path], ModuleType]:
    """Return a callable that creates and imports an empty Python package.

    Returns:
        A callable that creates a directory tree as an empty package
        hierarchy and imports the deepest package.
    """

    def create(path: Path) -> ModuleType:
        """Create an empty package directory at `path` and import it.

        Args:
            path: Path to the package directory, relative to the current
                working directory. `path` and every ancestor directory up to
                the current working directory are created and given
                `__init__.py` files. The imported package's dotted name is
                derived from this path.

        Returns:
            The imported package, empty of any definitions.
        """
        make_package_dir(path, root=Path(), content="")
        return import_module_with_file_fallback(path, name=path_as_module_name(path))

    return create
create_source_package
create_source_package(
    tmp_source_root_path: Path,
    create_package: Callable[[Path], ModuleType],
) -> Callable[[Path], ModuleType]

Return a callable that creates and imports a package under the source root.

Parameters:

Name Type Description Default
tmp_source_root_path Path

Temporary source root directory that paths passed to the returned callable are resolved against.

required
create_package Callable[[Path], ModuleType]

Fixture that creates and imports a package.

required

Returns:

Type Description
Callable[[Path], ModuleType]

A callable that creates an empty package under the temporary source

Callable[[Path], ModuleType]

root and imports it.

Source code in src/pyrig_fixtures/rig/tests/fixtures/modules.py
@pytest.fixture
def create_source_package(
    tmp_source_root_path: Path,
    create_package: Callable[[Path], ModuleType],
) -> Callable[[Path], ModuleType]:
    """Return a callable that creates and imports a package under the source root.

    Args:
        tmp_source_root_path: Temporary source root directory that paths
            passed to the returned callable are resolved against.
        create_package: Fixture that creates and imports a package.

    Returns:
        A callable that creates an empty package under the temporary source
        root and imports it.
    """

    def create(path: Path) -> ModuleType:
        """Create an empty package at `path` under the source root and import it.

        Args:
            path: Path to the package directory, relative to the temporary
                source root directory.

        Returns:
            The imported package, empty of any definitions.
        """
        with chdir(tmp_source_root_path):
            return create_package(path)

    return create
paths

Fixtures for building a temporary project/source/package directory tree.

Provides an empty, disposable instance of this ecosystem's conventional src layout (project root → source root → package root), so tests can exercise path-sensitive logic without touching the real project on disk.

tmp_package_root_path
tmp_package_root_path(
    tmp_project_root_path: Path,
    tmp_source_root_path: Path,
    create_source_package: Callable[[Path], ModuleType],
) -> tuple[Path, ModuleType]

Provide the temporary package root, already created and imported as a package.

Parameters:

Name Type Description Default
tmp_project_root_path Path

Temporary project root directory.

required
tmp_source_root_path Path

Temporary source root directory.

required
create_source_package Callable[[Path], ModuleType]

Callable that creates and imports a package at a path relative to the temporary source root.

required

Returns:

Type Description
Path

Tuple of (path, package): the package root directory nested inside

ModuleType

the temporary source root, and its imported package module.

Source code in src/pyrig_fixtures/rig/tests/fixtures/paths.py
@pytest.fixture
def tmp_package_root_path(
    tmp_project_root_path: Path,
    tmp_source_root_path: Path,
    create_source_package: Callable[[Path], ModuleType],
) -> tuple[Path, ModuleType]:
    """Provide the temporary package root, already created and imported as a package.

    Args:
        tmp_project_root_path: Temporary project root directory.
        tmp_source_root_path: Temporary source root directory.
        create_source_package: Callable that creates and imports a package at
            a path relative to the temporary source root.

    Returns:
        Tuple of `(path, package)`: the package root directory nested inside
        the temporary source root, and its imported package module.
    """
    path = tmp_project_root_path / PackageManager.I.package_root()

    package = create_source_package(path.relative_to(tmp_source_root_path))
    return path, package
tmp_project_root_path
tmp_project_root_path(tmp_path: Path) -> Path

Provide a temporary project root directory named after the current project.

Parameters:

Name Type Description Default
tmp_path Path

Pytest's per-test temporary directory.

required

Returns:

Type Description
Path

Path to the temporary project root directory, already created on disk.

Source code in src/pyrig_fixtures/rig/tests/fixtures/paths.py
@pytest.fixture
def tmp_project_root_path(tmp_path: Path) -> Path:
    """Provide a temporary project root directory named after the current project.

    Args:
        tmp_path: Pytest's per-test temporary directory.

    Returns:
        Path to the temporary project root directory, already created on disk.
    """
    path = tmp_path / PackageManager.I.project_name()
    path.mkdir()
    return path
tmp_source_root_path
tmp_source_root_path(tmp_project_root_path: Path) -> Path

Provide a temporary source root directory nested inside the project root.

Parameters:

Name Type Description Default
tmp_project_root_path Path

Temporary project root directory.

required

Returns:

Type Description
Path

Path to the temporary source root directory, already created on disk.

Source code in src/pyrig_fixtures/rig/tests/fixtures/paths.py
@pytest.fixture
def tmp_source_root_path(tmp_project_root_path: Path) -> Path:
    """Provide a temporary source root directory nested inside the project root.

    Args:
        tmp_project_root_path: Temporary project root directory.

    Returns:
        Path to the temporary source root directory, already created on disk.
    """
    path = tmp_project_root_path / PackageManager.I.source_root()
    path.mkdir()
    return path