Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inspect.iscoroutinefunction returns false on pool -> connection -> execute #1133

Open
stj opened this issue Mar 15, 2024 · 1 comment · May be fixed by #1134
Open

inspect.iscoroutinefunction returns false on pool -> connection -> execute #1133

stj opened this issue Mar 15, 2024 · 1 comment · May be fixed by #1134

Comments

@stj
Copy link

stj commented Mar 15, 2024

  • asyncpg version: 0.29
  • PostgreSQL version: 13.11
  • Do you use a PostgreSQL SaaS? If so, which? Can you reproduce
    the issue with a local PostgreSQL install?
    :
  • Python version: 3.12.2
  • Platform: linux
  • Do you use pgbouncer?: No
  • Did you install asyncpg with pip?: Yes
  • If you built asyncpg locally, which version of Cython did you use?: -
  • Can the issue be reproduced under both asyncio and
    uvloop?
    : n/a

We noticed that some of our tests clean-up yield RuntimeWarning: coroutine 'Connection.execute' was never awaited when the PYTHONASYNCIODEBUG envvar is set. While investigating, we found that this code in an unittest is the cause of the warning.
The root cause is the inspect.iscoroutinefunction on a asyncpg pool connection instance returns False, causing the TestCase class not to await it.
https://github.com/python/cpython/blob/16349868d396cc1bff5188de3638321e87fe0293/Lib/unittest/async_case.py#L112-L120

import asyncpg
import inspect
import unittest


class TestAsyncCleanupAwaitsAsyncpgPoolConnectionCoroutine(
    unittest.IsolatedAsyncioTestCase
):
    async def asyncSetUp(self):
        self.pool = await asyncpg.create_pool(min_size=1, max_size=1)
        self.addAsyncCleanup(self.pool.close)
        self.conn = await self.pool.acquire()
        self.addAsyncCleanup(self.pool.release, self.conn)

    async def test_cleanup_awaited(self):
        await self.conn.execute("SELECT now()")
        self.addAsyncCleanup(self.conn.execute, "SELECT now()")
        self.assertTrue(inspect.iscoroutinefunction(self.conn.execute))


class TestAsyncCleanupAwaitsAsyncpgConnectionCoroutine(
    unittest.IsolatedAsyncioTestCase
):
    async def asyncSetUp(self):
        self.conn = await asyncpg.connect()
        self.addAsyncCleanup(self.conn.close)

    async def test_cleanup_awaited(self):
        await self.conn.execute("SELECT now()")
        self.addAsyncCleanup(self.conn.execute, "SELECT now()")
        self.assertTrue(inspect.iscoroutinefunction(self.conn.execute))


if __name__ == "__main__":
    unittest.main()

When running this python snippet with PYTHONASYNCIODEBUG=1 and PYTHONWARNINGS=once and the output is

.F
/home/stj/.local/lib/python3.12/unittest/async_case.py:99: RuntimeWarning: coroutine 'Connection.execute' was never awaited
  self._callMaybeAsync(function, *args, **kwargs)
RuntimeWarning: Enable tracemalloc to get the object allocation traceback

======================================================================
FAIL: test_cleanup_awaited (__main__.TestAsyncCleanupAwaitsAsyncpgPoolConnectionCoroutine.test_cleanup_awaited)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/stj/.local/lib/python3.12/unittest/async_case.py", line 90, in _callTestMethod
    if self._callMaybeAsync(method) is not None:
       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/stj/.local/lib/python3.12/unittest/async_case.py", line 112, in _callMaybeAsync
    return self._asyncioRunner.run(
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/stj/.local/lib/python3.12/asyncio/runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/stj/.local/lib/python3.12/asyncio/base_events.py", line 687, in run_until_complete
    return future.result()
           ^^^^^^^^^^^^^^^
  File "/home/stj/test_asyncpg.py", line 21, in test_cleanup_awaited
    self.assertTrue(inspect.iscoroutinefunction(self.conn.execute))
AssertionError: False is not true

----------------------------------------------------------------------
Ran 2 tests in 0.060s

FAILED (failures=1)

Expectation is that both tests pass.

elprans added a commit that referenced this issue Mar 15, 2024
Use `markcoroutinefunction` (available in Python 3.12+) to make
`inspect.iscoroutinefunction()` return the correct answer for wrapped
connection methods.

Fixes: #1133
@elprans
Copy link
Member

elprans commented Mar 15, 2024

This is fairly easy to fix, though the mechanism to inform inspect about wrappers returning coroutine functions is only available in Python 3.12.

elprans added a commit that referenced this issue Mar 15, 2024
Use `markcoroutinefunction` (available in Python 3.12+) to make
`inspect.iscoroutinefunction()` return the correct answer for wrapped
connection methods.

Fixes: #1133
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants