cocoindex-io/cocoindex · error · ValueError

Primary key {schema.primary_key!r} value cannot be None.

Error message

Primary key {schema.primary_key!r} value cannot be None.

What it means

Guard in CollectionTarget.declare_row. The primary key value doubles as the zvec document id, which must be a concrete value; a None PK (e.g. an optional-typed field left unset in the declared row) cannot be converted to a document id, so the row would be silently unidentifiable. Raised when the row's PK field is None at declaration time. Ensure every declared row has a non-None primary key value.

Source

Thrown at python/cocoindex/connectors/zvec/_target.py:902

    _schema: CollectionSchema[RowT]

    def __init__(
        self,
        provider: coco.TargetStateProvider[_DocValue, None, coco.MaybePendingS],
        schema: CollectionSchema[RowT],
    ) -> None:
        self._provider = provider
        self._schema = schema

    def declare_row(self: "CollectionTarget[RowT]", *, row: RowT) -> None:
        """Declare a document (row) to be upserted to this collection.

        The primary-key value becomes the document id (converted to ``str``).
        """
        schema = self._schema
        pk_value = _row_get(row, schema.primary_key)
        if pk_value is None:
            raise ValueError(
                f"Primary key {schema.primary_key!r} value cannot be None."
            )
        doc_id = str(pk_value)

        vectors: dict[str, Any] = {}
        fields: dict[str, Any] = {}
        for name, col in schema.columns.items():
            if name == schema.primary_key:
                continue
            value = _row_get(row, name)
            if col.kind == "dense":
                vectors[name] = None if value is None else _to_float_list(value)
            elif col.kind == "sparse":
                vectors[name] = (
                    None
                    if value is None
                    else {int(k): float(v) for k, v in dict(value).items()}
                )

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Ensure every row has a non-None primary-key value before declaring.
  2. Generate the id in your @coco.fn before declare_row (e.g. uuid or hash of content).
  3. If the id can be absent, skip the row or use a sentinel/default value.

Example fix

// before
target.declare_row(row=Doc(id=None, vec=embedding))
// after
import uuid
target.declare_row(row=Doc(id=str(uuid.uuid4()), vec=embedding))
Defensive patterns

Strategy: validation

Validate before calling

pk = getattr(row, schema.primary_key, None)
if pk is None:
    raise ValueError(f"row missing primary key {schema.primary_key!r}")

Type guard

def has_pk(row, pk_name: str) -> bool:
    return getattr(row, pk_name, None) is not None

Try / catch

try:
    target.declare_row(row=row)
except ValueError as e:
    if "cannot be None" in str(e):
        logging.warning("skipping row with missing id: %r", row)

Prevention

When it happens

Trigger: Calling collection_target handler's declare_row(row=...) where the row's primary-key field is None (unset, or an Optional field defaulting to None).

Common situations: Optional id fields in dataclasses; rows built from upstream data with missing ids; autoincrement ids not yet assigned at declare time.

Related errors


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