Skip to content

Commit

Permalink
add a gevent+psycopg2 dialect for sqlalchemy
Browse files Browse the repository at this point in the history
  • Loading branch information
cutz committed Feb 4, 2020
1 parent 30d66cc commit c2adb66
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 1 deletion.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
'zope.container',
'zope.site',
'zope.generations',
'RelStorage==3.0b3',
'RelStorage',
'zc.zlibstorage',
'nti.i18n',
'z3c.schema',
Expand Down
3 changes: 3 additions & 0 deletions src/nti/app/environments/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import gevent.monkey
gevent.monkey.patch_all()

from nti.app.environments._monkey import patch
patch()

from zope.component import getGlobalSiteManager

import zope.i18nmessageid as zope_i18nmessageid
Expand Down
39 changes: 39 additions & 0 deletions src/nti/app/environments/_monkey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
When RelStorage is installed with psycopg2 and gevent a global wait callback is installed.
That wait callback expects the Relstorage custom connection which creates issues
when trying to use psycopg2 with sqlalchmey.
Install a custom dialect that uses the RelStorage connection for sqlalchemy.
"""

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

logger = __import__('logging').getLogger(__name__)

try:
from sqlalchemy.dialects.postgresql.psycopg2 import PGDialect_psycopg2
from relstorage.adapters.postgresql.drivers.psycopg2 import GeventPsycopg2Driver
import psycopg2
except ImportError:
pass
else:
class geventPostgresclient_dialect(PGDialect_psycopg2):
driver = "gevent+postgres"

def __init__(self, *args, **kwargs):
super(geventPostgresclient_dialect, self).__init__(*args, **kwargs)
self._conn_class = GeventPsycopg2Driver().connect

def connect(self, *args, **kwargs):
kwargs['connection_factory'] = self._conn_class
return psycopg2.connect(*args, **kwargs)

from sqlalchemy.dialects import registry
registry.register("gevent.postgres", __name__, "geventPostgresclient_dialect")

def patch():
pass
51 changes: 51 additions & 0 deletions src/nti/app/environments/tests/test_monkey.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from __future__ import division
from __future__ import print_function
from __future__ import absolute_import

import fudge

from hamcrest import not_none
from hamcrest import assert_that
from hamcrest import instance_of

import unittest

from sqlalchemy import create_engine

from .._monkey import geventPostgresclient_dialect


class TestPatchSqlalchemy(unittest.TestCase):

def test_postres_engine(self):
from .._monkey import patch
patch()
engine = create_engine('gevent+postgres:///testdb.db')
assert_that(engine, not_none())
assert_that(engine.dialect, instance_of(geventPostgresclient_dialect))

@fudge.patch('psycopg2.connect')
def test_connect_uses_relstorage(self, pconn):
from .._monkey import patch
patch()
engine = create_engine('gevent+postgres:///testdb.db')

class StopExecution(Exception):
pass

# Make sure connect get gets called with the right connection_factory
# Make calling it raise to stop all the crazy first connection initialization that sqlalchemy does
# no luck trying to mock all the necessary things
pconn.expects_call().with_args(database='testdb.db', connection_factory=engine.dialect._conn_class).raises(StopExecution)

try:
engine.connect()
except StopExecution:
pass




3 comments on commit c2adb66

@cutz
Copy link
Contributor Author

@cutz cutz commented on c2adb66 Feb 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jamadden meant to PR this but accidentally pushed it straight on master. mind taking a look?

@jamadden
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meant to PR this but accidentally pushed it straight on master.

Branch protection?

mind taking a look?

LGTM, matches our discussion. I'm assuming you verified functionality in your real scenario.

Thinking a bit more about zodb/relstorage#394, using Travis it's trivial to spin up PostgreSQL and MySQL instances to talk to. RelStorage already has a testing infrastructure that separates unit and integration (requiring a database) tests, plus the infrastructure to configure those databases. Maybe moving all of our gevent+ dialects there makes sense in order to take advantage of all that.

@cutz
Copy link
Contributor Author

@cutz cutz commented on c2adb66 Feb 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, matches our discussion. I'm assuming you verified functionality in your real scenario.

Yes, thank you.

Maybe moving all of our gevent+ dialects there makes sense in order to take advantage of all that.

Originally that sounded odd to me, but seeing as you only need them if you are in a mixed relstorage / SQLAlchemy environment it probably makes sense. +1 to having them somewhere we can share them.

Please sign in to comment.