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 and randomise parsed values the way they read:
import pytest_resources as pr
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
Install
uv add --group test "pytest-resources[serde]" # e-serde default codecs
uv add --group test pytest-resources # stdlib-only (json + tomllib)
The resources fixture auto-loads via the pytest11 entry point — no imports in your
conftest.py.
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 |
| 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.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pytest_resources-0.1.0.tar.gz | 20.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pytest_resources-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.6 kB
Release files / pytest_resources-0.1.0.tar.gz
| Download URL | pytest_resources-0.1.0.tar.gz |
|---|---|
| Size | 20.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
dee4eb1a85df9fdae43a7ec13c9005418151c8fff2fa97e03f8d55d3d6e95357
|
|
BLAKE2b-256 checksum How to use checksums |
1e7856dca2801dac7c8487fb85ffbdcaa97aa0980e96f49209bd4bf9d64b726b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / pytest_resources-0.1.0-py3-none-any.whl
| Download URL | pytest_resources-0.1.0-py3-none-any.whl |
|---|---|
| Size | 13.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6e99e9e6590404e8246113320d40a627baa7376dfd5dbd81fa20a2153a110d45
|
|
BLAKE2b-256 checksum How to use checksums |
795aeff3884ce92896a074c0e838791b53d60c371fb99fee42369a349dbc7600
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|