pytest-dev/pytest · error · UsageError

Plugins may be specified as a sequence or a ','-separated…

Error message

Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}

What it means

Raised by _get_plugin_specs_as_list() (src/_pytest/config/__init__.py:953) when the pytest_plugins variable in a conftest.py is not None, not a str, not a Sequence, and not a module (config/__init__.py:942-952 cover those cases). The final raise at capture.py:953-955 surfaces the bad value via UsageError so the user sees a clear message rather than a confusing AttributeError later.

Solutions

  1. Make pytest_plugins a string or a tuple/list of strings: `pytest_plugins = ('a', 'b')`
  2. If you must compute it dynamically, ensure the expression yields str or Sequence[str]
  3. Use None or omit the variable to disable plugin specs entirely

Example fix

// before
# conftest.py
pytest_plugins = {'foo': 'plugin_foo'}
// after
pytest_plugins = ('plugin_foo',)
Defensive patterns

Strategy: type-guard

Validate before calling

import collections.abc, types
ok = specs is None or isinstance(specs, (str, collections.abc.Sequence, types.ModuleType))
if not ok:
    raise TypeError('pytest_plugins must be None/str/Sequence/module')

Type guard

import collections.abc, types
def valid_plugin_specs(s) -> bool:
    return s is None or isinstance(s, (str, collections.abc.Sequence, types.ModuleType))

Prevention

When it happens

Trigger: Setting `pytest_plugins = 42` or `pytest_plugins = {'name': 'plugin'}` (a dict) in a conftest.py. None, str, list, tuple, and module types are accepted; anything else falls through to config/__init__.py:953.

Common situations: Typos in conftest.py; copying a dict-style config from another tool; assigning the result of a function call that returns a non-sequence; using a set literal `{...}` instead of a tuple `(...)`.

Related errors


AI-assisted analysis of pytest-dev/pytest@0d6fbdeffa (2026-08-11). Data as JSON: /api/errors/31d2059140956d23. Report an issue: GitHub.

Appendix: source

Thrown at src/_pytest/config/__init__.py:953


def _get_plugin_specs_as_list(
    specs: types.ModuleType | str | Sequence[str] | None,
) -> list[str]:
    """Parse a plugins specification into a list of plugin names."""
    # None means empty.
    if specs is None:
        return []
    # Workaround for #3899 - a submodule which happens to be called "pytest_plugins".
    if isinstance(specs, types.ModuleType):
        return []
    # Comma-separated list.
    if isinstance(specs, str):
        return specs.split(",") if specs else []
    # Direct specification.
    if isinstance(specs, collections.abc.Sequence):
        return list(specs)
    raise UsageError(
        f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}"
    )


def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
    """Given an iterable of file names in a source distribution, return the "names" that should
    be marked for assertion rewrite.

    For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in
    the assertion rewrite mechanism.

    This function has to deal with dist-info based distributions and egg based distributions
    (which are still very much in use for "editable" installs).

    Here are the file names as seen in a dist-info based distribution:

        pytest_mock/__init__.py
        pytest_mock/_version.py

View on GitHub (pinned to 0d6fbdeffa)