Published
A short post illustrating how to specify the type of a SQL expression addded to a model with a `column_property` in SQLAlchemy
I'm posting this as it may help anyone who's having the same problem I faced last week.
I was constructing a query with SQLAlchemy and I had some real
columns and one column_property. Mapping SQL expressions to a
model with hybrid properties or column_property is extremely
handy. Here's a link to the
official documentation,
However, one thing I couldn't explicitly find in the
documentation is how to specify the type of a column_property.
Usually, SQLAlchemy is able to infer the type of a property.
However, NullType is used as a default type for those cases
where a type cannot be determined.
For example, in the following example SQLAlchemy cannot determine
the type of the column best_foot, which returns the value of
the biggest value between left_foot and right_foot.
from sqlalchemy import Integer, funcfrom sqlalchemy.orm import ( column_property, declarative_base, mapped_column,)
Base = declarative_base()
class Player(Base): __tablename__ = "players" id = mapped_column(Integer, primary_key=True) left_foot = mapped_column(Integer) right_foot = mapped_column(Integer) best_foot = column_property( func.greatest(left_foot, right_foot), )
from sqlalchemy.types import NullTypeassert type(Player.best_foot.type) == NullTypeMore often than not, this doesn't make a big difference, and your
queries will work as expected; but when attempting a UNION ALL
query you might run into an error as Postgres expect all columns
in a UNION ALL query to be of the same type.
You can test this out real quick in psql with this query:
SELECT 1::int UNION ALL SELECT '1'::text;And you'll be greeted by an error message along these lines:
ERROR: UNION types integer and text cannot be matched.
While this simple query is obviously something you'll never find
in a real application, what I was trying to do involved a
UNION ALL between two queries with a column_property and
NullType kicked in.
Luckily, the solution is very simple, and since I've run into
this issue I always try to be explicit when creating a
column_property:
...
class Player(Base): __tablename__ = "players" id = mapped_column(Integer, primary_key=True) left_foot = mapped_column(Integer) right_foot = mapped_column(Integer) best_foot = column_property( func.greatest(left_foot, right_foot), func.cast(func.greatest(left_foot, right_foot), Integer), )
assert type(Player.best_foot.type) == IntegerThis will ensure that best_foot will have the specified type
and prevent a whole class of errors related to column types.
I hope this can help someone else out there!