Skip to content

Commit b12b00c

Browse files
committed
WL#14679: Allow custom class for data type conversion in Django backend
The Connector/Python Django backend doesn't allow a custom class used for data type conversion like in the regular connector, which uses `MySQLConnectionAbstract.set_converter_class()`. This worklog implements the infrastructure required to enable the usage of a user-defined custom class to convert non-default data types.
1 parent 91e901d commit b12b00c

File tree

3 files changed

+99
-6
lines changed

3 files changed

+99
-6
lines changed

CHANGES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ v8.0.29
1212
=======
1313

1414
- WL#14824: Remove Python 3.6 support
15+
- WL#14679: Allow custom class for data type conversion in Django backend
1516

1617
v8.0.28
1718
=======

lib/mysql/connector/django/base.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -276,12 +276,22 @@ class DatabaseWrapper(BaseDatabaseWrapper):
276276
def __init__(self, *args, **kwargs):
277277
super(DatabaseWrapper, self).__init__(*args, **kwargs)
278278

279-
try:
280-
self._use_pure = self.settings_dict['OPTIONS']['use_pure']
281-
except KeyError:
282-
self._use_pure = not HAVE_CEXT
283-
284-
self.converter = DjangoMySQLConverter()
279+
options = self.settings_dict.get('OPTIONS')
280+
if options:
281+
self._use_pure = options.get('use_pure', not HAVE_CEXT)
282+
converter_class = options.get(
283+
'converter_class',
284+
DjangoMySQLConverter,
285+
)
286+
if not issubclass(converter_class, DjangoMySQLConverter):
287+
raise ProgrammingError(
288+
'Converter class should be a subclass of '
289+
'mysql.connector.django.base.DjangoMySQLConverter'
290+
)
291+
self.converter = converter_class()
292+
else:
293+
self.converter = DjangoMySQLConverter()
294+
self._use_pure = not HAVE_CEXT
285295

286296
def __getattr__(self, attr):
287297
if attr.startswith("mysql_is"):

tests/test_django.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,13 @@
9999
# Have to load django.db to make importing db backend work for Django < 1.6
100100
import django.db # pylint: disable=W0611
101101
from django.db.backends.signals import connection_created
102+
from django.db.utils import DEFAULT_DB_ALIAS, load_backend
102103
from django.utils.safestring import SafeText
103104

104105
import mysql.connector
105106
from mysql.connector.django.introspection import FieldInfo
107+
from mysql.connector.conversion import MySQLConverter
108+
from mysql.connector.errors import ProgrammingError
106109

107110
if DJANGO_AVAILABLE:
108111
from mysql.connector.django.base import (
@@ -356,6 +359,85 @@ def test__DATETIME_to_python(self):
356359
settings.USE_TZ = False
357360

358361

362+
class CustomDjangoMySQLConverter(DjangoMySQLConverter):
363+
"""A custom Django MySQL converter."""
364+
365+
def _CUSTOMTYPE_to_python(self, value):
366+
try:
367+
return int(value)
368+
except ValueError:
369+
raise ValueError("Invalid value for CUSTOMTYPE conversion")
370+
371+
372+
class CustomDjangoMySQLConverterTests(tests.MySQLConnectorTests):
373+
"""Test the Django custom MySQL converter class."""
374+
375+
@staticmethod
376+
def create_connection(alias=DEFAULT_DB_ALIAS):
377+
django.db.connections.ensure_defaults(alias)
378+
django.db.connections.prepare_test_settings(alias)
379+
db = django.db.connections.databases[alias]
380+
backend = load_backend(db['ENGINE'])
381+
return backend.DatabaseWrapper(db, alias)
382+
383+
def test__TIME_to_python(self):
384+
value = b'10:11:12'
385+
django_converter = CustomDjangoMySQLConverter()
386+
self.assertEqual(
387+
datetime.time(10, 11, 12),
388+
django_converter._TIME_to_python(value, dsc=None),
389+
)
390+
391+
def test__DATETIME_to_python(self):
392+
value = b'1990-11-12 00:00:00'
393+
django_converter = CustomDjangoMySQLConverter()
394+
self.assertEqual(
395+
datetime.datetime(1990, 11, 12, 0, 0, 0),
396+
django_converter._DATETIME_to_python(value, dsc=None),
397+
)
398+
399+
settings.USE_TZ = True
400+
value = b'0000-00-00 00:00:00'
401+
django_converter = DjangoMySQLConverter()
402+
self.assertEqual(
403+
None,
404+
django_converter._DATETIME_to_python(value, dsc=None),
405+
)
406+
settings.USE_TZ = False
407+
408+
def test__CUSTOMTYPE_to_python(self):
409+
value = b'2021'
410+
django_converter = CustomDjangoMySQLConverter()
411+
self.assertEqual(
412+
2021,
413+
django_converter._CUSTOMTYPE_to_python(value),
414+
)
415+
416+
def test_invalid__CUSTOMTYPE_to_python(self):
417+
value = b'abc'
418+
django_converter = CustomDjangoMySQLConverter()
419+
self.assertRaises(
420+
ValueError,
421+
django_converter._CUSTOMTYPE_to_python,
422+
value,
423+
)
424+
425+
def test_invalid_converter_class(self):
426+
settings.DATABASES['default']['OPTIONS'] = {
427+
'converter_class': MySQLConverter,
428+
}
429+
self.assertRaises(ProgrammingError, self.create_connection)
430+
del settings.DATABASES['default']['OPTIONS']
431+
432+
def test_converter_class(self):
433+
settings.DATABASES['default']['OPTIONS'] = {
434+
'converter_class': CustomDjangoMySQLConverter,
435+
}
436+
cnx = self.create_connection()
437+
cnx.close()
438+
del settings.DATABASES['default']['OPTIONS']
439+
440+
359441
class BugOra20106629(tests.MySQLConnectorTests):
360442
"""CONNECTOR/PYTHON DJANGO BACKEND DOESN'T SUPPORT SAFETEXT"""
361443

0 commit comments

Comments
 (0)