diff --git a/django_mysqlpool/__init__.py b/django_mysqlpool/__init__.py index 11b26c0..f524dc2 100644 --- a/django_mysqlpool/__init__.py +++ b/django_mysqlpool/__init__.py @@ -12,7 +12,7 @@ from functools import wraps -__version__ = "0.2.1" +__version__ = "0.3.0" def auto_close_db(f): diff --git a/django_mysqlpool/backends/mysqlpool/base.py b/django_mysqlpool/backends/mysqlpool/base.py index b8bd156..df85da3 100644 --- a/django_mysqlpool/backends/mysqlpool/base.py +++ b/django_mysqlpool/backends/mysqlpool/base.py @@ -5,13 +5,9 @@ from __future__ import (absolute_import, print_function, unicode_literals, division) -# Make ``Foo()`` work the same in Python 2 as it does in Python 3. -__metaclass__ = type - - +import collections import os - from django.conf import settings from django.db.backends.mysql import base from django.core.exceptions import ImproperlyConfigured @@ -22,6 +18,10 @@ raise ImproperlyConfigured("Error loading SQLAlchemy module: %s" % e) +# Make ``Foo()`` work the same in Python 2 as it does in Python 3. +__metaclass__ = type + + # Global variable to hold the actual connection pool. MYSQLPOOL = None # Default pool type (QueuePool, SingletonThreadPool, AssertionPool, NullPool, @@ -32,15 +32,6 @@ DEFAULT_POOL_TIMEOUT = 119 -def isiterable(value): - """Determine whether ``value`` is iterable.""" - try: - iter(value) - return True - except TypeError: - return False - - class OldDatabaseProxy(): """Saves a reference to the old connect function. @@ -58,22 +49,51 @@ def connect(self, **kwargs): return self.old_connect(**kwargs) -class HashableDict(dict): +class HashableDict(collections.OrderedDict): """A dictionary that is hashable. This is not generally useful, but created specifically to hold the ``conv`` parameter that needs to be passed to MySQLdb. + + HT to `Alex Martelli`_ for the idea of using a shared ``__key`` function. + + .. _Alex Martelli: https://stackoverflow.com/a/1151686 """ + @staticmethod + def _is_iterable(test_me): + """Determine whether ``test_me`` is iterable.""" + try: + iter(test_me) + return True + except TypeError: + return False + + @staticmethod + def _tuplefy_as_needed(val): + return tuple(val) if HashableDict._is_iterable(val) else val + + def __key(self): + return tuple((k, HashableDict._tuplefy_as_needed(v)) + for k, v in self.items()) + def __hash__(self): """Calculate the hash of this ``dict``. - The hash is determined by converting to a sorted tuple of key-value - pairs and hashing that. + The hash is determined by converting to a sorted tuple of + key-value pairs and hashing that. + """ + return hash(self.__key()) + + def __eq__(self, other): + """Determine whether ``other`` is equal to this ``dict``. + + The implementation of this comparison uses the same underlying + mechanism as ``__hash__``, in order to guarantee that ``==`` and + ``.hash`` are in sync. """ - items = [(n, tuple(v)) for n, v in self.items() if isiterable(v)] - return hash(tuple(items)) + return self.__key() == other.__key() # Define this here so Django can import it. diff --git a/setup.py b/setup.py index 776cd3e..8ba9329 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,7 @@ REQUIRES = [ - "sqlalchemy >=0.7, <1.0", + "SQLAlchemy >=1.0, <2.0", ]