Database Clients¶
The database module provides asynchronous database clients for interacting with various database systems.
PostgreSQL Client¶
The PostgresClient
provides an asynchronous interface for interacting with PostgreSQL databases using asyncpg
.
Environment Variables¶
The PostgreSQL client can be configured using the following environment variables:
DB_HOST
: Database host (default: localhost)DB_PORT
: Database port (default: 5432)DB_NAME
: Database name (default: postgres)DB_USER
: Database user (required)DB_PASSWORD
: Database password (required)
Example .env
file:
# Either use individual components
DB_HOST=localhost
DB_PORT=5432
DB_NAME=mydb
DB_USER=myuser
DB_PASSWORD=mypassword
Basic Usage¶
from prs_commons.db import PostgresClient
import asyncio
import os
async def main():
# Create a client with connection pooling
client = PostgresClient()
# Connect to the database
await client.connect()
try:
# Execute a query
result = await client.fetch_one("SELECT * FROM users WHERE id = $1", 1)
print(f"User: {result}")
# Use transaction
async with client.connection() as conn:
await conn.execute("UPDATE users SET last_login = NOW() WHERE id = $1", 1)
# Transaction will be committed when the block exits
finally:
# Close the connection pool
await client.disconnect()
if __name__ == "__main__":
asyncio.run(main())
API Reference¶
- class prs_commons.db.postgres.PostgresClient(*args: Any, **kwargs: Any)[source]¶
Asynchronous PostgreSQL database client using asyncpg with singleton pattern.
This client provides a high-level interface for interacting with a PostgreSQL database asynchronously using connection pooling. Only one instance of this class will be created per process, ensuring a single connection pool is used.
- Parameters:
dsn – The connection string for the PostgreSQL database
min_size – Minimum number of connections in the pool (default: 1)
max_size – Maximum number of connections in the pool (default: 50)
**kwargs – Additional connection parameters passed to asyncpg.create_pool
- __init__(dsn=None, min_size=1, max_size=50, **kwargs)[source]
Initialize the PostgreSQL client.
Note: This will only initialize the instance once due to the singleton pattern. Subsequent calls with different parameters will be ignored.
- __init__(dsn: str | None = None, min_size: int = 1, max_size: int = 50, **kwargs: Any) None [source]¶
Initialize the PostgreSQL client.
Note: This will only initialize the instance once due to the singleton pattern. Subsequent calls with different parameters will be ignored.
- async connect() None [source]¶
Initialize the database connection pool.
Creates a connection pool with the configured parameters. This method is called automatically when the first database operation is performed.
The connection pool parameters are: - min_size: Minimum number of connections to keep open - max_size: Maximum number of connections to allow - Other parameters passed during client initialization
Note
This method is idempotent - calling it multiple times will only create the pool once.
- async disconnect() None [source]¶
Close all connections in the connection pool.
This method should be called when the database client is no longer needed to ensure proper cleanup of resources. After calling this method, the client can be reused by calling connect() again.
Note
It’s good practice to call this when your application shuts down.
- connection() AsyncIterator[Connection] [source]¶
Get a managed database connection with transaction support.
This context manager provides a connection from the pool and automatically: 1. Starts a new transaction 2. Commits on successful completion 3. Rolls back on any exception 4. Returns the connection to the pool
Example
async with db.connection() as conn: await conn.execute("INSERT INTO table VALUES ($1)", 1) # Changes are committed if no exceptions occur
- Yields:
asyncpg.Connection – A database connection from the pool
Note
Nested transactions are supported using savepoints. For example:
async with db.connection() as conn: # Outer transaction starts automatically await conn.execute("INSERT INTO users (name) VALUES ('user1')") try: # Start a nested transaction (savepoint) async with conn.transaction(): await conn.execute("INSERT INTO accounts (user_id, balance) VALUES (1, 100)") # This savepoint can be rolled back independently raise Exception("Something went wrong") except Exception as e: # Only the inner transaction is rolled back print(f"Caught error: {e}") # The outer transaction continues and will be committed await conn.execute("UPDATE users SET status = 'active' WHERE name = 'user1'") # The outer transaction is committed here if no exceptions
- Important:
If an exception occurs in the outer transaction after an inner transaction has committed, the entire transaction (including the committed savepoint) will be rolled back. This ensures transaction atomicity - either all changes complete successfully, or none of them do.
If you need the inner transaction to persist regardless of the outer transaction’s outcome, use separate database connections/transactions instead of nested transactions.
- async fetch_one(query: str, *args: Any, timeout: float | None = None) Dict[str, Any] | None [source]¶
Fetch a single row from the database.
- async fetch_all(query: str, *args: Any, timeout: float | None = None) List[Dict[str, Any]] [source]¶
Fetch multiple rows from the database.
- async execute(query: str, *args: Any, timeout: float | None = None) Tuple[bool, int | str] [source]¶
Execute a write query (INSERT, UPDATE, DELETE) and return the result.
This is a convenience method for executing write operations without explicit transaction management. Each call runs in its own transaction.
- Parameters:
query – The SQL query to execute
*args – Query parameters
timeout – Optional timeout in seconds
- Returns:
(True, affected_rows) on success
(False, error_message) on failure
- Return type:
Example
```python success, result = await db.execute(
“UPDATE users SET active = $1 WHERE id = $2”, True, 123
) if success:
print(f”Updated {result} rows”)
- async execute_returning(query: str, *args: Any, timeout: float | None = None) Tuple[bool, Dict[str, Any] | None] [source]¶
Execute a query that returns the affected row.
- Returns:
A tuple of (success, result_dict) where result_dict is the affected row or None if no rows were affected
- async __aenter__() PostgresClient [source]¶
Async context manager entry.
- async __aexit__(exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: Any | None) None [source]¶
Async context manager exit.
Error Handling¶
The client raises the following exceptions:
ValueError
: If required environment variables are missingasyncpg.PostgresError
: For database-related errorsException
: For other unexpected errors