Skip to content

Welcome to pgcrud

pgcrud is a Python package that makes Create, Read, Update, and Delete (CRUD) operations for PostgreSQL simple and fast.

Key Features

  • No ORM, only declarative expressions.
  • Built-in pydantic and msgspec support for data serialization & validation
  • Efficient handling of complex parent-child relationships
  • Perform operations sync or async.
  • Full type hint support.
  • Easy to integrate into existing projects.
  • Protection against SQL-Injection
  • Tailored to PostgreSQL with wide extensions support.

Dependencies

pgcrud's only dependency is psycopg including it's binary and pool extensions.

Optional

For data serialization & validation you can choose between:

Installation

pgcrud is not yet available on PyPI. However, you can install it using pip with the following command:

pip install git+https://github.com/dakivara/pgcrud.git

To use pydantic or msgspec, ensure you install them separately, as they are optional dependencies.

Do not download the pgcrud package from PyPi. This is an abandoned package and is not affiliated with us.

Cursor

pgcrud extends the psycopg cursor by directly fetching and validating the data with your preferred data serialization & validation library.

from pydantic import BaseModel

import pgcrud as pg


class Author(BaseModel):
    id: int
    name: str


with pg.connect('CONN_STR') as conn:
    with conn.cursor() as cursor:
        cursor[Author].execute("SELECT 1 AS id, 'J.K. Rowling' AS name").fetchone()
        # returns Author(id=1, name='J.K. Rowling')

        cursor[str].execute("SELECT 1").fetchone()
        # raises ValidationError: Input should be a valid string [type=string_type, input_value=1, input_type=int]
import asyncio    

from pydantic import BaseModel

import pgcrud as pg


class Author(BaseModel):
    id: int
    name: str


async def main():
    async with await pg.a.connect('CONN_STR') as conn:
        async with conn.cursor() as cursor:
            await cursor[Author].execute("SELECT 1 AS id, 'J.K. Rowling' AS name")
            await cursor.fetchone()
            # returns Author(id=1, name='J.K. Rowling')

            await cursor[str].execute("SELECT 1")
            await cursor.fetchone()
            # raises ValidationError: Input should be a valid string [type=string_type, input_value=1, input_type=int]


asyncio.run(main())

CRUD Examples

This is a high level overview of the pgcrud operations. For more detailed instructions and advanced examples, be sure to explore the rest of the documentation.

In the following examples, we use a simple Author model based on an author table with the columns id, name, and date_of_birth.

Create

The insert_one / insert_many methods are used to insert one / many records into a table. You must specify into which table you want to insert and which columns you want to populate. You can simply insert new records from Pydantic objects. Typically, the id column is autogenerated and you can retrieve it after insertion.

from datetime import date

from pydantic import BaseModel

import pgcrud as pg
from pgcrud import e


class AuthorInput(BaseModel):
    name: str
    date_of_birth: date


def insert_author(
        cursor: pg.Cursor,
        input_: AuthorInput,
) -> int | None:

    return pg.insert_one(
        cursor=cursor[int],
        insert_into=e.author[e.name, e.date_of_birth],
        values=input_,
        returning=e.id,
    )
from datetime import date

from pydantic import BaseModel

import pgcrud as pg
from pgcrud import e


class AuthorInput(BaseModel):
    name: str
    date_of_birth: date


async def insert_author(
        cursor: pg.a.Cursor,
        input_: AuthorInput,
) -> int | None:

    return await pg.a.insert_one(
        cursor=cursor[int],
        insert_into=e.author[e.name, e.date_of_birth],
        values=input_,
        returning=e.id,
    )

Read

The get_one / get_many methods are used to retrieve one / many records from a table. You must specify from with table you want to select the records. You can directly load the fetched records into a Pydantic model. The where condition specifies which records should be retrieved. If the where condition matches multiple records, the get_one method will return the first record that meets the filter criteria.

from datetime import date

from pydantic import BaseModel

import pgcrud as pg
from pgcrud import e


class Author(BaseModel):
    id: int
    name: str
    date_of_birth: date


def get_author(
        cursor: pg.Cursor,
        id_: int,
) -> Author | None:

    return pg.get_one(
        cursor=cursor[Author],
        select=(e.id, e.name, e.date_of_birth),
        from_=e.author,
        where=e.id == id_,
    )
from datetime import date

from pydantic import BaseModel

import pgcrud as pg
from pgcrud import e


class Author(BaseModel):
    id: int
    name: str
    date_of_birth: date


async def get_author(
        cursor: pg.a.Cursor,
        id_: int,
) -> Author | None:

    return await pg.a.get_one(
        cursor=cursor[Author],
        select=(e.id, e.name, e.date_of_birth),
        from_=e.author,
        where=e.id == id_,
    )

Update

The update_many method is used to update records in a table.1 You need to specify which table and which columns you want to update. The where condition specifies which records should be updated. To update a single record, you must ensure that the where condition targets a single record.

from datetime import date

from pydantic import BaseModel

import pgcrud as pg
from pgcrud import e


class AuthorUpdate(BaseModel):
    name: str
    date_of_birth: date


def update_author(
        cursor: pg.Cursor, 
        update: AuthorUpdate, 
        id_: int,
) -> None:

    pg.update_many(
        cursor=cursor,
        update=e.author,
        set_=((e.name, e.date_of_birth), update),
        where=e.id == id_,
    )
from datetime import date

from pydantic import BaseModel

import pgcrud as pg
from pgcrud import e


class AuthorUpdate(BaseModel):
    name: str
    date_of_birth: date


async def update_author(
        cursor: pg.a.Cursor, 
        update: AuthorUpdate,
        id_: int,
) -> None:

    await pg.a.update_many(
        cursor=cursor,
        update=e.author,
        set_=((e.name, e.date_of_birth), update),
        where=e.id == id_,
    )

Delete

The delete_many method is used to delete records from a table.2 You need to specify from which table you want to delete the records. The where condition specifies which records should be deleted. To delete a single record, you must ensure that the where condition targets a single record.

import pgcrud as pg
from pgcrud import e


def delete_author(
        cursor: pg.Cursor, 
        id_: int,
) -> None:

    pg.delete_many(
        cursor=cursor,
        delete_from=e.author,
        where=e.id == id_,
    )
import pgcrud as pg
from pgcrud import e


async def delete_author(
        cursor: pg.a.Cursor, 
        id_: int,
) -> None:

    await pg.a.delete_many(
        cursor=cursor,
        delete_from=e.author,
        where=e.id == id_,
    )

License

pgcrud is released under the MIT License.


  1. An update_one method does not exist because PostgreSQL's UPDATE command does not inherently target a single record. 

  2. A delete_one method does not exist because PostgreSQL's DELETE command does not inherently target a single record.