{"record":{"id":"28ac77d0e4083c8d","repo":"cocoindex-io/cocoindex","slug":"expected-tuple-loc-got-type-value-name","errorCode":null,"errorMessage":"expected tuple{loc}, got {type(value).__name__}","messagePattern":"expected tuple(.+?), got (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"python/cocoindex/_internal/datatype.py","lineNumber":336,"sourceCode":"                except TypeError:\n                    continue\n            loc = f\" at {path}\" if path else \"\"\n            raise TypeError(\n                f\"expected {tp}{loc}, got {type(value).__name__}: {value!r}\"\n            )\n\n        return check_union\n\n    # Tuple types\n    if origin is tuple:\n        if len(args) == 2 and args[1] is Ellipsis:\n            # Variable-length: tuple[X, ...]\n            if args[0] is Any:\n\n                def check_var_tuple_any(value: Any, path: str) -> None:\n                    if not isinstance(value, tuple):\n                        loc = f\" at {path}\" if path else \"\"\n                        raise TypeError(\n                            f\"expected tuple{loc}, got {type(value).__name__}\"\n                        )\n\n                return check_var_tuple_any\n\n            elem_fn = _build_check_fn(args[0])\n\n            def check_var_tuple(value: Any, path: str) -> None:\n                if not isinstance(value, tuple):\n                    loc = f\" at {path}\" if path else \"\"\n                    raise TypeError(f\"expected tuple{loc}, got {type(value).__name__}\")\n                for i, elem in enumerate(value):\n                    elem_fn(elem, f\"{path}[{i}]\")\n\n            return check_var_tuple\n\n        if args:\n            # Fixed-length: tuple[X, Y, ...]","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/cocoindex-io/cocoindex/blob/e84aa99b3292c5270a4b313b2a7137ad9ce8ab3b/python/cocoindex/_internal/datatype.py#L318-L354","documentation":"For a tuple[X, ...] (variable-length, Any element) annotation, TypeChecker emits check_var_tuple_any, which only verifies the value is a tuple instance. Any non-tuple (list, generator, numpy array) raises TypeError with an optional `at <path>` suffix.","triggerScenarios":"Passing a list, generator, or ndarray where a tuple[X, ...]-annotated value is validated by TypeChecker (e.g. stable memo keys, table keys built from tuples).","commonSituations":"Building keys from list(...) instead of tuple(...); passing a numpy array or itertools result where a tuple was declared; JSON round-trips turning tuples into lists.","solutions":["Wrap the value in tuple(...) before passing it","Change the annotation to Sequence[X] or list[X] if order matters but tuple semantics don't","Materialize generators/arrays into a tuple at construction of the key"],"exampleFix":"// before\nkey = [\"a\", \"b\"]  # annotated tuple[str, ...]\n\n// after\nkey = (\"a\", \"b\")","handlingStrategy":"type-guard","validationCode":"if not isinstance(value, tuple):\n    value = tuple(value)","typeGuard":"def is_tuple(v: object) -> TypeGuard[tuple]:\n    return isinstance(v, tuple)","tryCatchPattern":"try:\n    checker.check(key)\nexcept TypeError as e:\n    if \"expected tuple\" in str(e):\n        key = tuple(key)\n    else:\n        raise","preventionTips":["Always construct keys with tuple(...), never list(...)","Materialize generators and numpy arrays into tuples before keying","Beware JSON round-trips converting tuples to lists; rebuild as tuples after deserialization"],"tags":["python","typing","tuple","validation"],"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"}