Sqlalchemy表示自定义postgres范围类型

时间:2017-04-10 23:00:42

标签: python postgresql sqlalchemy flask-sqlalchemy psycopg2

我需要自定义范围类型。我试图代表每小时的范围。对于一周中的每一天,范围(datetime.time,datetime.time)而不是单独的TIME列,我希望尽可能访问Postgres / sqlalchemy范围运算符。

我正在寻找像TSRANGE这样的东西而不是正常时间(datetime.datetime,datetime.datetime)

在postgres本身,这非常有效。例如。

create type timerange as range (subtype = time);

create table schedule 
(
  id integer not null primary key,
  time_range timerange
);

insert into schedule 
values
(1, timerange(time '08:00', time '10:00', '[]')),
(2, timerange(time '10:00', time '12:00', '[]'));

select *
from schedule
where time_range @> time '09:00'

所以这就是问题所在。我如何表示我在SQLAlchemy中在Postgres中创建的这种自定义类型?子类TSRANGE,TypeDecorator on TIME,或者可能创建一个新的SQLALchemy UserDefinedType。我不太清楚要走哪条路。任何建议将不胜感激。谢谢!

1 个答案:

答案 0 :(得分:3)

要使用自定义范围类型need to dig a bit deeper

  

在实例化使用这些列类型的模型时,您应该传递您用于列类型的DBAPI驱动程序所期望的任何数据类型。对于psycopg2NumericRangeDateRangeDateTimeRangeDateTimeTZRange 或您已注册register_range()

换句话说,您必须使用DBAPI注册自定义范围类型 - 通常为psycopg2 - 并创建SQLAlchemy类型以匹配已注册的类型。 register_range()采用PostgreSQL range类型的名称,Range的(严格)子类和用于获取oid的连接/游标。它可以全局或本地向给定连接或光标注册新范围类型:

In [2]: import psycopg2.extras

创建模型实例时应使用的值类型:

In [3]: class TimeRange(psycopg2.extras.Range):
   ...:     pass
   ...: 

在SQLAlchemy中使用raw_connection()来获取基础psycopg2连接的代理。注册也许应该在实际实现中的设置函数中完成:

In [4]: conn = engine.raw_connection()

In [5]: cur = conn.cursor()

In [6]: psycopg2.extras.register_range('timerange', TimeRange, cur, globally=True)
Out[6]: <psycopg2._range.RangeCaster at 0x7f1c980dbe80>

In [7]: cur.close()

In [8]: conn.close()

接下来,创建SQLAlchemy范围类型以匹配已注册的TimeRangeTypeDecorator不合适,因为您没有使用现有类型。 UserDefinedType应该是所有全新类型的基础。对于范围运算符,包括RangeOperators mixin:

  

它适用于postgres方言中提供的所有范围类型,并且可以用于您自己创建的任何范围类型。

其余部分几乎都是直接从the predefined range types复制的:

In [11]: from sqlalchemy.dialects import postgresql

In [13]: from sqlalchemy import types as sqltypes

In [14]: class TIMERANGE(postgresql.ranges.RangeOperators, sqltypes.UserDefinedType):
    ...:     def get_col_spec(self, **kw):
    ...:         return 'timerange'

这只是反思所必需的。

In [16]: postgresql.base.ischema_names['timerange'] = TIMERANGE

然后只需创建表格并按正常方式使用:

In [17]: schedule = Table('schedule', metadata, autoload=True, autoload_with=engine)

In [18]: schedule
Out[18]: Table('schedule', MetaData(bind=Engine(postgresql:///sopython)), Column('id', INTEGER(), table=<schedule>, primary_key=True, nullable=False), Column('time_range', TIMERANGE(), table=<schedule>), schema=None)

In [19]: session.query(schedule).all()
Out[19]: 
[(1, TimeRange(datetime.time(8, 0), datetime.time(10, 0), '[]')),
 (2, TimeRange(datetime.time(10, 0), datetime.time(12, 0), '[]'))]

In [20]: session.query(schedule).\
    ...:     filter(schedule.c.time_range.contains(time(9, 0))).\
    ...:     all()
2017-04-11 10:01:23,864 INFO sqlalchemy.engine.base.Engine SELECT schedule.id AS schedule_id, schedule.time_range AS schedule_time_range 
FROM schedule 
WHERE schedule.time_range @> %(time_range_1)s
2017-04-11 10:01:23,864 INFO sqlalchemy.engine.base.Engine {'time_range_1': datetime.time(9, 0)}
Out[20]: [(1, TimeRange(datetime.time(8, 0), datetime.time(10, 0), '[]'))]