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
{
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 */
/*
*/
if (sscanf(str, "%4u-%2u-%2u %2u:%2u:%2u.%6u", &year, &month, &day,
&hour, &minute, &second, µsecond) < 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,
{
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 */
/*
*/
if (sscanf(str, "%2u:%2u:%2u.%6u", &hour, &minute, &second,
µsecond) < 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);
{
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);
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
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)
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())
--- /dev/null
+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"
+ )
+
+