pytest-dev/pytest · error · AttributeError

has no attribute

Error message

{target!r} has no attribute {name!r}

What it means

An AttributeError raised by MonkeyPatch.setattr when raising=True (the default) and the target does not already have the named attribute. pytest refuses to create brand-new attributes by default to catch typos and patches against the wrong object; oldval is NOTSET when getattr returns the sentinel. The message shows the target repr and the attribute name.

Solutions

  1. Verify the attribute exists on the exact object you are patching (use hasattr).
  2. Pass `raising=False` if you intentionally want to create a new attribute.
  3. Correct the dotted path to point at the object that actually owns the attribute (beware re-exports).

Example fix

# before
monkeypatch.setattr(obj, 'attr_name', value)
# AttributeError: <Obj> has no attribute 'attr_name'
# after
monkeypatch.setattr(obj, 'real_attr', value)
# or, to create a new attribute:
monkeypatch.setattr(obj, 'attr_name', value, raising=False)
Defensive patterns

Strategy: validation

Validate before calling

def can_patch(obj, name) -> bool:
    return hasattr(obj, name)

Try / catch

try:
    monkeypatch.setattr(obj, 'attr', value)
except AttributeError:
    monkeypatch.setattr(obj, 'attr', value, raising=False)

Prevention

When it happens

Trigger: Calling `monkeypatch.setattr(obj, 'nonexistent', value)` with the default raising=True on an object that lacks the attribute; or `monkeypatch.setattr('mymod.X.typo', value)` where the attribute name is misspelled.

Common situations: Typos in the attribute name, patching the wrong class/module (e.g. a copy vs the original), version skew where the attribute was removed, or genuinely intending to add a new attribute without opting in.

Related errors


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

Appendix: source

Thrown at src/_pytest/monkeypatch.py:242

            if not isinstance(target, str):
                raise TypeError(
                    "use setattr(target, name, value) or "
                    "setattr(target, value) with target being a dotted "
                    "import string"
                )
            value = name
            name, target = derive_importpath(target, raising)
        else:
            if not isinstance(name, str):
                raise TypeError(
                    "use setattr(target, name, value) with name being a string or "
                    "setattr(target, value) with target being a dotted "
                    "import string"
                )

        oldval = getattr(target, name, NOTSET)
        if raising and oldval is NOTSET:
            raise AttributeError(f"{target!r} has no attribute {name!r}")

        # avoid class descriptors like staticmethod/classmethod
        if inspect.isclass(target):
            oldval = target.__dict__.get(name, NOTSET)
        setattr(target, name, value)
        self._setattr.append((target, name, oldval))

    def delattr(
        self,
        target: object | str,
        name: str | NotSetType = NOTSET,
        raising: bool = True,
    ) -> None:
        """Delete attribute ``name`` from ``target``.

        If no ``name`` is specified and ``target`` is a string
        it will be interpreted as a dotted import path with the
        last part being the attribute name.

View on GitHub (pinned to 0d6fbdeffa)