ErrLookup › Background articles › AttributeError: module 'X' has no attribute 'Y' — what Python's missing-attribute error means across libraries
AttributeError: module 'X' has no attribute 'Y' — what Python's missing-attribute error means across libraries
AttributeError: module 'x' has no attribute 'y' is the error Python raises when attribute lookup fails, and developers meet it most often importing from lazily-loaded packages (selenium.webdriver, vllm, litellm, scrapling, headroom.providers) where the name is misspelled, lives only in a submodule, or was renamed or removed in another release. The same exception class also enforces API rules — class-only pydantic metadata, read-only Selenium objects, unknown config fields — and can even wrap a missing dependency. Working out which of these you hit is the first step to the fix.
Distilled from 193 documented records across 45 repositories.
Background
AttributeError is the interpreter's native signal that an attribute lookup failed. There is no separate exception type for modules, classes, or instances, so libraries of every kind funnel very different problems into this one class. In the records collected here, the single largest producer is PEP 562 module-level __getattr__: packages such as selenium.webdriver, vllm, litellm, scrapling, poetry.utils.helpers, headroom.providers and SQLAlchemy's private sqlalchemy.orm.evaluator keep explicit lazy-import maps so that heavy submodules load on demand; only names registered in the map resolve on access, and every other name falls through to the standard "module ... has no attribute ..." message. From the caller's side this is indistinguishable from a genuine typo, which is also the most common cause.
A second group of records uses the same exception as a migration and deprecation tool. pydantic_core.core_schema, CPython's ast module, and poetry.utils.helpers keep small tables of deprecated names that emit a warning and redirect to a replacement, while names outside the table fail cleanly with AttributeError. Other libraries raise it deliberately to retire an API: crawl4ai raises it when the removed markdown_v2 attribute is touched or when legacy kwargs such as provider or api_token reach generate_schema(), and SQLAlchemy 2.0 renamed EvaluatorCompiler to _EvaluatorCompiler so old imports miss. In these records the same message text can mean "misspelled", "moved", or "deliberately removed"; a deprecation hint, when present, is the fastest way to tell them apart.
A third group enforces access rules through descriptors and validators rather than lookup failure. Pydantic's LazyClassAttribute raises when schema metadata such as model_fields is read on an instance instead of the class; Selenium's FedCM Account objects are read-only snapshots whose descriptor __set__ always throws; headroom's harness config validates assigned field names and rejects rebinding of its nested config objects; psycopg2's Identifier.string refuses multi-part identifiers (the plural .strings always works); and pandas' Arrow duration accessors raise AttributeError — rather than ValueError — precisely so the .dt dispatcher reports "not supported for this dtype". SQLAlchemy raises it when a concrete-inheritance subclass touches a relationship that was not redeclared on it, and when an association proxy's value_attr cannot be resolved to a directly mapped attribute. A couple of records even reuse the message wording as a printed fail-fast label for pipeline guards, which shows how recognizable the phrasing has become.
Finally, the exception can mask a different root cause. Langflow's generated lazy packages catch ModuleNotFoundError and ImportError from deferred imports and re-raise them as AttributeError("Could not import ... from ..."), so a missing transitive dependency surfaces as an attribute failure — the real error sits in the chained __cause__. Pydantic's migration shim raises a plain AttributeError for '__path__' as a structural guard so the import machinery treats submodules as non-packages; tooling that probes this attribute sees an error that is by design, not a bug. Message wording and strictness are library-specific: some raise the standard f-string naming the module and attribute, others name the exact field, embed a migration hint, or chain the original exception.
Common causes
- Misspelled or nonexistent name on a lazy package.PEP 562 __getattr__ resolves only the names in the library's lazy-import map (selenium.webdriver, vllm, litellm, scrapling, headroom.providers). Any name outside the map — a typo or a name that never existed — raises the standard 'module has no attribute' message.
- Name lives in a submodule, not the package root.The symbol exists in the installed library but is not re-exported at the level being accessed — for example scrapling fetcher classes under scrapling.fetchers, vllm engine internals under vllm.config, or selenium.webdriver.common.by.By. Top-level attribute access misses even though the import would succeed from the submodule.
- Attribute removed or renamed across versions.crawl4ai removed markdown_v2 in favor of markdown, pydantic_core deprecated its V1-era core_schema names, poetry moved download helpers to poetry.utils.download, and SQLAlchemy renamed EvaluatorCompiler to _EvaluatorCompiler. Code written against one release fails after upgrade with no hint unless the changelog is checked.
- Class-only attribute accessed on an instance.Pydantic's model_fields, model_computed_fields and similar metadata are descriptors that raise on instance access. Generic helpers that call getattr(obj, 'model_fields') without checking isinstance(obj, type) fail whenever they receive an instance.
- Assignment to read-only or unknown attributes.Selenium FedCM Account properties always throw on __set__; headroom's config rejects unknown field names and rebinding of the nested 'proxy'/'headroom' objects; crawl4ai's generate_schema rejects deprecated flat kwargs. These are deliberate guards, not lookup failures.
- Accessor applied to the wrong shape or dtype.psycopg2's Identifier.string refuses identifiers built from more than one part (use .strings); pandas duration accessors such as .dt.days raise on non-duration pyarrow dtypes; SQLAlchemy concrete-inheritance subclasses raise for relationships not redeclared on the subclass, and association proxies whose value_attr is not directly mapped raise on comparisons.
- Wrapped import failure.Langflow's generated lazy packages re-raise ModuleNotFoundError and ImportError as AttributeError('Could not import ...'). The attribute name is valid; the real cause — typically a missing transitive dependency of the component module — is in the chained exception.
- Tooling and introspection probes.Pickling, deepcopy, static analysis, and import machinery probe attributes such as __path__ or walk module namespaces, tripping lazy __getattr__ shims (pydantic's __path__ guard, sqlalchemy.orm.evaluator) even though the user's own code did nothing wrong.
What usually fixes it
- Verify names against the installed package before assuming a typo: call dir(module) or read __all__ / the lazy-import map, then import from the defining submodule instead of the package root.
- Inspect the chained exception (__cause__) when the message wraps an error — a missing dependency or an ImportError inside the module often hides behind the AttributeError text.
- Respect access rules: read class-only metadata on the class (type(obj).model_fields), treat read-only objects as snapshots and copy their fields into a dict before transforming, and use supported setters/builders instead of generic assignment.
- After upgrading a library, check the changelog and migration notes for renamed or removed names, pin the version you tested against, and code to that version's documented exports.
- Guard dynamic access: use getattr(module, name, None) for feature detection, isinstance(obj, type) checks in generic helpers, and importlib.util.find_spec() instead of probing __path__ directly.
Documented occurrences
- module {__name__!r} has no attribute {name!r}(sqlalchemy/sqlalchemy)
- module {__name__!r} has no attribute {attr_name!r}(langflow-ai/langflow)
- {self.name!r} attribute of {owner.__name__!r} is class-only(pydantic/pydantic)
- Could not import {attr_name!r} from {__name__!r}: {e}(langflow-ai/langflow)
- module {module!r} has no attribute {name!r}(pydantic/pydantic)
- unknown Headroom harness config field: {name}(headroomlabs-ai/headroom)
- module {__name__!r} has no attribute {name!r}(SeleniumHQ/selenium)
- module 'graphify' has no attribute {name!r}(Graphify-Labs/graphify)
- The 'markdown_v2' attribute is deprecated and has been removed. Please use 'markdown' instead, which now returns a MarkdownGenerationResult, with following properties: - raw_markdown: The raw markdown string - markdown_with_citations: The markdown string with citations - references_markdown: The markdown string with references - fit_markdown: The markdown string with fit text (unclecode/crawl4ai)
- the Identifier wraps more than one than one string(psycopg/psycopg2)
- module {__name__!r} has no attribute {name!r}(Graphify-Labs/graphify)
- Concrete %s does not implement attribute %r at the instance level. Add this property explicitly to %s.(sqlalchemy/sqlalchemy)
- module 'ast' has no attribute {attr!r}(python/cpython)
- {name} is read-only; mutate its fields instead(headroomlabs-ai/headroom)
- module 'selenium.webdriver' has no attribute {name!r}(SeleniumHQ/selenium)
- module '{__name__}' has no attribute '{name}'(browser-use/browser-use)
- module 'pydantic_core' has no attribute '{attr_name}'(pydantic/pydantic)
- unknown ProxyConfig field: {key}(headroomlabs-ai/headroom)
- Cannot set readonly attribute(SeleniumHQ/selenium)
- module {__name__!r} has no attribute {name!r}(D4Vinci/Scrapling)
…and 173 more across the corpus — use search.
Honest provenance: generated on 2026-08-15 from AI-assisted analysis of the linked records. See how records are made.