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:

Tuple[bool, Union[int, str]]

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 missing

  • asyncpg.PostgresError: For database-related errors

  • Exception: For other unexpected errors

See Also