├── django_postgrespool
├── __init__.py
└── base.py
├── MANIFEST.in
├── .gitignore
├── LICENSE
├── setup.py
└── README.rst
/django_postgrespool/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.py[co]
2 |
3 | # Packages
4 | *.egg
5 | *.egg-info
6 | dist
7 | build
8 | eggs
9 | parts
10 | bin
11 | var
12 | sdist
13 | develop-eggs
14 | .installed.cfg
15 |
16 | # Installer logs
17 | pip-log.txt
18 |
19 | # Unit test / coverage reports
20 | .coverage
21 | .tox
22 |
23 | #Translations
24 | *.mo
25 |
26 | #Mr Developer
27 | .mr.developer.cfg
28 |
29 | # Sublime Codeintel
30 | .codeintel/
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012 Kenneth Reitz
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import os
5 | import sys
6 |
7 | try:
8 | from setuptools import setup
9 | except ImportError:
10 | from distutils.core import setup
11 |
12 |
13 | if sys.argv[-1] == "publish":
14 | os.system("python setup.py sdist upload")
15 | sys.exit()
16 |
17 | required = [
18 | 'psycopg2',
19 | 'sqlalchemy'
20 | ]
21 |
22 | setup(
23 | name='django-postgrespool',
24 | version='0.3.0',
25 | description='Postgres Connection Pooling for Django.',
26 | long_description=open('README.rst').read(),
27 | author='Kenneth Reitz',
28 | author_email='me@kennethreitz.com',
29 | url='https://github.com/kennethreitz/django-postgrespool',
30 | packages= ['django_postgrespool'],
31 | install_requires=required,
32 | license='MIT',
33 | classifiers=(
34 | # 'Development Status :: 5 - Production/Stable',
35 | 'Intended Audience :: Developers',
36 | 'Natural Language :: English',
37 | 'License :: OSI Approved :: MIT License',
38 | 'Programming Language :: Python',
39 | # 'Programming Language :: Python :: 2.5',
40 | # 'Programming Language :: Python :: 2.6',
41 | 'Programming Language :: Python :: 2.7',
42 | # 'Programming Language :: Python :: 3.0',
43 | # 'Programming Language :: Python :: 3.1',
44 | ),
45 | )
46 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Django-PostgresPool
2 | ===================
3 |
4 | This is a simple Postgres Connection Pooling backend for Django 1.4+, powered by the lovely and beautiful SQLAlchemy.
5 |
6 |
7 | Usage
8 | -----
9 |
10 | Using Django-PostgresPool is simple, just set ``django_postgrespool`` as your connection engine:
11 |
12 | ::
13 |
14 | DATABASES = {
15 | 'default': {
16 | 'ENGINE': 'django_postgrespool'
17 |
18 |
19 | If you're using the `dj-database-url `_ module:
20 |
21 | ::
22 |
23 | import dj_database_url
24 |
25 | DATABASES = {'default': dj_database_url.config(engine='django_postgrespool')}
26 |
27 | If you're using `south `_:
28 |
29 | ::
30 |
31 | SOUTH_DATABASE_ADAPTERS = {
32 | 'default': 'south.db.postgresql_psycopg2'
33 | }
34 |
35 |
36 | Everything should work as expected.
37 |
38 |
39 | Installation
40 | ------------
41 |
42 | Installing Django-PostgresPool is simple, with pip::
43 |
44 | $ pip install django-postgrespool
45 |
46 | Configuration
47 | -------------
48 |
49 | Optionally, you can provide additional options to pass to SQLAlchemy's pool creation::
50 |
51 | DATABASE_POOL_ARGS = {
52 | 'max_overflow': 10,
53 | 'pool_size': 5,
54 | 'recycle': 300
55 | }
56 |
57 | Here's a basic explanation of two of these options:
58 |
59 | * **pool_size** – The *minimum* number of connections to maintain in the pool.
60 | * **max_overflow** – The maximum *overflow* size of the pool. This is not the maximum size of the pool.
61 |
62 | The total number of "sleeping" connections the pool will allow is ``pool_size``.
63 | The total simultaneous connections the pool will allow is ``pool_size + max_overflow``.
64 |
65 | As an example, databases in the `Heroku Postgres `_ starter tier have a maximum connection limit of 20. In that case your ``pool_size`` and ``max_overflow``, when combined, should not exceed 20.
66 |
67 | Check out the official `SQLAlchemy Connection Pooling `_ docs to learn more about the optoins that can be defined in ``DATABASE_POOL_ARGS``.
68 |
69 | Django 1.3 Support
70 | ------------------
71 |
72 | django-postgrespool currently supports Django 1.4 and greater. See `this ticket `_ for 1.3 support.
73 |
--------------------------------------------------------------------------------
/django_postgrespool/base.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | import logging
4 | from functools import partial
5 |
6 | from sqlalchemy import event
7 | from sqlalchemy.pool import manage, QueuePool
8 | from psycopg2 import InterfaceError, ProgrammingError, OperationalError
9 |
10 | # from django.db import transaction
11 |
12 | from django.conf import settings
13 | try:
14 | # Django >= 1.9
15 | from django.db.backends.postgresql.base import *
16 | from django.db.backends.postgresql.base import DatabaseWrapper as Psycopg2DatabaseWrapper
17 | from django.db.backends.postgresql.creation import DatabaseCreation as Psycopg2DatabaseCreation
18 | except ImportError:
19 | from django.db.backends.postgresql_psycopg2.base import *
20 | from django.db.backends.postgresql_psycopg2.base import DatabaseWrapper as Psycopg2DatabaseWrapper
21 | from django.db.backends.postgresql_psycopg2.creation import DatabaseCreation as Psycopg2DatabaseCreation
22 |
23 | POOL_SETTINGS = 'DATABASE_POOL_ARGS'
24 |
25 | # DATABASE_POOL_ARGS should be something like:
26 | # {'max_overflow':10, 'pool_size':5, 'recycle':300}
27 | pool_args = getattr(settings, POOL_SETTINGS, {})
28 | db_pool = manage(Database, **pool_args)
29 |
30 | log = logging.getLogger('z.pool')
31 |
32 | def _log(message, *args):
33 | log.debug(message)
34 |
35 | # Only hook up the listeners if we are in debug mode.
36 | if settings.DEBUG:
37 | event.listen(QueuePool, 'checkout', partial(_log, 'retrieved from pool'))
38 | event.listen(QueuePool, 'checkin', partial(_log, 'returned to pool'))
39 | event.listen(QueuePool, 'connect', partial(_log, 'new connection'))
40 |
41 |
42 | def is_disconnect(e, connection, cursor):
43 | """
44 | Connection state check from SQLAlchemy:
45 | https://bitbucket.org/sqlalchemy/sqlalchemy/src/tip/lib/sqlalchemy/dialects/postgresql/psycopg2.py
46 | """
47 | if isinstance(e, OperationalError):
48 | # these error messages from libpq: interfaces/libpq/fe-misc.c.
49 | # TODO: these are sent through gettext in libpq and we can't
50 | # check within other locales - consider using connection.closed
51 | return 'terminating connection' in str(e) or \
52 | 'closed the connection' in str(e) or \
53 | 'connection not open' in str(e) or \
54 | 'could not receive data from server' in str(e)
55 | elif isinstance(e, InterfaceError):
56 | # psycopg2 client errors, psycopg2/conenction.h, psycopg2/cursor.h
57 | return 'connection already closed' in str(e) or \
58 | 'cursor already closed' in str(e)
59 | elif isinstance(e, ProgrammingError):
60 | # not sure where this path is originally from, it may
61 | # be obsolete. It really says "losed", not "closed".
62 | return "closed the connection unexpectedly" in str(e)
63 | else:
64 | return False
65 |
66 |
67 | class DatabaseCreation(Psycopg2DatabaseCreation):
68 | def destroy_test_db(self, *args, **kw):
69 | """Ensure connection pool is disposed before trying to drop database."""
70 | self.connection._dispose()
71 | super(DatabaseCreation, self).destroy_test_db(*args, **kw)
72 |
73 |
74 | class DatabaseWrapper(Psycopg2DatabaseWrapper):
75 | """SQLAlchemy FTW."""
76 |
77 | def __init__(self, *args, **kwargs):
78 | super(DatabaseWrapper, self).__init__(*args, **kwargs)
79 | self.creation = DatabaseCreation(self)
80 |
81 | def _commit(self):
82 | if self.connection is not None and self.is_usable():
83 | with self.wrap_database_errors:
84 | return self.connection.commit()
85 |
86 | def _rollback(self):
87 | if self.connection is not None and self.is_usable():
88 | with self.wrap_database_errors:
89 | return self.connection.rollback()
90 |
91 | def _dispose(self):
92 | """Dispose of the pool for this instance, closing all connections."""
93 | self.close()
94 | # _DBProxy.dispose doesn't actually call dispose on the pool
95 | conn_params = self.get_connection_params()
96 | key = db_pool._serialize(**conn_params)
97 | try:
98 | pool = db_pool.pools[key]
99 | except KeyError:
100 | pass
101 | else:
102 | pool.dispose()
103 | del db_pool.pools[key]
104 |
105 | def is_usable(self):
106 | # https://github.com/kennethreitz/django-postgrespool/issues/24
107 | return not self.connection.closed
108 |
--------------------------------------------------------------------------------