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 1vQ2bi-007ATE-39 for psycopg@arkaria.postgresql.org; Mon, 01 Dec 2025 12:02:19 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vQ2bh-002Nso-1Q for psycopg@arkaria.postgresql.org; Mon, 01 Dec 2025 12:02:17 +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 1vQ2bg-002Nsg-2z for psycopg@lists.postgresql.org; Mon, 01 Dec 2025 12:02:17 +0000 Received: from mail-yx1-xb132.google.com ([2607:f8b0:4864:20::b132]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vQ2bd-002ZHk-1m for psycopg@postgresql.org; Mon, 01 Dec 2025 12:02:16 +0000 Received: by mail-yx1-xb132.google.com with SMTP id 956f58d0204a3-63e19642764so2838357d50.1 for ; Mon, 01 Dec 2025 04:02:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1764590533; x=1765195333; darn=postgresql.org; h=to:subject:message-id:date:from:mime-version:from:to:cc:subject :date:message-id:reply-to; bh=mX2yFyN6UFGmPrxgkZUnvEV+mY1tPlmHFaZ7JR5Nxtw=; b=T/Psgt3ycxBJmShvHOOHzyGGHT2eTQ00n3kIKYC0uPQkUPcmq7P97W5lCA9yzwCLDF CZwlayqhZtK5/7GHMvGsm31J5pYNyu2vs+AZWBWOWOsDbIv9wrzAbdgqV+PaOydJTcdW DPvX1BOVArc9tNegHTmWBKIkHSBui54fmDOJ1knf1RttjZuBHKFurGf6P0rN6rCNLS9O 4wvq7q6B8vXm9bwy5TpIlv73R7yhiUhfluxbCWXs14s0Xc6J1yQO3aW07SdgWt2+KNDO VGUzUMNm0dkn7R2ZRCr5HY0RsE2x6qDDKCQJgEyFRH2kZ6Ys65/W9VviP+vsrTI5iYXM D+XQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1764590533; x=1765195333; h=to:subject:message-id:date:from:mime-version:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=mX2yFyN6UFGmPrxgkZUnvEV+mY1tPlmHFaZ7JR5Nxtw=; b=FtQGnA05ep1gkMKP6BWBwXlIf6qlYEPKywgMyZ2zDp77w/ekaTklZr4wEsF2k5Sjf6 iefcMQkx8Db5i9BhJ7MuBOzdnsTtUa79vu3MqWMmY8iy5n8tzVVp7dE0vGXrGbHo1+7S dIHjpQigZmjB4g4fX/UAnEb1Sdp/FxxXGd9GqGk9SWfLFEPE2Cx4rUrZmTq8dduAaLuX dtu7FcDlS5XxjcxI6ciJaEqFPbxSFfTJcHsWSblVZ1yZ9ipaARQK+dsTsHotf35eycao Dgh+CJoQqYD8Hz6MTuF5JhGxnqJidQr+bn30rEDgxMLCqTw6gq38oA/cTwGbRFT9sC/G xkDw== X-Gm-Message-State: AOJu0YxQHkCEVUDBRe/8VhuaTWllbEBJUPz13tiXl3ZqEWsy3X6xo+Ij lhBybwZwBDFVLcN0ACMH8QuC1TAp4IVUSh59cadQh2lMAXYPaa+o205bgDHtNugY3XOCeZOYvMZ aCucO0rSJZ2AMhSjZN9UQmoGkGy3AH4wOQ2o2 X-Gm-Gg: ASbGncu1ViAxliGMlkAG2ePV9PjoAXZV/wvfpiA0ZgPSFbGlqMBiRepxOA4KOeyBT1g L6SASTBqdDXcB84kSIAv2/Lm4vLUjX7jlp4EzNvICaBwkSpfErN1kXxFwra78TeabQS9L72cSCA GqKhwoE0k67qmjxjDU04BWScoY6G5uLrwj2yE1cR/lbYdusNR3M2K/aVTaRKI+vxB1mm4jGZ1HN UR+h5EdOTrE1IwMYy/omeLJR67dPrfHcMmGUc3YNJ8CYnd86ts/ahK6yMMz0i/HafzK4qYreSEi M4E= X-Google-Smtp-Source: AGHT+IGZCeLDULlVZIw1EV03FOlTu8d9RyKq7nHXNZVs7lUmxhllHzM2Fzwy9LuqB/hHC/sr4p81SV7Y6k29ld0PXeo= X-Received: by 2002:a53:c84a:0:b0:63e:4264:878b with SMTP id 956f58d0204a3-64302ad8107mr21241751d50.58.1764590532673; Mon, 01 Dec 2025 04:02:12 -0800 (PST) MIME-Version: 1.0 From: Daniele Varrazzo Date: Mon, 1 Dec 2025 13:02:01 +0100 X-Gm-Features: AWmQ_bnQhOc9osuPWaG6m59fk9ekxDDBwMBqM7FZIvYmmVCK9mK6TCpdDZUQ56M Message-ID: Subject: Psycopg 3.3 released To: Psycopg Content-Type: multipart/alternative; boundary="000000000000dfc5b30644e2bfe2" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000dfc5b30644e2bfe2 Content-Type: text/plain; charset="UTF-8" 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 , 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 psycopg.sql 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}") Check out the complete t-string support documentation for inspiration= ! More flexible composite adaptation Previously, it was only possible to adapt PostgreSQL composites to Python sequence types with a strict 1:1 mapping to the fields of the database type. 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 , 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 hap= py, 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 is 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 draining the pool the connections already in the pool would remain stale after the adapters have been configured. ...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 and the poo= l release notes ! 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 the 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 language and database features, please consider supporting the project =F0=9F=92=9C Thank you very much, and happy hacking! -- Daniele --000000000000dfc5b30644e2bfe2 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
We have released Psycopg 3.3 =E2=80=94 and you should be e= xcited about it!

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

How does it look? Something lik= e:

def fetch_person(conn, name):

=C2=A0 =C2=A0 # &#= 39;name' will be handled safely: as a server-side parameter or=C2=A0 =C2=A0 # correctly quoted an= d escaped if client-side binding is required
=C2=A0 =C2=A0 cur =3D conn.execute(t"SELECT * FROM p= eople WHERE name =3D {name}")
=C2=A0 =C2=A0 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?), b= ut now paired with the safety and adaptation flexibility of Psycopg 3. Temp= late strings also help you generate dynamic SQL statements much more succin= ctly than with the psycopg.sql module:

def delete_something(conn, table_name, nam= e):

=C2=A0 =C2=A0 # Mixing client-side query composi= tion with server-side parameters binding
=C2=A0 =C2=A0 conn.execute(t"DELETE FROM {table_name:i} = WHERE name =3D {name}")

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

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

More flexible composite adaptation

Previously, it was = only possible to adapt PostgreSQL composites to Python sequen= ce types with a strict 1:1 mapping to the fields of the database type.
<= br>We have now gained extra flexibility: we can customize both how to creat= e generic Python objects, for example ones only taking keyword arguments, a= nd how to extract a sequence of fields from the attributes of non-sequence = objects... Dataclasses anyone?

from dataclasses import dataclassfrom psycopg.types.composite import= CompositeInfo, register_composite

@dataclass<= br>class MiniPerson:
=C2=A0 =C2=A0 age: int
=C2=A0 =C2=A0 name: str
=C2=A0 =C2=A0 height: float | None =3D None

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

=
=C2=A0 =C2=A0 def to_db(se= lf, info):
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 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(
=C2= =A0 =C2=A0 info, conn, factory=3DMiniPerson,
=C2=A0 =C2=A0 make_object=3DMiniPerson.from_db, make_sequ= ence=3DMiniPerson.to_db)

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

conn.execute(

=C2=A0 =C2=A0 "SELECT (%(person)s).name || '= next year will be ' || =C2=A0(%(person)s).age + 1",
=C2=A0 =C2=A0 {"person": MiniP= erson(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 pro= bably seen false positives when calling fetchone(). Even if you are 100% ce= rtain your query will return a row, fetchone() is annotated as possibly ret= urning None =E2=80=94 so type checkers complain about patterns like:
cur.= execute("SELECT count(*) FROM my_table") =C2=A0# Always returns e= xactly one value
count =3D cur.fetchone()[0] =C2=A0 # Error: value of ty= pe "tuple | None" is not indexable


In Ps= ycopg 3.3, the cursor has become an iterator, whereas it was previously only an = iterable<= /a>. The distinction is subtle but meaningful: an iterator holds its own it= eration 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 re= cord =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 po= ol=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 drai= n() method is now available to re-create all connections in a pool. Thi= s is helpful, for instance, when the database needs to be introspected to f= ind the OIDs of extension types to register: without draining the pool the = connections already in the pool would remain stale after the adapters have = been configured.

...And more!

Other i= mprovements include greater flexibility when navigating results after a fet= chmany() call or after statements returning multiple result sets, the abili= ty to reconfigure loaders after a query has run, and many other assorted en= hancements. You can find the full list in the psycopg release notes and = the pool release notes!

Your help is w= elcome

Psycopg is the de-facto standard for communication bet= ween Python and PostgreSQL =E2=80=94 two major components powering countles= s businesses and mission-critical infrastructure. Maintaining such an impor= tant library to the highest standards of reliability, performance and secur= ity requires a lot of care and ongoing work.

If you use Python and P= ostgreSQL and want to help ensure that the interface between them remains r= obust and continues to improve, supporting new language and database featur= es, please consider suppo= rting the project =F0=9F=92=9C

Thank you very much, and happy ha= cking!

-- Daniele
--000000000000dfc5b30644e2bfe2--