D4Vinci/Scrapling · error · TypeError

Callable filter function must have at least one argument to

Error message

Callable filter function must have at least one argument to take `Selector` objects.

What it means

A callable passed to find_all() takes zero parameters, so it cannot receive the Selector objects it is supposed to filter. scrapling inspects the callable's signature with inspect.signature and requires at least one positional parameter.

Source

Thrown at scrapling/parser.py:743

                if not all(map(lambda x: isinstance(x, str), arg)):
                    raise TypeError("Nested Iterables are not accepted, only iterables of tag names are accepted")
                tags.update(set(arg))

            elif isinstance(arg, dict):
                if not all([(isinstance(k, str) and isinstance(v, str)) for k, v in arg.items()]):
                    raise TypeError(
                        "Nested dictionaries are not accepted, only string keys and string values are accepted"
                    )
                attributes.update(arg)

            elif isinstance(arg, re_Pattern):
                patterns.add(arg)

            elif callable(arg):
                if len(signature(arg).parameters) > 0:
                    functions.append(arg)
                else:
                    raise TypeError(
                        "Callable filter function must have at least one argument to take `Selector` objects."
                    )

            else:
                raise TypeError(f'Argument with type "{type(arg)}" is not accepted, please read the docs.')

        if not all([(isinstance(k, str) and isinstance(v, str)) for k, v in kwargs.items()]):
            raise TypeError("Only string values are accepted for arguments")

        for attribute_name, value in kwargs.items():
            # Only replace names for kwargs, replacing them in dictionaries doesn't make sense
            attribute_name = _whitelisted.get(attribute_name, attribute_name)
            attributes[attribute_name] = value

        # It's easier and faster to build a selector than traversing the tree
        tags = tags or set("*")
        for tag in tags:
            selector = tag

View on GitHub (pinned to 5d213a2d47)

Solutions

  1. Add the element parameter to the callable: def keep(s): return s.attrib.get('data-ok') == '1'.
  2. If using functools.partial, ensure at least one positional slot remains free for the Selector.
  3. Note that builtins with optional-only args may also trip the signature check — wrap them in a lambda taking one argument.

Example fix

# before
selector.find_all(lambda: True)  # TypeError

# after
selector.find_all(lambda s: True)
Defensive patterns

Strategy: validation

Validate before calling

from inspect import signature

def takes_at_least_one_arg(fn) -> bool:
    try:
        return len(signature(fn).parameters) > 0
    except (TypeError, ValueError):
        return False

assert takes_at_least_one_arg(my_filter), 'filter must accept a Selector argument'

Prevention

When it happens

Trigger: find_all(lambda: True) or find_all(some_zero_arg_function). Lambdas with one arg like lambda s: s.attrib.get('x') are fine.

Common situations: Refactoring a filter from a constant-returning helper; passing a functools.partial that consumed all positional arguments; passing a class's __init__-less instance method bound with no free parameters left.

Related errors


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