# PostgreSQL and Async Python

July 2, 2026 Python Back-end Database Performance

Any Python web service will need database integration, and what better database than PostgreSQL. To access the DB in a reasonably efficient manner, asyncio is needed. Here we walk through the common use-case.

Installing packages

$ pip install asyncio asyncpg pydantic

Establishing connection

import asyncio
import logging
import asyncpg
from asyncpg import Pool


async def main() -> None:
    db_config = DbConfig()

    # use asyncpg.connect for single connection.
    db: Pool = await asyncpg.create_pool(
        host=db_config.host,
        database=db_config.database,
        user=db_config.user,
        password=db_config.password,
        min_size=db_config.min_pool_size,
        max_size=db_config.max_pool_size,
    )
    logging.info("database connection established")


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    try:
        asyncio.run(main(), debug=True)
    except Exception as ex:
        logging.error(ex)

Defining Models and Repos

Consider the following SQL schema. We will be using this schema for examples.

create table reviews (
  review_id uuid primary key
  , public_review text not null
  , private_review text null
  , rating int not null
  , submitted_at timestamp not null
);
from pydantic import BaseModel
from uuid import UUID


class ReviewModel(BaseModel):
    review_id: UUID
    public_review: str
    private_review: str | None
    rating: int
    submitted_at: datetime
    

class ReviewWithTotalCount(ReviewModel):
    total_count: int

    def to_review(self) -> ReviewModel:
        return ReviewModel(**self.model_dump())
class ReviewsRepo:
    __db: Pool

    def __init__(self, db: Pool) -> None:
        self.__db = db

    __create_review_query = """
        insert into reviews 
            (review_id, public_review, private_review, rating, submitted_at)
        values 
            ($1, $2, $3, $4, $5) 
    """

    async def create_review(self, r: ReviewModel) -> None:
        await self.__db.execute(
            self.__create_review_query,
            r.review_id,
            r.public_review,
            r.private_review,
            r.rating,
            r.submitted_at,
        )

    __count_reviews_query = """
        select count(*) from reviews        
    """

    async def count_reviews(self) -> int:
        count = await self.__db.fetchval(self.__count_reviews_query)
        return count

    __list_reviews_query = """
        select * from reviews 
    """

    async def list_reviews(self) -> list[ReviewModel]:
        rows = await self.__db.fetch(self.__list_reviews_query)
        return [ReviewModel(**row) for row in rows]

    __list_review_paginated_query = """
        select
            rv.*,
            count(*) over() as total_count
        from reviews rv
        limit $1
        offset $2 
    """

    async def list_reviews_paginated(
        self, limit=10, offset=0
    ) -> Tuple[list[ReviewModel], int]:
        rows = await self.__db.fetch(self.__list_review_paginated_query, limit, offset)
        parsed = [ReviewWithTotalCount(**row) for row in rows]
        if len(parsed) == 0:
            return ([], 0)

        count = parsed[0].total_count
        return ([review.to_review() for review in parsed], count)

    __find_review_by_id_query = """
      select * from reviews
      where review_id = $1  
    """

    async def find_review_by_id(self, review_id: UUID) -> Optional[ReviewModel]:
        row = await self.__db.fetchrow(self.__find_review_by_id_query, review_id)
        if row is None:
            return None
        return ReviewModel(**row)

    __update_review_query = """
       update reviews
       set public_review = $2,
           private_review = $3,
           rating = $4,
           submitted_at = $5
       where review_id = $1 
    """

    async def update_review(self, r: ReviewModel) -> None:
        await self.__db.execute(
            self.__update_review_query,
            r.review_id,
            r.public_review,
            r.private_review,
            r.rating,
            r.submitted_at,
        )

    __delete_review_query = """
        delete from reviews
        where review_id = $1  
    """

    async def delete_review(self, review_id: UUID) -> None:
        await self.__db.execute(self.__delete_review_query, review_id)

Note: asyncpg does not support named parameters in queries.