pytest-dev/pytest · error · AttributeError

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

Error message

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

What it means

`annotated_getattr` is used while resolving a dotted import path: after importing the parent module, each remaining segment is fetched via `getattr`. If the attribute is missing, AttributeError is re-raised with a message naming the object type, the import path being resolved (`ann`), and the missing attribute name.

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 98b357f69e)

Solutions

  1. Check the spelling of every segment against the actual module with `hasattr(module, 'attr')`.
  2. Pin or upgrade the target library to a version that has the attribute, or update the path to the new name.
  3. Pass `raising=False` to `setattr` if you intend to create a brand-new attribute.

Example fix

# before
monkeypatch.setattr('os.getcw', fake)
# after
monkeypatch.setattr('os.getcwd', fake)
Defensive patterns

Strategy: validation

Validate before calling

def attr_exists(dotted: str) -> bool:
    module, attr = dotted.rsplit('.', 1)
    import importlib
    try:
        return hasattr(importlib.import_module(module), attr)
    except ImportError:
        return False

Prevention

When it happens

Trigger: `monkeypatch.setattr('os.getcwd', ...)` where the last segment does not exist on the resolved module; typos like `os.getcwd` -> `os.getcw`; patching a private name that was renamed.

Common situations: Typos in the dotted path; library version changed/renamed/removed the attribute; patching a function injected lazily at runtime that doesn't exist at import time.

Related errors


AI-assisted analysis of pytest-dev/pytest@98b357f69e (2026-08-04). Data as JSON: /data/errors/f58a4dd2d1ed3ba4.json. Report an issue: GitHub.