# 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
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: |
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.
- 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
For further information about the differences between the packages see
:ref:`pq-impl`.
+.. warning::
+
+ The binary installation is not supported by PyPy.
+
.. _local-installation:
pip install "psycopg[c]"
+.. warning::
+
+ The local installation is not supported by PyPy.
+
.. _pure-python-installation:
^^^^^^^^^^^^^^^^^^^^^^^^^^^
- Fix :ref:`interaction with gevent <gevent>` (:ticket:`#527`).
+- Add support for PyPy (:ticket:`#686`).
.. _gevent: https://www.gevent.org/
@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
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
[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 =
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
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
# 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}")
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
p.wait()
ref = weakref.ref(p)
del p
+ gc_collect()
assert not ref()
assert not recwarn, [str(w.message) for w in recwarn.list]
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
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]
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:
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
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:
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
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)
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)
# 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
@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)]
)
@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)]
)
import string
import hashlib
+import sys
from io import BytesIO, StringIO
from random import choice, randrange
from itertools import cycle
@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)],
@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)],
import pytest
import psycopg
+import sys
from psycopg import pq, rows, errors as e
from psycopg.adapt import PyFormat
@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"])
import pytest
import psycopg
+import sys
from psycopg import pq, rows, errors as e
from psycopg.adapt import PyFormat
@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"])
import pytest
import psycopg
+import sys
from psycopg import rows
from .utils import gc_collect, gc_count
@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):
import pytest
import psycopg
+import sys
from psycopg import rows
from .utils import gc_collect, gc_count
@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):
# 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
@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"])
import pytest
import psycopg
+import sys
from psycopg import pq, rows, errors as e
from psycopg.adapt import PyFormat
@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"])
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")
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)
from psycopg.pq import Format
from .acompat import alist
+from .utils import gc_collect
pytestmark = pytest.mark.crdb_skip("server-side cursor")
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)