{"record":{"id":"938963ed5b8cf523","repo":"cocoindex-io/cocoindex","slug":"memo-key-transform-for-kwargs-must-return-dict","errorCode":null,"errorMessage":"memo_key transform for **kwargs must return dict, got {type(transformed).__name__}","messagePattern":"memo_key transform for \\*\\*kwargs must return dict, got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/_internal/function.py","lineNumber":512,"sourceCode":"    for key, value in kwargs.items():\n        if key in memo_key_plan.keyword_specs:\n            key_fn = memo_key_plan.keyword_specs[key]\n            if key_fn is None:\n                continue  # Exclude this kwarg\n            new_kwargs[key] = key_fn(value)\n        else:\n            unmatched_kwargs[key] = value\n\n    # Apply varkw override if present (whole **kwargs parameter)\n    if not is_not_set(memo_key_plan.varkw_override):\n        if memo_key_plan.varkw_override is None:\n            # Exclude entire **kwargs\n            unmatched_kwargs = {}\n        else:\n            # Transform entire unmatched kwargs dict\n            transformed = memo_key_plan.varkw_override(unmatched_kwargs)\n            if not isinstance(transformed, dict):\n                raise TypeError(\n                    f\"memo_key transform for **kwargs must return dict, \"\n                    f\"got {type(transformed).__name__}\"\n                )\n            unmatched_kwargs = transformed\n\n    # Merge matched and unmatched kwargs\n    new_kwargs.update(unmatched_kwargs)\n\n    return final_args, new_kwargs\n\n\ndef _normalize_memo_key(\n    fn: Callable[..., Any], memo_key: MemoKeySpec\n) -> PreparedMemoKeySpec | None:\n    \"\"\"Validate and compile per-parameter memo-key overrides once.\"\"\"\n    if memo_key is None:\n        return None\n","sourceCodeStart":494,"sourceCodeEnd":530,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/_internal/function.py#L494-L530","documentation":"A bug guard in the memo-key machinery, not user input validation: when a @coco.fn(memo=True) function has a varkw_override for **kwargs, _apply_memo_key applies that transform to the collected unmatched keyword arguments and expects it to yield a dict (the fingerprintable key material for the whole kwargs bag). A transform returning anything else would silently produce an unusable memo key, so this TypeError fires to surface a malformed user-supplied transform function at call time. Fix the varkw_override transform so it returns a dict.","triggerScenarios":"@coco.fn with a memo_key transform for **kwargs (e.g. key \"**\" or varkw override) whose callable returns a list of pairs, None, or a non-dict mapping-like object, for a function accepting **kwargs.","commonSituations":"Returning sorted(kwargs.items()) (a list); returning a custom Mapping type; forgetting dict() around a filtered comprehension.","solutions":["Return a plain dict from the kwargs transform: dict(transformed)","If returning items pairs, wrap with dict(sorted(kwargs.items()))","Ensure the transform handles empty kwargs by returning {}"],"exampleFix":"# before\ndef key(**kw):\n    return sorted(kw.items())\n\n# after\ndef key(**kw):\n    return dict(sorted(kw.items()))","handlingStrategy":"validation","validationCode":"def as_dict(result):\n    if not isinstance(result, dict):\n        raise TypeError(\"kwargs transform must return dict\")\n    return result","typeGuard":"def is_dict(x: object) -> TypeGuard[dict]:\n    return isinstance(x, dict)","tryCatchPattern":null,"preventionTips":["Return dict(...), never sorted(kwargs.items()) lists","Annotate transform return type as dict","Cover kwargs transforms with unit tests"],"tags":["memoization","type-error","decorator-config"],"backgroundTag":"type-mismatch","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"}