Skip to main content

Pytest resource guard infrastructure for enforcing test marks on external resource usage

Project description

resource-guards

Pytest infrastructure for enforcing that tests declare their external resource usage via marks.

Resource guards catch two classes of bugs:

  • Missing marks: a test calls an external resource without the corresponding @pytest.mark.<resource>. The guard fails the test with a clear message.
  • Superfluous marks: a test carries a resource mark but never actually invokes the resource. The guard fails the test so the mark doesn't rot.

How it works

There are two guard mechanisms, covering CLI binaries and Python SDKs respectively.

Binary guards create wrapper scripts that shadow the real binary on PATH. During a test, the wrapper checks environment variables to decide whether the test is allowed to use the binary. If not, it records a tracking file and exits 127. If yes, it records a tracking file and delegates to the real binary.

SDK guards monkeypatch a chokepoint in a Python SDK. The monkeypatched function calls enforce_sdk_guard(), which checks the same environment variables and either raises ResourceGuardViolation or records a tracking file.

Both mechanisms use per-test tracking files so the makereport hook can detect violations even when the test swallows errors or handles non-zero exit codes.

Basic usage

In your conftest.py, register each resource you want to guard with register_resource_guard(), then add pytest_configure, pytest_sessionstart, and pytest_sessionfinish hooks as shown below. register_guarded_resource_markers registers the pytest marks for all guarded resources in one call.

# conftest.py
from imbue.resource_guards.resource_guards import (
    register_guarded_resource_markers,
    register_resource_guard,
    start_resource_guards,
    stop_resource_guards,
)

register_resource_guard("tmux")
register_resource_guard("rsync")

def pytest_configure(config):
    register_guarded_resource_markers(config)

def pytest_sessionstart(session):
    start_resource_guards(session)

def pytest_sessionfinish(session, exitstatus):
    stop_resource_guards()

Then mark your tests:

import pytest

@pytest.mark.tmux
def test_agent_creates_tmux_session():
    ...

Usage for multi-package projects

When a project is split across multiple packages, listing every guard in every consumer's conftest.py becomes a maintenance hazard: each package has to know which guards every other package's tools need, and a forgotten line silently downgrades a guarded mark back to "unknown". Resource guards solve this by letting the package that owns a tool declare its guards through a resource_guards entry point group, and letting consumers pick them up automatically with one call.

Each entry point's value is a callable that takes no arguments and registers one or more guards via register_resource_guard() and/or register_sdk_guard()/create_sdk_method_guard():

# library's pyproject.toml
[project.entry-points.resource_guards]
my_lib = "imbue.my_lib.register_guards:register_my_guard"
# library's register_guards.py
from imbue.resource_guards.resource_guards import register_resource_guard

def register_my_guard():
    register_resource_guard("my_tool")

The consumer's conftest.py then replaces explicit register_resource_guard(...) calls with a single register_all_resource_guards(), which imports and invokes every entry point in the group:

# consumer's conftest.py
from imbue.resource_guards.resource_guards import (
    register_all_resource_guards,
    register_guarded_resource_markers,
    start_resource_guards,
    stop_resource_guards,
)

register_all_resource_guards()

def pytest_configure(config):
    register_guarded_resource_markers(config)

def pytest_sessionstart(session):
    start_resource_guards(session)

def pytest_sessionfinish(session, exitstatus):
    stop_resource_guards()

The library that owns a tool is the natural place to declare its guard, and consumers don't need to know which guards exist in advance.

Writing a custom SDK guard

You can guard any Python SDK by registering an install/cleanup pair:

from imbue.resource_guards.resource_guards import enforce_sdk_guard
from imbue.resource_guards.resource_guards import register_sdk_guard

_originals = {}

def _install():
    _originals["send"] = SomeClient.send
    SomeClient.send = _guarded_send

def _cleanup():
    if "send" in _originals:
        SomeClient.send = _originals["send"]
        _originals.clear()

def _guarded_send(self, *args, **kwargs):
    enforce_sdk_guard("my_sdk")
    return _originals["send"](self, *args, **kwargs)

register_sdk_guard("my_sdk", _install, _cleanup)

The key requirement is that your monkeypatch calls enforce_sdk_guard("my_sdk") at the SDK's chokepoint -- the single method through which all external calls flow.

Compatibility with pytest-xdist

Binary guards work transparently with xdist. The controller process creates the wrapper scripts and modifies PATH; workers inherit both via environment variables. SDK guards are installed independently in each process (controller and workers), since monkeypatches are process-local.

Project details


Download files

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

Source Distribution

resource_guards-0.1.6.tar.gz (20.8 kB view details)

Uploaded Source

Built Distribution

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

resource_guards-0.1.6-py3-none-any.whl (10.5 kB view details)

Uploaded Python 3

File details

Details for the file resource_guards-0.1.6.tar.gz.

File metadata

  • Download URL: resource_guards-0.1.6.tar.gz
  • Upload date:
  • Size: 20.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for resource_guards-0.1.6.tar.gz
Algorithm Hash digest
SHA256 d933161eafa48811eb55a005eb832a0aed89b9610a291cbe69c59bc0bf2cf4fc
MD5 249390aefd7a8fa44be9be3cf3e6c19c
BLAKE2b-256 24ce867257ba9d43ae7a54c723891cf9c8af19faa36458ecbf0236b21f8b928a

See more details on using hashes here.

Provenance

The following attestation bundles were made for resource_guards-0.1.6.tar.gz:

Publisher: publish.yml on imbue-ai/mngr

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

File details

Details for the file resource_guards-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: resource_guards-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 10.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for resource_guards-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 2e386d8f8f5f78f69fb18808166a224f1e25dc1671fcc86c4ba7a11e6335298d
MD5 5ef5adbe3c1be4548c8682659054b231
BLAKE2b-256 44877bd85b5ecc6dabce49e0063af182f5256c4d3d1de0c4631572a6531aa262

See more details on using hashes here.

Provenance

The following attestation bundles were made for resource_guards-0.1.6-py3-none-any.whl:

Publisher: publish.yml on imbue-ai/mngr

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page