cocoindex-io/cocoindex · error · ValueError

Cannot specify both row_factory and row_type

Error message

Cannot specify both row_factory and row_type

What it means

PostgresSource row customization accepts either a row_factory callable or a row_type record class, but not both, since each fully determines how rows are built and which columns are resolved. Specifying both is ambiguous, so __init__ raises ValueError immediately.

Source

Thrown at python/cocoindex/connectors/postgres/_source.py:205

        table_name: str,
        columns: Sequence[str] | None = ...,
        pg_schema_name: str | None = ...,
        row_factory: None = ...,
        row_type: type[RowT],
    ) -> None: ...

    def __init__(
        self,
        pool: asyncpg.Pool,
        *,
        table_name: str,
        columns: Sequence[str] | None = None,
        pg_schema_name: str | None = None,
        row_factory: Callable[[dict[str, Any]], RowT] | None = None,
        row_type: type[RowT] | None = None,
    ) -> None:
        if row_factory is not None and row_type is not None:
            raise ValueError("Cannot specify both row_factory and row_type")

        # Determine columns based on row_type
        resolved_columns: Sequence[str] | None = columns
        if row_type is not None:
            if not is_record_type(row_type):
                raise TypeError(
                    f"row_type must be a record type (dataclass, NamedTuple, or Pydantic model), "
                    f"got {row_type}"
                )
            record_info = RecordType(row_type)
            field_names = [f.name for f in record_info.fields]
            field_set = frozenset(field_names)

            if columns is not None:
                # Validate that all specified columns exist in the record type
                invalid_cols = [c for c in columns if c not in field_set]
                if invalid_cols:
                    raise ValueError(

View on GitHub (pinned to e84aa99b32)

Solutions

  1. Remove either the row_factory or the row_type argument so only one is passed.
  2. If you need custom row construction, keep row_factory and drop row_type.
  3. If you want typed records (dataclass/NamedTuple/Pydantic), keep row_type and delete row_factory.

Example fix

// before
src = PostgresSource(table="users", row_factory=build_user, row_type=User)
// after
src = PostgresSource(table="users", row_type=User)
Defensive patterns

Strategy: validation

Validate before calling

assert not (row_factory is not None and row_type is not None), "Pass only one of row_factory or row_type"

Type guard

def has_conflict(kwargs: dict) -> bool:
    return kwargs.get("row_factory") is not None and kwargs.get("row_type") is not None

Try / catch

try:
    src = PostgresSource(table="t", row_factory=f, row_type=R)
except ValueError as e:
    if "both row_factory and row_type" in str(e):
        src = PostgresSource(table="t", row_type=R)

Prevention

When it happens

Trigger: Calling PostgresSource(...) with both a non-None row_factory and a non-None row_type argument, e.g. PostgresSource(table='t', row_factory=my_fn, row_type=MyRecord).

Common situations: Developers migrating from a factory function to a typed record leave the old row_factory kwarg in place while adding row_type for type hints; copying example code that sets one option without noticing the other.

Related errors


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