{"id":"f58a4dd2d1ed3ba4","repo":"pytest-dev/pytest","slug":"type-obj-name-r-object-at-ann-has-no-attr","errorCode":null,"errorMessage":"{type(obj).__name__!r} object at {ann} has no attribute {name!r}","messagePattern":"(.+?) object at (.+?) has no attribute (.+?)","errorType":"exception","errorClass":"AttributeError","httpStatus":null,"severity":"error","filePath":"src/_pytest/monkeypatch.py","lineNumber":97,"sourceCode":"        # We use explicit un-nesting of the handling block in order\n        # to avoid nested exceptions.\n        try:\n            importlib.import_module(used)\n        except ImportError as ex:\n            expected = str(ex).split()[-1]\n            if expected == used:\n                raise\n            else:\n                raise ImportError(f\"import error in {used}: {ex}\") from ex\n        found = annotated_getattr(found, part, used)\n    return found\n\n\ndef annotated_getattr(obj: object, name: str, ann: str) -> object:\n    try:\n        obj = getattr(obj, name)\n    except AttributeError as e:\n        raise AttributeError(\n            f\"{type(obj).__name__!r} object at {ann} has no attribute {name!r}\"\n        ) from e\n    return obj\n\n\ndef derive_importpath(import_path: str, raising: bool) -> tuple[str, object]:\n    if not isinstance(import_path, str) or \".\" not in import_path:\n        raise TypeError(f\"must be absolute import path string, not {import_path!r}\")\n    module, attr = import_path.rsplit(\".\", 1)\n    target = resolve(module)\n    if raising:\n        annotated_getattr(target, attr, ann=module)\n    return attr, target\n\n\n@final\nclass MonkeyPatch:\n    \"\"\"Helper to conveniently monkeypatch attributes/items/environment","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/pytest-dev/pytest/blob/98b357f69e380da908740a212288d73b2ee06687/src/_pytest/monkeypatch.py#L79-L115","documentation":"`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.","triggerScenarios":"`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.","commonSituations":"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.","solutions":["Check the spelling of every segment against the actual module with `hasattr(module, 'attr')`.","Pin or upgrade the target library to a version that has the attribute, or update the path to the new name.","Pass `raising=False` to `setattr` if you intend to create a brand-new attribute."],"exampleFix":"# before\nmonkeypatch.setattr('os.getcw', fake)\n# after\nmonkeypatch.setattr('os.getcwd', fake)","handlingStrategy":"validation","validationCode":"def attr_exists(dotted: str) -> bool:\n    module, attr = dotted.rsplit('.', 1)\n    import importlib\n    try:\n        return hasattr(importlib.import_module(module), attr)\n    except ImportError:\n        return False","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify the attribute exists at import time before patching.","Patch where the name is looked up, not where it is defined."],"tags":["pytest","monkeypatch","attribute-error","setattr"],"analyzedSha":"98b357f69e380da908740a212288d73b2ee06687","analyzedAt":"2026-08-04T20:26:34.442Z","schemaVersion":2}