Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-psycopg for openSUSE:Factory 
checked in at 2026-05-18 17:48:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-psycopg (Old)
 and      /work/SRC/openSUSE:Factory/.python-psycopg.new.1966 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-psycopg"

Mon May 18 17:48:18 2026 rev:14 rq:1353778 version:3.3.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-psycopg/python-psycopg.changes    
2026-02-23 16:14:47.362875171 +0100
+++ /work/SRC/openSUSE:Factory/.python-psycopg.new.1966/python-psycopg.changes  
2026-05-18 17:49:13.731307809 +0200
@@ -1,0 +2,11 @@
+Mon May 18 10:30:30 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 3.3.4:
+  * Fix possible spurious connection timeout in systems with very
+    long uptimes in C extension (:ticket:`#1280`).
+  * Fix client-side adaptation of enums whose name require quotes
+    (:ticket:`#1298`).
+  * Consistently populate ~Cursor.statusmessage after
+    ~Cursor.executemany() (:ticket:`#1302`).
+
+-------------------------------------------------------------------

Old:
----
  psycopg-3.3.3.tar.gz

New:
----
  psycopg-3.3.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-psycopg.spec ++++++
--- /var/tmp/diff_new_pack.piYz9g/_old  2026-05-18 17:49:15.323373597 +0200
+++ /var/tmp/diff_new_pack.piYz9g/_new  2026-05-18 17:49:15.343374423 +0200
@@ -19,7 +19,7 @@
 %{?sle15_python_module_pythons}
 Name:           python-psycopg
 # This needs to upgraded in lockstep with python-psycopg-c
-Version:        3.3.3
+Version:        3.3.4
 Release:        0
 Summary:        PostgreSQL database adapter for Python
 License:        LGPL-3.0-only

++++++ psycopg-3.3.3.tar.gz -> psycopg-3.3.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/.flake8 new/psycopg-3.3.4/.flake8
--- old/psycopg-3.3.3/.flake8   2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.flake8   2026-05-01 22:41:15.000000000 +0200
@@ -6,7 +6,7 @@
 [flake8]
 max-line-length = 88
 ignore = W503, E203, E704
-extend-exclude = .venv build tests/test_tstring.py
+extend-exclude = .venv build
 per-file-ignores =
     # Autogenerated section
     psycopg/psycopg/errors.py: E125, E128, E302
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/psycopg-3.3.3/.github/workflows/build-and-cache-libpq.yml 
new/psycopg-3.3.4/.github/workflows/build-and-cache-libpq.yml
--- old/psycopg-3.3.3/.github/workflows/build-and-cache-libpq.yml       
2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.github/workflows/build-and-cache-libpq.yml       
2026-05-01 22:41:15.000000000 +0200
@@ -81,7 +81,7 @@
 
       - name: Set up QEMU for multi-arch build
         # Check https://github.com/docker/setup-qemu-action for newer versions.
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
         with:
           # https://github.com/pypa/cibuildwheel/discussions/2256
           image: tonistiigi/binfmt:qemu-v8.1.5
@@ -93,7 +93,7 @@
           key: libpq-${{ matrix.platform }}-${{ matrix.arch }}-${{ 
env.LIBPQ_VERSION }}-${{ env.OPENSSL_VERSION }}${{ env.PQ_FLAGS }}
 
       - name: Build wheels
-        uses: pypa/[email protected]
+        uses: pypa/[email protected]
         with:
           package-dir: psycopg_c
         env:
@@ -137,7 +137,7 @@
           key: libpq-macos-${{ env.LIBPQ_VERSION }}-${{ matrix.arch }}-${{ 
env.OPENSSL_VERSION }}${{ env.PQ_FLAGS }}
 
       - name: Build wheels
-        uses: pypa/[email protected]
+        uses: pypa/[email protected]
         with:
           package-dir: psycopg_c
         env:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/.github/workflows/lint.yml 
new/psycopg-3.3.4/.github/workflows/lint.yml
--- old/psycopg-3.3.3/.github/workflows/lint.yml        2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.github/workflows/lint.yml        2026-05-01 
22:41:15.000000000 +0200
@@ -24,7 +24,7 @@
 
       - uses: actions/setup-python@v6
         with:
-          python-version: "3.11"
+          python-version: "3.14"
 
       - name: install packages to tests
         run: pip install ./psycopg[dev,test]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/.github/workflows/packages-bin.yml 
new/psycopg-3.3.4/.github/workflows/packages-bin.yml
--- old/psycopg-3.3.3/.github/workflows/packages-bin.yml        2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.github/workflows/packages-bin.yml        2026-05-01 
22:41:15.000000000 +0200
@@ -57,7 +57,7 @@
 
       - name: Set up QEMU for multi-arch build
         # Check https://github.com/docker/setup-qemu-action for newer versions.
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
         with:
           # https://github.com/pypa/cibuildwheel/discussions/2256
           image: tonistiigi/binfmt:qemu-v8.1.5
@@ -72,7 +72,7 @@
         run: python3 ./tools/ci/copy_to_binary.py
 
       - name: Build wheels
-        uses: pypa/[email protected]
+        uses: pypa/[email protected]
         with:
           package-dir: psycopg_binary
         env:
@@ -100,7 +100,7 @@
             PSYCOPG_TEST_WANT_LIBPQ_IMPORT=${{ env.LIBPQ_VERSION }}
             PYTEST_ADDOPTS="-m 'not slow and not flakey' --color yes"
 
-      - uses: actions/upload-artifact@v6
+      - uses: actions/upload-artifact@v7
         with:
           name: linux-${{matrix.pyver}}-${{matrix.platform}}_${{matrix.arch}}
           path: ./wheelhouse/*.whl
@@ -146,7 +146,7 @@
         run: python3 ./tools/ci/copy_to_binary.py
 
       - name: Build wheels
-        uses: pypa/[email protected]
+        uses: pypa/[email protected]
         with:
           package-dir: psycopg_binary
         env:
@@ -167,7 +167,7 @@
             PYTEST_ADDOPTS="-m 'not slow and not flakey and not proxy' --color 
yes"
 
       - name: Upload artifacts
-        uses: actions/upload-artifact@v6
+        uses: actions/upload-artifact@v7
         with:
           name: macos-${{matrix.pyver}}-${{matrix.arch}}
           path: ./wheelhouse/*.whl
@@ -204,7 +204,7 @@
         shell: powershell
 
       - name: Export GitHub Actions cache environment variables
-        uses: actions/github-script@v8
+        uses: actions/github-script@v9
         with:
           script: |
             const path = require('path')
@@ -217,7 +217,7 @@
         run: python3 ./tools/ci/copy_to_binary.py
 
       - name: Build wheels
-        uses: pypa/[email protected]
+        uses: pypa/[email protected]
         with:
           package-dir: psycopg_binary
         env:
@@ -237,7 +237,7 @@
             PSYCOPG_TEST_WANT_LIBPQ_IMPORT=">= 16"
             PYTEST_ADDOPTS="-m 'not slow and not flakey and not proxy' --color 
yes"
 
-      - uses: actions/upload-artifact@v6
+      - uses: actions/upload-artifact@v7
         with:
           name: windows-${{matrix.pyver}}-${{matrix.arch}}
           path: ./wheelhouse/*.whl
@@ -253,7 +253,7 @@
       - windows
     steps:
       - name: Merge Artifacts
-        uses: actions/upload-artifact/merge@v6
+        uses: actions/upload-artifact/merge@v7
         with:
           name: psycopg-binary-artifact
           delete-merged: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/.github/workflows/packages-pool.yml 
new/psycopg-3.3.4/.github/workflows/packages-pool.yml
--- old/psycopg-3.3.3/.github/workflows/packages-pool.yml       2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.github/workflows/packages-pool.yml       2026-05-01 
22:41:15.000000000 +0200
@@ -25,7 +25,7 @@
 
       - uses: actions/setup-python@v6
         with:
-          python-version: "3.10"
+          python-version: "3.14"
 
       - name: Install the build package
         run: pip install build
@@ -42,7 +42,7 @@
           PSYCOPG_TEST_DSN: "host=127.0.0.1 user=postgres"
           PGPASSWORD: password
 
-      - uses: actions/upload-artifact@v6
+      - uses: actions/upload-artifact@v7
         with:
           name: ${{ matrix.package }}-${{ matrix.format }}
           path: ./dist/*
@@ -67,7 +67,7 @@
       - sdist
     steps:
       - name: Merge Artifacts
-        uses: actions/upload-artifact/merge@v6
+        uses: actions/upload-artifact/merge@v7
         with:
           name: psycopg-pool-artifact
           delete-merged: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/.github/workflows/packages-src.yml 
new/psycopg-3.3.4/.github/workflows/packages-src.yml
--- old/psycopg-3.3.3/.github/workflows/packages-src.yml        2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.github/workflows/packages-src.yml        2026-05-01 
22:41:15.000000000 +0200
@@ -27,7 +27,7 @@
 
       - uses: actions/setup-python@v6
         with:
-          python-version: "3.10"
+          python-version: "3.14"
 
       - name: Install the build package
         run: pip install build
@@ -50,7 +50,7 @@
           PSYCOPG_TEST_DSN: "host=127.0.0.1 user=postgres"
           PGPASSWORD: password
 
-      - uses: actions/upload-artifact@v6
+      - uses: actions/upload-artifact@v7
         with:
           name: ${{ matrix.package }}-${{ matrix.format }}-${{ matrix.impl }}
           path: ./dist/*
@@ -74,7 +74,7 @@
       - sdist
     steps:
       - name: Merge Artifacts
-        uses: actions/upload-artifact/merge@v6
+        uses: actions/upload-artifact/merge@v7
         with:
           name: psycopg-src-artifact
           delete-merged: true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/.github/workflows/tests.yml 
new/psycopg-3.3.4/.github/workflows/tests.yml
--- old/psycopg-3.3.3/.github/workflows/tests.yml       2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/.github/workflows/tests.yml       2026-05-01 
22:41:15.000000000 +0200
@@ -315,7 +315,7 @@
 
       - name: Export GitHub Actions cache environment variables
       # 
https://learn.microsoft.com/en-us/vcpkg/consume/binary-caching-github-actions-cache
-        uses: actions/github-script@v8
+        uses: actions/github-script@v9
         with:
           script: |
             const path = require('path')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/docs/api/pool.rst 
new/psycopg-3.3.4/docs/api/pool.rst
--- old/psycopg-3.3.3/docs/api/pool.rst 2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/docs/api/pool.rst 2026-05-01 22:41:15.000000000 +0200
@@ -228,6 +228,8 @@
           with ConnectionPool(...) as pool:
               # code using the pool
 
+   .. autoattribute:: closed
+
    .. automethod:: wait
 
    .. attribute:: name
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/docs/news.rst 
new/psycopg-3.3.4/docs/news.rst
--- old/psycopg-3.3.3/docs/news.rst     2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/docs/news.rst     2026-05-01 22:41:15.000000000 +0200
@@ -10,6 +10,17 @@
 Current release
 ---------------
 
+Psycopg 3.3.4
+^^^^^^^^^^^^^
+
+- Fix possible spurious connection timeout in systems with very long uptimes
+  in C extension (:ticket:`#1280`).
+- Fix client-side adaptation of enums whose name require quotes
+  (:ticket:`#1298`).
+- Consistently populate `~Cursor.statusmessage` after `~Cursor.executemany()`
+  (:ticket:`#1302`).
+
+
 Psycopg 3.3.3
 ^^^^^^^^^^^^^
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/docs/news_pool.rst 
new/psycopg-3.3.4/docs/news_pool.rst
--- old/psycopg-3.3.3/docs/news_pool.rst        2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/docs/news_pool.rst        2026-05-01 22:41:15.000000000 
+0200
@@ -10,6 +10,15 @@
 Current release
 ---------------
 
+psycopg_pool 3.3.1
+^^^^^^^^^^^^^^^^^^
+
+- Fix residual race condition catching `~asyncio.CancelledError` on connection
+  (:ticket:`#1275`).
+- Fix race condition constructing the lock that makes sync
+  `~ConnectionPool.open()` thread-safe (:ticket:`#1300`).
+
+
 psycopg_pool 3.3.0
 ------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/psycopg/_adapters_map.py 
new/psycopg-3.3.4/psycopg/psycopg/_adapters_map.py
--- old/psycopg-3.3.3/psycopg/psycopg/_adapters_map.py  2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg/psycopg/_adapters_map.py  2026-05-01 
22:41:15.000000000 +0200
@@ -212,8 +212,10 @@
 
         # Look for the right class, including looking at superclasses
         for scls in cls.__mro__:
-            if scls in dmap:
+            try:
                 return dmap[scls]
+            except KeyError:
+                pass
 
             # If the adapter is not found, look for its name as a string
             fqn = scls.__module__ + "." + scls.__qualname__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/psycopg/_capabilities.py 
new/psycopg-3.3.4/psycopg/psycopg/_capabilities.py
--- old/psycopg-3.3.3/psycopg/psycopg/_capabilities.py  2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg/psycopg/_capabilities.py  2026-05-01 
22:41:15.000000000 +0200
@@ -98,9 +98,9 @@
 
         The expletive messages, are left to the user.
         """
-        if feature in self._cache:
+        try:
             msg = self._cache[feature]
-        else:
+        except KeyError:
             msg = self._get_unsupported_message(feature, want_version)
             self._cache[feature] = msg
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/psycopg/_cursor_base.py 
new/psycopg-3.3.4/psycopg/psycopg/_cursor_base.py
--- old/psycopg-3.3.3/psycopg/psycopg/_cursor_base.py   2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg/psycopg/_cursor_base.py   2026-05-01 
22:41:15.000000000 +0200
@@ -49,7 +49,7 @@
     __slots__ = """
         _conn format _adapters arraysize _closed _results pgresult _pos
         _iresult _rowcount _query _tx _last_query _row_factory _make_row
-        _pgconn _execmany_returning
+        _pgconn _execmany_returning _statusmessage
         __weakref__
         """.split()
 
@@ -79,6 +79,7 @@
         self._pos = 0
         self._iresult = 0
         self._rowcount = -1
+        self._statusmessage: bytes | None = None
         self._query: PostgresQuery | None
         # None if executemany() not executing, True/False according to 
returning state
         self._execmany_returning: bool | None = None
@@ -178,8 +179,7 @@
 
         `!None` if the cursor doesn't have a result available.
         """
-        msg = self.pgresult.command_status if self.pgresult else None
-        return msg.decode() if msg else None
+        return self._statusmessage.decode() if self._statusmessage else None
 
     def _make_row_maker(self) -> RowMaker[Row]:
         raise NotImplementedError
@@ -525,6 +525,7 @@
             nrows = self.pgresult.command_tuples
             self._rowcount = nrows if nrows is not None else -1
 
+        self._statusmessage = res.command_status
         self._make_row = self._make_row_maker()
 
     def _set_results(self, results: list[PGresult]) -> None:
@@ -540,9 +541,12 @@
                 self._select_current_result(0)
         else:
             # In non-returning case, set rowcount to the cumulated number of
-            # rows of executed queries.
-            for res in results:
-                self._rowcount += res.command_tuples or 0
+            # rows of executed queries. Keep the last result's command_status
+            # so that statusmessage is available after the batch completes.
+            if results:
+                self._statusmessage = results[-1].command_status
+                for res in results:
+                    self._rowcount += res.command_tuples or 0
 
     @classmethod
     def _loaders_changed(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/psycopg/types/composite.py 
new/psycopg-3.3.4/psycopg/psycopg/types/composite.py
--- old/psycopg-3.3.3/psycopg/psycopg/types/composite.py        2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg/psycopg/types/composite.py        2026-05-01 
22:41:15.000000000 +0200
@@ -206,8 +206,10 @@
         return tx.load_sequence(record)
 
     def _get_transformer(self, key: tuple[int, ...]) -> abc.Transformer:
-        if key in self._txs:
+        try:
             return self._txs[key]
+        except KeyError:
+            pass
 
         tx = Transformer(self._ctx)
         tx.set_loader_types([*key], self.format)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/psycopg/types/enum.py 
new/psycopg-3.3.4/psycopg/psycopg/types/enum.py
--- old/psycopg-3.3.3/psycopg/psycopg/types/enum.py     2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg/psycopg/types/enum.py     2026-05-01 
22:41:15.000000000 +0200
@@ -43,9 +43,13 @@
         name: str,
         oid: int,
         array_oid: int,
+        # A bit ugly: this should have been a keyword-only argument, but it has
+        # been it the wild accepting a positional argument for too long to fix.
         labels: Sequence[str],
+        *,
+        regtype: str = "",
     ):
-        super().__init__(name, oid, array_oid)
+        super().__init__(name, oid, array_oid, regtype=regtype)
         self.labels = labels
         # Will be set by register_enum()
         self.enum: type[Enum] | None = None
@@ -53,18 +57,18 @@
     @classmethod
     def _get_info_query(cls, conn: BaseConnection[Any]) -> QueryNoTemplate:
         return sql.SQL("""\
-SELECT name, oid, array_oid, array_agg(label) AS labels
+SELECT name, oid, array_oid, regtype, array_agg(label) AS labels
 FROM (
     SELECT
         t.typname AS name, t.oid AS oid, t.typarray AS array_oid,
-        e.enumlabel AS label
+        t.oid::regtype::text AS regtype, e.enumlabel AS label
     FROM pg_type t
     LEFT JOIN  pg_enum e
     ON e.enumtypid = t.oid
     WHERE t.oid = {regtype}
     ORDER BY e.enumsortorder
 ) x
-GROUP BY name, oid, array_oid
+GROUP BY name, oid, array_oid, regtype
 """).format(regtype=cls._to_regtype(conn))
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/psycopg/types/uuid.py 
new/psycopg-3.3.4/psycopg/psycopg/types/uuid.py
--- old/psycopg-3.3.3/psycopg/psycopg/types/uuid.py     2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg/psycopg/types/uuid.py     2026-05-01 
22:41:15.000000000 +0200
@@ -25,14 +25,14 @@
     oid = _oids.UUID_OID
 
     def dump(self, obj: uuid.UUID) -> Buffer | None:
-        return obj.hex.encode()
+        return b"%032x" % obj.int
 
 
 class UUIDBinaryDumper(UUIDDumper):
     format = Format.BINARY
 
     def dump(self, obj: uuid.UUID) -> Buffer | None:
-        return obj.bytes
+        return obj.int.to_bytes(16, "big")
 
 
 class UUIDLoader(Loader):
@@ -43,18 +43,14 @@
             from uuid import UUID
 
     def load(self, data: Buffer) -> uuid.UUID:
-        if isinstance(data, memoryview):
-            data = bytes(data)
-        return UUID(data.decode())
+        return UUID((bytes(data) if isinstance(data, memoryview) else 
data).decode())
 
 
 class UUIDBinaryLoader(UUIDLoader):
     format = Format.BINARY
 
     def load(self, data: Buffer) -> uuid.UUID:
-        if isinstance(data, memoryview):
-            data = bytes(data)
-        return UUID(bytes=data)
+        return UUID(bytes=(bytes(data) if isinstance(data, memoryview) else 
data))
 
 
 def register_default_adapters(context: AdaptContext) -> None:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg/pyproject.toml 
new/psycopg-3.3.4/psycopg/pyproject.toml
--- old/psycopg-3.3.3/psycopg/pyproject.toml    2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/psycopg/pyproject.toml    2026-05-01 22:41:15.000000000 
+0200
@@ -7,7 +7,7 @@
 description = "PostgreSQL database adapter for Python"
 
 # STOP AND READ! if you change:
-version = "3.3.3"
+version = "3.3.4"
 # also change:
 # - `docs/news.rst` to declare this as the current version or an unreleased 
one;
 # - `psycopg_c/pyproject.toml` to the same version;
@@ -60,10 +60,10 @@
 
 [project.optional-dependencies]
 c = [
-    "psycopg-c == 3.3.3; implementation_name != \"pypy\"",
+    "psycopg-c == 3.3.4; implementation_name != \"pypy\"",
 ]
 binary = [
-    "psycopg-binary == 3.3.3; implementation_name != \"pypy\"",
+    "psycopg-binary == 3.3.4; implementation_name != \"pypy\"",
 ]
 pool = [
     "psycopg-pool",
@@ -93,10 +93,10 @@
     "wheel >= 0.37",
 ]
 docs = [
-    "Sphinx >= 5.0",
-    "furo == 2022.6.21",
-    "sphinx-autobuild >= 2021.3.14",
-    "sphinx-autodoc-typehints >= 1.12",
+    "Sphinx >= 9.1",
+    "furo == 2025.12.19",
+    "sphinx-autobuild >= 2025.8.25",
+    "sphinx-autodoc-typehints >= 3.10.2",
 ]
 
 [tool.setuptools]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/psycopg-3.3.3/psycopg_c/build_backend/psycopg_build_ext.py 
new/psycopg-3.3.4/psycopg_c/build_backend/psycopg_build_ext.py
--- old/psycopg-3.3.3/psycopg_c/build_backend/psycopg_build_ext.py      
2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg_c/build_backend/psycopg_build_ext.py      
2026-05-01 22:41:15.000000000 +0200
@@ -9,10 +9,12 @@
 
 import os
 import sys
+import logging
 import subprocess as sp
-from distutils import log
 from distutils.command.build_ext import build_ext
 
+log = logging.getLogger(__name__)
+
 
 def get_config(what: str) -> str:
     pg_config = "pg_config"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/psycopg-3.3.3/psycopg_c/psycopg_c/_psycopg/generators.pyx 
new/psycopg-3.3.4/psycopg_c/psycopg_c/_psycopg/generators.pyx
--- old/psycopg-3.3.3/psycopg_c/psycopg_c/_psycopg/generators.pyx       
2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg_c/psycopg_c/_psycopg/generators.pyx       
2026-05-01 22:41:15.000000000 +0200
@@ -38,7 +38,7 @@
     cdef int conn_status = libpq.PQstatus(pgconn_ptr)
     cdef int poll_status
     cdef object wait, ready
-    cdef float deadline = 0.0
+    cdef double deadline = 0.0
 
     if timeout:
         deadline = monotonic() + timeout
@@ -89,7 +89,7 @@
 def cancel(pq.PGcancelConn cancel_conn, *, timeout: float = 0.0) -> 
PQGenConn[None]:
     cdef libpq.PGcancelConn *pgcancelconn_ptr = cancel_conn.pgcancelconn_ptr
     cdef int status
-    cdef float deadline = 0.0
+    cdef double deadline = 0.0
 
     if timeout:
         deadline = monotonic() + timeout
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg_c/pyproject.toml 
new/psycopg-3.3.4/psycopg_c/pyproject.toml
--- old/psycopg-3.3.3/psycopg_c/pyproject.toml  2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/psycopg_c/pyproject.toml  2026-05-01 22:41:15.000000000 
+0200
@@ -24,7 +24,7 @@
 [project]
 name = "psycopg-c"
 description = "PostgreSQL database adapter for Python -- C optimisation 
distribution"
-version = "3.3.3"
+version = "3.3.4"
 license = "LGPL-3.0-only"
 license-files = ["LICENSE.txt"]
 classifiers = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg_pool/psycopg_pool/base.py 
new/psycopg-3.3.4/psycopg_pool/psycopg_pool/base.py
--- old/psycopg-3.3.3/psycopg_pool/psycopg_pool/base.py 2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg_pool/psycopg_pool/base.py 2026-05-01 
22:41:15.000000000 +0200
@@ -11,8 +11,6 @@
 from typing import TYPE_CHECKING, Any
 from collections import Counter, deque
 
-from psycopg import errors as e
-
 from .errors import PoolClosed
 
 if TYPE_CHECKING:
@@ -126,15 +124,13 @@
         if max_size < min_size:
             raise ValueError("max_size must be greater or equal than min_size")
         if min_size == max_size == 0:
-            raise ValueError("if min_size is 0 max_size must be greater or 
than 0")
+            raise ValueError("if min_size is 0 max_size must be greater than 
0")
 
         return min_size, max_size
 
     def _check_open(self) -> None:
         if self._closed and self._opened:
-            raise e.OperationalError(
-                "pool has already been opened/closed and cannot be reused"
-            )
+            raise PoolClosed("pool has already been opened/closed and cannot 
be reused")
 
     def _check_open_getconn(self) -> None:
         if self._closed:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg_pool/psycopg_pool/pool.py 
new/psycopg-3.3.4/psycopg_pool/psycopg_pool/pool.py
--- old/psycopg-3.3.3/psycopg_pool/psycopg_pool/pool.py 2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg_pool/psycopg_pool/pool.py 2026-05-01 
22:41:15.000000000 +0200
@@ -105,6 +105,10 @@
             num_workers=num_workers,
         )
 
+        # Construct the lock during single-threaded `__init__` so that
+        # threads concurrently calling `open()` can't race on it.
+        self._lock = Lock()
+
         if open is None:
             open = self._open_implicit = True
 
@@ -261,6 +265,8 @@
             try:
                 conn = pos.wait(timeout=timeout)
             except CLIENT_EXCEPTIONS:
+                if pos.conn:
+                    self.run_task(ReturnConnection(self, pos.conn, 
from_getconn=True))
                 self._stats[self._REQUESTS_ERRORS] += 1
                 raise
             finally:
@@ -378,8 +384,6 @@
         because the pool was initialized with *open* = `!True`) but you cannot
         currently re-open a closed pool.
         """
-        # Make sure the lock is created after there is an event loop
-        self._ensure_lock()
 
         with self._lock:
             self._open()
@@ -393,9 +397,6 @@
 
         self._check_open()
 
-        # A lock has been most likely, but not necessarily, created in 
`open()`.
-        self._ensure_lock()
-
         # Create these objects now to attach them to the right loop.
         # See #219
         self._tasks = Queue()
@@ -407,17 +408,6 @@
         self._start_workers()
         self._start_initial_tasks()
 
-    def _ensure_lock(self) -> None:
-        """Make sure the pool lock is created.
-
-        In async code, also make sure that the loop is running.
-        """
-
-        try:
-            self._lock
-        except AttributeError:
-            self._lock = Lock()
-
     def _start_workers(self) -> None:
         self._sched_runner = spawn(self._sched.run, 
name=f"{self.name}-scheduler")
         assert not self._workers
@@ -894,11 +884,11 @@
                 except CLIENT_EXCEPTIONS as ex:
                     self.error = ex
 
-        if self.conn:
-            return self.conn
-        else:
-            assert self.error
+        if self.error:
             raise self.error
+        else:
+            assert self.conn
+            return self.conn
 
     def set(self, conn: CT) -> bool:
         """Signal the client waiting that a connection is ready.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/psycopg-3.3.3/psycopg_pool/psycopg_pool/pool_async.py 
new/psycopg-3.3.4/psycopg_pool/psycopg_pool/pool_async.py
--- old/psycopg-3.3.3/psycopg_pool/psycopg_pool/pool_async.py   2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg_pool/psycopg_pool/pool_async.py   2026-05-01 
22:41:15.000000000 +0200
@@ -117,6 +117,10 @@
         if True:  # ASYNC
             if open:
                 self._warn_open_async()
+        else:
+            # Construct the lock during single-threaded `__init__` so that
+            # threads concurrently calling `open()` can't race on it.
+            self._lock = ALock()
 
         if open is None:
             open = self._open_implicit = True
@@ -298,6 +302,8 @@
             try:
                 conn = await pos.wait(timeout=timeout)
             except CLIENT_EXCEPTIONS:
+                if pos.conn:
+                    self.run_task(ReturnConnection(self, pos.conn, 
from_getconn=True))
                 self._stats[self._REQUESTS_ERRORS] += 1
                 raise
             finally:
@@ -416,8 +422,9 @@
         because the pool was initialized with *open* = `!True`) but you cannot
         currently re-open a closed pool.
         """
-        # Make sure the lock is created after there is an event loop
-        self._ensure_lock()
+        if True:  # ASYNC
+            # Make sure the lock is created after there is an event loop
+            self._ensure_lock()
 
         async with self._lock:
             self._open()
@@ -431,8 +438,10 @@
 
         self._check_open()
 
-        # A lock has been most likely, but not necessarily, created in 
`open()`.
-        self._ensure_lock()
+        if True:  # ASYNC
+            # A lock has been most likely, but not necessarily, created
+            # in `open()`. The sync pool creates it in `__init__()`.
+            self._ensure_lock()
 
         # Create these objects now to attach them to the right loop.
         # See #219
@@ -445,12 +454,16 @@
         self._start_workers()
         self._start_initial_tasks()
 
-    def _ensure_lock(self) -> None:
-        """Make sure the pool lock is created.
+    if True:  # ASYNC
+
+        def _ensure_lock(self) -> None:
+            """Make sure the pool lock is created and the loop is running."""
+            try:
+                self._lock
+                return
+            except AttributeError:
+                pass
 
-        In async code, also make sure that the loop is running.
-        """
-        if True:  # ASYNC
             try:
                 asyncio.get_running_loop()
             except RuntimeError:
@@ -458,9 +471,6 @@
                     f"{type(self).__name__} open with no running loop"
                 ) from None
 
-        try:
-            self._lock
-        except AttributeError:
             self._lock = ALock()
 
     def _start_workers(self) -> None:
@@ -957,11 +967,11 @@
                 except CLIENT_EXCEPTIONS as ex:
                     self.error = ex
 
-        if self.conn:
-            return self.conn
-        else:
-            assert self.error
+        if self.error:
             raise self.error
+        else:
+            assert self.conn
+            return self.conn
 
     async def set(self, conn: ACT) -> bool:
         """Signal the client waiting that a connection is ready.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/psycopg_pool/pyproject.toml 
new/psycopg-3.3.4/psycopg_pool/pyproject.toml
--- old/psycopg-3.3.3/psycopg_pool/pyproject.toml       2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/psycopg_pool/pyproject.toml       2026-05-01 
22:41:15.000000000 +0200
@@ -7,7 +7,7 @@
 description = "Connection Pool for Psycopg"
 
 # STOP AND READ! if you change:
-version = "3.3.1.dev1"
+version = "3.3.1"
 # also change:
 # - `docs/news_pool.rst` to declare this version current or unreleased
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/pyproject.toml 
new/psycopg-3.3.4/pyproject.toml
--- old/psycopg-3.3.3/pyproject.toml    2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/pyproject.toml    2026-05-01 22:41:15.000000000 +0200
@@ -42,6 +42,16 @@
     | tests/test_tstring\.py
 )'''
 
+# Don't use SQLite cache as it highlights Mypy concurrency issues.
+# See https://github.com/python/mypy/issues/21136
+# The issue is triggered by parallel mypy runs under pre-commit, setting
+# the parameter here allows the use of the same cache both by pre-commit and
+# every other Mypy usage.
+#
+# Check https://github.com/python/mypy/issues/13916 in the future to see
+# if new ways to run mypy under pre-commit emerge.
+sqlite_cache = false
+
 [[tool.mypy.overrides]]
 module = [
     "numpy.*",
@@ -68,6 +78,3 @@
 length_sort = true
 multi_line_output = 9
 sort_order = "psycopg"  # requires the isort-psycopg module
-
-[tool.black]
-force_exclude = "tests/test_tstring.py"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/constraints.txt 
new/psycopg-3.3.4/tests/constraints.txt
--- old/psycopg-3.3.3/tests/constraints.txt     2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/tests/constraints.txt     2026-05-01 22:41:15.000000000 
+0200
@@ -24,10 +24,10 @@
 wheel == 0.37
 
 # From the 'docs' extra
-Sphinx == 5.0
-furo == 2022.6.21
-sphinx-autobuild == 2021.3.14
-sphinx-autodoc-typehints == 1.12.0
+Sphinx == 9.1.0
+furo == 2025.12.19
+sphinx-autobuild == 2025.8.25
+sphinx-autodoc-typehints == 3.10.2
 
 # Build tools
 wheel == 0.37
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/fix_crdb.py 
new/psycopg-3.3.4/tests/fix_crdb.py
--- old/psycopg-3.3.3/tests/fix_crdb.py 2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/fix_crdb.py 2026-05-01 22:41:15.000000000 +0200
@@ -86,6 +86,7 @@
     "batch statements": 44803,
     "begin_read_only": 87012,
     "binary decimal": 82492,
+    "broken regtype": 169272,
     "cancel": 41335,
     "cast adds tz": 51692,
     "cidr": 18846,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/pool/test_pool.py 
new/psycopg-3.3.4/tests/pool/test_pool.py
--- old/psycopg-3.3.3/tests/pool/test_pool.py   2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/tests/pool/test_pool.py   2026-05-01 22:41:15.000000000 
+0200
@@ -15,8 +15,9 @@
 from psycopg.pq import TransactionStatus
 from psycopg.rows import Row, TupleRow, class_row
 
+from .. import acompat
 from ..utils import assert_type, set_autocommit, skip_free_threaded
-from ..acompat import Event, gather, skip_sync, sleep, spawn
+from ..acompat import Event, gather, sleep, spawn
 from .test_pool_common import delay_connection
 
 try:
@@ -520,7 +521,9 @@
 
 @pytest.mark.slow
 @pytest.mark.timing
[email protected]("async_cb", [pytest.param(True, marks=skip_sync), 
False])
[email protected](
+    "async_cb", [pytest.param(True, marks=acompat.skip_sync), False]
+)
 def test_reconnect_failure(proxy, async_cb):
     proxy.start()
 
@@ -937,60 +940,6 @@
         logger.setLevel(old_level)
 
 
-@skip_sync
-def test_cancellation_in_queue(dsn):
-    # https://github.com/psycopg/psycopg/issues/509
-
-    nconns = 3
-
-    with pool.ConnectionPool(dsn, min_size=nconns, timeout=1) as p:
-        p.wait()
-
-        got_conns = []
-        ev = Event()
-
-        def worker(i):
-            try:
-                logging.info("worker %s started", i)
-                with p.connection() as conn:
-                    logging.info("worker %s got conn", i)
-                    cur = conn.execute("select 1")
-                    assert cur.fetchone() == (1,)
-
-                    got_conns.append(conn)
-                    if len(got_conns) >= nconns:
-                        ev.set()
-
-                    sleep(5)
-            except BaseException as ex:
-                logging.info("worker %s stopped: %r", i, ex)
-                raise
-
-        # Start tasks taking up all the connections and getting in the queue
-        tasks = [spawn(worker, (i,)) for i in range(nconns * 3)]
-
-        # wait until the pool has served all the connections and clients are 
queued.
-        assert ev.wait(3.0)
-        for i in range(10):
-            if p.get_stats().get("requests_queued", 0):
-                break
-            else:
-                sleep(0.1)
-        else:
-            pytest.fail("no client got in the queue")
-
-        [task.cancel() for task in reversed(tasks)]
-        gather(*tasks, return_exceptions=True, timeout=1.0)
-
-        stats = p.get_stats()
-        assert stats["pool_available"] == 3
-        assert stats.get("requests_waiting", 0) == 0
-
-        with p.connection() as conn:
-            cur = conn.execute("select 1")
-            assert cur.fetchone() == (1,)
-
-
 @pytest.mark.slow
 @pytest.mark.timing
 def test_check_backoff(dsn, caplog, monkeypatch):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/pool/test_pool_async.py 
new/psycopg-3.3.4/tests/pool/test_pool_async.py
--- old/psycopg-3.3.3/tests/pool/test_pool_async.py     2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/pool/test_pool_async.py     2026-05-01 
22:41:15.000000000 +0200
@@ -12,6 +12,7 @@
 from psycopg.pq import TransactionStatus
 from psycopg.rows import Row, TupleRow, class_row
 
+from .. import acompat
 from ..utils import assert_type, set_autocommit, skip_free_threaded
 from ..acompat import AEvent, asleep, gather, skip_sync, spawn
 from .test_pool_common_async import delay_connection
@@ -522,7 +523,9 @@
 
 @pytest.mark.slow
 @pytest.mark.timing
[email protected]("async_cb", [pytest.param(True, marks=skip_sync), 
False])
[email protected](
+    "async_cb", [pytest.param(True, marks=acompat.skip_sync), False]
+)
 async def test_reconnect_failure(proxy, async_cb):
     proxy.start()
 
@@ -993,6 +996,69 @@
             assert await cur.fetchone() == (1,)
 
 
+@skip_sync
[email protected]_skip("backend pid")
+async def test_cancelled_waiter_assigned_conn_is_reclaimed(dsn, monkeypatch):
+    from asyncio import CancelledError
+
+    from psycopg_pool.pool_async import WaitingClient
+
+    from .test_pool_common_async import ensure_waiting
+
+    assigned = AEvent()
+    release = AEvent()
+
+    async def set_blocked(self, conn):
+        async with self._cond:
+            if self.conn or self.error:
+                return False
+
+            self.conn = conn
+            assigned.set()
+            await release.wait()
+            self._cond.notify_all()
+            return True
+
+    monkeypatch.setattr(WaitingClient, "set", set_blocked)
+
+    async with pool.AsyncConnectionPool(dsn, min_size=1, max_size=1, 
timeout=1) as p:
+        await p.wait()
+
+        held_conn = await p.getconn()
+        held_pid = held_conn.info.backend_pid
+        waiter = spawn(p.getconn)
+        await ensure_waiting(p)
+
+        putter = spawn(p.putconn, args=(held_conn,))
+        await assigned.wait()
+
+        waiter.cancel()
+        release.set()
+
+        try:
+            unexpected_conn = await waiter
+        except CancelledError:
+            pass
+        else:
+            await p.putconn(unexpected_conn)
+            pytest.fail("cancelled waiter returned a connection instead of 
raising")
+
+        await gather(putter)
+
+        stats = p.get_stats()
+        assert stats["pool_available"] == 1
+        assert stats.get("requests_waiting", 0) == 0
+        assert stats["requests_errors"] == 1
+
+        reclaimed_conn = await p.getconn()
+        try:
+            assert reclaimed_conn.info.backend_pid == held_pid
+            cur = await reclaimed_conn.execute("select 1")
+            assert await cur.fetchone() == (1,)
+        finally:
+            await p.putconn(reclaimed_conn)
+
+
 @pytest.mark.slow
 @pytest.mark.timing
 async def test_check_backoff(dsn, caplog, monkeypatch):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/pool/test_pool_common.py 
new/psycopg-3.3.4/tests/pool/test_pool_common.py
--- old/psycopg-3.3.3/tests/pool/test_pool_common.py    2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/pool/test_pool_common.py    2026-05-01 
22:41:15.000000000 +0200
@@ -6,14 +6,13 @@
 import logging
 from time import time
 from typing import Any
-from asyncio import CancelledError
 
 import pytest
 
 import psycopg
 
 from ..utils import set_autocommit
-from ..acompat import Event, gather, is_alive, skip_async, skip_sync, sleep, 
spawn
+from ..acompat import Event, gather, is_alive, skip_async, sleep, spawn
 
 try:
     import psycopg_pool as pool
@@ -492,7 +491,7 @@
     assert p._sched_runner is None
     assert not p._workers
 
-    with pytest.raises(psycopg.OperationalError, match="cannot be reused"):
+    with pytest.raises(pool.PoolClosed, match="cannot be reused"):
         p.open()
 
 
@@ -640,112 +639,6 @@
     assert time() - t0 <= 1.5
 
 
-@skip_sync
-def test_cancellation_in_queue(pool_cls, dsn):
-    # https://github.com/psycopg/psycopg/issues/509
-
-    nconns = 3
-
-    with pool_cls(
-        dsn, min_size=min_size(pool_cls, nconns), max_size=nconns, timeout=1
-    ) as p:
-        p.wait()
-
-        got_conns = []
-        ev = Event()
-
-        def worker(i):
-            try:
-                logging.info("worker %s started", i)
-                with p.connection() as conn:
-                    logging.info("worker %s got conn", i)
-                    cur = conn.execute("select 1")
-                    assert cur.fetchone() == (1,)
-
-                    got_conns.append(conn)
-                    if len(got_conns) >= nconns:
-                        ev.set()
-
-                    sleep(5)
-            except BaseException as ex:
-                logging.info("worker %s stopped: %r", i, ex)
-                raise
-
-        # Start tasks taking up all the connections and getting in the queue
-        tasks = [spawn(worker, (i,)) for i in range(nconns * 3)]
-
-        # wait until the pool has served all the connections and clients are 
queued.
-        assert ev.wait(3.0)
-        for i in range(10):
-            if p.get_stats().get("requests_queued", 0):
-                break
-            else:
-                sleep(0.1)
-        else:
-            pytest.fail("no client got in the queue")
-
-        [task.cancel() for task in reversed(tasks)]
-        gather(*tasks, return_exceptions=True, timeout=1.0)
-
-        stats = p.get_stats()
-        assert stats["pool_available"] == min_size(pool_cls, nconns)
-        assert stats.get("requests_waiting", 0) == 0
-
-        with p.connection() as conn:
-            cur = conn.execute("select 1")
-            assert cur.fetchone() == (1,)
-
-
-@skip_sync
-def test_cancel_on_check(pool_cls, dsn):
-    do_cancel = True
-
-    def check(conn):
-        nonlocal do_cancel
-        if do_cancel:
-            do_cancel = False
-            raise CancelledError()
-
-        pool_cls.check_connection(conn)
-
-    with pool_cls(dsn, min_size=min_size(pool_cls, 1), check=check, 
timeout=1.0) as p:
-        try:
-            with p.connection() as conn:
-                conn.execute("select 1")
-        except CancelledError:
-            pass
-
-        with p.connection() as conn:
-            conn.execute("select 1")
-
-
-@skip_sync
-def test_cancel_on_rollback(pool_cls, dsn, monkeypatch):
-    do_cancel = False
-
-    with pool_cls(dsn, min_size=min_size(pool_cls, 1), timeout=1.0) as p:
-        with p.connection() as conn:
-
-            def rollback(self):
-                if do_cancel:
-                    raise CancelledError()
-                else:
-                    type(self).rollback(self)
-
-            monkeypatch.setattr(type(conn), "rollback", rollback)
-            conn.execute("select 1")
-
-        do_cancel = True
-        with pytest.raises((psycopg.errors.SyntaxError, CancelledError)):
-            with p.connection() as conn:
-                conn.execute("selexx 2")
-
-        do_cancel = False
-        with p.connection() as conn:
-            cur = conn.execute("select 3")
-            assert cur.fetchone() == (3,)
-
-
 @pytest.mark.crdb_skip("backend pid")
 def test_drain(pool_cls, dsn):
     pids1 = set()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/pool/test_pool_common_async.py 
new/psycopg-3.3.4/tests/pool/test_pool_common_async.py
--- old/psycopg-3.3.3/tests/pool/test_pool_common_async.py      2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/pool/test_pool_common_async.py      2026-05-01 
22:41:15.000000000 +0200
@@ -3,7 +3,6 @@
 import logging
 from time import time
 from typing import Any
-from asyncio import CancelledError
 
 import pytest
 
@@ -505,7 +504,7 @@
     assert p._sched_runner is None
     assert not p._workers
 
-    with pytest.raises(psycopg.OperationalError, match="cannot be reused"):
+    with pytest.raises(pool.PoolClosed, match="cannot be reused"):
         await p.open()
 
 
@@ -709,6 +708,8 @@
 
 @skip_sync
 async def test_cancel_on_check(pool_cls, dsn):
+    from asyncio import CancelledError
+
     do_cancel = True
 
     async def check(conn):
@@ -734,6 +735,8 @@
 
 @skip_sync
 async def test_cancel_on_rollback(pool_cls, dsn, monkeypatch):
+    from asyncio import CancelledError
+
     do_cancel = False
 
     async with pool_cls(dsn, min_size=min_size(pool_cls, 1), timeout=1.0) as p:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/pool/test_pool_null.py 
new/psycopg-3.3.4/tests/pool/test_pool_null.py
--- old/psycopg-3.3.3/tests/pool/test_pool_null.py      2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/pool/test_pool_null.py      2026-05-01 
22:41:15.000000000 +0200
@@ -14,7 +14,7 @@
 from psycopg.rows import Row, TupleRow, class_row
 
 from ..utils import assert_type, set_autocommit
-from ..acompat import Event, gather, skip_sync, sleep, spawn
+from ..acompat import gather, sleep, spawn
 from .test_pool_common import delay_connection, ensure_waiting
 
 try:
@@ -446,59 +446,6 @@
         assert 200 <= stats["connections_ms"] < 300
 
 
-@skip_sync
-def test_cancellation_in_queue(dsn):
-    # https://github.com/psycopg/psycopg/issues/509
-
-    nconns = 3
-
-    with pool.NullConnectionPool(dsn, min_size=0, max_size=nconns, timeout=1) 
as p:
-        p.wait()
-
-        got_conns = []
-        ev = Event()
-
-        def worker(i):
-            try:
-                logging.info("worker %s started", i)
-                with p.connection() as conn:
-                    logging.info("worker %s got conn", i)
-                    cur = conn.execute("select 1")
-                    assert cur.fetchone() == (1,)
-
-                    got_conns.append(conn)
-                    if len(got_conns) >= nconns:
-                        ev.set()
-
-                    sleep(5)
-            except BaseException as ex:
-                logging.info("worker %s stopped: %r", i, ex)
-                raise
-
-        # Start tasks taking up all the connections and getting in the queue
-        tasks = [spawn(worker, (i,)) for i in range(nconns * 3)]
-
-        # wait until the pool has served all the connections and clients are 
queued.
-        assert ev.wait(3.0)
-        for i in range(10):
-            if p.get_stats().get("requests_queued", 0):
-                break
-            else:
-                sleep(0.1)
-        else:
-            pytest.fail("no client got in the queue")
-
-        [task.cancel() for task in reversed(tasks)]
-        gather(*tasks, return_exceptions=True, timeout=1.0)
-
-        stats = p.get_stats()
-        assert stats.get("requests_waiting", 0) == 0
-
-        with p.connection() as conn:
-            cur = conn.execute("select 1")
-            assert cur.fetchone() == (1,)
-
-
 def test_close_returns(dsn):
     # Mostly test the interface; close is close even if it goes via putconn().
     with pool.NullConnectionPool(dsn, close_returns=True) as p:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/pool/test_pool_null_async.py 
new/psycopg-3.3.4/tests/pool/test_pool_null_async.py
--- old/psycopg-3.3.3/tests/pool/test_pool_null_async.py        2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/pool/test_pool_null_async.py        2026-05-01 
22:41:15.000000000 +0200
@@ -11,7 +11,7 @@
 from psycopg.rows import Row, TupleRow, class_row
 
 from ..utils import assert_type, set_autocommit
-from ..acompat import AEvent, asleep, gather, skip_sync, spawn
+from ..acompat import asleep, gather, skip_sync, spawn
 from .test_pool_common_async import delay_connection, ensure_waiting
 
 try:
@@ -447,6 +447,8 @@
 
 @skip_sync
 async def test_cancellation_in_queue(dsn):
+    from ..acompat import AEvent
+
     # https://github.com/psycopg/psycopg/issues/509
 
     nconns = 3
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/test_connection.py 
new/psycopg-3.3.4/tests/test_connection.py
--- old/psycopg-3.3.3/tests/test_connection.py  2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/tests/test_connection.py  2026-05-01 22:41:15.000000000 
+0200
@@ -19,7 +19,7 @@
 from psycopg.conninfo import conninfo_to_dict, make_conninfo, 
timeout_from_conninfo
 from psycopg._conninfo_utils import get_param
 
-from .acompat import skip_async, skip_sync, sleep
+from .acompat import skip_async, sleep
 from .test_adapt import make_bin_dumper, make_dumper
 from ._test_cursor import my_row_factory
 from ._test_connection import testctx  # noqa: F401  # fixture
@@ -122,6 +122,20 @@
     assert elapsed == pytest.approx(2.0, 0.1)
 
 
[email protected](pq.__impl__ == "python", reason="only affects C extension")
+def test_connect_timeout_large_monotonic(conn_cls, dsn, monkeypatch):
+    # Regression: deadline was stored as C float (32-bit). At ~777 days of
+    # process uptime the float32 ULP reaches 8 s, so a 2-second timeout is
+    # silently rounded away, causing ConnectionTimeout to fire immediately.
+    # 2^26 = 67_108_864 s ≈ 777 days is the minimum value where this occurs
+    # with psycopg's enforced 2-second minimum connect_timeout.
+    from psycopg._cmodule import _psycopg
+
+    monkeypatch.setattr(_psycopg, "monotonic", lambda: 67108864.5)
+    with conn_cls.connect(dsn, connect_timeout=2):
+        pass
+
+
 @pytest.mark.slow
 @pytest.mark.timing
 def test_multi_hosts(conn_cls, proxy, dsn, monkeypatch):
@@ -412,13 +426,6 @@
     assert conn.pgconn.transaction_status == pq.TransactionStatus.INTRANS
 
 
-@skip_sync
-def test_autocommit_readonly_property(conn):
-    with pytest.raises(AttributeError):
-        conn.autocommit = True
-    assert not conn.autocommit
-
-
 def test_autocommit(conn):
     assert conn.autocommit is False
     conn.set_autocommit(True)
@@ -702,13 +709,6 @@
     assert current == default
 
 
-@skip_sync
[email protected]("param", tx_params)
-def test_transaction_param_readonly_property(conn, param):
-    with pytest.raises(AttributeError):
-        setattr(conn, param.name, None)
-
-
 @pytest.mark.parametrize("autocommit", [True, False])
 @pytest.mark.parametrize("param", tx_params_isolation)
 def test_set_transaction_param_implicit(conn, param, autocommit):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/test_connection_async.py 
new/psycopg-3.3.4/tests/test_connection_async.py
--- old/psycopg-3.3.3/tests/test_connection_async.py    2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/test_connection_async.py    2026-05-01 
22:41:15.000000000 +0200
@@ -117,6 +117,20 @@
     assert elapsed == pytest.approx(2.0, 0.1)
 
 
[email protected](pq.__impl__ == "python", reason="only affects C extension")
+async def test_connect_timeout_large_monotonic(aconn_cls, dsn, monkeypatch):
+    # Regression: deadline was stored as C float (32-bit). At ~777 days of
+    # process uptime the float32 ULP reaches 8 s, so a 2-second timeout is
+    # silently rounded away, causing ConnectionTimeout to fire immediately.
+    # 2^26 = 67_108_864 s ≈ 777 days is the minimum value where this occurs
+    # with psycopg's enforced 2-second minimum connect_timeout.
+    from psycopg._cmodule import _psycopg
+
+    monkeypatch.setattr(_psycopg, "monotonic", lambda: 67108864.5)
+    async with await aconn_cls.connect(dsn, connect_timeout=2):
+        pass
+
+
 @pytest.mark.slow
 @pytest.mark.timing
 async def test_multi_hosts(aconn_cls, proxy, dsn, monkeypatch):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/test_cursor_common.py 
new/psycopg-3.3.4/tests/test_cursor_common.py
--- old/psycopg-3.3.3/tests/test_cursor_common.py       2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/test_cursor_common.py       2026-05-01 
22:41:15.000000000 +0200
@@ -150,6 +150,9 @@
     cur.execute("select generate_series(1, 10)")
     assert cur.statusmessage == "SELECT 10"
 
+    cur.execute("")
+    assert cur.statusmessage is None
+
     cur.execute("create table statusmessage ()")
     assert cur.statusmessage == "CREATE TABLE"
 
@@ -398,6 +401,26 @@
     assert cur.rowcount == 2
 
 
[email protected]("returning", [False, True])
+def test_executemany_statusmessage(conn, execmany, returning):
+    cur = conn.cursor()
+    cur.executemany(
+        ph(cur, "insert into execmany(num, data) values (%s, %s)"),
+        [(10, "hello"), (20, "world")],
+        returning=returning,
+    )
+    assert cur.rowcount == (1 if returning else 2)
+    assert cur.statusmessage is not None
+    assert cur.statusmessage.startswith("INSERT")
+
+    cur.executemany(
+        ph(cur, "insert into execmany(num, data) values (%s, %s)"),
+        [],
+        returning=returning,
+    )
+    assert cur.statusmessage is None
+
+
 def test_executemany_returning(conn, execmany):
     cur = conn.cursor()
     cur.executemany(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/test_cursor_common_async.py 
new/psycopg-3.3.4/tests/test_cursor_common_async.py
--- old/psycopg-3.3.3/tests/test_cursor_common_async.py 2026-02-18 
13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/test_cursor_common_async.py 2026-05-01 
22:41:15.000000000 +0200
@@ -148,6 +148,9 @@
     await cur.execute("select generate_series(1, 10)")
     assert cur.statusmessage == "SELECT 10"
 
+    await cur.execute("")
+    assert cur.statusmessage is None
+
     await cur.execute("create table statusmessage ()")
     assert cur.statusmessage == "CREATE TABLE"
 
@@ -400,6 +403,26 @@
     assert cur.rowcount == 2
 
 
[email protected]("returning", [False, True])
+async def test_executemany_statusmessage(aconn, execmany, returning):
+    cur = aconn.cursor()
+    await cur.executemany(
+        ph(cur, "insert into execmany(num, data) values (%s, %s)"),
+        [(10, "hello"), (20, "world")],
+        returning=returning,
+    )
+    assert cur.rowcount == (1 if returning else 2)
+    assert cur.statusmessage is not None
+    assert cur.statusmessage.startswith("INSERT")
+
+    await cur.executemany(
+        ph(cur, "insert into execmany(num, data) values (%s, %s)"),
+        [],
+        returning=returning,
+    )
+    assert cur.statusmessage is None
+
+
 async def test_executemany_returning(aconn, execmany):
     cur = aconn.cursor()
     await cur.executemany(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/test_sql.py 
new/psycopg-3.3.4/tests/test_sql.py
--- old/psycopg-3.3.3/tests/test_sql.py 2026-02-18 13:12:21.000000000 +0100
+++ new/psycopg-3.3.4/tests/test_sql.py 2026-05-01 22:41:15.000000000 +0200
@@ -401,7 +401,7 @@
         assert sql.Literal("foo").as_string(conn) == "'foo'"
 
     @pytest.mark.crdb_skip("composite")  # create type, actually
-    @pytest.mark.parametrize("name", ["a-b", f"{eur}", "order", "foo bar"])
+    @pytest.mark.parametrize("name", ["a-b", f"{eur}", "order", "foo bar", 
"FooBar"])
     def test_invalid_name(self, conn, name):
         if conn.info.parameter_status("is_superuser") != "on":
             pytest.skip("not a superuser")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/test_tstring.py 
new/psycopg-3.3.4/tests/test_tstring.py
--- old/psycopg-3.3.3/tests/test_tstring.py     2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/tests/test_tstring.py     2026-05-01 22:41:15.000000000 
+0200
@@ -16,7 +16,7 @@
 
 async def test_connection_no_params(aconn):
     with pytest.raises(TypeError):
-        await aconn.execute(t"select 1", [])    # noqa: F542
+        await aconn.execute(t"select 1", [])  # noqa: F542
 
 
 async def test_cursor_no_params(aconn):
@@ -170,7 +170,7 @@
     lit = sql.Literal(42)
     cur = await aconn.execute(t"select {lit:l} as foo")
     assert await cur.fetchone() == (42,)
-    assert cur._query.query == b'select 42 as foo'
+    assert cur._query.query == b"select 42 as foo"
 
     with pytest.raises(psycopg.ProgrammingError, match=r"sql\.Literal.*':l'"):
         await aconn.execute(t"select {lit} as foo")
@@ -185,7 +185,7 @@
 @pytest.mark.xfail(reason="Template.join() needed")
 async def test_template_join(aconn):
     ts = [t"{i} as {name:i}" for i, name in enumerate(("foo", "bar", "baz"))]
-    fields = t','.join(ts)  # noqa: F542
+    fields = t",".join(ts)  # noqa: F542
     cur = await aconn.execute(t"select {fields}")
     assert await cur.fetchone() == (0, 1, 2)
     assert cur.description[0].name == "foo"
@@ -194,7 +194,7 @@
 
 async def test_sql_join(aconn):
     ts = [t"{i} as {name:i}" for i, name in enumerate(("foo", "bar", "baz"))]
-    fields = sql.SQL(',').join(ts)
+    fields = sql.SQL(",").join(ts)
     cur = await aconn.execute(t"select {fields:q}")
     assert await cur.fetchone() == (0, 1, 2)
     assert cur.description[0].name == "foo"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tests/types/test_enum.py 
new/psycopg-3.3.4/tests/types/test_enum.py
--- old/psycopg-3.3.3/tests/types/test_enum.py  2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/tests/types/test_enum.py  2026-05-01 22:41:15.000000000 
+0200
@@ -2,6 +2,7 @@
 
 import pytest
 
+from psycopg import ClientCursor
 from psycopg import errors as e
 from psycopg import pq, sql
 from psycopg.adapt import PyFormat
@@ -36,6 +37,12 @@
     THREE = 3
 
 
+class CamelCaseEnum(int, Enum):
+    one = 1
+    TWO = 2
+    Three = 3
+
+
 enum_cases = [PureTestEnum, StrTestEnum, IntTestEnum]
 encodings = ["utf8", crdb_encoding("latin1")]
 
@@ -44,10 +51,12 @@
 def make_test_enums(request, svcconn):
     for enum in enum_cases + [NonAsciiEnum]:
         ensure_enum(enum, svcconn)
+    ensure_enum(CamelCaseEnum, svcconn, name="CamelCaseEnum")
 
 
-def ensure_enum(enum, conn):
-    name = enum.__name__.lower()
+def ensure_enum(enum, conn, name=""):
+    if not name:
+        name = enum.__name__.lower()
     labels = list(enum.__members__)
     conn.execute(sql.SQL("""
             drop type if exists {name};
@@ -114,6 +123,20 @@
         assert cur.fetchone()[0] == enum[label]
 
 
[email protected]_skip("broken regtype")
[email protected]("fmt_in", PyFormat)
+def test_enum_quoted_name(conn, fmt_in):
+    enum = CamelCaseEnum
+
+    info = EnumInfo.fetch(conn, sql.Identifier(enum.__name__))
+    register_enum(info, conn, enum=enum)
+
+    cur = ClientCursor(conn)
+    for value in enum:
+        cur.execute(f"select %{fmt_in.value}", [value])
+        assert next(cur)[0] is value
+
+
 @pytest.mark.crdb_skip("encoding")
 @pytest.mark.parametrize("enum", enum_cases)
 @pytest.mark.parametrize("fmt_in", PyFormat)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/psycopg-3.3.3/tools/async_to_sync.py 
new/psycopg-3.3.4/tools/async_to_sync.py
--- old/psycopg-3.3.3/tools/async_to_sync.py    2026-02-18 13:12:21.000000000 
+0100
+++ new/psycopg-3.3.4/tools/async_to_sync.py    2026-05-01 22:41:15.000000000 
+0200
@@ -27,7 +27,7 @@
 # The version of Python officially used for the conversion.
 # Output may differ in other versions.
 # Should be consistent with the Python version used in lint.yml
-PYVER = "3.11"
+PYVER = "3.14"
 
 ALL_INPUTS = """
     psycopg/psycopg/_conninfo_attempts_async.py
@@ -183,7 +183,8 @@
 
 ENTRYPOINT ["tools/async_to_sync.py"]
 """
-        cmdline = [engine, "build", "--tag", tag, "-f", "-", str(PROJECT_DIR)]
+        cmdline = [engine, "build", "--network=host", "--tag", tag, "-f", "-"]
+        cmdline += [str(PROJECT_DIR)]
         sp.run(cmdline, check=True, text=True, input=containerfile)
 
     cmdline = sys.argv[1:]
@@ -347,7 +348,7 @@
         "wait_timeout": "wait",
     }
     _skip_imports = {
-        "acompat": {"alist", "anext"},
+        "acompat": {"alist", "anext", "skip_sync"},
         "_acompat": {"ensure_async"},
     }
 
@@ -357,6 +358,10 @@
         return node
 
     def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> ast.AST:
+        for deco in node.decorator_list:
+            match deco:
+                case ast.Name(id="skip_sync"):
+                    return None
         self._fix_docstring(node.body)
         node.name = self.names_map.get(node.name, node.name)
         for arg in node.args.args:

Reply via email to