]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
chore: add support for PyPy.
authorNick Pope <nick.pope@infogrid.io>
Tue, 28 Nov 2023 12:46:59 +0000 (12:46 +0000)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 2 Dec 2023 00:09:16 +0000 (01:09 +0100)
24 files changed:
.github/workflows/tests.yml
docs/basic/install.rst
docs/news.rst
psycopg/psycopg/pq/pq_ctypes.py
psycopg/setup.cfg
psycopg_c/setup.cfg
psycopg_pool/setup.cfg
tests/fix_pq.py
tests/pool/test_pool.py
tests/pool/test_pool_async.py
tests/pool/test_pool_common.py
tests/pool/test_pool_common_async.py
tests/test_connection.py
tests/test_connection_async.py
tests/test_copy.py
tests/test_copy_async.py
tests/test_cursor.py
tests/test_cursor_async.py
tests/test_cursor_client.py
tests/test_cursor_client_async.py
tests/test_cursor_raw.py
tests/test_cursor_raw_async.py
tests/test_cursor_server.py
tests/test_cursor_server_async.py

index a1195fe27ef2c7a4828309c0a51e4d69d9ae45b8..0e9af93bb8751908c3b91572ccdc762edb2aa044 100644 (file)
@@ -48,6 +48,10 @@ jobs:
           # Test with minimum dependencies versions
           - {impl: c, python: "3.8", ext: min, postgres: "postgres:15"}
 
+          # Test with PyPy.
+          - {impl: python, python: "pypy3.9", postgres: "postgres:13"}
+          - {impl: python, python: "pypy3.10", postgres: "postgres:14"}
+
     env:
       PSYCOPG_IMPL: ${{ matrix.impl }}
       DEPS: ./psycopg[test] ./psycopg_pool
@@ -101,6 +105,11 @@ jobs:
           echo "DEPS=$DEPS numpy" >> $GITHUB_ENV
           echo "MARKERS=$MARKERS numpy" >> $GITHUB_ENV
 
+      - name: Exclude certain tests from pypy
+        if: ${{ startsWith(matrix.python, 'pypy') }}
+        run: |
+          echo "NOT_MARKERS=$NOT_MARKERS timing" >> $GITHUB_ENV
+
       - name: Configure to use the oldest dependencies
         if: ${{ matrix.ext == 'min' }}
         run: |
index f09fbc637be48f32e9708a3439ed9f01afefe695..13719d404713adcc796d36f7a13a1ea6103e2c32 100644 (file)
@@ -6,7 +6,7 @@ Installation
 In short, if you use a :ref:`supported system<supported-systems>`::
 
     pip install --upgrade pip           # upgrade pip to at least 20.3
-    pip install "psycopg[binary]"
+    pip install "psycopg[binary]"       # remove [binary] for PyPy
 
 and you should be :ref:`ready to start <module-usage>`. Read further for
 alternative ways to install.
@@ -27,6 +27,10 @@ The Psycopg version documented here has *official and tested* support for:
   - Python 3.6 supported before Psycopg 3.1
   - Python 3.7 supported before Psycopg 3.2
 
+- PyPy: from version 3.9 to 3.10
+
+  - **Note:** Only the pure Python version is supported.
+
 - PostgreSQL: from version 10 to 16
 - OS: Linux, macOS, Windows
 
@@ -76,6 +80,10 @@ installation <local-installation>` or a :ref:`pure Python installation
     For further information about the differences between the packages see
     :ref:`pq-impl`.
 
+.. warning::
+
+   The binary installation is not supported by PyPy.
+
 
 .. _local-installation:
 
@@ -103,6 +111,10 @@ If your build prerequisites are in place you can run::
 
     pip install "psycopg[c]"
 
+.. warning::
+
+   The local installation is not supported by PyPy.
+
 
 .. _pure-python-installation:
 
index 38be6143221f68aa09358f9133a737e8e274e812..de009ffce01b0bf23b44580b9eb51be52ad936dc 100644 (file)
@@ -34,6 +34,7 @@ Psycopg 3.1.14 (unreleased)
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 - Fix :ref:`interaction with gevent <gevent>` (:ticket:`#527`).
+- Add support for PyPy (:ticket:`#686`).
 
 .. _gevent: https://www.gevent.org/
 
index 5f32031ca981bacd84cd6086b1af4795d26d1485..f04a803679fcabbd6ba8395b0c1a4c974533a640 100644 (file)
@@ -47,7 +47,10 @@ def version() -> int:
 
 @impl.PQnoticeReceiver  # type: ignore
 def notice_receiver(arg: c_void_p, result_ptr: impl.PGresult_struct) -> None:
-    pgconn = cast(arg, POINTER(py_object)).contents.value()
+    pgconn = cast(arg, POINTER(py_object)).contents.value
+    if callable(pgconn):  # Not a weak reference on PyPy.
+        pgconn = pgconn()
+
     if not (pgconn and pgconn.notice_handler):
         return
 
index 900418f57308698b1f1e696c244f81f12bafc85a..891b53bfc6ce2b4da46d97ab51eeea66c915a024 100644 (file)
@@ -38,6 +38,8 @@ classifiers =
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
     Programming Language :: Python :: 3.12
+    Programming Language :: Python :: Implementation :: CPython
+    Programming Language :: Python :: Implementation :: PyPy
     Topic :: Database
     Topic :: Database :: Front-Ends
     Topic :: Software Development
@@ -58,9 +60,9 @@ install_requires =
 
 [options.extras_require]
 c =
-    psycopg-c == 3.2.0.dev1
+    psycopg-c == 3.2.0.dev1; implementation_name != "pypy"
 binary =
-    psycopg-binary == 3.2.0.dev1
+    psycopg-binary == 3.2.0.dev1; implementation_name != "pypy"
 pool =
     psycopg-pool
 test =
index 730596f4d8ee91b5dba47551ddab222ef1523fae..87bc8a39a054e625b7d7fe2fd2f98dc33b30b222 100644 (file)
@@ -22,12 +22,14 @@ classifiers =
     Operating System :: MacOS :: MacOS X
     Operating System :: Microsoft :: Windows
     Operating System :: POSIX
+    Programming Language :: Cython
     Programming Language :: Python :: 3
     Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
     Programming Language :: Python :: 3.12
+    Programming Language :: Python :: Implementation :: CPython
     Topic :: Database
     Topic :: Database :: Front-Ends
     Topic :: Software Development
index feafbae2a079526978c6ab25609f6343988bdbd5..f14f31e9b249f80b601be683013bb1ae24ef00b2 100644 (file)
@@ -32,6 +32,8 @@ classifiers =
     Programming Language :: Python :: 3.10
     Programming Language :: Python :: 3.11
     Programming Language :: Python :: 3.12
+    Programming Language :: Python :: Implementation :: CPython
+    Programming Language :: Python :: Implementation :: PyPy
     Topic :: Database
     Topic :: Database :: Front-Ends
     Topic :: Software Development
index 6811a26c32a3337bf0d1364684be9c06c82ee3a9..917dfc9193e9c558778034979587cf175a188b7f 100644 (file)
@@ -53,7 +53,7 @@ def libpq():
         # Not available when testing the binary package
         libname = find_libpq_full_path()
         assert libname, "libpq libname not found"
-        return ctypes.pydll.LoadLibrary(libname)
+        return ctypes.cdll.LoadLibrary(libname)
     except Exception as e:
         if pq.__impl__ == "binary":
             pytest.skip(f"can't load libpq for testing: {e}")
index 6323420e2dd8b4d96832d162b70c9abc22029caa..11a4cf30d87adb04cc90438041459df79d79c112 100644 (file)
@@ -12,7 +12,7 @@ import psycopg
 from psycopg.pq import TransactionStatus
 from psycopg.rows import class_row, Row, TupleRow
 
-from ..utils import assert_type, Counter, set_autocommit
+from ..utils import assert_type, Counter, gc_collect, set_autocommit
 from ..acompat import Event, spawn, gather, sleep, skip_sync
 from .test_pool_common import delay_connection
 
@@ -367,6 +367,7 @@ def test_del_no_warning(dsn, recwarn):
     p.wait()
     ref = weakref.ref(p)
     del p
+    gc_collect()
     assert not ref()
     assert not recwarn, [str(w.message) for w in recwarn.list]
 
index 7319e00b444bc216f0e3de5d5e1c30b7991453e9..0d6dbdfd08fdf464210833a3462a5873583d8aa3 100644 (file)
@@ -9,7 +9,7 @@ import psycopg
 from psycopg.pq import TransactionStatus
 from psycopg.rows import class_row, Row, TupleRow
 
-from ..utils import assert_type, Counter, set_autocommit
+from ..utils import assert_type, Counter, gc_collect, set_autocommit
 from ..acompat import AEvent, spawn, gather, asleep, skip_sync
 from .test_pool_common_async import delay_connection
 
@@ -371,6 +371,7 @@ async def test_del_no_warning(dsn, recwarn):
     await p.wait()
     ref = weakref.ref(p)
     del p
+    gc_collect()
     assert not ref()
     assert not recwarn, [str(w.message) for w in recwarn.list]
 
index ee8d03ffdeb0d53d0f14852844738573fbc3f086..9165eb785369fd1c2508b2a5ffc3527e4a92ba41 100644 (file)
@@ -9,7 +9,7 @@ import pytest
 
 import psycopg
 
-from ..utils import set_autocommit
+from ..utils import gc_collect, set_autocommit
 from ..acompat import Event, spawn, gather, sleep, is_alive, skip_async, skip_sync
 
 try:
@@ -352,6 +352,7 @@ def test_del_stops_threads(pool_cls, dsn):
     assert p._sched_runner is not None
     ts = [p._sched_runner] + p._workers
     del p
+    gc_collect()
     sleep(0.1)
     for t in ts:
         assert not is_alive(t), t
index dbcfbbe46c078e5b535f62ef70e994a39c33da3f..e1b7a2584e0de9135648be2fba347c5211092ff8 100644 (file)
@@ -6,7 +6,7 @@ import pytest
 
 import psycopg
 
-from ..utils import set_autocommit
+from ..utils import gc_collect, set_autocommit
 from ..acompat import AEvent, spawn, gather, asleep, is_alive, skip_async, skip_sync
 
 try:
@@ -369,6 +369,7 @@ async def test_del_stops_threads(pool_cls, dsn):
     assert p._sched_runner is not None
     ts = [p._sched_runner] + p._workers
     del p
+    gc_collect()
     await asleep(0.1)
     for t in ts:
         assert not is_alive(t), t
index c72fd7d06e1aa3cfb43c06043d62a73028f3a71a..ef066653fac5c37f36c1ed8edef4f817bdce2e11 100644 (file)
@@ -145,11 +145,13 @@ def test_connection_warn_close(conn_cls, dsn, recwarn):
 
     conn = conn_cls.connect(dsn)
     del conn
+    gc_collect()
     assert "IDLE" in str(recwarn.pop(ResourceWarning).message)
 
     conn = conn_cls.connect(dsn)
     conn.execute("select 1")
     del conn
+    gc_collect()
     assert "INTRANS" in str(recwarn.pop(ResourceWarning).message)
 
     conn = conn_cls.connect(dsn)
index e61307e31549e8b425513d7536076a17474f1dcc..d0110a01aabfd434b8cdbdb943d27f48a007de9d 100644 (file)
@@ -142,11 +142,13 @@ async def test_connection_warn_close(aconn_cls, dsn, recwarn):
 
     conn = await aconn_cls.connect(dsn)
     del conn
+    gc_collect()
     assert "IDLE" in str(recwarn.pop(ResourceWarning).message)
 
     conn = await aconn_cls.connect(dsn)
     await conn.execute("select 1")
     del conn
+    gc_collect()
     assert "INTRANS" in str(recwarn.pop(ResourceWarning).message)
 
     conn = await aconn_cls.connect(dsn)
index 4b3e182e65dd524f308d44af310225bbc933db3f..25d15f2f8bc843941f342d270bc208ca1756ff30 100644 (file)
@@ -3,6 +3,7 @@
 # DO NOT CHANGE! Change the original file instead.
 import string
 import hashlib
+import sys
 from io import BytesIO, StringIO
 from random import choice, randrange
 from itertools import cycle
@@ -673,6 +674,9 @@ def test_connection_writer(conn, format, buffer):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize(
     "fmt, set_types", [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)]
 )
@@ -728,6 +732,9 @@ def test_copy_to_leaks(conn_cls, dsn, faker, fmt, set_types, method):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize(
     "fmt, set_types", [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)]
 )
index 68c21d8ef87adf3193011f1b9f70f66255e5fbb6..807d94bd577da817a84dfc14de1fdbdad50ad725 100644 (file)
@@ -1,5 +1,6 @@
 import string
 import hashlib
+import sys
 from io import BytesIO, StringIO
 from random import choice, randrange
 from itertools import cycle
@@ -690,6 +691,9 @@ async def test_connection_writer(aconn, format, buffer):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize(
     "fmt, set_types",
     [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)],
@@ -746,6 +750,9 @@ async def test_copy_to_leaks(aconn_cls, dsn, faker, fmt, set_types, method):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize(
     "fmt, set_types",
     [(Format.TEXT, True), (Format.TEXT, False), (Format.BINARY, True)],
index 86d2fd7ef1a9783a71e135e7e19b2000777a5c6a..845b0941ce06fb8de468c85fef1cb6abfa8a2918 100644 (file)
@@ -7,6 +7,7 @@ Tests for psycopg.Cursor that are not supposed to pass for subclasses.
 
 import pytest
 import psycopg
+import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
@@ -65,6 +66,9 @@ def test_query_params_executemany(conn):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
index 1ebc827cda249f956ae047159d0687299d198a36..00e10883c4363329255541f10e1285fe5c84f885 100644 (file)
@@ -4,6 +4,7 @@ Tests for psycopg.Cursor that are not supposed to pass for subclasses.
 
 import pytest
 import psycopg
+import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
@@ -64,6 +65,9 @@ async def test_query_params_executemany(aconn):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
index bac6567ca3a19d9e4ad80bf0c424d3e63abfa6a1..a0284d17a97817f068ca5694ad9b154cecb5c2c6 100644 (file)
@@ -5,6 +5,7 @@ import datetime as dt
 
 import pytest
 import psycopg
+import sys
 from psycopg import rows
 
 from .utils import gc_collect, gc_count
@@ -78,6 +79,9 @@ def test_query_params_executemany(conn):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
 def test_leak(conn_cls, dsn, faker, fetch, row_factory):
index d1abbe84915cfd407c5eec9690af866b020e1f03..a3ba12ae6ab3111c9647fbeaaced88b7c7ba0d26 100644 (file)
@@ -2,6 +2,7 @@ import datetime as dt
 
 import pytest
 import psycopg
+import sys
 from psycopg import rows
 
 from .utils import gc_collect, gc_count
@@ -77,6 +78,9 @@ async def test_query_params_executemany(aconn):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
 @pytest.mark.parametrize("row_factory", ["tuple_row", "dict_row", "namedtuple_row"])
 async def test_leak(aconn_cls, dsn, faker, fetch, row_factory):
index 683aa6446b40a8725bcb301542737f04f7fe4165..76726aa8d8a312677642bc77919e3fac4e27f8c6 100644 (file)
@@ -3,6 +3,7 @@
 # DO NOT CHANGE! Change the original file instead.
 import pytest
 import psycopg
+import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
@@ -71,6 +72,9 @@ def test_query_params_executemany(conn):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
index b207b28c83c53924e611a94379b61b430f846b09..24d69cebb2591e18664546c4e67a18aded0f11d7 100644 (file)
@@ -1,5 +1,6 @@
 import pytest
 import psycopg
+import sys
 from psycopg import pq, rows, errors as e
 from psycopg.adapt import PyFormat
 
@@ -68,6 +69,9 @@ async def test_query_params_executemany(aconn):
 
 
 @pytest.mark.slow
+@pytest.mark.skipif(
+    sys.implementation.name == "pypy", reason="depends on refcount semantics"
+)
 @pytest.mark.parametrize("fmt", PyFormat)
 @pytest.mark.parametrize("fmt_out", pq.Format)
 @pytest.mark.parametrize("fetch", ["one", "many", "all", "iter"])
index 03dc7af537d18e15938f2f21afad5c608bb9d409..9fdfdbda71d282a1a167052fb75a1f374d87f458 100644 (file)
@@ -7,6 +7,7 @@ import psycopg
 from psycopg import rows, errors as e
 from psycopg.pq import Format
 
+from .utils import gc_collect
 
 pytestmark = pytest.mark.crdb_skip("server-side cursor")
 
@@ -260,6 +261,7 @@ def test_warn_close(conn, recwarn):
     cur = conn.cursor("foo")
     cur.execute("select generate_series(1, 10) as bar")
     del cur
+    gc_collect()
     assert ".close()" in str(recwarn.pop(ResourceWarning).message)
 
 
index 7317c52d8bdc78731fc1ed8478f17848b03f8569..0417ab3c3bc6cd4556eefbb3cce2f777f28f17df 100644 (file)
@@ -5,6 +5,7 @@ from psycopg import rows, errors as e
 from psycopg.pq import Format
 
 from .acompat import alist
+from .utils import gc_collect
 
 pytestmark = pytest.mark.crdb_skip("server-side cursor")
 
@@ -266,6 +267,7 @@ async def test_warn_close(aconn, recwarn):
     cur = aconn.cursor("foo")
     await cur.execute("select generate_series(1, 10) as bar")
     del cur
+    gc_collect()
     assert ".close()" in str(recwarn.pop(ResourceWarning).message)