D4Vinci/Scrapling · error · TypeError

page_setup must be callable, got {type(self.page_setup).__na

Error message

page_setup must be callable, got {type(self.page_setup).__name__}

What it means

Same __post_init__ validation as page_action but for `page_setup`: the value must be callable because it is invoked as a callback on the page (page_setup(page)) before navigation. A truthy non-callable (string, dict, result of a call) raises TypeError at config construction.

Source

Thrown at scrapling/engines/_browsers/_validators.py:101

    locale: str | None = None
    real_chrome: bool = False
    cdp_url: Optional[str] = None
    useragent: Optional[str] = None
    extra_flags: Optional[List[str]] = None
    blocked_domains: Optional[Set[str]] = None
    block_ads: bool = False
    retries: RetriesCount = 3
    retry_delay: Seconds = 1
    capture_xhr: str | None = None
    executable_path: Optional[str] = None
    dns_over_https: bool = False

    def __post_init__(self):  # pragma: no cover
        """Custom validation after msgspec validation"""
        if self.page_action and not callable(self.page_action):
            raise TypeError(f"page_action must be callable, got {type(self.page_action).__name__}")
        if self.page_setup and not callable(self.page_setup):
            raise TypeError(f"page_setup must be callable, got {type(self.page_setup).__name__}")
        if self.proxy and self.proxy_rotator:
            raise ValueError(
                "Cannot use 'proxy_rotator' together with 'proxy'. "
                "Use either a static proxy or proxy rotation, not both."
            )
        if self.proxy:
            self.proxy = construct_proxy_dict(self.proxy)
        if self.cdp_url:
            cdp_msg = _is_invalid_cdp_url(self.cdp_url)
            if cdp_msg:
                raise ValueError(cdp_msg)

        if not self.cookies:
            self.cookies = []
        if not self.extra_flags:
            self.extra_flags = []
        if not self.selector_config:
            self.selector_config = {}

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Pass the reference: page_setup=my_setup (no parentheses)
  2. Resolve string names from config to real functions before creating the session/fetch params
  3. For async sessions use an async def callback

Example fix

// before
session.fetch(url, page_setup=setup_page())  # calls now, passes result

// after
session.fetch(url, page_setup=setup_page)
Defensive patterns

Strategy: type-guard

Validate before calling

if page_setup is not None and not callable(page_setup):
    raise TypeError('page_setup must be callable')
session.fetch(url, page_setup=page_setup)

Type guard

def is_valid_page_setup(fn) -> bool:
    return fn is None or callable(fn)

Try / catch

try:
    session.fetch(url, page_setup=cb)
except TypeError as e:
    if 'page_setup must be callable' in str(e):
        cb = resolve_callback(cb); session.fetch(url, page_setup=cb)
    else:
        raise

Prevention

When it happens

Trigger: Passing page_setup as a string name, passing page_setup=setup_page() (calling instead of referencing), or passing a non-function object from dynamic config.

Common situations: Config-driven scraper setups where callbacks are serialized, or accidentally adding parentheses when passing the function.

Related errors


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