]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- [bug] Improved error messages when a non-string
authorMike Bayer <mike_mp@zzzcomputing.com>
Sun, 22 Jan 2012 17:11:13 +0000 (12:11 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Sun, 22 Jan 2012 17:11:13 +0000 (12:11 -0500)
or invalid string is passed to any of the
date/time processors used by SQLite, including
C and Python versions.  [ticket:2382]
- changed the import model of processors.py so that we can
get at the pure python versions and C versions simultaneously
in tests.

CHANGES
lib/sqlalchemy/cextension/processors.c
lib/sqlalchemy/processors.py
test/engine/test_processors.py [new file with mode: 0644]

diff --git a/CHANGES b/CHANGES
index 37b06a110885694c53998a93134d60496e1b38ef..bec9a3252d9891c428726af7a9f93eeb75d356b3 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -45,6 +45,11 @@ CHANGES
     all DBAPIs support this yet, such as 
     psycopg2. [ticket:2371]
 
+  - [bug] Improved error messages when a non-string
+    or invalid string is passed to any of the
+    date/time processors used by SQLite, including
+    C and Python versions.  [ticket:2382]
+
 - sqlite
   - [bug] the "name" of an FK constraint in SQLite
     is reflected as "None", not "0" or other 
index 36745c8178e09c96f4727a914a5975e592850978..68758afc8c6d19aeae444e4283ac0e2136ee2d71 100644 (file)
@@ -66,13 +66,21 @@ str_to_datetime(PyObject *self, PyObject *arg)
 {
     const char *str;
     unsigned int year, month, day, hour, minute, second, microsecond = 0;
+    PyObject *err_repr;
 
     if (arg == Py_None)
         Py_RETURN_NONE;
 
     str = PyString_AsString(arg);
-    if (str == NULL)
+    if (str == NULL) {
+        err_repr = PyObject_Repr(arg);
+        PyErr_Format(
+                PyExc_ValueError,
+                "Couldn't parse datetime string '%s' - value is not a string.",
+                PyString_AsString(err_repr));
+        Py_DECREF(err_repr);
         return NULL;
+    }
 
     /* microseconds are optional */
     /*
@@ -82,7 +90,12 @@ str_to_datetime(PyObject *self, PyObject *arg)
     */
     if (sscanf(str, "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day,
                &hour, &minute, &second, &microsecond) < 6) {
-        PyErr_SetString(PyExc_ValueError, "Couldn't parse datetime string.");
+        err_repr = PyObject_Repr(arg);
+        PyErr_Format(
+                PyExc_ValueError,
+                "Couldn't parse datetime string: %s",
+                PyString_AsString(err_repr));
+        Py_DECREF(err_repr);
         return NULL;
     }
     return PyDateTime_FromDateAndTime(year, month, day,
@@ -94,13 +107,21 @@ str_to_time(PyObject *self, PyObject *arg)
 {
     const char *str;
     unsigned int hour, minute, second, microsecond = 0;
+    PyObject *err_repr;
 
     if (arg == Py_None)
         Py_RETURN_NONE;
 
     str = PyString_AsString(arg);
-    if (str == NULL)
+    if (str == NULL) {
+        err_repr = PyObject_Repr(arg);
+        PyErr_Format(
+                PyExc_ValueError,
+                "Couldn't parse time string '%s' - value is not a string.",
+                PyString_AsString(err_repr));
+        Py_DECREF(err_repr);
         return NULL;
+    }
 
     /* microseconds are optional */
     /*
@@ -110,7 +131,12 @@ str_to_time(PyObject *self, PyObject *arg)
     */
     if (sscanf(str, "%2u:%2u:%2u.%6u", &hour, &minute, &second,
                &microsecond) < 3) {
-        PyErr_SetString(PyExc_ValueError, "Couldn't parse time string.");
+        err_repr = PyObject_Repr(arg);
+        PyErr_Format(
+                PyExc_ValueError,
+                "Couldn't parse time string: %s",
+                PyString_AsString(err_repr));
+        Py_DECREF(err_repr);
         return NULL;
     }
     return PyTime_FromTime(hour, minute, second, microsecond);
@@ -121,16 +147,29 @@ str_to_date(PyObject *self, PyObject *arg)
 {
     const char *str;
     unsigned int year, month, day;
+    PyObject *err_repr;
 
     if (arg == Py_None)
         Py_RETURN_NONE;
 
     str = PyString_AsString(arg);
-    if (str == NULL)
+    if (str == NULL) {
+        err_repr = PyObject_Repr(arg);
+        PyErr_Format(
+                PyExc_ValueError,
+                "Couldn't parse date string '%s' - value is not a string.",
+                PyString_AsString(err_repr));
+        Py_DECREF(err_repr);
         return NULL;
+    }
 
     if (sscanf(str, "%4u-%2u-%2u", &year, &month, &day) != 3) {
-        PyErr_SetString(PyExc_ValueError, "Couldn't parse date string.");
+        err_repr = PyObject_Repr(arg);
+        PyErr_Format(
+                PyExc_ValueError,
+                "Couldn't parse date string: %s",
+                PyString_AsString(err_repr));
+        Py_DECREF(err_repr);
         return NULL;
     }
     return PyDate_FromDate(year, month, day);
index dd789a44ebdd8f8d304bf5d3bbc668407f1912ca..c4bac28347bada4dfe4c05e6f2d6cfbe6556cfd4 100644 (file)
@@ -24,9 +24,14 @@ def str_to_datetime_processor_factory(regexp, type_):
         if value is None:
             return None
         else:
-            m = rmatch(value)
+            try:
+                m = rmatch(value)
+            except TypeError:
+                raise ValueError("Couldn't parse %s string '%r' "
+                                "- value is not a string." % (type_.__name__ , value))
             if m is None:
-                raise ValueError("Couldn't parse %s string." % type_.__name__)
+                raise ValueError("Couldn't parse %s string: "
+                                "'%s'" % (type_.__name__ , value))
             return type_(*map(int, m.groups(0)))
     return process
 
@@ -36,29 +41,7 @@ def boolean_to_int(value):
     else:
         return int(value)
 
-try:
-    from sqlalchemy.cprocessors import UnicodeResultProcessor, \
-                                       DecimalResultProcessor, \
-                                       to_float, to_str, int_to_boolean, \
-                                       str_to_datetime, str_to_time, \
-                                       str_to_date
-
-    def to_unicode_processor_factory(encoding, errors=None):
-        # this is cumbersome but it would be even more so on the C side
-        if errors is not None:
-            return UnicodeResultProcessor(encoding, errors).process
-        else:
-            return UnicodeResultProcessor(encoding).process
-
-    def to_decimal_processor_factory(target_class, scale=10):
-        # Note that the scale argument is not taken into account for integer
-        # values in the C implementation while it is in the Python one. 
-        # For example, the Python implementation might return 
-        # Decimal('5.00000') whereas the C implementation will 
-        # return Decimal('5'). These are equivalent of course.
-        return DecimalResultProcessor(target_class, "%%.%df" % scale).process
-
-except ImportError:
+def py_fallback():
     def to_unicode_processor_factory(encoding, errors=None):
         decoder = codecs.getdecoder(encoding)
 
@@ -109,4 +92,30 @@ except ImportError:
                                                         datetime.datetime)
     str_to_time = str_to_datetime_processor_factory(TIME_RE, datetime.time)
     str_to_date = str_to_datetime_processor_factory(DATE_RE, datetime.date)
+    return locals()
+
+try:
+    from sqlalchemy.cprocessors import UnicodeResultProcessor, \
+                                       DecimalResultProcessor, \
+                                       to_float, to_str, int_to_boolean, \
+                                       str_to_datetime, str_to_time, \
+                                       str_to_date
+
+    def to_unicode_processor_factory(encoding, errors=None):
+        # this is cumbersome but it would be even more so on the C side
+        if errors is not None:
+            return UnicodeResultProcessor(encoding, errors).process
+        else:
+            return UnicodeResultProcessor(encoding).process
+
+    def to_decimal_processor_factory(target_class, scale=10):
+        # Note that the scale argument is not taken into account for integer
+        # values in the C implementation while it is in the Python one. 
+        # For example, the Python implementation might return 
+        # Decimal('5.00000') whereas the C implementation will 
+        # return Decimal('5'). These are equivalent of course.
+        return DecimalResultProcessor(target_class, "%%.%df" % scale).process
+
+except ImportError:
+    globals().update(py_fallback())
 
diff --git a/test/engine/test_processors.py b/test/engine/test_processors.py
new file mode 100644 (file)
index 0000000..48c0fb8
--- /dev/null
@@ -0,0 +1,107 @@
+from test.lib.testing import eq_, assert_raises, assert_raises_message
+from test.lib import testing, fixtures
+
+try:
+    from sqlalchemy.cprocessors import str_to_datetime as c_str_to_datetime, \
+                                    str_to_date as c_str_to_date, \
+                                    str_to_time as c_str_to_time
+    from sqlalchemy.processors import py_fallback
+    for key, value in py_fallback().items():
+        globals()["py_%s" % key] = value
+except:
+    from sqlalchemy.processors import str_to_datetime as py_str_to_datetime, \
+                                        str_to_date as py_str_to_date, \
+                                        str_to_time as py_str_to_time
+
+class DateProcessorTest(fixtures.TestBase):
+    @testing.requires.cextensions
+    def test_c_date_no_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse date string '2012' - value is not a string",
+            c_str_to_date, 2012
+        )
+
+    def test_py_date_no_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse date string '2012' - value is not a string",
+            py_str_to_date, 2012
+        )
+
+    @testing.requires.cextensions
+    def test_c_datetime_no_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse datetime string '2012' - value is not a string",
+            c_str_to_datetime, 2012
+        )
+
+    def test_py_datetime_no_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse datetime string '2012' - value is not a string",
+            py_str_to_datetime, 2012
+        )
+
+    @testing.requires.cextensions
+    def test_c_time_no_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse time string '2012' - value is not a string",
+            c_str_to_time, 2012
+        )
+
+    def test_py_time_no_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse time string '2012' - value is not a string",
+            py_str_to_time, 2012
+        )
+
+    @testing.requires.cextensions
+    def test_c_date_invalid_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse date string: '5:a'",
+            c_str_to_date, "5:a"
+        )
+
+    def test_py_date_invalid_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse date string: '5:a'",
+            py_str_to_date, "5:a"
+        )
+
+    @testing.requires.cextensions
+    def test_c_datetime_invalid_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse datetime string: '5:a'",
+            c_str_to_datetime, "5:a"
+        )
+
+    def test_py_datetime_invalid_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse datetime string: '5:a'",
+            py_str_to_datetime, "5:a"
+        )
+
+    @testing.requires.cextensions
+    def test_c_time_invalid_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse time string: '5:a'",
+            c_str_to_time, "5:a"
+        )
+
+    def test_py_time_invalid_string(self):
+        assert_raises_message(
+            ValueError,
+            "Couldn't parse time string: '5:a'",
+            py_str_to_time, "5:a"
+        )
+
+