{"record":{"id":"6b2f2be2f07033ad","repo":"D4Vinci/Scrapling","slug":"strategy-must-be-callable-got-type-strategy-n","errorCode":null,"errorMessage":"strategy must be callable, got {type(strategy).__name__}","messagePattern":"strategy must be callable, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"scrapling/engines/toolbelt/proxy_rotation.py","lineNumber":68,"sourceCode":"\n    def __init__(\n        self,\n        proxies: List[ProxyType],\n        strategy: RotationStrategy = cyclic_rotation,\n    ):\n        \"\"\"\n        Initialize the proxy rotator.\n\n        :param proxies: List of proxy URLs or Playwright-style proxy dicts.\n            - String format: \"http://proxy1:8080\" or \"http://user:pass@proxy:8080\"\n            - Dict format: {\"server\": \"http://proxy:8080\", \"username\": \"user\", \"password\": \"pass\"}\n        :param strategy: Rotation strategy function. Takes (proxies, current_index) and returns (proxy, next_index). Defaults to cyclic_rotation.\n        \"\"\"\n        if not proxies:\n            raise ValueError(\"At least one proxy must be provided\")\n\n        if not callable(strategy):\n            raise TypeError(f\"strategy must be callable, got {type(strategy).__name__}\")\n\n        self._strategy = strategy\n        self._lock = Lock()\n\n        # Validate and store proxies\n        self._proxies: List[ProxyType] = []\n        self._proxy_to_index: Dict[str, int] = {}  # O(1) lookup by unique key (server + username)\n        for i, proxy in enumerate(proxies):\n            if isinstance(proxy, (str, dict)):\n                if isinstance(proxy, dict) and \"server\" not in proxy:\n                    raise ValueError(\"Proxy dict must have a 'server' key\")\n\n                self._proxy_to_index[_get_proxy_key(proxy)] = i\n                self._proxies.append(proxy)\n            else:\n                raise TypeError(f\"Invalid proxy type: {type(proxy)}. Expected str or dict.\")\n\n        self._current_index = 0","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/engines/toolbelt/proxy_rotation.py#L50-L86","documentation":"Raised by `ProxyRotator.__init__` when the `strategy` argument is not callable. The rotation strategy is a function taking (proxies, current_index) and returning (proxy, next_index); passing anything that cannot be called (a string name, a number, None) fails this check.","triggerScenarios":"ProxyRotator(proxies=pool, strategy='random') (passing the name instead of a function), strategy=None, or strategy=random_rotation() (calling the function and passing its result instead of the function itself).","commonSituations":"Config files storing the strategy as a string that is passed through verbatim; accidentally invoking the strategy with () at the call site; copy-pasting example code that instantiates instead of references.","solutions":["Pass the function object itself: from scrapling.engines.toolbelt.proxy_rotation import random_rotation; ProxyRotator(proxies, strategy=random_rotation).","If config gives a string, map it to the function first: strategies = {'random': random_rotation, 'cyclic': cyclic_rotation}.","Do not add parentheses after the strategy name when passing it."],"exampleFix":"# before\nrotator = ProxyRotator(proxies, strategy='random_rotation')\n\n# after\nfrom scrapling.engines.toolbelt.proxy_rotation import random_rotation\nrotator = ProxyRotator(proxies, strategy=random_rotation)","handlingStrategy":"type-guard","validationCode":"assert callable(strategy), f\"strategy must be a function, got {type(strategy).__name__}\"\nrotator = ProxyRotator(proxies=pool, strategy=strategy)","typeGuard":"from typing import Any, Callable\n\ndef is_rotation_strategy(value: Any) -> bool:\n    return callable(value) and not isinstance(value, (str, bytes))","tryCatchPattern":"try:\n    ProxyRotator(pool, strategy=strategy)\nexcept TypeError as e:\n    if \"strategy must be callable\" in str(e):\n        from scrapling.engines.toolbelt.proxy_rotation import cyclic_rotation\n        strategy = cyclic_rotation  # safe default\n    else:\n        raise","preventionTips":["Pass function objects, never names or results (no parentheses).","Map config strings to functions via a dict lookup.","Test custom strategies with two-element pools in unit tests."],"tags":["proxy-rotation","type-error","callable","validation"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}