From 33fafe601082ea0715a1a796a0db3ccad982c81c Mon Sep 17 00:00:00 2001 From: breken-ai <312387581+breken-ai@users.noreply.github.com> Date: Wed, 30 Sep 2026 00:06:37 -0700 Subject: [PATCH] Honor the port query parameter when the DSN has a host For a DSN such as postgresql://user@host/db?port=6432, the host list parser filled in the default port 5432 for the host, and the port query parameter was then ignored because a port was already set. The connection went to port 5432 instead of 6432. libpq, and asyncpg before multi-host support was added in v0.18.0, use the query parameter. Only an explicit port argument now takes precedence over the port query parameter; the query parameter overrides ports from the DSN host list. --- asyncpg/connect_utils.py | 7 +++++- tests/test_connect.py | 53 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/asyncpg/connect_utils.py b/asyncpg/connect_utils.py index 528739d0..5c80e0e5 100644 --- a/asyncpg/connect_utils.py +++ b/asyncpg/connect_utils.py @@ -339,6 +339,11 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, else: dsn_user = dsn_password = '' + # An explicit `port` argument wins over the DSN, but a `port` + # query parameter overrides the port from the DSN host list, + # like in libpq. + explicit_port = port + if not host and dsn_hostspec: host, port = _parse_hostlist(dsn_hostspec, port, unquote=True) @@ -358,7 +363,7 @@ def _parse_connect_dsn_and_args(*, dsn, host, port, user, if 'port' in query: val = query.pop('port') - if not port and val: + if not explicit_port and val: port = [int(p) for p in val.split(',')] if 'host' in query: diff --git a/tests/test_connect.py b/tests/test_connect.py index 66341f40..e0307a66 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -1035,6 +1035,59 @@ class TestConnectParams(tb.TestCase): ) }, + { + 'name': 'dsn_host_with_port_in_params', + 'dsn': 'postgresql://user@host/db?port=6432', + 'result': ( + [('host', 6432)], + { + 'user': 'user', + 'database': 'db', + 'target_session_attrs': 'any', + } + ) + }, + + { + 'name': 'dsn_host_port_overridden_by_port_in_params', + 'dsn': 'postgresql://user@host:5433/db?port=6432', + 'result': ( + [('host', 6432)], + { + 'user': 'user', + 'database': 'db', + 'target_session_attrs': 'any', + } + ) + }, + + { + 'name': 'dsn_multi_host_with_ports_in_params', + 'dsn': 'postgresql://user@host1,host2/db?port=6432,6433', + 'result': ( + [('host1', 6432), ('host2', 6433)], + { + 'user': 'user', + 'database': 'db', + 'target_session_attrs': 'any', + } + ) + }, + + { + 'name': 'dsn_host_port_in_params_explicit_port_wins', + 'dsn': 'postgresql://user@host/db?port=6432', + 'port': 7000, + 'result': ( + [('host', 7000)], + { + 'user': 'user', + 'database': 'db', + 'target_session_attrs': 'any', + } + ) + }, + { 'name': 'dsn_only_illegal_protocol', 'dsn': 'pq:///dbname?host=/unix_sock/test&user=spam',