【发布时间】:2011-12-25 22:37:30
【问题描述】:
我们有 sqlite 数据库,而日期时间实际上是以 Excel 格式存储的(这是有充分理由的;这是我们系统选择的标准表示,并且 sqlite 数据库可以被多种语言/系统访问)
最近几个月一直在将 Python 引入其中并取得了巨大成功,SQLAlchemy 就是其中的一部分。 sqlite3 dbapi 层能够在 SQLite 缺少给定 SQL 函数的情况下快速绑定自定义 Python 函数,这一点特别值得赞赏。
我写了一个 ExcelDateTime 类型的装饰器,在从 sqlite 数据库中检索结果集时效果很好; Python 得到正确的日期时间。
但是,我在绑定自定义 python 函数时遇到了真正的问题,这些函数期望输入参数是 python 日期时间;我原以为这就是 bindparam 的用途,但我显然遗漏了一些东西,因为我无法让这种情况发挥作用。不幸的是,修改函数以从 excel 日期时间转换为 python 日期时间不是一种选择,也不能更改数据库中日期时间的表示,因为可能有多个系统/语言可以访问它。
下面的代码是一个独立的示例,可以“按原样”运行,并且代表了问题。自定义函数“get_month”已创建,但失败,因为它接收原始数据,而不是来自“Born”列的类型转换数据。最后你可以看到我到目前为止所尝试的,以及它吐出的错误......
我正在尝试做的事情是不可能的吗?或者是否有不同的方法来确保绑定函数接收到适当的 python 类型?这是迄今为止我唯一无法克服的问题,如果能找到解决方案真是太好了!
import sqlalchemy.types as types
from sqlalchemy import create_engine, Table, Column, Integer, String, MetaData
from sqlalchemy.sql.expression import bindparam
from sqlalchemy.sql import select, text
from sqlalchemy.interfaces import PoolListener
import datetime
# setup type decorator for excel<->python date conversions
class ExcelDateTime( types.TypeDecorator ):
impl = types.FLOAT
def process_result_value( self, value, dialect ):
lxdays = int( value )
lxsecs = int( round((value-lxdays) * 86400.0) )
if lxsecs == 86400:
lxsecs = 0
lxdays += 1
return ( datetime.datetime.fromordinal(lxdays+693594)
+ datetime.timedelta(seconds=lxsecs) )
def process_bind_param( self, value, dialect ):
if( value < 200000 ): # already excel float?
return value
elif( isinstance(value,datetime.date) ):
return value.toordinal() - 693594.0
elif( isinstance(value,datetime.datetime) ):
date_part = value.toordinal() - 693594.0
time_part = ((value.hour*3600) + (value.minute*60) + value.second) / 86400.0
return date_part + time_part # time part = day fraction
# create sqlite memory db via sqlalchemy
def get_month( dt ):
return dt.month
class ConnectionFactory( PoolListener ):
def connect( self, dbapi_con, con_record ):
dbapi_con.create_function( 'GET_MONTH',1,get_month )
eng = create_engine('sqlite:///:memory:',listeners=[ConnectionFactory()])
eng.dialect.dbapi.enable_callback_tracebacks( 1 ) # show better errors from user functions
meta = MetaData()
birthdays = Table('Birthdays', meta, Column('Name',String,primary_key=True), Column('Born',ExcelDateTime), Column('BirthMonth',Integer))
meta.create_all(eng)
dbconn = eng.connect()
dbconn.execute( "INSERT INTO Birthdays VALUES('Jimi Hendrix',15672,NULL)" )
# demonstrate the type decorator works and we get proper datetimes out
res = dbconn.execute( select([birthdays]) )
tuple(res)
# >>> ((u'Jimi Hendrix', datetime.datetime(1942, 11, 27, 0, 0)),)
# simple attempt (blows up with "AttributeError: 'float' object has no attribute 'month'")
dbconn.execute( text("UPDATE Birthdays SET BirthMonth = GET_MONTH(Born)") )
# more involved attempt( blows up with "InterfaceError: (InterfaceError) Error binding parameter 0 - probably unsupported type")
dbconn.execute( text( "UPDATE Birthdays SET BirthMonth = GET_MONTH(:Born)",
bindparams=[bindparam('Born',ExcelDateTime)],
typemap={'Born':ExcelDateTime} ),
Born=birthdays.c.Born )
非常感谢。
【问题讨论】:
标签: python sqlite datetime sqlalchemy