I’ve got a database table in a Postgres 15 database, it’s got two columns (amongst others), one is of type daterange
, the other is tstzrange
.
We’ve got some code that inserts to this table with raw SQL (via sqlalchemy core) by converting the sqlalchemy.dialects.postgresql.Range
to a psycopg2.extras.DateRange
or psycopg2.extras.DateTimeTzRange
. This works fine.
I’m trying to hook up the Sqlalchemy ORM but I’m getting the following error when I call commit()
on my session:
sqlalchemy.exc.ProgrammingError: (psycopg2.errors.UndefinedFunction)
function daterange(timestamp with time zone, unknown, unknown) does not exist
LINE 1: ...y.co.uk', '81740d75-d75a-4187-8b13-e28ac2e4dbf8', daterange(...
^
HINT: No function matches the given name and argument types.
You might need to add explicit type casts.
[SQL: INSERT INTO "user" (user_id, username, full_name, provider_subject_id, effective, asserted)
SELECT p0::VARCHAR, p1::VARCHAR, p2::VARCHAR, p3::VARCHAR,
p4::DATERANGE, p5::TSTZRANGE FROM (VALUES (%(user_id__0)s,
%(username__0)s, %(full_name__0)s,
%(pro ... 233 characters truncated ... en(p0, p1, p2, p3, p4, p5, sen_counter)
ORDER BY sen_counter RETURNING "user".id, "user".id AS id__1]
[parameters: {'effective__0': DateRange(datetime.datetime(2024, 4, 30, 13, 43, 31, 985502, tzinfo=<UTC>), None, '[)'),
'provider_subject_id__0': '81740d75-d75a-4187-8b13-e28ac2e4dbf8',
'full_name__0': '[email protected]',
'user_id__0': '81740d75-d75a-4187-8b13-e28ac2e4dbf8',
'asserted__0': DateTimeTZRange(datetime.datetime(2024, 4, 30, 13, 43, 31, 985513, tzinfo=<UTC>), None, '[)'),
'username__0': '[email protected]',
'effective__1': DateRange(datetime.datetime(2024, 4, 30, 13, 43, 31, 985502, tzinfo=<UTC>), None, '[)'),
'provider_subject_id__1': '81740d75-d75a-4187-8b13-e28ac2e4dbf8',
'full_name__1': '[email protected]',
'user_id__1': '81740d75-d75a-4187-8b13-e28ac2e4dbf8',
'asserted__1': DateTimeTZRange(datetime.datetime(2024, 4, 30, 13, 43, 31, 985513, tzinfo=<UTC>), None, '[)'),
'username__1': '[email protected]'}]
(Background on this error at: https://sqlalche.me/e/20/f405)
My UserModel
looks like:
class ModelBase(DeclarativeBase):
pass
class UserModel(ModelBase):
__tablename__ = "user"
id: Mapped[int] = mapped_column(primary_key=True)
user_id: Mapped[str] = mapped_column(String)
username: Mapped[str] = mapped_column(String)
full_name: Mapped[str] = mapped_column(String)
provider_subject_id: Mapped[str] = mapped_column(String)
effective: Mapped[DATERANGE] = Column(DATERANGE)
asserted: Mapped[TSTZRANGE] = Column(TSTZRANGE)
The user table is defined as so:
from sqlalchemy import MetaData
metadata_obj = MetaData()
Table("user", metadata_obj,
Column("id", PRIMARY_KEY_TYPE, primary_key=True),
Column("user_id", data_types.CODE_TYPE, nullable=False),
Column("role", data_types.CODE_TYPE, nullable=True),
Column("username", data_types.CODE_TYPE, nullable=False),
Column("full_name", data_types.SHORT_FIXED_STRING_TYPE, nullable=False),
Column("provider_subject_id", data_types.CODE_TYPE, nullable=True),
Column("effective", DATERANGE, nullable=False),
Column("asserted", TSTZRANGE, nullable=False)
)
I get the above error when I try to commit my session, like so:
session = session_factory()
user = UserModel(
# ...
asserted = DateTimeTZRange(asserted_value.lower, asserted_value.upper, bounds=asserted_value.bounds)
effective = DateRange(effective_value.lower, effective_value.upper, bounds=effective_value.bounds)
)
session.add(user)
session.commit() #💥
So my gut says this should work, because it’s working with raw SQL, but also I’m a python/sqlalchemy/postgres n00b, so what do I know.
My gut also thinks there’s some mapping special sauce I’m missing, but again… what do I know?
2
Answers
A simple working example to verify it can work:
Using
date
objects:I’m not sure that you should be using the psycopg2 datatypes here. The docs recommend using the PostgreSQL dialect’s
Range
type.Technically (at least per the SQLAlchemy docs, the model columns should be type-hinted as
Range
s of the appropriate Python type, but that doesn’t have any effect on the insert operation as far as I can tell.A complete example would look like this:
Producing this output: