{"record":{"id":"b6d7b7832f98d77c","repo":"D4Vinci/Scrapling","slug":"storage-class-must-be-wrapped-with-lru-cache-decor","errorCode":null,"errorMessage":"Storage class must be wrapped with lru_cache decorator, see docs for info","messagePattern":"Storage class must be wrapped with lru_cache decorator, see docs for info","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scrapling/parser.py","lineNumber":176,"sourceCode":"\n            if self._is_text_node(root):\n                self.__adaptive_enabled = False\n                return\n\n        self.__adaptive_enabled = bool(adaptive)\n\n        if self.__adaptive_enabled:\n            if _storage is not None:\n                self._storage = _storage\n            else:\n                if not storage_args:\n                    storage_args = {\n                        \"storage_file\": __DEFAULT_DB_FILE__,\n                        \"url\": url,\n                    }\n\n                if not hasattr(storage, \"__wrapped__\"):\n                    raise ValueError(\"Storage class must be wrapped with lru_cache decorator, see docs for info\")\n\n                if not issubclass(storage.__wrapped__, StorageSystemMixin):  # pragma: no cover\n                    raise ValueError(\"Storage system must be inherited from class `StorageSystemMixin`\")\n\n                self._storage = storage(**storage_args)\n\n    def __getitem__(self, key: str) -> TextHandler:\n        if self._is_text_node(self._root):\n            raise TypeError(\"Text nodes do not have attributes\")\n        return self.attrib[key]\n\n    def __contains__(self, key: str) -> bool:\n        if self._is_text_node(self._root):\n            return False\n        return key in self.attrib\n\n    # Node functionalities, I wanted to move to a separate Mixin class, but it had a slight impact on performance\n    @staticmethod","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/parser.py#L158-L194","documentation":"ValueError raised when adaptive mode is enabled and a custom `storage` class is supplied that is not wrapped with functools.lru_cache. Scrapling requires `storage` to be an lru_cache-wrapped callable (it checks for `storage.__wrapped__`, which lru_cache sets) so the class can be cached/reused and later unwrapped to inspect the true type. A bare class or function fails the `hasattr(storage, '__wrapped__')` check.","triggerScenarios":"Selector(content=html, adaptive=True, storage=SQLiteStorage) where SQLiteStorage is a plain class; defining a custom StorageSystemMixin subclass and passing it directly without decorating it with @lru_cache; passing a lambda or instance instead of the decorated class.","commonSituations":"Writing a custom storage backend and missing the documented decorator step; upgrading scrapling versions where the lru_cache requirement was introduced; copying example code that omitted the decorator.","solutions":["Wrap the storage class with functools.lru_cache before passing: from functools import lru_cache; @lru_cache class MyStorage(StorageSystemMixin): ...","Or use the library's built-in storage class as shown in the docs, which is already wrapped.","Double-check you are passing the class (wrapped), not an instance."],"exampleFix":"# before\nclass MyStorage(StorageSystemMixin):\n    ...\nSelector(html, adaptive=True, storage=MyStorage)  # ValueError\n\n# after\nfrom functools import lru_cache\n@lru_cache\nclass MyStorage(StorageSystemMixin):\n    ...\nSelector(html, adaptive=True, storage=MyStorage)","handlingStrategy":"validation","validationCode":"from functools import lru_cache\n\ndef is_lru_wrapped(storage) -> bool:\n    return callable(storage) and hasattr(storage, \"__wrapped__\")\n\nassert is_lru_wrapped(storage), \"decorate the storage class with @lru_cache first\"\nSelector(content=html, adaptive=True, storage=storage)","typeGuard":"def is_valid_storage_class(storage: object) -> bool:\n    return callable(storage) and hasattr(storage, \"__wrapped__\")","tryCatchPattern":"try:\n    sel = Selector(content=html, adaptive=True, storage=storage)\nexcept ValueError as e:\n    if \"lru_cache\" in str(e):\n        storage = lru_cache(storage)\n        sel = Selector(content=html, adaptive=True, storage=storage)\n    else:\n        raise","preventionTips":["Always define custom storage as @lru_cache class MyStorage(StorageSystemMixin).","Pass the class, not an instance.","Copy the storage example from the scrapling docs verbatim, then customize."],"tags":["parser","adaptive","storage","validation","decorator"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}