Skip to main content

Persistent pydantic models - A single model for shaping, creating, accessing, storing data within a Database

Documentation Status PyPI versionUnit & Integration Tests

Key Features

  • Flexible Data Types
  • One Model for type validation & database access
  • Dynamic / Explicit Model Relationships
  • Integrated Redis Caching Support
  • Built in support for Alembic Migrations or Automatic Migration on Schema Changes

Documentation

https://pydbantic.readthedocs.io/en/latest/

Setup

$ pip install pydbantic
$ pip install pydbantic[sqlite]
$ pip install pydbantic[mysql]
$ pip install pydbantic[postgres]

Basic Usage - Model

from typing import List, Optional, Union
from pydbantic import DataBaseModel, PrimaryKey, Unique

class Department(DataBaseModel):
    department_id: str = PrimaryKey()
    name: str = Unique()
    company: str
    is_sensitive: bool = False
    positions: List[Optional['Positions']] = []  # One to Many

class Positions(DataBaseModel):
    position_id: str = PrimaryKey()
    name: str
    department: Department = None               # One to One mapping
    employees: List[Optional['Employee']] = []  # One to Many

class EmployeeInfo(DataBaseModel):
    ssn: str = PrimaryKey()
    first_name: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]
    new: Optional[str]
    employee: Optional[Union['Employee', dict]] = None # One to One

class Employee(DataBaseModel):
    employee_id: str = PrimaryKey()
    employee_info: Optional[EmployeeInfo] = None  # One to One
    position: List[Optional[Positions]] = []      # One to Many
    salary: float
    is_employed: bool
    date_employed: Optional[str]

Basic Usage - Connecting a Database to Models

import asyncio
from pydbantic import Database
from models import Employee, EmployeeInfo, Positions, Department

async def main():
    db = await Database.create(
        'sqlite:///test.db',
        tables=[
            Employee,
            EmployeeInfo,
            Positions,
            Department
        ]
    )

if __name__ == '__main__':
    asyncio.run(main())

Model Usage

Import and use the models where you need them. As long as DB as already been created, the Models can access the & Use the connected DB

from models import (
    Employee,
    EmployeeInfo,
    Position,
    Department
)

Model - Creation

    # create department
    hr_department = await Department.create(
        id='d1234',
        name='hr'
        company='abc-company',
        is_sensitive=True,
    )

Via instance using insert or save

    hr_department = Department.create(
        id='d1234',
        name='hr'
        company='abc-company',
        is_sensitive=True,
    )

    await hr_department.insert()
    await hr_department.save()

Insert with related models

    # create a Position in Hr Department
    hr_manager = Position.create(
        id='p1234',
        name='manager',
        department=hr_department
    )

    # create instance on an hr employee
    hr_emp_info = EmployeeInfo.create(
        ssn='123-456-789',
        first_name='john',
        last_name='doe',
        address='123 lane',
        city='snake city',
        zip=12345
    )

    # create an hr employee
    hr_employee = await Employee.create(
        id='e1234',
        employee_info=hr_emp_info,
        position=hr_manager,
        is_employed=True,
        date_employed='1970-01-01'
    )

Filtering

    # get all hr managers currently employed
    managers = await Employee.filter(
        Employee.position==hr_manager, # conditional
        is_employed=True               # key-word argument
    )

    first_100_employees = await Employee.all(
        limit=100
    )

See also filtering operators

Deleting

    # one by one
    for manager in await Employee.filter(
        position=hr_manager,
        is_employed=False
    ):
        await manager.delete()
    # all at once
    await Employee.delete_filter(
        Employee.is_employed == True
    )

Updating

    # raise salary of all managers
    for manager in await Employee.filter(
        position=hr_manager,
        is_employed=False
    ):
        manager.salary = manager.salary + 1000.0
        await manager.update() # or manager.save()

.save() results in a new row created in Employee table as well as the related EmployeeInfo, Position, Department tables if not yet created. s

What is pydbantic

pydbantic was built to solve some of the most common pain developers may face working with databases.

  • migrations
  • model creation / management
  • dynamic relationships
  • caching

pydbantic believes that related data should be stored together, in the shape the developer plans to use

pydbantic knows data is rarely flat or follows a set schema

pydbantic understand migrations are not fun, and does them for you

pydbantic speaks many types

Pillars

Models

pydbantic most basic object is a DataBaseModel. This object may be comprised of almost any pickle-able python object, though you are encouraged to stay within the type-validation land by using pydantic's BaseModels and validators.

Primary Keys

DataBaseModel 's also have a primary key, which is the first item defined in a model or marked with = PrimaryKey()

class NotesBm(DataBaseModel):
    id: str = PrimaryKey()
    text: Optional[str]  # optional
    data: DataModel      # required
    coordinates: tuple   # required
    items: list          # required
    nested: dict = {'nested': True} # Optional - w/ Default

Model Types & Typing

DataBaseModel items are capable of being multiple layers deep following pydantic model validation

  • Primary Key - First Item, must be unique
  • Required - items without default values are assumed required
  • Optional - marked explicitly with typing.Optional or with a default value
  • Union - Accepts Either specified input type Union[str|int]
  • List[item] - Lists of specified items

Input data-types without a standard built-in db serialization, are serialized using pickle and stored as bytes. More on this later.

Migrations

pydbantic can handle migrations automatically in response to detected model changes: New Field, Removed Field, Modified Field, Renamed Field, Primary Key Changes.

pydbantic is also able to integrate with Alembic to perform migrations, see Migrations Using Alembic

Cache

Adding cache with Redis is easy with pydbantic, and is complete with built in cache invalidation. I.E when a query becomes outdated(from insertion / update / deletion), it will be expelled from cache.

    db = await Database.create(
        'sqlite:///test.db',
        tables=[Employee],
        cache_enabled=True,
        redis_url="redis://localhost"
    )

Models with arrays of Foreign Objects

DataBaseModel models can support arrays of both BaseModels and other DataBaseModel. Just like single DataBaseModel references, data is stored in separate tables, and populated automatically when the child DataBaseModel is instantiated.

from uuid import uuid4
from datetime import datetime
from typing import List, Optional
from pydbantic import DataBaseModel, PrimaryKey


def time_now():
    return datetime.now().isoformat()
def get_uuid4():
    return str(uuid4())

class Coordinate(DataBaseModel):
    id: str = PrimaryKey(default=get_uuid4)
    lat_long: tuple
    journeys: List[Optional["Journey"]] = []

class Journey(DataBaseModel):
    trip_id: str = PrimaryKey(default=get_uuid4)
    waypoints: List[Optional[Coordinate]] = []

Metadata

Release files for pydbantic 0.0.47

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for pydbantic 0.0.47
File Interpreter ABI Platform
pydbantic-0.0.47-py3-none-any.whl Python 3 none any Details

Release files / pydbantic-0.0.47-py3-none-any.whl

Download URL pydbantic-0.0.47-py3-none-any.whl
Size 28.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0761eafa9bd14e5c1d173c58be92404cda998ed8f80b35369b10e44aac4eb346
BLAKE2b-256 checksum
How to use checksums
720b774ca49bb199f5b4d50bc959dcaf38ae504a5482036036be188a1f9f26a7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.8.18

Release history Release notifications | RSS feed

This release

0.0.47 This release

1 release file

0.0.46

1 release file

0.0.45

1 release file

0.0.44

1 release file

0.0.43

1 release file

0.0.41

1 release file

0.0.40

1 release file

0.0.39

1 release file

0.0.38

1 release file

0.0.37

1 release file

0.0.36

1 release file

0.0.35

1 release file

0.0.34

1 release file

0.0.33

1 release file

0.0.32

1 release file

0.0.31

1 release file

0.0.29

1 release file

0.0.28

1 release file

0.0.27

1 release file

0.0.25

1 release file

0.0.24

1 release file

0.0.23

1 release file

0.0.22

1 release file

0.0.21

1 release file

0.0.20

1 release file

0.0.19

1 release file

0.0.18

1 release file

0.0.17

1 release file

0.0.16

1 release file

0.0.15

1 release file

0.0.14

1 release file

0.0.13

1 release file

0.0.12

1 release file

0.0.11

1 release file

0.0.10

1 release file

0.0.9

1 release file

0.0.8

1 release file

0.0.7

1 release file

0.0.6

1 release file

0.0.5

1 release file

0.0.4

1 release file

0.0.3

1 release file

0.0.2

1 release file

0.0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page