]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- added Interval type to types.py [ticket:595]
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 17 Jun 2007 01:18:31 +0000 (01:18 +0000)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 17 Jun 2007 01:18:31 +0000 (01:18 +0000)
CHANGES
lib/sqlalchemy/types.py
test/sql/testtypes.py

diff --git a/CHANGES b/CHANGES
index 58c6c48335d84b45a99de415d2321bc5da426c59..47346e93d9f9bb6ba70a3f0d55eb40e4603bbd5b 100644 (file)
--- 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
index ce642c1588f6f36892e70ec7be94490baa7a85ad..7b2d49c3787e03891d18640305f7061a5898c94a 100644 (file)
@@ -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
index 4da3760d20d1c45d9bc69c39f45e5359c6227681..9de80823530c34831dc166db703c2b4044326f9c 100644 (file)
@@ -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