Conversation
- Add AcquireContext Protocol and make TimeoutAcquireContext generic - Make BasePoolManager, PoolAcquireContext, AbstractBalancerPolicy generic over PoolT/ConnT with proper type annotations on all abstract methods - Make Stopwatch generic over key type, fix _cache type (int -> float) - Make balancer policies generic over PoolT - Fix asyncsqlalchemy release_to_pool param name to match base signature - Fix psycopg3 _is_master with explicit None check for fetchone() - Narrow asyncpg import ignore to type: ignore[import-untyped] Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ract driver package Extract BasePoolManager internals into dedicated modules (abc, acquire, constants, health, pool_manager) and move driver implementations from hasql/*.py into hasql/driver/ package. Introduce PoolDriver ABC with generics to replace abstract methods on pool manager. Bump to v0.10.0. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add PoolStats, PoolMetrics, HasqlGauges dataclasses to metrics.py - Add pool_stats() to PoolDriver ABC, deprecate driver_metrics() - Rewrite BasePoolManager.metrics() with per-pool enrichment (role, health, response_time, in_flight, driver-specific extras) - Track context-managed connections for accurate in_flight counts - Extract PoolState into dedicated pool_state.py module - Break balancer_policy → pool_manager import cycle via PoolStateProvider protocol - Add OTLP example scripts for all 5 drivers (example/otlp/) - Add metrics + OTLP sections to README.rst - Add docs/metrics-dashboard-example.md with Grafana layout guide - Merge migration guides into docs/migration-0.9.0-to-0.11.0.md - Maintain backward compat: Metrics.drivers property, driver_metrics() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ivers Fix 11 bugs found during code review: - acquire.py: use covariant TypeVar in AcquireContext protocol, guard __aexit__ against None state - pool_state.py: fix TOCTOU race in wait_for_master/replica_pools using wait_for inside lock - metrics.py: use defaultdict(float) for _acquire_time, wrap yields in try/finally - utils.py: preserve scheme in Dsn.with_() instead of dropping to default - driver/asyncpg.py: use .get() fallback to avoid KeyError on cache miss - driver/psycopg3.py: guard putconn against None connection - driver/asyncsqlalchemy.py: handle None host, support postgres:// scheme alias - pool_manager.py: fix _clear() to snapshot connections before clearing dict, fix error message - docs: rename migration guide to 0.10.0, document broken driver import paths Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce public API from 18+ methods/properties to 8 public methods plus __aenter__/__aexit__. Add proxy methods (ready, wait_masters_ready, available_pool_count) on the manager for convenience. Remove deprecated public accessors and proxy methods, privatize internal attributes. - Rename pool_state → _pool_state, balancer → _balancer, etc. - Add ready(), wait_masters_ready(), available_pool_count proxy - Remove release(), terminate(), __iter__, driver, host(), etc. - Privatize register/unregister_connection - Update all internal callers (health.py, acquire.py) - Update tests, migration docs, and README Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e cleanup - Replace asyncpg cached_hosts dict[int, str] with WeakKeyDictionary to prevent stale lookups on id(pool) reuse after GC - Modernize type annotations: Optional[X] → X | None, Dict → dict, List → list, Set → set, imports from collections.abc - Rename balancer _pool_manager → _pool_state to match actual type - Return new dict from prepare_pool_factory_kwargs (no input mutation) - Defensive copies of dsn_list and pool_factory_kwargs in PoolState - Clean up driver context on _register_connection failure in acquire - Add PoolManagerClosingError instead of misusing CancelledError - Simplify driver tests to test drivers directly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Inject dependencies into PoolAcquireContext instead of accessing pool_manager private fields (encapsulation) - Make all PoolAcquireContext fields private - Remove return from release_to_pool in aiopg/psycopg3 (CQS) - Rename misleading params in CalculateMetrics (pool→host, dsn→host) - Add defensive copies in CalculateMetrics.metrics() and Dsn.params - Modernize type annotations in utils.py (Optional→X|None, etc.) - Simplify dead branch in PoolState.ready() - Add __all__ to metrics module - Update tests for new PoolAcquireContext constructor API - Convert loop-assert to @pytest.mark.parametrize - Use public property pool_state.driver in test Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The
hasql/base.pymodule (~750 lines) contained everything: the abstractBasePoolManagerclass, protocols (AcquireContext), connection contexts (TimeoutAcquireContext,PoolAcquireContext), constants, and pool health monitoring logic. Each driver (aiopg, asyncpg, psycopg3, asyncsqlalchemy) was implemented by subclassingBasePoolManagerand overriding ~10 abstract methods, which led to:BasePoolManagerAnyand# type: ignoreinstead of genericshasql/aiopg.py,hasql/asyncpg.py, etc.)Solution
Architecture: composition over inheritance.
Split
base.pyinto focused modules:hasql/abc.py— abstractPoolDriver[PoolT, ConnT]interface with genericshasql/acquire.py—AcquireContext,TimeoutAcquireContext,PoolAcquireContexthasql/constants.py—DEFAULT_REFRESH_DELAY,DEFAULT_ACQUIRE_TIMEOUT, etc.hasql/health.py—PoolHealthMonitor(master/replica role monitoring)hasql/pool_manager.py— concreteBasePoolManagerthat accepts adriver: PoolDriverExtract drivers into
hasql/driver/package:hasql/driver/aiopg.py,hasql/driver/asyncpg.py,hasql/driver/psycopg3.py,hasql/driver/asyncsqlalchemy.py,hasql/driver/aiopg_sa.py,hasql/driver/asyncpgsa.pyPoolDriverABCPoolManageris a thin wrapper that passes the appropriate driver toBasePoolManagerType safety:
type: ignoreandAnyin favor ofGeneric[PoolT, ConnT]Backward compatibility: