sqlalchemy/sqlalchemy · error · ArgumentError

Unexpected value for attribute '{base.__name__}.{name}'. Exp

Error message

Unexpected value for attribute '{base.__name__}.{name}'. Expected a Column, not: {type(obj)}

What it means

_extract_columns_from_class iterates each non-dunder attribute of a TypedColumns subclass. An attribute is expected to be either None (annotation-only) or a Column instance. Any other value type (an int, a string, a ColumnClause, a relationship, a random object) is rejected with ArgumentError showing the actual type, because the column-extraction logic has no way to interpret it.

Source

Thrown at lib/sqlalchemy/sql/_annotated_cols.py:350

                    if anno_sqltype is None and not col.foreign_keys:
                        raise ArgumentError(
                            "Python typing annotation is required for "
                            f"attribute '{base.__name__}.{name}' when "
                            "primary argument(s) for Column construct are "
                            "None or not present"
                        )
                    elif anno_sqltype is not None:
                        col._set_type(anno_sqltype)

                if (
                    nullable is not _NoArg.NO_ARG
                    and col._user_defined_nullable is NULL_UNSPECIFIED
                    and not col.primary_key
                ):
                    col.nullable = nullable
                columns[name] = col
            else:
                raise ArgumentError(
                    f"Unexpected value for attribute '{base.__name__}.{name}'"
                    f". Expected a Column, not: {type(obj)}"
                )

    # Return columns as a list
    return list(columns.values())


@util.preload_module("sqlalchemy.sql.schema")
def _collect_annotation(
    cls: type[Any], name: str, module: str, raw_annotation: _AnnotationScanType
) -> _AnnotationScanType | Literal[_NoArg.NO_ARG]:
    Column = util.preloaded.sql_schema.Column

    _locals = {"Column": Column, "Named": Named}
    # _ClassScanAbstractConfig._collect_annotation & _extract_mapped_subtype
    try:
        annotation = sa_typing.de_stringify_annotation(

View on GitHub (pinned to d9b44cb731)

Solutions

  1. Remove the non-column attribute or move it elsewhere (constant, mixin, separate class)
  2. If it should be a column, use schema.Column: `from sqlalchemy import Column` and assign a Column instance
  3. If it is metadata for type checkers only, prefix with an underscore or place it outside the TypedColumns subclass

Example fix

# before
from sqlalchemy.sql import column  # wrong column
class Cols(TypedColumns):
    c = column('x')  # ColumnClause, not schema.Column -> rejected

# after
from sqlalchemy import Column, String
class Cols(TypedColumns):
    c = Column('x', String)
Defensive patterns

Strategy: type-guard

Validate before calling

from sqlalchemy import Column

def validate_attribute(name, obj) -> None:
    if obj is not None and not isinstance(obj, Column):
        raise TypeError(
            f"TypedColumns attribute {name!r} must be a Column, "
            f"got {type(obj).__name__}"
        )

Type guard

from sqlalchemy import Column

def is_valid_column_attr(obj) -> bool:
    """True only for None or a Column (valid TypedColumns attribute values)."""
    return obj is None or isinstance(obj, Column)

Prevention

When it happens

Trigger: Assigning a non-Column value as a class attribute in a TypedColumns subclass: `count = 5`, `label = 'default'`, `related = relationship(...)`, or `c = column('x')` (a ColumnClause, not schema.Column).

Common situations: Treating TypedColumns like an ORM mapped class and adding class-level constants, defaults, or relationship() calls. Also occurs when importing the wrong Column (e.g. sqlalchemy.sql.expression.column instead of sqlalchemy.Column).

Related errors


AI-assisted analysis of sqlalchemy/sqlalchemy@d9b44cb731 (2026-08-01). Data as JSON: /data/errors/3cfbc8128e19a1a8.json. Report an issue: GitHub.