'INT', 'CHAR', 'VARCHAR', 'NCHAR', 'TEXT', 'FLOAT', 'DECIMAL',
'TIMESTAMP', 'DATETIME', 'CLOB', 'BLOB', 'BOOLEAN', 'String', 'Integer', 'SmallInteger','Smallinteger',
'Numeric', 'Float', 'DateTime', 'Date', 'Time', 'Binary', 'Boolean', 'Unicode', 'PickleType', 'NULLTYPE',
- 'SMALLINT', 'DATE', 'TIME'
+ 'SMALLINT', 'DATE', 'TIME','Interval'
]
from sqlalchemy import util, exceptions
import inspect, weakref
+import datetime as dt
try:
import cPickle as pickle
except:
try:
return self.impl_dict[dialect]
except:
- typedesc = dialect.type_descriptor(self.impl)
+ typedesc = self.load_dialect_impl(dialect)
tt = self.copy()
if not isinstance(tt, self.__class__):
raise exceptions.AssertionError("Type object %s does not properly implement the copy() method, it must return an object of type %s" % (self, self.__class__))
self.impl_dict[dialect] = tt
return tt
+ def load_dialect_impl(self, dialect):
+ """loads the dialect-specific implementation of this type.
+
+ by default calls dialect.type_descriptor(self.impl), but
+ can be overridden to provide different behavior.
+ """
+
+ return dialect.type_descriptor(self.impl)
+
def __getattr__(self, key):
"""Proxy all other undefined accessors to the underlying implementation."""
class Boolean(TypeEngine):
pass
+
+class Interval(TypeDecorator):
+ """Type to be used in Column statements to store python timedeltas.
+
+ If it's possible it uses native engine features to store timedeltas
+ (now it's only PostgreSQL Interval type), if there is no such it
+ fallbacks to DateTime storage with converting from/to timedelta on the fly
+
+ Converting is very simple - just use epoch(zero timestamp, 01.01.1970) as
+ base, so if we need to store timedelta = 1 day (24 hours) in database it
+ will be stored as DateTime = '2nd Jan 1970 00:00', see convert_bind_param
+ and convert_result_value to actual conversion code
+ """
+ impl = None
+
+ def __init__(self,*args,**kwargs):
+ #avoid of getting instance of None type in __init__ of TypeDecorator
+ pass
+
+ def load_dialect_impl(self, dialect):
+ import sqlalchemy.databases.postgres as pg
+ """Checks if engine has native implementation of timedelta python type,
+ if so it returns right class to handle it, if there is no native support,
+ it fallback to engine's DateTime implementation class
+ """
+
+ if self.__hasNativeImpl(dialect):
+ #For now, only PostgreSQL has native timedelta types support
+ return pg.PGInterval()
+ else:
+ #All others should fallback to DateTime
+ return dialect.type_descriptor(DateTime)
+
+ def __hasNativeImpl(self,dialect):
+ import sqlalchemy.databases.postgres as pg
+ return dialect.__class__ in [pg.PGDialect]
+
+ def convert_bind_param(self, value, dialect):
+ if not self.__hasNativeImpl(dialect):
+ tmpval = dt.datetime.utcfromtimestamp(0) + value
+ return self.impl.convert_bind_param(tmpval,dialect)
+ else:
+ return self.impl.convert_bind_param(value,dialect)
+
+ def convert_result_value(self, value, dialect):
+ retval = self.impl.convert_result_value(value,dialect)
+ if not self.__hasNativeImpl(dialect):
+ return retval - dt.datetime.utcfromtimestamp(0)
+ else:
+ return retval
+
+ def is_mutable(self):
+ #neither datetime, nor PGInterval are mutable types
+ return False
class FLOAT(Float):pass
class TEXT(String):pass
finally:
t.drop()
+class IntervalTest(AssertMixin):
+ def setUpAll(self):
+ global interval_table, metadata
+ metadata = BoundMetaData(testbase.db)
+ interval_table = Table("intervaltable", metadata,
+ Column("id", Integer, primary_key=True),
+ Column("interval", Interval),
+ )
+ metadata.create_all()
+
+ def tearDownAll(self):
+ metadata.drop_all()
+
+ def test_roundtrip(self):
+ delta = datetime.datetime(2006, 10, 5) - datetime.datetime(2005, 8, 17)
+ interval_table.insert().execute(interval=delta)
+ assert interval_table.select().execute().fetchone()['interval'] == delta
+
+
class TimezoneTest(AssertMixin):
"""test timezone-aware datetimes. psycopg will return a datetime with a tzinfo attached to it,
if postgres returns it. python then will not let you compare a datetime with a tzinfo to a datetime