{"record":{"id":"2681be796ddab699","repo":"cocoindex-io/cocoindex","slug":"unsupported-type-for-memoization-key-type-obj-r","errorCode":null,"errorMessage":"Unsupported type for memoization key: {type(obj)!r}. Provide __coco_memo_key__() or register a memo key function.","messagePattern":"Unsupported type for memoization key: (.+?)\\. Provide __coco_memo_key__\\(\\) or register a memo key function\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/_internal/memo_fingerprint.py","lineNumber":366,"sourceCode":"        elts = [_canonicalize(e, _seen, state_methods) for e in obj]\n        elts.sort(key=_stable_sort_key)\n        return (\"set\", tuple(elts))\n\n    # 5) Dataclass instances\n    if _is_dataclass_instance(obj):\n        return _canonicalize_dataclass(obj, _seen, state_methods)\n\n    # 6) Pydantic v2 models\n    if _is_pydantic_model(obj):\n        return _canonicalize_pydantic(obj, _seen, state_methods)\n\n    # 7) Fallback\n    try:\n        payload = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)\n        # Tag to avoid colliding with user-provided raw bytes.\n        return (\"pickle\", payload)\n    except Exception:\n        raise TypeError(\n            f\"Unsupported type for memoization key: {type(obj)!r}. \"\n            \"Provide __coco_memo_key__() or register a memo key function.\"\n        ) from None\n\n\ndef _make_call_canonical(\n    func: typing.Callable[..., object],\n    args: tuple[object, ...],\n    kwargs: dict[str, object],\n    state_methods: list[StateFnEntry],\n    *,\n    version: str | int | None = None,\n    prefix_args: tuple[object, ...] = (),\n) -> Fingerprintable:\n    function_identity = (\n        canonical_module_name(func),\n        getattr(func, \"__qualname__\", None),\n    )","sourceCodeStart":348,"sourceCodeEnd":384,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/_internal/memo_fingerprint.py#L348-L384","documentation":"Memoization keys are built by canonicalizing every argument. When an object has no __coco_memo_key__() method, no registered memo key function, and cannot even be pickled as a fallback, the library raises TypeError because a stable key cannot be computed. This protects memoization correctness: without a stable key, cached results could be wrongly reused.","triggerScenarios":"Passing an object that defines no __coco_memo_key__(), is not a supported builtin/dataclass/pydantic type, and is not picklable (e.g. lambdas, open sockets, locks, generators, thread locals) as an argument to a @coco.fn(memo=True) function.","commonSituations":"Passing closures, local functions, database connections, asyncio objects, or third-party objects with exotic internals into memoized functions; often happens after refactoring a function signature to accept a richer object.","solutions":["Add a __coco_memo_key__() method to the class returning a stable, hashable representation of the identity-relevant state.","Call coco.register_memo_key_function(Type, fn) to register a key function for types you do not own.","Pass a fingerprintable proxy (str path, int seed, tuple of primitives) instead of the unpicklable object.","Turn off memoization for that function (memo=False) if stable keying is impossible."],"exampleFix":"// before\n@coco.fn(memo=True)\nasync def process(conn: asyncpg.Connection) -> int: ...  # connections are unpicklable\n\n// after\nclass Job:\n    def __coco_memo_key__(self):\n        return (\"Job\", self.job_id)\n\n@coco.fn(memo=True)\nasync def process(job: Job) -> int: ...","handlingStrategy":"type-guard","validationCode":"import pickle\nif not hasattr(obj, \"__coco_memo_key__\"):\n    try:\n        pickle.dumps(obj)\n    except Exception:\n        raise ValueError(f\"{type(obj).__name__} is not memo-keyable\")","typeGuard":"def is_memo_keyable(obj: object) -> bool:\n    return hasattr(obj, \"__coco_memo_key__\") or isinstance(obj, (str, int, float, bytes, bool, type(None), tuple, frozenset))","tryCatchPattern":"try:\n    result = await memoized_fn(obj)\nexcept TypeError as e:\n    if \"Unsupported type for memoization key\" in str(e):\n        result = await memoized_fn(extract_key(obj))\n    else:\n        raise","preventionTips":["Define __coco_memo_key__() on any custom class used as a memoized argument","Prefer passing stable identifiers instead of live objects","Never pass lambdas, locks, sockets, or asyncio primitives to memoized functions"],"tags":["python","memoization","pickle","type-error"],"backgroundTag":"invalid-argument-value","analyzedSha":"e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b","analyzedAt":"2026-09-08T15:59:19.997Z","contentChangedAt":"2026-09-08T15:59:19.997Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}