Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vQ4I6-008V1W-29 for pgsql-announce@arkaria.postgresql.org; Mon, 01 Dec 2025 13:50:11 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vQ4I4-002yIH-38 for pgsql-announce@arkaria.postgresql.org; Mon, 01 Dec 2025 13:50:09 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vQ4I3-002yHP-2w for pgsql-announce@lists.postgresql.org; Mon, 01 Dec 2025 13:50:08 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vQ4I0-002a4i-2H for pgsql-announce@lists.postgresql.org; Mon, 01 Dec 2025 13:50:07 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:From:To:Subject: MIME-Version:Content-Type:Sender:Cc:Content-Transfer-Encoding:Content-ID: Content-Description:In-Reply-To:References; bh=9Ft3+0n3LNHnV1gMv5Yi+zPrRYxLC1zO9ZZHhQeS0bI=; b=4+kiiaxoo0SMfjlzyFx6Ub8ibI OLVix4T0BKfdE7hH4v+7QvgNBA+KfejQyQ0NYRRziQyvmwHsnR2jfAgI5sUS9KtS/+TZNWYqXsV/V 1g1Q75ThqkzYyd4QnWXdDiP+QzQa5UoECHoO/G12IfTx/8vcRDnJ2b3esx4NhjDi/oVcWJTkNSZhj ku7K/isbQ2jdfFEsHtWl5C4lWDPqklO43tdseiang/8tF3sk9f7ic5O4FpkjOUCR/3WHnUxTCfYff BPyBA3xCjv6owEgY/xsgA6sAJWg0NwTb/GoWG5ZBEKSIH0xDknw62oXrNBKsZ6iIe6XA06wZEquPj ZyEuF7qQ==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vQ4I0-003WjT-2e for pgsql-announce@lists.postgresql.org; Mon, 01 Dec 2025 13:50:05 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vQ4Hy-002AXq-07 for pgsql-announce@lists.postgresql.org; Mon, 01 Dec 2025 13:50:03 +0000 Content-Type: multipart/mixed; boundary="===============5254279808576425095==" MIME-Version: 1.0 Subject: Psycopg 3.3 released To: PostgreSQL Announce From: Psycopg Development Team via PostgreSQL Announce Reply-To: daniele.varrazzo@gmail.com Date: Mon, 01 Dec 2025 13:49:40 +0000 Message-ID: <176459698053.2921403.7149872675156794193@wrigleys.postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-pglister-tags: community,related X-pglister-tagsig: 032c674c84b08276dc62e4633eb854b2b0e4934138c88011aff14013709ff593 List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --===============5254279808576425095== Content-Type: multipart/alternative; boundary="===============1615756500209674563==" MIME-Version: 1.0 --===============1615756500209674563== Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable We have released Psycopg 3.3 =E2=80=94 and you should be excited about it! # Template string queries This version lets you take advantage of one of the biggest innovation in Python 3.14: the [template strings][1], which allow you to write [expressive and safe queries][2]. [1]: https://docs.python.org/3.14/whatsnew/3.14.html#pep-750-template-strin= g-literals [2]: https://www.psycopg.org/psycopg3/docs/basic/tstrings.html How does it look? Something like: def fetch_person(conn, name): # 'name' will be handled safely: as a server-side parameter or # correctly quoted and escaped if client-side binding is required cur =3D conn.execute(t"SELECT * FROM people WHERE name =3D {name}") return cur.fetchone() The syntax is the same as that of [f-strings][3], introduced back in the venerable Python 3.6 (perhaps the feature that finally ended Python 2?), but now paired with the safety and adaptation flexibility of Psycopg 3. Template strings also help you generate dynamic SQL statements much more succinctly than with the [`psycopg.sql`][4] module: def delete_something(conn, table_name, name): # Mixing client-side query composition with server-side parameters = binding conn.execute(t"DELETE FROM {table_name:i} WHERE name =3D {name}") # Composing non-parametric statements entirely client-side conn.execute(t"NOTIFY {table_name + '.deleted':i}, {name:l}") [3]: https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-l= iterals [4]: https://www.psycopg.org/psycopg3/docs/api/sql.html Check out the complete [t-string support documentation][5] for inspiration! [5]: https://www.psycopg.org/psycopg3/docs/basic/tstrings.html # More flexible composite adaptation Previously, it was only possible to [adapt PostgreSQL composites][6] to Pyt= hon sequence types with a strict 1:1 mapping to the fields of the database type. [6]: https://www.psycopg.org/psycopg3/docs/basic/pgtypes.html#adapt-composi= te We have now gained extra flexibility: we can customize both how to create generic Python objects, for example ones only taking keyword arguments, and how to extract a sequence of fields from the attributes of non-sequence objects... Dataclasses anyone? from dataclasses import dataclass from psycopg.types.composite import CompositeInfo, register_composite @dataclass class MiniPerson: age: int name: str height: float | None =3D None @classmethod def from_db(cls, seq, info): return cls(name=3Dseq[0], age=3Dseq[1]) def to_db(self, info): return [self.name, self.age] conn.execute("CREATE TYPE mini_person AS (name text, age int)") info =3D CompositeInfo.fetch(conn, "mini_person") register_composite( info, conn, factory=3DMiniPerson, make_object=3DMiniPerson.from_db, make_sequence=3DMiniPerson.to_db) conn.execute("SELECT ('John', 33)::mini_person").fetchone()[0] # MiniPerson(age=3D33, name=3D'John', height=3DNone) conn.execute( "SELECT (%(person)s).name || ' next year will be ' || (%(person)s)= .age + 1", {"person": MiniPerson(name=3D"John", age=3D33)}, ).fetchone()[0] # 'John next year will be 34' # Solving the 'fetchone()' annoyance with type checkers If you use Mypy or other type checkers with Psycopg, you've probably seen false positives when calling ``fetchone()``. Even if you are 100% certain your query will return a row, ``fetchone()`` is annotated as possibly returning ``None`` =E2=80=94 so type checkers complain about patterns like: cur.execute("SELECT count(*) FROM my_table") # Always returns exactly = one value count =3D cur.fetchone()[0] # Error: value of type "tuple | None" is = not indexable In Psycopg 3.3, the cursor has become an [iterator][7], whereas it was previously only an [iterable][8]. The distinction is subtle but meaningful:= an iterator holds its own iteration state and does not need to create a new object for each pass. More importantly, this change means you can use [`next()`][9] or [`anext()`= ][10] to retrieve a record =E2=80=94 and these functions never return ``None``. This makes My= py happy, and probably you too: cur.execute("SELECT count(*) FROM my_table") count =3D next(cur)[0] [7]: https://docs.python.org/3/glossary.html#term-iterator [8]: https://docs.python.org/3/glossary.html#term-iterable [9]: https://docs.python.org/3/library/functions.html#next [10]: https://docs.python.org/3/library/functions.html#anext # Improvements to the connection pools A connection pool=E2=80=99s parameters can now be changed dynamically =E2= =80=94 useful for example to support short-lived secret tokens as passwords, as requested by some cloud database providers. A useful [`drain()`][11] method is now available to re-create all connectio= ns in a pool. This is helpful, for instance, when the database needs to be introspected to find the OIDs of extension types to register: without drain= ing the pool the connections already in the pool would remain stale after the adapters have been configured. [11]: https://www.psycopg.org/psycopg3/docs/api/pool.html#psycopg_pool.Conn= ectionPool.drain # ...And more! Other improvements include greater flexibility when navigating results after a ``fetchmany()`` call or after statements returning multiple result sets, the ability to reconfigure loaders after a query has run, and many other assorted enhancements. You can find the full list in the [psycopg release notes][12] and the [pool release notes][13]! [12]: https://www.psycopg.org/psycopg3/docs/news.html#psycopg-3-3-0 [13]: https://www.psycopg.org/psycopg3/docs/news_pool.html#psycopg-pool-3-3= -0 # Your help is welcome Psycopg is the de-facto standard for communication between Python and PostgreSQL =E2=80=94 two major components powering countless businesses and mission-critical infrastructure. Maintaining such an important library to t= he highest standards of reliability, performance and security requires a lot of care and ongoing work. If you use Python and PostgreSQL and want to help ensure that the interface between them remains robust and continues to improve, supporting new langua= ge and database features, please consider [supporting the project](https://git= hub.com/sponsors/dvarrazzo) =F0=9F=92=9C Thank you very much, and happy hacking! --===============1615756500209674563== Content-Type: text/html; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Psycopg 3.3 released
 

Psycopg 3.3 released

We have released Psycopg 3.3 =E2=80=94 and = you should be excited about it!

Template string queries

This version lets you take advantage of one= of the biggest innovation in Python 3.14: the template strings, which allow you to write expressive and safe queries= .

How does it look? Something like:

def fetch_person(conn, name):

    # 'name' will be handled safely: as a server-side parameter or
    # correctly quoted and escaped if client-side binding is required
    cur =3D conn.execute(t"SELECT * FROM people WHERE name =3D {name}")
    return cur.fetchone()

The syntax is the same as that of f-strings,= introduced back in the venerable Python 3.6 (perhaps the feature that finally ended Python 2?), but now paired with the safety and adaptation flexibility of Psycopg 3. Template strings also help you generate dynamic SQL statements much more succinctly than with the psy= copg.sql module:

def delete_something(conn, table_name, name):

    # Mixing client-side query composition with server-side parameters bind=
ing
    conn.execute(t"DELETE FROM {table_name:i} WHERE name =3D {name}")

    # Composing non-parametric statements entirely client-side
    conn.execute(t"NOTIFY {table_name + '.deleted':i}, {name:l}")

Check out the complete t-string support documentation for inspirat= ion!

More flexible composite adaptation

Previously, it was only possible to adapt PostgreSQL co= mposites to Python sequence types with a strict 1:1 mapping to the fields of the database type= .

We have now gained extra flexibility: we ca= n customize both how to create generic Python objects, for example ones only taking keyword arguments, and how to extract a sequence of fields from the attributes of non-sequence objects... Dataclasses anyone?

from dataclasses import dataclass
from psycopg.types.composite import CompositeInfo, register_composite

@dataclass
class MiniPerson:
    age: int
    name: str
    height: float | None =3D None

    @classmethod
    def from_db(cls, seq, info):
        return cls(name=3Dseq[0], age=3Dseq[1])

    def to_db(self, info):
        return [self.name, self.age]

conn.execute("CREATE TYPE mini_person AS (name text, age int)")
info =3D CompositeInfo.fetch(conn, "mini_person")

register_composite(
    info, conn, factory=3DMiniPerson,
    make_object=3DMiniPerson.from_db, make_sequence=3DMiniPerson.to_db)

conn.execute("SELECT ('John', 33)::mini_person").fetchone()[0]
# MiniPerson(age=3D33, name=3D'John', height=3DNone)

conn.execute(
    "SELECT (%(person)s).name || ' next year will be ' ||  (%(person)s).age=
 + 1",
    {"person": MiniPerson(name=3D"John", age=3D33)},
).fetchone()[0]
# 'John next year will be 34'

Solving the 'fetchone()' annoyance with type checkers

If you use Mypy or other type checkers with= Psycopg, you've probably seen false positives when calling fetchone(). Even if you are 100% = certain your query will return a row, fetchone() is annotated as possi= bly returning None =E2=80=94 so type checkers complain about patte= rns like:

cur.execute("SELECT count(*) FROM my_table")  # Always returns e=
xactly one value
count =3D cur.fetchone()[0]   # Error: value of type "tuple | None" is not =
indexable

In Psycopg 3.3, the cursor has become an iterator, whereas it was previously only an iterable= . The distinction is subtle but meaningful: an iterator holds its own iteration state and does not need to create a new object for each pass.

More importantly, this change means you can= use next() or= anext() to= retrieve a record =E2=80=94 and these functions never return None. This= makes Mypy happy, and probably you too:

cur.execute("SELECT count(*) FROM my_table")
count =3D next(cur)[0]

Improvements to the connection pools

A connection pool=E2=80=99s parameters can = now be changed dynamically =E2=80=94 useful for example to support short-lived secret tokens as passwords, as requested by some cloud database providers.

A useful drain() method i= s now available to re-create all connections in a pool. This is helpful, for instance, when the database needs to be introspected to find the OIDs of extension types to register: without drain= ing the pool the connections already in the pool would remain stale after the adapters have been configured.

...And more!

Other improvements include greater flexibil= ity when navigating results after a fetchmany() call or after statements returning multiple resu= lt sets, the ability to reconfigure loaders after a query has run, and many other assorted enhancements. You can find the full list in the psycopg release notes and the pool release notes!

Your help is welcome

Psycopg is the de-facto standard for commun= ication between Python and PostgreSQL =E2=80=94 two major components powering countless businesses and mission-critical infrastructure. Maintaining such an important library to t= he highest standards of reliability, performance and security requires a lot of care and ongoing work.

If you use Python and PostgreSQL and want t= o help ensure that the interface between them remains robust and continues to improve, supporting new langua= ge and database features, please consider supporti= ng the project =F0=9F=92=9C

Thank you very much, and happy hacking!

This email was sent to you from Psycopg Development Team. It was delivered = on their behalf by the PostgreSQL project. Any questions about the content of the message shou= ld be sent to Psycopg Development Team.

You were sent this email as a subscriber of the pgsql-announce mai= linglist, for for one of the content tags Community or Related Open Source. To unsubscribe from further emails, or change which emails you want to receive, please click th= e personal unsubscribe link that you can find in the headers of this email, or visit https://lists.postgresql.org/unsubscribe/.
 
--===============1615756500209674563==-- --===============5254279808576425095==--