fix: host/port defaults no longer shadow a dsn's embedded host/port (psql-5)

Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
2026-07-02 17:30:10 -04:00
parent c3205f0614
commit 1ae280e5c3
3 changed files with 31 additions and 6 deletions
+9 -3
View File
@@ -10,18 +10,18 @@ a sibling of the `mongo` lib. Class is **`PsqlDB`**.
`requirements.txt`: `requirements.txt`:
``` ```
psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.3 psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.4
``` ```
Direct: Direct:
```bash ```bash
pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.3" pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.4"
``` ```
Pulls `asyncpg`. Pulls `asyncpg`.
Drop the `@v0.1.3` suffix from the line above to install the latest unpinned. Drop the `@v0.1.4` suffix from the line above to install the latest unpinned.
## The two-layer API ## The two-layer API
@@ -63,6 +63,12 @@ async with PsqlDB(database="app", user="postgres") as db:
await db.insert("events", {"kind": "login"}) await db.insert("events", {"kind": "login"})
``` ```
`host`/`port` default to `None`, not `"localhost"`/`5432` — asyncpg only reads a `dsn`'s
embedded host/port when the `host`/`port` kwargs are falsy, so passing `dsn=...` in
`pool_kwargs` (with no `host`/`port` of your own) lets the dsn's server reach asyncpg
instead of being silently overridden. The no-dsn path above still defaults to
`localhost:5432` when you don't pass `host`.
### Layer 2 — raw SQL for the complex queries ### Layer 2 — raw SQL for the complex queries
```python ```python
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "psql" name = "psql"
version = "0.1.3" version = "0.1.4"
description = "async postgres wrapper over asyncpg: two-layer API (friendly verbs + raw escape hatch), fail-loud, config-free" description = "async postgres wrapper over asyncpg: two-layer API (friendly verbs + raw escape hatch), fail-loud, config-free"
requires-python = ">=3.10" requires-python = ">=3.10"
dependencies = [ dependencies = [
+21 -2
View File
@@ -18,6 +18,13 @@ lifecycle:
validates it with `SELECT 1` so a bad host/credentials fails loud immediately rather validates it with `SELECT 1` so a bad host/credentials fails loud immediately rather
than on the first real op, and returns self. close() closes the pool. than on the first real op, and returns self. close() closes the pool.
dsn:
host/port default to None, not "localhost"/5432 — asyncpg only reads a dsn's
embedded host/port when the host/port kwargs are falsy, so a hardcoded default would
silently shadow the dsn's server. pass dsn=... in pool_kwargs alone (no host/port) to
let the dsn's own host/port reach asyncpg; the no-dsn path still defaults to
localhost:5432.
two-layer API: two-layer API:
LAYER 1 — friendly, portable verbs for simple single-table CRUD. these hide the LAYER 1 — friendly, portable verbs for simple single-table CRUD. these hide the
dialect and are byte-for-byte identical to the `mysql` lib, so a dev swaps psql<->mysql dialect and are byte-for-byte identical to the `mysql` lib, so a dev swaps psql<->mysql
@@ -75,8 +82,8 @@ class PsqlDB:
def __init__( def __init__(
self, self,
host: str = "localhost", host: Optional[str] = None,
port: int = 5432, port: Optional[int] = None,
database: Optional[str] = None, database: Optional[str] = None,
user: Optional[str] = None, user: Optional[str] = None,
password: Optional[str] = None, password: Optional[str] = None,
@@ -91,7 +98,19 @@ class PsqlDB:
host/port/database/user/password/min_size/max_size/command_timeout are injected by host/port/database/user/password/min_size/max_size/command_timeout are injected by
the caller. extra pool_kwargs pass through to asyncpg.create_pool (ssl, server_ the caller. extra pool_kwargs pass through to asyncpg.create_pool (ssl, server_
settings, dsn, etc). `host` may be a unix socket directory as well as a hostname. settings, dsn, etc). `host` may be a unix socket directory as well as a hostname.
host/port default to None here (not "localhost"/5432) because asyncpg only reads
a dsn's embedded host/port when the host/port kwargs are falsy — a hardcoded
default would silently shadow the dsn's server and connect you to the wrong one.
when no dsn is passed, host/port fall back to localhost:5432 (the common no-dsn
path is unchanged); when a dsn is passed, host/port stay None unless the caller
explicitly overrides them, letting the dsn's own host/port reach asyncpg.
""" """
if "dsn" not in pool_kwargs:
if host is None:
host = "localhost"
if port is None:
port = 5432
self._config = dict( self._config = dict(
host=host, host=host,
port=port, port=port,