Current release
---------------
+Psycopg 3.0.8
+^^^^^^^^^^^^^
+
+- Decode connection errors in the ``client_encoding`` specified in the
+ connection string, if available (:ticket:`#194`).
+
+
Psycopg 3.0.7
^^^^^^^^^^^^^
py_codecs: Dict[Union[bytes, str], str] = {}
py_codecs.update((k.encode(), v) for k, v in _py_codecs.items())
+# Add an alias without underscore, for lenient lookups
+py_codecs.update(
+ (k.replace("_", "").encode(), v) for k, v in _py_codecs.items() if "_" in k
+)
+
pg_codecs = {v: k.encode() for k, v in _py_codecs.items()}
def pgconn_encoding(pgconn: "PGconn") -> str:
+ """
+ Return the Python encoding name of a connection.
+
+ Default to utf8 if the connection has no encoding info.
+ """
pgenc = pgconn.parameter_status(b"client_encoding") or b"UTF8"
return pg2pyenc(pgenc)
+def conninfo_encoding(conninfo: str) -> str:
+ """
+ Return the Python encoding name passed in a conninfo string. Default to utf8.
+
+ Because the input is likely to come from the user and not normalised by the
+ server, be somewhat lenient (non-case-sensitive lookup, ignore noise chars).
+ """
+ from .conninfo import conninfo_to_dict
+
+ params = conninfo_to_dict(conninfo)
+ pgenc = params.get("client_encoding")
+ if pgenc:
+ pgenc = pgenc.replace("-", "").replace("_", "").upper().encode()
+ if pgenc in py_codecs:
+ return py_codecs[pgenc]
+
+ return "utf-8"
+
+
def py2pgenc(name: str) -> bytes:
"""Convert a Python encoding name to PostgreSQL encoding name.
from .abc import PQGen, PQGenConn
from .pq.abc import PGconn, PGresult
from .waiting import Wait, Ready
-from ._encodings import py_codecs
+from ._encodings import py_codecs, conninfo_encoding
logger = logging.getLogger(__name__)
conn = pq.PGconn.connect_start(conninfo.encode())
while 1:
if conn.status == ConnStatus.BAD:
+ encoding = conninfo_encoding(conninfo)
raise e.OperationalError(
- f"connection is bad: {pq.error_message(conn)}"
+ f"connection is bad: {pq.error_message(conn, encoding=encoding)}"
)
status = conn.connect_poll()
elif status == PollingStatus.WRITING:
yield conn.socket, Wait.W
elif status == PollingStatus.FAILED:
+ encoding = conninfo_encoding(conninfo)
raise e.OperationalError(
- f"connection failed: {pq.error_message(conn)}"
+ f"connection failed: {pq.error_message(conn, encoding=encoding)}"
)
else:
raise e.InternalError(f"unexpected poll status: {status}")
from psycopg.pq import abc, error_message
from psycopg.abc import PQGen
from psycopg.waiting import Wait, Ready
+from psycopg._encodings import conninfo_encoding
cdef object WAIT_W = Wait.W
cdef object WAIT_R = Wait.R
while 1:
if conn_status == libpq.CONNECTION_BAD:
+ encoding = conninfo_encoding(conninfo)
raise e.OperationalError(
- f"connection is bad: {error_message(conn)}"
+ f"connection is bad: {error_message(conn, encoding=encoding)}"
)
poll_status = libpq.PQconnectPoll(pgconn_ptr)
elif poll_status == libpq.PGRES_POLLING_WRITING:
yield (libpq.PQsocket(pgconn_ptr), WAIT_W)
elif poll_status == libpq.PGRES_POLLING_FAILED:
+ encoding = conninfo_encoding(conninfo)
raise e.OperationalError(
- f"connection failed: {error_message(conn)}"
+ f"connection failed: {error_message(conn, encoding=encoding)}"
)
else:
raise e.InternalError(f"unexpected poll status: {poll_status}")
def test_pg2py_missing(pgenc):
with pytest.raises(psycopg.NotSupportedError):
encodings.pg2pyenc(pgenc.encode())
+
+
+@pytest.mark.parametrize(
+ "conninfo, pyenc",
+ [
+ ("", "utf-8"),
+ ("user=foo, dbname=bar", "utf-8"),
+ ("user=foo, dbname=bar, client_encoding=EUC_JP", "euc_jp"),
+ ("user=foo, dbname=bar, client_encoding=euc-jp", "euc_jp"),
+ ("user=foo, dbname=bar, client_encoding=WAT", "utf-8"),
+ ],
+)
+def test_conninfo_encoding(conninfo, pyenc):
+ assert encodings.conninfo_encoding(conninfo) == pyenc