D4Vinci/Scrapling · error · ValueError
Storage class must be wrapped with lru_cache decorator, see
Error message
Storage class must be wrapped with lru_cache decorator, see docs for info
What it means
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.
Source
Thrown at scrapling/parser.py:176
if self._is_text_node(root):
self.__adaptive_enabled = False
return
self.__adaptive_enabled = bool(adaptive)
if self.__adaptive_enabled:
if _storage is not None:
self._storage = _storage
else:
if not storage_args:
storage_args = {
"storage_file": __DEFAULT_DB_FILE__,
"url": url,
}
if not hasattr(storage, "__wrapped__"):
raise ValueError("Storage class must be wrapped with lru_cache decorator, see docs for info")
if not issubclass(storage.__wrapped__, StorageSystemMixin): # pragma: no cover
raise ValueError("Storage system must be inherited from class `StorageSystemMixin`")
self._storage = storage(**storage_args)
def __getitem__(self, key: str) -> TextHandler:
if self._is_text_node(self._root):
raise TypeError("Text nodes do not have attributes")
return self.attrib[key]
def __contains__(self, key: str) -> bool:
if self._is_text_node(self._root):
return False
return key in self.attrib
# Node functionalities, I wanted to move to a separate Mixin class, but it had a slight impact on performance
@staticmethodView on GitHub (pinned to 5d213a2d47)
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.
Example fix
# before
class MyStorage(StorageSystemMixin):
...
Selector(html, adaptive=True, storage=MyStorage) # ValueError
# after
from functools import lru_cache
@lru_cache
class MyStorage(StorageSystemMixin):
...
Selector(html, adaptive=True, storage=MyStorage) Defensive patterns
Strategy: validation
Validate before calling
from functools import lru_cache
def is_lru_wrapped(storage) -> bool:
return callable(storage) and hasattr(storage, "__wrapped__")
assert is_lru_wrapped(storage), "decorate the storage class with @lru_cache first"
Selector(content=html, adaptive=True, storage=storage) Type guard
def is_valid_storage_class(storage: object) -> bool:
return callable(storage) and hasattr(storage, "__wrapped__") Try / catch
try:
sel = Selector(content=html, adaptive=True, storage=storage)
except ValueError as e:
if "lru_cache" in str(e):
storage = lru_cache(storage)
sel = Selector(content=html, adaptive=True, storage=storage)
else:
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Storage system must be inherited from class `StorageSystemMi
- Unknown parser argument: "{key}"; maybe you meant {cls.parse
- You must pass a keyword to configure, current keywords: {cls
- Selector class needs HTML content, or root arguments to work
- content argument must be str or bytes, got {type(content)}
AI-assisted analysis of D4Vinci/Scrapling@5d213a2d47 (2026-08-14).
Data as JSON: /api/errors/b6d7b7832f98d77c.
Report an issue: GitHub.