From 4e75a800f1fcdf2110688b27394e6ec8df9cef47 Mon Sep 17 00:00:00 2001 From: disqualifier Date: Wed, 1 Jul 2026 00:28:27 -0400 Subject: [PATCH] fix: connect() never orphans a pool (psql-3) connect() now closes an existing pool before re-connecting (no orphan on double-connect), and tears down the freshly-built pool if the SELECT-1 validation fails before re-raising (no leaked pool on a failed connect). verified vs embedded postgres. bump v0.1.1 -> v0.1.2 Signed-off-by: disqualifier --- README.md | 6 +++--- pyproject.toml | 2 +- src/psql/psql.py | 18 ++++++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index d41b164..582c908 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,18 @@ a sibling of the `mongo` lib. Class is **`PsqlDB`**. `requirements.txt`: ``` -psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.1 +psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.2 ``` Direct: ```bash -pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.1" +pip install "psql @ git+ssh://git@git.rethinkstudios.io/rethink-public/psql.git@v0.1.2" ``` Pulls `asyncpg`. -Drop the `@v0.1.1` suffix from the line above to install the latest unpinned. +Drop the `@v0.1.2` suffix from the line above to install the latest unpinned. ## The two-layer API diff --git a/pyproject.toml b/pyproject.toml index 8579d95..b6fe72f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "psql" -version = "0.1.1" +version = "0.1.2" description = "async postgres wrapper over asyncpg: two-layer API (friendly verbs + raw escape hatch), fail-loud, config-free" requires-python = ">=3.10" dependencies = [ diff --git a/src/psql/psql.py b/src/psql/psql.py index 39204c5..f39c57e 100644 --- a/src/psql/psql.py +++ b/src/psql/psql.py @@ -108,14 +108,24 @@ class PsqlDB: async def connect(self) -> "PsqlDB": """build the pool and validate it with SELECT 1; fail loud on bad config - returns self so callers can write `db = await PsqlDB(...).connect()`. + returns self so callers can write `db = await PsqlDB(...).connect()`. if called + again on an already-connected instance the previous pool is closed first (no + orphaned pool); if the SELECT-1 validation fails the freshly-built pool is torn + down before re-raising, so a failed connect() never leaks a live pool. """ + if self._pool is not None: + await self.close() + pool = await asyncpg.create_pool(**self._config) try: - self._pool = await asyncpg.create_pool(**self._config) - await self._pool.fetchval("SELECT 1") + await pool.fetchval("SELECT 1") except _DRIVER_ERRORS: - log.exception("psql.connect() failed") + log.exception("psql.connect() validation failed") + await pool.close() raise + except BaseException: + await pool.close() + raise + self._pool = pool return self async def close(self) -> None: