Skip to content

Commit 6ca1f28

Browse files
committed
Make PoolConnectionProxy more dynamic. Fixes #155.
Specifically, allow proxied Connection objects to use overridden public methods.
1 parent 550c408 commit 6ca1f28

File tree

2 files changed

+28
-17
lines changed

2 files changed

+28
-17
lines changed

asyncpg/pool.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,6 @@ class PoolConnectionProxyMeta(type):
1818

1919
def __new__(mcls, name, bases, dct, *, wrap=False):
2020
if wrap:
21-
def get_wrapper(meth):
22-
def wrapper(self, *args, **kwargs):
23-
return self._dispatch_method_call(meth, args, kwargs)
24-
return wrapper
25-
2621
for attrname in dir(connection.Connection):
2722
if attrname.startswith('_') or attrname in dct:
2823
continue
@@ -31,7 +26,7 @@ def wrapper(self, *args, **kwargs):
3126
if not inspect.isfunction(meth):
3227
continue
3328

34-
wrapper = get_wrapper(meth)
29+
wrapper = mcls._wrap_connection_method(attrname)
3530
wrapper = functools.update_wrapper(wrapper, meth)
3631
dct[attrname] = wrapper
3732

@@ -44,6 +39,21 @@ def __init__(cls, name, bases, dct, *, wrap=False):
4439
# Needed for Python 3.5 to handle `wrap` class keyword argument.
4540
super().__init__(name, bases, dct)
4641

42+
@staticmethod
43+
def _wrap_connection_method(meth_name):
44+
def call_con_method(self, *args, **kwargs):
45+
# This method will be owned by PoolConnectionProxy class.
46+
if self._con is None:
47+
raise exceptions.InterfaceError(
48+
'cannot call Connection.{}(): '
49+
'connection has been released back to the pool'.format(
50+
meth_name))
51+
52+
meth = getattr(self._con.__class__, meth_name)
53+
return meth(self._con, *args, **kwargs)
54+
55+
return call_con_method
56+
4757

4858
class PoolConnectionProxy(connection._ConnectionProxy,
4959
metaclass=PoolConnectionProxyMeta,
@@ -57,6 +67,10 @@ def __init__(self, holder: 'PoolConnectionHolder',
5767
self._holder = holder
5868
con._set_proxy(self)
5969

70+
def __getattr__(self, attr):
71+
# Proxy all unresolved attributes to the wrapped Connection object.
72+
return getattr(self._con, attr)
73+
6074
def _detach(self):
6175
if self._con is None:
6276
raise exceptions.InterfaceError(
@@ -65,15 +79,6 @@ def _detach(self):
6579
con, self._con = self._con, None
6680
con._set_proxy(None)
6781

68-
def _dispatch_method_call(self, meth, args, kwargs):
69-
if self._con is None:
70-
raise exceptions.InterfaceError(
71-
'cannot call Connection.{}(): '
72-
'connection has been released back to the pool'.format(
73-
meth.__name__))
74-
75-
return meth(self._con, *args, **kwargs)
76-
7782
def __repr__(self):
7883
if self._con is None:
7984
return '<{classname} [released] {id:#x}>'.format(

tests/test_pool.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,17 @@ async def test_pool_config_persistence(self):
356356
cons = set()
357357

358358
class MyConnection(asyncpg.Connection):
359-
pass
359+
async def foo(self):
360+
return 42
361+
362+
async def fetchval(self, query):
363+
res = await super().fetchval(query)
364+
return res + 1
360365

361366
async def test(pool):
362367
async with pool.acquire() as con:
363-
await con.fetchval('SELECT 1')
368+
self.assertEqual(await con.fetchval('SELECT 1'), 2)
369+
self.assertEqual(await con.foo(), 42)
364370
self.assertTrue(isinstance(con, MyConnection))
365371
self.assertEqual(con._con._config.statement_cache_size, 3)
366372
cons.add(con)

0 commit comments

Comments
 (0)