changeset 84:566baac88764 MySQLdb

Ensure NULLs are returned as None by default. Return bad time values unchanged instead of None.
author Andy Dustman <adustman@users.sourceforge.net>
date Wed, 07 Sep 2011 20:59:54 -0400
parents e705129ff06f
children c16ae20b964d
files MySQLdb/converters.py MySQLdb/cursors.py MySQLdb/times.py
diffstat 3 files changed, 92 insertions(+), 69 deletions(-) [+]
line wrap: on
line diff
--- a/MySQLdb/converters.py	Tue Aug 31 22:28:13 2010 -0400
+++ b/MySQLdb/converters.py	Wed Sep 07 20:59:54 2011 -0400
@@ -9,8 +9,8 @@
 from _mysql import NULL
 from MySQLdb.constants import FIELD_TYPE, FLAG
 from MySQLdb.times import datetime_to_sql, timedelta_to_sql, \
-     timedelta_or_None, datetime_or_None, date_or_None, \
-     mysql_timestamp_converter
+     timedelta_or_orig, datetime_or_orig, date_or_orig, \
+     timestamp_or_orig
 from types import InstanceType
 import array
 import datetime
@@ -45,6 +45,24 @@
     """Convert None to NULL."""
     return NULL # duh
 
+def None_if_NULL(func):
+    if func is None: return func
+    def _None_if_NULL(value):
+        if value is None: return value
+        return func(value)
+    _None_if_NULL.__name__ = func.__name__+"_or_None_if_NULL"
+    return _None_if_NULL
+
+
+int_or_None_if_NULL = None_if_NULL(int)
+float_or_None_if_NULL = None_if_NULL(float)
+Decimal_or_None_if_NULL = None_if_NULL(Decimal)
+SET_to_Set_or_None_if_NULL = None_if_NULL(SET_to_Set)
+timestamp_or_None_if_NULL = None_if_NULL(timestamp_or_orig)
+datetime_or_None_if_NULL = None_if_NULL(datetime_or_orig)
+date_or_None_if_NULL = None_if_NULL(date_or_orig)
+timedelta_or_None_if_NULL = None_if_NULL(timedelta_or_orig)
+
 def object_to_quoted_sql(connection, obj):
     """Convert something into a SQL string literal."""
     if hasattr(obj, "__unicode__"):
@@ -89,21 +107,21 @@
 # character sets, etc.). This should always be used as the last
 # resort.
 simple_field_decoders = {
-    FIELD_TYPE.TINY: int,
-    FIELD_TYPE.SHORT: int,
-    FIELD_TYPE.LONG: int,
-    FIELD_TYPE.FLOAT: float,
-    FIELD_TYPE.DOUBLE: float,
-    FIELD_TYPE.DECIMAL: Decimal,
-    FIELD_TYPE.NEWDECIMAL: Decimal,
-    FIELD_TYPE.LONGLONG: int,
-    FIELD_TYPE.INT24: int,
-    FIELD_TYPE.YEAR: int,
-    FIELD_TYPE.SET: SET_to_Set,
-    FIELD_TYPE.TIMESTAMP: mysql_timestamp_converter,
-    FIELD_TYPE.DATETIME: datetime_or_None,
-    FIELD_TYPE.TIME: timedelta_or_None,
-    FIELD_TYPE.DATE: date_or_None,   
+    FIELD_TYPE.TINY: int_or_None_if_NULL,
+    FIELD_TYPE.SHORT: int_or_None_if_NULL,
+    FIELD_TYPE.LONG: int_or_None_if_NULL,
+    FIELD_TYPE.FLOAT: float_or_None_if_NULL,
+    FIELD_TYPE.DOUBLE: float_or_None_if_NULL,
+    FIELD_TYPE.DECIMAL: Decimal_or_None_if_NULL,
+    FIELD_TYPE.NEWDECIMAL: Decimal_or_None_if_NULL,
+    FIELD_TYPE.LONGLONG: int_or_None_if_NULL,
+    FIELD_TYPE.INT24: int_or_None_if_NULL,
+    FIELD_TYPE.YEAR: int_or_None_if_NULL,
+    FIELD_TYPE.SET: SET_to_Set_or_None_if_NULL,
+    FIELD_TYPE.TIMESTAMP: timestamp_or_None_if_NULL,
+    FIELD_TYPE.DATETIME: datetime_or_None_if_NULL,
+    FIELD_TYPE.TIME: timedelta_or_None_if_NULL,
+    FIELD_TYPE.DATE: date_or_None_if_NULL,
 }
 
 # Decoder protocol
--- a/MySQLdb/cursors.py	Tue Aug 31 22:28:13 2010 -0400
+++ b/MySQLdb/cursors.py	Wed Sep 07 20:59:54 2011 -0400
@@ -194,9 +194,9 @@
                 self.errorhandler(self, TypeError, msg)
         except:
             exc, value, traceback = sys.exc_info()
-            del traceback
             self.messages.append((exc, value))
             self.errorhandler(self, exc, value)
+            del traceback
 
         if not self._defer_warnings:
             self._warning_check()
--- a/MySQLdb/times.py	Tue Aug 31 22:28:13 2010 -0400
+++ b/MySQLdb/times.py	Wed Sep 07 20:59:54 2011 -0400
@@ -2,6 +2,9 @@
 times module
 ------------
 
+WARNING: The doctests only pass if you're in the right timezone and
+daylight savings time setting. XXX
+
 This module provides some help functions for dealing with MySQL data.
 Most of these you will not have to use directly.
 
@@ -20,7 +23,7 @@
     """Convert UNIX ticks into a date instance.
     
       >>> DateFromTicks(1172466380)
-      datetime.date(2007, 2, 25)
+      datetime.date(2007, 2, 26)
       >>> DateFromTicks(0)
       datetime.date(1969, 12, 31)
       >>> DateFromTicks(2**31-1)
@@ -34,7 +37,7 @@
     """Convert UNIX ticks into a time instance.
     
       >>> TimeFromTicks(1172466380)
-      datetime.time(23, 6, 20)
+      datetime.time(0, 6, 20)
       >>> TimeFromTicks(0)
       datetime.time(18, 0)
       >>> TimeFromTicks(2**31-1)
@@ -81,20 +84,20 @@
     """
     return obj.strftime("%Y-%m-%d %H:%M:%S")
 
-def datetime_or_None(obj):
+def datetime_or_orig(obj):
     """Returns a DATETIME or TIMESTAMP column value as a datetime object:
     
-      >>> datetime_or_None('2007-02-25 23:06:20')
+      >>> datetime_or_orig('2007-02-25 23:06:20')
       datetime.datetime(2007, 2, 25, 23, 6, 20)
-      >>> datetime_or_None('2007-02-25T23:06:20')
+      >>> datetime_or_orig('2007-02-25T23:06:20')
       datetime.datetime(2007, 2, 25, 23, 6, 20)
     
-    Illegal values are returned as None:
+    Illegal values are returned unchanged:
     
-      >>> datetime_or_None('2007-02-31T23:06:20') is None
-      True
-      >>> datetime_or_None('0000-00-00 00:00:00') is None
-      True
+      >>> datetime_or_orig('2007-02-31T23:06:20')
+      '2007-02-31T23:06:20'
+      >>> datetime_or_orig('0000-00-00 00:00:00')
+      '0000-00-00 00:00:00'
    
     """
     if ' ' in obj:
@@ -102,26 +105,26 @@
     elif 'T' in obj:
         sep = 'T'
     else:
-        return date_or_None(obj)
+        return date_or_orig(obj)
 
     try:
         ymd, hms = obj.split(sep, 1)
         return datetime(*[ int(x) for x in ymd.split('-')+hms.split(':') ])
     except ValueError:
-        return date_or_None(obj)
+        return obj
 
-def timedelta_or_None(obj):
+def timedelta_or_orig(obj):
     """Returns a TIME column as a timedelta object:
 
-      >>> timedelta_or_None('25:06:17')
+      >>> timedelta_or_orig('25:06:17')
       datetime.timedelta(1, 3977)
-      >>> timedelta_or_None('-25:06:17')
+      >>> timedelta_or_orig('-25:06:17')
       datetime.timedelta(-2, 83177)
       
-    Illegal values are returned as None:
+    Illegal values are returned unchanged:
     
-      >>> timedelta_or_None('random crap') is None
-      True
+      >>> timedelta_or_orig('random crap')
+      'random crap'
    
     Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
     can accept values as (+|-)DD HH:MM:SS. The latter format will not
@@ -141,20 +144,20 @@
         else:
             return tdelta
     except ValueError:
-        return None
+        return obj
 
-def time_or_None(obj):
+def time_or_orig(obj):
     """Returns a TIME column as a time object:
 
-      >>> time_or_None('15:06:17')
+      >>> time_or_orig('15:06:17')
       datetime.time(15, 6, 17)
       
-    Illegal values are returned as None:
+    Illegal values are returned unchanged:
  
-      >>> time_or_None('-25:06:17') is None
-      True
-      >>> time_or_None('random crap') is None
-      True
+      >>> time_or_orig('-25:06:17')
+      '-25:06:17'
+      >>> time_or_orig('random crap')
+      'random crap'
    
     Note that MySQL always returns TIME columns as (+|-)HH:MM:SS, but
     can accept values as (+|-)DD HH:MM:SS. The latter format will not
@@ -171,26 +174,26 @@
         return time(hour=int(hour), minute=int(minute), second=int(second),
                     microsecond=int(modf(float(second))[0]*1000000))
     except ValueError:
-        return None
+        return obj
 
-def date_or_None(obj):
+def date_or_orig(obj):
     """Returns a DATE column as a date object:
 
-      >>> date_or_None('2007-02-26')
+      >>> date_or_orig('2007-02-26')
       datetime.date(2007, 2, 26)
       
-    Illegal values are returned as None:
+    Illegal values are returned unchanged:
  
-      >>> date_or_None('2007-02-31') is None
-      True
-      >>> date_or_None('0000-00-00') is None
-      True
+      >>> date_or_orig('2007-02-31')
+      '2007-02-31'
+      >>> date_or_orig('0000-00-00')
+      '0000-00-00'
     
     """
     try:
         return date(*map(int, obj.split('-', 2)))
     except ValueError:
-        return None
+        return obj
 
 def datetime_to_sql(connection, obj):
     """Format a DateTime object as an ISO timestamp."""
@@ -200,39 +203,41 @@
     """Format a timedelta as an SQL literal."""
     return connection.string_literal(timedelta_to_str(obj))
 
-def mysql_timestamp_converter(timestamp):
+def timestamp_or_orig(timestamp):
     """Convert a MySQL TIMESTAMP to a Timestamp object.
 
     MySQL >= 4.1 returns TIMESTAMP in the same format as DATETIME:
     
-      >>> mysql_timestamp_converter('2007-02-25 22:32:17')
+      >>> timestamp_or_orig('2007-02-25 22:32:17')
       datetime.datetime(2007, 2, 25, 22, 32, 17)
     
     MySQL < 4.1 uses a big string of numbers:
     
-      >>> mysql_timestamp_converter('20070225223217')
+      >>> timestamp_or_orig('20070225223217')
       datetime.datetime(2007, 2, 25, 22, 32, 17)
     
-    Illegal values are returned as None:
+    Illegal values are returned unchanged:
     
-      >>> mysql_timestamp_converter('2007-02-31 22:32:17') is None
-      True
-      >>> mysql_timestamp_converter('00000000000000') is None
-      True
+      >>> timestamp_or_orig('2007-02-31 22:32:17')
+      '2007-02-31 22:32:17'
+      >>> timestamp_or_orig('00000000000000')
+      '00000000000000'
       
     """
-    if timestamp[4] == '-':
-        return datetime_or_None(timestamp)
-    timestamp += "0"*(14-len(timestamp)) # padding
-    year, month, day, hour, minute, second = \
-        int(timestamp[:4]), int(timestamp[4:6]), int(timestamp[6:8]), \
-        int(timestamp[8:10]), int(timestamp[10:12]), int(timestamp[12:14])
+    try:
+        if timestamp[4] == '-':
+            return datetime_or_orig(timestamp)
+        timestamp += "0"*(14-len(timestamp)) # padding
+        year, month, day, hour, minute, second = \
+            int(timestamp[:4]), int(timestamp[4:6]), int(timestamp[6:8]), \
+            int(timestamp[8:10]), int(timestamp[10:12]), int(timestamp[12:14])
+    except IndexError:
+        return timestamp
     try:
         return datetime(year, month, day, hour, minute, second)
     except ValueError:
-        return None
+        return timestamp
 
 if __name__ == "__main__":
     import doctest
     doctest.testmod()
-    
\ No newline at end of file