pytest-dev/pytest · error · AttributeError

object at has no attribute

Error message

{type(obj).__name__!r} object at {ann} has no attribute {name!r}

What it means

An AttributeError raised by annotated_getattr when an object along the dotted import path does not have the requested attribute. Unlike a bare getattr, this version annotates the error with the type of the object and the human-readable path segment, making it easier to locate which part of 'a.b.c' was missing the attribute.

Solutions

  1. Verify the attribute exists: `python -c 'from mymod import MyClass; print(hasattr(MyClass, 'attr'))'`.
  2. Check the spelling and the class/module you are patching; use the leaf object directly.
  3. Pass raising=False if you intend to create a new attribute (for setattr).
  4. Update the path to match the current library version.

Example fix

# before
monkeypatch.setattr('mymod.MyClass.notamethod', new)
# after
monkeypatch.setattr('mymod.MyClass.realmethod', new)
Defensive patterns

Strategy: validation

Validate before calling

def attr_exists(dotted: str) -> bool:
    parts = dotted.rsplit('.', 1)
    if len(parts) != 2:
        return False
    mod, attr = parts
    try:
        import importlib
        target = importlib.import_module(mod)
    except ImportError:
        return False
    return hasattr(target, attr)

Try / catch

try:
    monkeypatch.setattr('mymod.X.attr', value)
except AttributeError as e:
    # the message names the type and missing attribute
    ...

Prevention

When it happens

Trigger: Calling `monkeypatch.setattr('mymod.MyClass.missing_attr', value)` where MyClass exists but `missing_attr` does not; or `monkeypatch.delattr` on a non-existent attribute with raising=True. The annotated path is the module string passed in.

Common situations: Typos in the attribute name, version skew where an attribute was renamed/removed, patching a method that lives on a subclass, or wrong module re-export.

Related errors


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

Appendix: source

Thrown at src/_pytest/monkeypatch.py:97

        # We use explicit un-nesting of the handling block in order
        # to avoid nested exceptions.
        try:
            importlib.import_module(used)
        except ImportError as ex:
            expected = str(ex).split()[-1]
            if expected == used:
                raise
            else:
                raise ImportError(f"import error in {used}: {ex}") from ex
        found = annotated_getattr(found, part, used)
    return found


def annotated_getattr(obj: object, name: str, ann: str) -> object:
    try:
        obj = getattr(obj, name)
    except AttributeError as e:
        raise AttributeError(
            f"{type(obj).__name__!r} object at {ann} has no attribute {name!r}"
        ) from e
    return obj


def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]:
    if not isinstance(import_path, str) or "." not in import_path:
        raise TypeError(f"must be absolute import path string, not {import_path!r}")
    module, attr = import_path.rsplit(".", 1)
    target = resolve(module)
    if raising:
        annotated_getattr(target, attr, ann=module)
    return attr, target


@final
class MonkeyPatch:
    """Helper to conveniently monkeypatch attributes/items/environment

View on GitHub (pinned to 0d6fbdeffa)