From: Mike Bayer Date: Sun, 17 Jun 2007 01:18:31 +0000 (+0000) Subject: - added Interval type to types.py [ticket:595] X-Git-Tag: rel_0_3_9~76 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ba5ecb379698830f4112a52d42cadbfeb5c8d09;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - added Interval type to types.py [ticket:595] --- diff --git a/CHANGES b/CHANGES index 58c6c48335..47346e93d9 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,7 @@ would not return selectable.c.col, if the selectable is a join of a table and another join involving the same table. messed up ORM decision making [ticket:593] + - added Interval type to types.py [ticket:595] - mysql - added 'fields' to reserved words [ticket:590] - oracle diff --git a/lib/sqlalchemy/types.py b/lib/sqlalchemy/types.py index ce642c1588..7b2d49c378 100644 --- a/lib/sqlalchemy/types.py +++ b/lib/sqlalchemy/types.py @@ -8,11 +8,12 @@ __all__ = [ 'TypeEngine', 'TypeDecorator', 'NullTypeEngine', '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: @@ -89,7 +90,7 @@ class TypeDecorator(AbstractType): 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__)) @@ -97,6 +98,15 @@ class TypeDecorator(AbstractType): 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.""" @@ -334,6 +344,60 @@ class PickleType(MutableType, TypeDecorator): 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 diff --git a/test/sql/testtypes.py b/test/sql/testtypes.py index 4da3760d20..9de8082353 100644 --- a/test/sql/testtypes.py +++ b/test/sql/testtypes.py @@ -337,6 +337,25 @@ class DateTest(AssertMixin): 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