cocoindex-io/cocoindex · error · TypeError

Document key must be a string, got {type(key)}

Error message

Document key must be a string, got {type(key)}

What it means

The Valkey row/document handler's `reconcile` requires document keys to be plain `str` because the key is embedded into a hash key string (`_make_hash_key(self._index_name, key)`) and passed to `_validate_name`. A non-string key (int, bytes, UUID, tuple) raises this TypeError before any name validation or network call.

Source

Thrown at python/cocoindex/connectors/valkey/_target.py:332

            batch = Batch(is_atomic=True)
            batch.delete([hash_key])
            batch.hset(hash_key, fields)  # type: ignore[arg-type]
            tasks.append(
                asyncio.ensure_future(self._client.exec(batch, raise_on_error=True))
            )

        await asyncio.gather(*tasks)

    def reconcile(
        self,
        key: coco.StableKey,
        desired_state: Document | coco.NonExistenceType,
        prev_possible_records: Collection[_DocumentFingerprint],
        prev_may_be_missing: bool,
        /,
    ) -> coco.TargetReconcileOutput[_DocumentAction, _DocumentFingerprint] | None:
        if not isinstance(key, str):
            raise TypeError(f"Document key must be a string, got {type(key)}")

        _validate_name(key, "doc_id")
        hash_key = _make_hash_key(self._index_name, key)

        if coco.is_non_existence(desired_state):
            if not prev_possible_records and not prev_may_be_missing:
                return None
            return coco.TargetReconcileOutput(
                action=_DocumentAction(hash_key=hash_key, fields=None),
                sink=self._sink,
                tracking_record=coco.NON_EXISTENCE,
            )

        # Build fingerprint from vector + payload
        target_fp = fingerprint_object(
            (desired_state.vector, desired_state.payload)
            if not isinstance(desired_state.vector, np.ndarray)
            else (desired_state.vector.tolist(), desired_state.payload)

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Convert the key to a string before declaring the document: str(id) or str(uuid).
  2. Use a stable canonical string form (e.g. str(uuid_obj) rather than bytes) so reconciliation matches across runs.
  3. If keys come from a database, cast the id column/field to text at read time.

Example fix

// before
await doc_handler.reconcile(123, doc_state, ...)

// after
await doc_handler.reconcile(str(123), doc_state, ...)
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(key, str):
    key = str(key)

Type guard

def is_str_key(k: object) -> TypeGuard[str]:
    return isinstance(k, str)

Try / catch

try:
    await handler.reconcile(key, state, ...)
except TypeError as e:
    if "Document key must be a string" in str(e):
        await handler.reconcile(str(key), state, ...)
    else:
        raise

Prevention

When it happens

Trigger: Declaring documents keyed by integers (Row(id=123, ...)), UUID objects, or bytes — i.e. calling reconcile with a key whose type is not str.

Common situations: Source data with integer primary keys (auto-increment DB ids) passed through unconverted; UUID objects from ORM models; keys read from JSON that were expected to be strings but parsed as numbers.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08). Data as JSON: /api/errors/492d674374d7dd1d. Report an issue: GitHub.