Plugin — Loaders¶
The discovery primitives behind the plugin's generated-fixture loading: finding
pytest-plugin modules inside a package tree or a single module file, and assembling the
recursive pytest_plugins list from the configuration.
The loaders¶
PackageLoader walks a package directory; ModuleLoader inspects one module file. Both
share the name/required constructor fields and the abstract BaseLoader contract
(from_config factory + load). load mutates the accumulator list passed to it and
returns nothing.
Building a loader from config¶
from_config accepts a bare dotted name or a mapping:
PackageLoader.from_config("api") # required=True
PackageLoader.from_config({"name": "api", "required": False}) # mapping form
ModuleLoader.from_config("my_plugin.conftest")
PackageLoader("api") # positional construction; required=True (default)
ModuleLoader("my_plugin.conftest")
Discovering modules¶
modules: list[str] = []
PackageLoader.from_config({"name": "api", "required": False}).load(modules)
# modules -> ['api.orders.get_orders.api', 'api.users.create_user.api', ...]
ModuleLoader.from_config("my_plugin.conftest").load(modules)
# appends 'my_plugin.conftest' when it is a pytest plugin
The loader config section¶
loader:
packages:
- api # bare dotted name → required=True by default
- name: generated.fixtures # mapping form → required taken from the item
required: false
modules:
- my_plugin.conftest
packages— dotted package names to walk recursively (every subdir with__init__.py).modules— single dotted module files.- Each item is a
str(required defaults to True) or a mapping{name, required}. - Both lists default to empty when the section is absent;
install()then falls back to its built-in default[PackageLoader("api", required=False)]— theapi/tree is discovered out of the box.
Preconditions and side effects¶
- A module counts as a pytest plugin when it exposes a public
pytest_-prefixed attribute (a hook) or an attribute carrying a pytest-fixture marker. Detection uses a trial import. required=True(default) raisesOSError(package) /FileNotFoundError(module) when the target is missing;required=Falseis a no-op.- A directory is walked only when it contains
__init__.py. - Trial imports do not pollute
sys.modules: a module (and any ancestor package) absent before the probe is removed afterwards. - Loaders run synchronously at
install()time; eachloadmutates the shared accumulator in place.