D4Vinci/Scrapling · error · AttributeError

module {__name__!r} has no attribute {name!r}

Error message

module {__name__!r} has no attribute {name!r}

What it means

Standard AttributeError raised by the module-level `__getattr__` in scrapling/fetchers/__init__.py. This module lazily imports its fetcher classes; when you access an attribute that is not in the `_LAZY_IMPORTS` mapping (e.g. a misspelled class name or a name that moved), the fallback `__getattr__` raises with the exact module and attribute names. Note this is normal Python behavior for unknown attributes, not a corruption of the package.

Source

Thrown at scrapling/fetchers/__init__.py:43

    "AsyncFetcher",
    "ProxyRotator",
    "FetcherSession",
    "DynamicFetcher",
    "DynamicSession",
    "AsyncDynamicSession",
    "StealthyFetcher",
    "StealthySession",
    "AsyncStealthySession",
]


def __getattr__(name: str) -> Any:
    if name in _LAZY_IMPORTS:
        module_path, class_name = _LAZY_IMPORTS[name]
        module = __import__(module_path, fromlist=[class_name])
        return getattr(module, class_name)
    else:
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def __dir__() -> list[str]:
    """Support for dir() and autocomplete."""
    return sorted(list(_LAZY_IMPORTS.keys()))

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Run `dir(scrapling.fetchers)` — the __dir__ hook lists exactly what is exported — and use one of those names.
  2. Check the changelog/release notes if the name worked in a previous version; it may have been renamed.
  3. Import the module directly and use getattr with a guarded default if you support multiple versions.
  4. Verify you are not looking for a helper that lives elsewhere (e.g. scrapling.engines...) rather than in scrapling.fetchers.

Example fix

# before
from scrapling.fetchers import AsyncFetcher  # AttributeError

# after
import scrapling.fetchers as sf
print(dir(sf))  # see valid names, e.g. StealthyFetcher, StealthySession, AsyncStealthySession
from scrapling.fetchers import StealthyFetcher
Defensive patterns

Strategy: type-guard

Validate before calling

import scrapling.fetchers as sf

name = "StealthyFetcher"
fetcher_cls = getattr(sf, name, None) or getattr(sf, "DynamicFetcher")  # explicit fallback
if fetcher_cls is None:
    raise ImportError(f"scrapling.fetchers has no {name}; available: {dir(sf)}")

Type guard

def fetcher_exists(name: str) -> bool:
    import scrapling.fetchers as sf
    return name in dir(sf)  # __dir__ lists lazily exported names

Try / catch

try:
    from scrapling.fetchers import StealthyFetcher
except AttributeError as e:
    raise ImportError(f"name moved/renamed in this scrapling version: {e}") from e

Prevention

When it happens

Trigger: `from scrapling.fetchers import Fetcher` when the class is actually named something else (e.g. `Fetcher` vs the real exported names like StealthyFetcher/StealthySession), `scrapling.fetchers.AsyncFetcher` for a name not in _LAZY_IMPORTS, or accessing a name that was renamed across a version bump.

Common situations: Upgrading scrapling and using an old class name that was removed/renamed; IDE autocompleting a plausible-but-wrong name; tutorials targeting a different scrapling version; `dir()` shows only lazily exported names.

Related errors


AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14). Data as JSON: /api/errors/f07c0418603182ba. Report an issue: GitHub.