Skip to main content

pytest-resources

Lazy, typed, attribute-navigable fixtures for test resource files — JSON / JSONC / YAML / TOML / INI decoded through e-serde by default, extensible to any canonical bytes -> object loader, and anything else handed back as raw bytes.

You point it at one or more directories of sample files; it recursively indexes the tree without reading anything, and you reach, filter, randomise and synthesize parsed values the way they read:

from dataclasses import dataclass

import pytest_resources as pr


@dataclass
class User:
    name: str
    age: int


def test_profile(resources: pr.Resources):
    user = resources.structured.user          # sample dir -> dict (decoded, cached)
    body = resources.unstructured.intro       # a .md -> str
    blob = resources.binary.logo              # no codec bound -> raw bytes

    jsons = resources.data.select("*.json")   # filter a folder (glob / regex / kind)
    pick = resources.data.choice("*.json")    # random pick, seeded per run

    ada = resources.make(User, name="ada")    # random model instance    [objects]
    squad = resources.batch(User, 8)          # a typed list of them     [objects]
    pdf = resources.file("report.pdf")        # synthetic file, indexed [files]

Install

uv add --group test "pytest-resources[serde]"   # e-serde default codecs
uv add --group test pytest-resources            # stdlib-only (json + tomllib)
uv add --group test "pytest-resources[objects]" # resources.make()/batch(): random model objects
uv add --group test "pytest-resources[files]"   # resources.file(): synthetic files, 25 formats
uv add --group test "pytest-resources[random]"  # both

The resources fixture auto-loads via the pytest11 entry point — no imports in your conftest.py. Everything (index, navigation, synthesis) is async-test friendly: make/batch are pure in-memory CPU and abuild_resources/awalk keep the loop free; calling a synthesis method without its extra raises ExtraNotInstalledError with the exact install command — the plugin never hard-depends on polyfactory or faker-file.

What you get

Idea Mechanism
Recursive index, zero read each root is walked once (off the loop); only paths are held
Lazy + cached a file decodes on first access and is memoized for the session
Attr / item / iter views r.a.b, r["a"]["b"], list(r.a) → list of decoded values
Extract from a folder select/first · paths · walk/awalk (sync/async, lazy) · similar (fuzzy) · as_dict, by glob / regex / kind
Randomise the pick r.a.choice(...) (session-seeded) or pass your own rng
Synthesize on the fly make(Model | hint, seed, **pins) / batch(Model | hint, n) for objects, file(kind | name, ...) for real files adopted into the tree — optional [objects] / [files] / [random] extras
Typed by suffix FileType (StrEnum) resolves the extension; the loader table decodes
Open-ended fallback CSV/TSV/PDF/… and any unknown kind stay raw bytes
Many roots, one tree CLI / ini / pytest_resources_roots hook, merged (later wins), auto-created
Pluggable codecs override or add any kind through the pytest_resource_loaders hook
e-serde default, optional native Rust/C config codecs when installed; stdlib otherwise
Friendly misses EntryNotFoundError / KeyError carry a "did you mean" hint

Configuring the roots

By default the plugin indexes <rootdir>/tests/resources (and creates it if missing). Point it at one or several directories:

# pyproject.toml
[tool.pytest.ini_options]
resources_root = ["fixtures", "fixtures/shared"]   # default: <rootdir>/tests/resources

Or per run (--resources-root is repeatable), or additively from your conftest.py:

from pathlib import Path

def pytest_resources_roots(roots):
    roots.append(Path("tests/fixtures/shared"))

CLI wins over ini, which wins over the default. Every resolved root is merged into one navigable tree; on a top-level clash the later root wins.

Custom loaders (the extension seam)

The plugin never assumes e-serde. Bind any parser declaratively in your conftest.py:

from pytest_resources import FileType
import yaml, csv, io

def pytest_resource_loaders(register):
    register({FileType.CSV: lambda b: list(csv.DictReader(io.StringIO(b.decode())))})
    register({FileType.YAML: yaml.safe_load})

register merges into the session's table, so you can override a default kind as easily as add an unknown one. Kinds left unbound hand back raw bytes.

Programmatic use

Outside pytest, build a tree yourself:

from pathlib import Path
import pytest_resources as pr

tree = pr.build_resources(Path("fixtures"))                      # sync, one root
tree = pr.build_resources([Path("a"), Path("b")])               # merged roots, later wins
tree = await pr.abuild_resources(Path("fixtures"))              # off the event loop
table = pr.default_loaders()                                    # best available codecs

Development

make install   # uv sync + git hooks
make check     # ruff + ty + tach + validate + tests
make ci        # everything the pipeline runs (adds coverage gate + docs + zizmor)

License

MIT

Release files for pytest-resources 0.2.2

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-resources 0.2.2
File Size Uploaded
pytest_resources-0.2.2.tar.gz 28.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pytest-resources 0.2.2
File Interpreter ABI Platform
pytest_resources-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 47.5 kB

Release files / pytest_resources-0.2.2.tar.gz

Download URL pytest_resources-0.2.2.tar.gz
Size 28.9 kB
Tags Source
SHA-256 checksum
How to use checksums
feb6a35dcabc0a44f5a1d2969bc130b7f0ee90333f530ce9cc8c4aec2e83263c
BLAKE2b-256 checksum
How to use checksums
fb5aca37d61aeb36f14f7f92f736010785d7d7a0c04d693b893c4eaab6b7f020
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / pytest_resources-0.2.2-py3-none-any.whl

Download URL pytest_resources-0.2.2-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9e25eb6f152a1e4fd9bda0fec59af789f59d1c15a3677b88cfb90d0c9db2c24f
BLAKE2b-256 checksum
How to use checksums
b53c688f282881580fb76fc1dbf2d13658f0fc499cb98655daefafe3dc6114f2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.3

2 release files

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release 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