{"record":{"id":"bac2f9af8aeb29f4","repo":"D4Vinci/Scrapling","slug":"storage-system-must-be-inherited-from-class-stora","errorCode":null,"errorMessage":"Storage system must be inherited from class `StorageSystemMixin`","messagePattern":"Storage system must be inherited from class `StorageSystemMixin`","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"scrapling/parser.py","lineNumber":179,"sourceCode":"                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\n    def _is_text_node(\n        element: HtmlElement | _ElementUnicodeResult,\n    ) -> bool:","sourceCodeStart":161,"sourceCodeEnd":197,"githubUrl":"https://github.com/D4Vinci/Scrapling/blob/5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f/scrapling/parser.py#L161-L197","documentation":"ValueError raised after the lru_cache check: scrapling unwraps the decorated `storage` argument via `storage.__wrapped__` and verifies it is a subclass of `StorageSystemMixin`. If the wrapped callable is some other class (arbitrary lru_cache-wrapped class/function), the storage system does not implement the interface Selector needs and is rejected. Marked `# pragma: no cover` — an internal invariant in normal use.","triggerScenarios":"Passing an lru_cache-wrapped class that does not inherit from scrapling's StorageSystemMixin, e.g. @lru_cache class MyDB: ... used with Selector(..., adaptive=True, storage=MyDB); or passing the decorator itself rather than a storage class.","commonSituations":"Adapting a third-party storage/key-value class and forgetting the mixin; following the lru_cache requirement but skipping the inheritance requirement from the docs.","solutions":["Inherit from scrapling's StorageSystemMixin: class MyStorage(StorageSystemMixin): ... and keep the @lru_cache wrapper.","Implement the required save/load interface defined by StorageSystemMixin on your subclass.","If you do not need custom persistence, drop the storage argument and use the default storage."],"exampleFix":"# before\nfrom functools import lru_cache\n@lru_cache\nclass MyStorage:  # no mixin\n    ...\n\n# after\nfrom functools import lru_cache\nfrom scrapling.core.storage import StorageSystemMixin\n@lru_cache\nclass MyStorage(StorageSystemMixin):\n    ...","handlingStrategy":"type-guard","validationCode":"from scrapling.core.storage import StorageSystemMixin\n\ndef is_storage_subclass(storage) -> bool:\n    inner = getattr(storage, \"__wrapped__\", None)\n    return isinstance(inner, type) and issubclass(inner, StorageSystemMixin)\n\nassert is_storage_subclass(storage), \"storage must subclass StorageSystemMixin (and be @lru_cache wrapped)\"","typeGuard":"def is_valid_storage(storage: object) -> bool:\n    inner = getattr(storage, \"__wrapped__\", None)\n    return isinstance(inner, type) and issubclass(inner, StorageSystemMixin)","tryCatchPattern":"try:\n    sel = Selector(content=html, adaptive=True, storage=storage)\nexcept ValueError as e:\n    if \"StorageSystemMixin\" in str(e):\n        raise ConfigError(\"custom storage must inherit StorageSystemMixin\") from e\n    raise","preventionTips":["Inherit StorageSystemMixin on every custom storage class.","Implement its full interface (save/load hooks) before use.","If custom persistence is not needed, omit storage and use the default."],"tags":["parser","adaptive","storage","inheritance","validation"],"backgroundTag":null,"analyzedSha":"5d213a2d4764002bfc4fed33c32fe09fa8b0bf7f","analyzedAt":"2026-08-14T22:23:09.440Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}