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
- Convert the key to a string before declaring the document: str(id) or str(uuid).
- Use a stable canonical string form (e.g. str(uuid_obj) rather than bytes) so reconciliation matches across runs.
- 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
- Cast all document keys with str() (or str(uuid)) at the data-ingestion boundary
- Convert numeric/UUID primary keys to text in the source query
- Pick one canonical string form per entity and use it everywhere so reconciliation stays stable
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
- Index key must be a (db_key, index_name) tuple, got {key!r}
- timeout() requires a datetime.timedelta
- {type(obj).__name__} cannot be used as a memoization key. Th
- Unsupported type for memoization key: {type(obj)!r}. Provide
- record_type must be a record type (dataclass, NamedTuple, Py
AI-assisted analysis of cocoindex-io/cocoindex@e84aa99b32 (2026-09-08).
Data as JSON: /api/errors/492d674374d7dd1d.
Report an issue: GitHub.