pytest-dev/pytest · error · ValueError

{self} is the lower-most scope

Error message

{self} is the lower-most scope

What it means

pytest models fixture lifetimes as an ordered Scope enum: Function < Class < Module < Package < Session. Scope.next_lower() walks one step down that chain. Function is at index 0, so it has no lower neighbor and the call refuses rather than return a sentinel. The error is raised from internal scope-walking code, not from user-facing config parsing, so it signals a programming bug in pytest internals or a plugin that walks scopes without a bounds check.

Source

Thrown at src/_pytest/scope.py:46

              ->>> higher ->>>

    Function < Class < Module < Package < Session

              <<<- lower  <<<-
    """

    # Scopes need to be listed from lower to higher.
    Function = "function"
    Class = "class"
    Module = "module"
    Package = "package"
    Session = "session"

    def next_lower(self) -> Scope:
        """Return the next lower scope."""
        index = _SCOPE_INDICES[self]
        if index == 0:
            raise ValueError(f"{self} is the lower-most scope")
        return _ALL_SCOPES[index - 1]

    def next_higher(self) -> Scope:
        """Return the next higher scope."""
        index = _SCOPE_INDICES[self]
        if index == len(_SCOPE_INDICES) - 1:
            raise ValueError(f"{self} is the upper-most scope")
        return _ALL_SCOPES[index + 1]

    def __lt__(self, other: Scope) -> bool:
        self_index = _SCOPE_INDICES[self]
        other_index = _SCOPE_INDICES[other]
        return self_index < other_index

    @classmethod
    def from_user(
        cls, scope_name: ScopeName, descr: str, where: str | None = None
    ) -> Scope:

View on GitHub (pinned to 0d6fbdeffa)

Solutions

  1. Guard the call: check `scope is not Scope.Function` (or `scope != _ALL_SCOPES[0]`) before invoking next_lower().
  2. Iterate the module-level HIGH_SCOPES list (all scopes except Function) instead of walking next_lower() from the bottom.
  3. If you genuinely need a sentinel below Function, model it as a separate value rather than relying on next_lower() to produce one.

Example fix

# before
lower = scope.next_lower()

# after
if scope is Scope.Function:
    lower = None
else:
    lower = scope.next_lower()
Defensive patterns

Strategy: validation

Validate before calling

from _pytest.scope import Scope, _ALL_SCOPES

def safe_next_lower(scope: Scope):
    # Validate before calling next_lower() to avoid ValueError on Function.
    if scope is _ALL_SCOPES[0]:
        return None
    return scope.next_lower()

Type guard

from _pytest.scope import Scope, _ALL_SCOPES

def has_lower_scope(scope: Scope) -> bool:
    """True when next_lower() is safe to call."""
    return scope is not _ALL_SCOPES[0]

Try / catch

try:
    lower = scope.next_lower()
except ValueError:
    # scope is Function; nothing below it
    lower = None

Prevention

When it happens

Trigger: Calling Scope.Function.next_lower() directly, or any helper that iterates _ALL_SCOPES downward starting from Function. Reachable only via code that holds a Scope enum member and asks for the next-lower one; not produced by ini config or CLI flags.

Common situations: Plugin authors implementing fixture finalization that walks scopes; porting an old pytest fork whose scope list differed; an internal refactor that drops the bounds check before calling next_lower(). End users running stock pytest effectively never see this.

Related errors


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