diff --git a/.github/workflows/ci.yml b/.github/workflows/ci_psycopg2.yml similarity index 94% rename from .github/workflows/ci.yml rename to .github/workflows/ci_psycopg2.yml index f54537a..fdfeb15 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci_psycopg2.yml @@ -1,4 +1,4 @@ -name: CI +name: CI-psycopg2 'on': push: branches: @@ -60,6 +60,7 @@ jobs: - uses: actions/checkout@v4 - run: pip install django==${{ matrix.django-version}} - run: pip install psycopg2 + - run: pip install psycogreen - run: pip install gevent - run: python setup.py -q install - - run: python runtests.py + - run: python runtests_psycopg2.py diff --git a/.github/workflows/ci_psycopg3.yml b/.github/workflows/ci_psycopg3.yml new file mode 100644 index 0000000..6e254c1 --- /dev/null +++ b/.github/workflows/ci_psycopg3.yml @@ -0,0 +1,56 @@ +name: CI-psycopg3 +'on': + push: + branches: + - master + pull_request: + branches: + - master +jobs: + build: + env: + POSTGRES_USER: postgres + PGPASSWORD: postgres + runs-on: '${{ matrix.os }}' + strategy: + matrix: + include: + - os: ubuntu-latest + python-version: '3.8' + django-version: '4.2.9' + - os: ubuntu-latest + python-version: '3.9' + django-version: '4.2.9' + - os: ubuntu-latest + python-version: '3.10' + django-version: '4.2.9' + - os: ubuntu-latest + python-version: '3.11' + django-version: '4.2.9' + - os: ubuntu-latest + python-version: '3.12' + django-version: '4.2.9' + services: + postgres: + image: postgres + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + steps: + - name: 'Set up Python ${{ matrix.python-version }}' + uses: actions/setup-python@v5 + with: + python-version: '${{ matrix.python-version }}' + - uses: actions/checkout@v4 + - run: pip install django==${{ matrix.django-version}} + - run: pip install psycopg[binary] + - run: pip install gevent + - run: python setup.py -q install + - run: python runtests_psycopg3.py diff --git a/README.md b/README.md index 566b3dc..b27f372 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,21 @@ django-db-geventpool Another DB pool using gevent for PostgreSQL DB. -If **gevent** is not installed, the pool will use **eventlet** as fallback. + +psycopg3 +--------- + +Django, since 4.2, supports psycopg3. One of the advantages is that gevent is supported without needing extra patches, just install the package + +``` +$ pip install psycopg[binary] +``` + psycopg2 -------- -django-db-geventpool requires psycopg2: +If **gevent** is not installed, the pool will use **eventlet** as fallback. - `psycopg2>=2.5.1` for CPython 2 and 3 (or [psycopg2-binary](https://pypi.org/project/psycopg2-binary/)---see @@ -22,33 +31,32 @@ django-db-geventpool requires psycopg2: release](http://initd.org/psycopg/articles/2018/02/08/psycopg-274-released/)) - `psycopg2cffi>=2.7` for PyPy -Patch psycopg2 --------------- + Patch psycopg2 + -------------- -Before using the pool, psycopg2 must be patched with psycogreen, if you -are using [gunicorn webserver](http://www.gunicorn.org/), a good place -is the -[post\_fork()](http://docs.gunicorn.org/en/latest/settings.html#post-fork) -function at the config file: + Before using the pool, psycopg2 must be patched with psycogreen, if you + are using [gunicorn webserver](http://www.gunicorn.org/), a good place + is the + [post\_fork()](http://docs.gunicorn.org/en/latest/settings.html#post-fork) + function at the config file: -``` {.python} -from psycogreen.gevent import patch_psycopg # use this if you use gevent workers -from psycogreen.eventlet import patch_psycopg # use this if you use eventlet workers + ``` {.python} + from psycogreen.gevent import patch_psycopg # use this if you use gevent workers + from psycogreen.eventlet import patch_psycopg # use this if you use eventlet workers -def post_fork(server, worker): - patch_psycopg() - worker.log.info("Made Psycopg2 Green") -``` + def post_fork(server, worker): + patch_psycopg() + worker.log.info("Made Psycopg2 Green") + ``` Settings -------- -> - -> -> Set *ENGINE* in your database settings to: +> - Set *ENGINE* in your database settings to: > -> : - *\'django\_db\_geventpool.backends.postgresql\_psycopg2\'* -> - For postgis: *\'django\_db\_geventpool.backends.postgis\'* +> - For psycopg3: 'django_db_geventpool.backends.postgresql_psycopg3' +> - For psycopg2: 'django_db_geventpool.backends.postgresql_psycopg2' +> - For postgis: 'django_db_geventpool.backends.postgis' > > - Add *MAX\_CONNS* to *OPTIONS* to set the maximun number of > connections allowed to database (default=4) @@ -64,7 +72,7 @@ Settings ``` {.python} DATABASES = { 'default': { - 'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg2', + 'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg', 'NAME': 'db', 'USER': 'postgres', 'PASSWORD': 'postgres', diff --git a/django_db_geventpool/backends/base.py b/django_db_geventpool/backends/base.py new file mode 100755 index 0000000..d5c90ec --- /dev/null +++ b/django_db_geventpool/backends/base.py @@ -0,0 +1,93 @@ +import logging +import sys + +try: + from gevent.lock import Semaphore +except ImportError: + from eventlet.semaphore import Semaphore + +from .creation import DatabaseCreation + +logger = logging.getLogger("django.geventpool") + +connection_pools = {} +connection_pools_lock = Semaphore(value=1) + + +class DatabaseWrapperMixin(object): + pool_class = None + creation_class = DatabaseCreation + INTRANS = None + + def __init__(self, *args, **kwargs): + self._pool = None + super().__init__(*args, **kwargs) + self.creation = self.creation_class(self) + + @property + def pool(self): + if self._pool is not None: + return self._pool + with connection_pools_lock: + if self.alias not in connection_pools: + self._pool = self.pool_class(**self.get_connection_params()) + connection_pools[self.alias] = self._pool + else: + self._pool = connection_pools[self.alias] + return self._pool + + def get_new_connection(self, conn_params: dict): + if self.connection is None: + self.connection = self.pool.get() + self.closed_in_transaction = False + return self.connection + + def get_connection_params(self) -> dict: + conn_params = super().get_connection_params() + for attr in ["MAX_CONNS", "REUSE_CONNS"]: + if attr in self.settings_dict["OPTIONS"]: + conn_params[attr] = self.settings_dict["OPTIONS"][attr] + return conn_params + + def close(self): + self.validate_thread_sharing() + if self.closed_in_transaction or self.connection is None: + return # no need to close anything + try: + self._close() + except: + # In some cases (database restart, network connection lost etc...) + # the connection to the database is lost without giving Django a + # notification. If we don't set self.connection to None, the error + # will occur at every request. + self.connection = None + logger.warning( + "psycopg2 error while closing the connection.", exc_info=sys.exc_info() + ) + raise + finally: + self.set_clean() + + def close_if_unusable_or_obsolete(self): + # Always close the connection because it's not (usually) really being closed. + self.close() + + def _close(self): + if self.connection.closed: + self.pool.closeall() + else: + if self.connection.info.transaction_status == self.INTRANS: + self.connection.rollback() + self.connection.autocommit = True + with self.wrap_database_errors: + self.pool.put(self.connection) + self.connection = None + + def closeall(self): + for pool in connection_pools.values(): + pool.closeall() + + def set_clean(self): + if self.in_atomic_block: + self.closed_in_transaction = True + self.needs_rollback = True diff --git a/django_db_geventpool/backends/postgresql_psycopg2/creation.py b/django_db_geventpool/backends/creation.py similarity index 51% rename from django_db_geventpool/backends/postgresql_psycopg2/creation.py rename to django_db_geventpool/backends/creation.py index 0122102..f6f3186 100644 --- a/django_db_geventpool/backends/postgresql_psycopg2/creation.py +++ b/django_db_geventpool/backends/creation.py @@ -1,16 +1,16 @@ -# coding=utf-8 - -from django.db.backends.postgresql.creation import DatabaseCreation as OriginalDatabaseCreation +from django.db.backends.postgresql.creation import ( + DatabaseCreation as OriginalDatabaseCreation, +) class DatabaseCreationMixin(object): def _create_test_db(self, verbosity, autoclobber, keepdb=False): self.connection.closeall() - return super(DatabaseCreationMixin, self)._create_test_db(verbosity, autoclobber, keepdb) + return super()._create_test_db(verbosity, autoclobber, keepdb) def _destroy_test_db(self, test_database_name, verbosity): self.connection.closeall() - return super(DatabaseCreationMixin, self)._destroy_test_db(test_database_name, verbosity) + return super()._destroy_test_db(test_database_name, verbosity) class DatabaseCreation(DatabaseCreationMixin, OriginalDatabaseCreation): diff --git a/django_db_geventpool/backends/postgresql_psycopg2/psycopg2_pool.py b/django_db_geventpool/backends/pool.py similarity index 59% rename from django_db_geventpool/backends/postgresql_psycopg2/psycopg2_pool.py rename to django_db_geventpool/backends/pool.py index 1d8568f..697eedd 100755 --- a/django_db_geventpool/backends/postgresql_psycopg2/psycopg2_pool.py +++ b/django_db_geventpool/backends/pool.py @@ -1,13 +1,11 @@ -# coding=utf-8 - # this file is a modified version of the psycopg2 used at gevent examples # to be compatible with django, also checks if # DB connection is closed and reopen it: # https://github.com/surfly/gevent/blob/master/examples/psycopg2_pool.py import logging -import sys import weakref -logger = logging.getLogger('django.geventpool') + +logger = logging.getLogger("django.geventpool") try: from gevent import queue @@ -16,27 +14,11 @@ from eventlet import queue from ...utils import NullContextRLock as RLock -try: - from psycopg2 import connect, DatabaseError - import psycopg2.extras -except ImportError as e: - from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e) - -if sys.version_info[0] >= 3: - integer_types = int, -else: - import __builtin__ - integer_types = int, __builtin__.long - -class DatabaseConnectionPool(object): - def __init__(self, maxsize=100, reuse=100): - if not isinstance(maxsize, integer_types): - raise TypeError('Expected integer, got %r' % (maxsize,)) - if not isinstance(reuse, integer_types): - raise TypeError('Expected integer, got %r' % (reuse,)) +class DatabaseConnectionPool: + DBERROR = None + def __init__(self, maxsize: int = 100, reuse: int = 100): # Use a WeakSet here so, even if we fail to discard the connection # when it is being closed, or it is closed outside of here, the item # will be removed automatically @@ -61,7 +43,7 @@ def get(self): # check connection is still valid self.check_usable(conn) logger.debug("DB connection reused") - except DatabaseError: + except self.DBERROR: logger.debug("DB connection was closed, creating a new one") conn = None except queue.Empty: @@ -100,24 +82,3 @@ def closeall(self): self._conns.discard(conn) logger.debug("DB connections all closed") - - -class PostgresConnectionPool(DatabaseConnectionPool): - def __init__(self, *args, **kwargs): - self.connect = kwargs.pop('connect', connect) - self.connection = None - maxsize = kwargs.pop('MAX_CONNS', 4) - reuse = kwargs.pop('REUSE_CONNS', maxsize) - self.args = args - self.kwargs = kwargs - super(PostgresConnectionPool, self).__init__(maxsize, reuse) - - def create_connection(self): - conn = self.connect(*self.args, **self.kwargs) - # set correct encoding - conn.set_client_encoding('UTF8') - psycopg2.extras.register_default_jsonb(conn_or_curs=conn, loads=lambda x: x) - return conn - - def check_usable(self, connection): - connection.cursor().execute('SELECT 1') diff --git a/django_db_geventpool/backends/postgis/base.py b/django_db_geventpool/backends/postgis/base.py index 1472a2d..2e32fee 100644 --- a/django_db_geventpool/backends/postgis/base.py +++ b/django_db_geventpool/backends/postgis/base.py @@ -1,8 +1,13 @@ -# coding=utf-8 - -from django.contrib.gis.db.backends.postgis.base import DatabaseWrapper as OriginalDatabaseWrapper - -from django_db_geventpool.backends.postgresql_psycopg2.base import DatabaseWrapperMixin +from django.contrib.gis.db.backends.postgis.base import ( + DatabaseWrapper as OriginalDatabaseWrapper, +) + +try: + import psycopg # noqa + from ..postgresql_psycopg3.base import DatabaseWrapperMixin +except ImportError: + # fallback to psycopg3 + from ..postgresql_psycopg2.base import DatabaseWrapperMixin class DatabaseWrapper(DatabaseWrapperMixin, OriginalDatabaseWrapper): diff --git a/django_db_geventpool/backends/postgresql_psycopg2/base.py b/django_db_geventpool/backends/postgresql_psycopg2/base.py index 715f27c..4facc0e 100755 --- a/django_db_geventpool/backends/postgresql_psycopg2/base.py +++ b/django_db_geventpool/backends/postgresql_psycopg2/base.py @@ -1,105 +1,41 @@ -# coding=utf-8 - -import logging -import sys - try: + import psycopg2 + import psycopg2.extras import psycopg2.extensions except ImportError as e: from django.core.exceptions import ImproperlyConfigured - raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e) -try: - from gevent.lock import Semaphore -except ImportError: - from eventlet.semaphore import Semaphore - -from django.db.backends.postgresql.base import DatabaseWrapper as OriginalDatabaseWrapper + raise ImproperlyConfigured("Error loading psycopg2 module: %s" % e) -from . import creation, psycopg2_pool +from django.db.backends.postgresql.base import ( + DatabaseWrapper as OriginalDatabaseWrapper, +) -logger = logging.getLogger('django.geventpool') +from .. import base, pool -connection_pools = {} -connection_pools_lock = Semaphore(value=1) +class PostgresConnectionPool(pool.DatabaseConnectionPool): + DBERROR = psycopg2.DatabaseError -class DatabaseWrapperMixin(object): def __init__(self, *args, **kwargs): - self._pool = None - super(DatabaseWrapperMixin, self).__init__(*args, **kwargs) - self.creation = creation.DatabaseCreation(self) - - @property - def pool(self): - if self._pool is not None: - return self._pool - connection_pools_lock.acquire() - if self.alias not in connection_pools: - self._pool = psycopg2_pool.PostgresConnectionPool( - **self.get_connection_params()) - connection_pools[self.alias] = self._pool - else: - self._pool = connection_pools[self.alias] - connection_pools_lock.release() - return self._pool - - def get_new_connection(self, conn_params): - if self.connection is None: - self.connection = self.pool.get() - self.closed_in_transaction = False - return self.connection - - def get_connection_params(self): - conn_params = super(DatabaseWrapperMixin, self).get_connection_params() - for attr in ['MAX_CONNS', 'REUSE_CONNS']: - if attr in self.settings_dict['OPTIONS']: - conn_params[attr] = self.settings_dict['OPTIONS'][attr] - return conn_params - - def close(self): - self.validate_thread_sharing() - if self.closed_in_transaction or self.connection is None: - return # no need to close anything - try: - self._close() - except: - # In some cases (database restart, network connection lost etc...) - # the connection to the database is lost without giving Django a - # notification. If we don't set self.connection to None, the error - # will occur at every request. - self.connection = None - logger.warning( - 'psycopg2 error while closing the connection.', - exc_info=sys.exc_info()) - raise - finally: - self.set_clean() - - def close_if_unusable_or_obsolete(self): - # Always close the connection because it's not (usually) really being closed. - self.close() - - def _close(self): - if self.connection.closed: - self.pool.closeall() - else: - if self.connection.get_transaction_status() == psycopg2.extensions.TRANSACTION_STATUS_INTRANS: - self.connection.rollback() - self.connection.autocommit = True - with self.wrap_database_errors: - self.pool.put(self.connection) + self.connect = kwargs.pop("connect", psycopg2.connect) self.connection = None + maxsize = kwargs.pop("MAX_CONNS", 4) + reuse = kwargs.pop("REUSE_CONNS", maxsize) + self.args = args + self.kwargs = kwargs + self.kwargs["client_encoding"] = "UTF8" + super().__init__(maxsize, reuse) - def closeall(self): - for pool in connection_pools.values(): - pool.closeall() + def create_connection(self): + conn = self.connect(*self.args, **self.kwargs) + psycopg2.extras.register_default_jsonb(conn_or_curs=conn, loads=lambda x: x) + return conn - def set_clean(self): - if self.in_atomic_block: - self.closed_in_transaction = True - self.needs_rollback = True + def check_usable(self, connection): + connection.cursor().execute("SELECT 1") -class DatabaseWrapper(DatabaseWrapperMixin, OriginalDatabaseWrapper): - pass +class DatabaseWrapper(base.DatabaseWrapperMixin, OriginalDatabaseWrapper): + pool_class = PostgresConnectionPool + INTRANS = psycopg2.extensions.TRANSACTION_STATUS_INTRANS diff --git a/django_db_geventpool/backends/postgresql_psycopg3/__init__,py b/django_db_geventpool/backends/postgresql_psycopg3/__init__,py new file mode 100644 index 0000000..e69de29 diff --git a/django_db_geventpool/backends/postgresql_psycopg3/base.py b/django_db_geventpool/backends/postgresql_psycopg3/base.py new file mode 100755 index 0000000..5f326ce --- /dev/null +++ b/django_db_geventpool/backends/postgresql_psycopg3/base.py @@ -0,0 +1,38 @@ +try: + import psycopg +except ImportError as e: + from django.core.exceptions import ImproperlyConfigured + + raise ImproperlyConfigured("Error loading psycopg3 module: %s" % e) + +from django.db.backends.postgresql.base import ( + DatabaseWrapper as OriginalDatabaseWrapper, +) + +from .. import base, pool + + +class PostgresConnectionPool(pool.DatabaseConnectionPool): + DBERROR = psycopg.DatabaseError + + def __init__(self, *args, **kwargs): + self.connect = kwargs.pop("connect", psycopg.connect) + self.connection = None + maxsize = kwargs.pop("MAX_CONNS", 4) + reuse = kwargs.pop("REUSE_CONNS", maxsize) + self.args = args + self.kwargs = kwargs + self.kwargs["client_encoding"] = "UTF8" + super().__init__(maxsize, reuse) + + def create_connection(self): + conn = self.connect(*self.args, **self.kwargs) + return conn + + def check_usable(self, connection): + connection.cursor().execute("SELECT 1") + + +class DatabaseWrapper(base.DatabaseWrapperMixin, OriginalDatabaseWrapper): + pool_class = PostgresConnectionPool + INTRANS = psycopg.pq.TransactionStatus.INTRANS diff --git a/django_db_geventpool/utils.py b/django_db_geventpool/utils.py index d3c4459..fb199fb 100644 --- a/django_db_geventpool/utils.py +++ b/django_db_geventpool/utils.py @@ -1,7 +1,4 @@ -# coding=utf-8 - from functools import wraps -from contextlib import contextmanager from django.core.signals import request_finished @@ -12,7 +9,8 @@ def wrapper(*args, **kwargs): try: return f(*args, **kwargs) finally: - request_finished.send(sender='greenlet') + request_finished.send(sender="greenlet") + return wrapper diff --git a/runtests.py b/runtests_psycopg2.py similarity index 58% rename from runtests.py rename to runtests_psycopg2.py index 5491776..b2e664f 100644 --- a/runtests.py +++ b/runtests_psycopg2.py @@ -1,15 +1,18 @@ #!/usr/bin/env python import sys import gevent.monkey + gevent.monkey.patch_all() try: from psycopg2cffi import compat + compat.register() except ImportError: pass import psycogreen.gevent + psycogreen.gevent.patch_psycopg() import django @@ -20,19 +23,19 @@ settings.configure( DEBUG=True, DATABASES={ - 'default': { - 'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg2', - 'NAME': 'test', - 'USER': 'postgres', - 'PASSWORD': 'postgres', - 'ATOMIC_REQUESTS': False, - 'CONN_MAX_AGE': 0, - 'HOST': 'localhost', + "default": { + "ENGINE": "django_db_geventpool.backends.postgresql_psycopg2", + "NAME": "test", + "USER": "postgres", + "PASSWORD": "postgres", + "ATOMIC_REQUESTS": False, + "CONN_MAX_AGE": 0, + "HOST": "localhost", } }, INSTALLED_APPS=( - 'tests', - 'django_db_geventpool', + "tests", + "django_db_geventpool", ), USE_TZ=True, ) @@ -40,6 +43,6 @@ test_runner = DiscoverRunner(verbosity=2) -failures = test_runner.run_tests(['tests']) +failures = test_runner.run_tests(["tests"]) if failures: sys.exit(failures) diff --git a/runtests_psycopg3.py b/runtests_psycopg3.py new file mode 100644 index 0000000..ff6cba8 --- /dev/null +++ b/runtests_psycopg3.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +import sys +import gevent.monkey + +gevent.monkey.patch_all() + +import django +from django.conf import settings +from django.test.runner import DiscoverRunner + + +settings.configure( + DEBUG=True, + DATABASES={ + "default": { + "ENGINE": "django_db_geventpool.backends.postgresql_psycopg3", + "NAME": "test", + "USER": "postgres", + "PASSWORD": "postgres", + "ATOMIC_REQUESTS": False, + "CONN_MAX_AGE": 0, + "HOST": "localhost", + } + }, + INSTALLED_APPS=( + "tests", + "django_db_geventpool", + ), + USE_TZ=True, +) +django.setup() + +test_runner = DiscoverRunner(verbosity=2) + +failures = test_runner.run_tests(["tests"]) +if failures: + sys.exit(failures) diff --git a/setup.py b/setup.py index 8e433f2..4c0b736 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -# coding=utf-8 import codecs from setuptools import setup, find_packages @@ -6,37 +5,36 @@ def long_description(): try: - with codecs.open('README.md', 'r', 'utf-8') as f: + with codecs.open("README.md", "r", "utf-8") as f: return f.read() - except: - return 'Error loading README.md' + except Exception: + return "Error loading README.md" setup( - name='django-db-geventpool', - version='4.0.2', + name="django-db-geventpool", + version="4.0.3", install_requires=[ - 'django>=3.1', - 'psycogreen>=1.0', + "django>=3.1", ], - url='https://github.com/jneight/django-db-geventpool', - description='Add a DB connection pool using gevent to django', + url="https://github.com/jneight/django-db-geventpool", + description="Add a DB connection pool using gevent to django", long_description=long_description(), - long_description_content_type='text/markdown', + long_description_content_type="text/markdown", packages=find_packages(), include_package_data=True, - license='Apache 2.0', + license="Apache 2.0", classifiers=[ - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: 3.1', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Software Development :: Libraries :: Application Frameworks', + "Environment :: Web Environment", + "Framework :: Django", + "Framework :: Django :: 3.1", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Software Development :: Libraries :: Application Frameworks", ], - author='Javier Cordero Martinez', - author_email='j@jcmz.me' + author="Javier Cordero Martinez", + author_email="j@jcmz.me", ) diff --git a/tests/models.py b/tests/models.py index 4a91299..51e3732 100644 --- a/tests/models.py +++ b/tests/models.py @@ -1,5 +1,3 @@ -# coding=utf-8 - from django.db import models diff --git a/tests/tests.py b/tests/tests.py index ba51ffc..fdd23e1 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,31 +1,26 @@ -# coding=utf-8 - import gevent -try: - from django.utils.unittest import TestCase -except ImportError: # removed at 1.9 - from unittest import TestCase +from django.test import TestCase +from django_db_geventpool.utils import close_connection from .models import TestModel -from django_db_geventpool.utils import close_connection - @close_connection def test_multiple_connections(count): - print('Test {0} starts'.format(count)) + print("Test {0} starts".format(count)) for x in range(0, 20): assert len(TestModel.objects.all()) == 1 - print('Test {0} ends'.format(count)) + print("Test {0} ends".format(count)) class ModelTest(TestCase): - databases = {'default'} + databases = {"default"} + def test_model_save(self): data = { - 'charfield': 'testing save', - 'jsonfield': {'test': 'value'}, + "charfield": "testing save", + "jsonfield": {"test": "value"}, } pk = TestModel.objects.create(**data).pk