Database and ORM

pantra is not bound to any ORM or database engine and works without any database needed. However, specifically to flexible web framework needs I was designed QuazyDB ORM. This section contains step-to-step guide to install connection to Postgres with QuazyDB.

Installation

QuazyDB is not included in general package, but it could be installed as optional package:

$ pip install pantra[quazydb,psycopg]

Note

psycopg is also required to connect to Postgres database.

Optionally, prepare empty database on Postgres:

CREATE USER pantra WITH PASSWORD 'pantra';
CREATE DATABASE pantra WITH OWNER pantra;

Then create apps/system/data/databases.xml:

databases.xml
<databases>
    <postgres name="db" schema="system" conninfo="postgresql://pantra:pantra@localhost/pantra"/>
</databases>

System schema

Put several meta tables to test:

Click here to expand/collapse code
apps/system/data/__init__.py
import uuid
import hashlib

from quazy import *

from pantra.ctx import *

_SCHEMA_ = "system"

class Document(DBTable):
    _lookup_field_ = 'number'
    _meta_ = True
    id: int = DBField(pk=True, ux=UX(blank=True, hidden=True))

    number: int = DBField(ux=UX(width=6, blank=True))
    date: datetime = DBField(default=datetime.now)

    def __str__(self):
        return f'{_(type(self).__name__)} - {self.number} - {session.locale.datetime(self.date)}'

    class Row(DBTable):
        _meta_ = True
        pos: int = DBField(ux=UX(width=5))
        data: FieldBody


class Catalog(DBTable):
    _lookup_field_ = 'name'
    _meta_ = True
    id: int = DBField(pk=True, ux=UX(blank=True, hidden=True))

    number: int = DBField(ux=UX(width=6, blank=True))
    name: str | None

    def __str__(self):
        return self.name

    @classmethod
    def _view_(cls, item: DBQueryField[typing.Self]):
        return item.name

    def check_number(self, db: DBFactory):
        table_class = self.__class__
        if not self.number:
            q = db.query(table_class)
            max = (q
                   .exclude(id=q.arg(self.id).coalesce(0))
                   .fetch_max('number'))
            self.number = max and max + 1 or 1
        else:
            look_up = db.get(table_class, number=self.number)
            if look_up != self:
                raise ValueError(f'Catalog number <{self.number}> is not unique')

    def _before_insert(self, db: DBFactory):
        self.check_number(db)

    def _before_update(self, db: DBFactory):
        self.check_number(db)

from pantra.models import expose_database
db = expose_database('system')
db.bind_module()

It is also recommended to generate stub file: apps/system/data/__init__.pyi:

$ pantra database.stub system

Then activate migrations:

$ pantra database.migration.activate system

Application schema

Put several files to run simple database application.

apps/storage/data/database.xml
<databases xmlns="app.datebases">
    <reuse name="db" app="system" schema="storage"/>
</databases>
Click here to expand/collapse code
apps/storage/data/__init__.py
from __future__ import annotations
from apps.system.data import Catalog, Document

_SCHEMA_ = "storage"


class Storage(Catalog):
    pass


class Unit(Catalog):
    pass


class Good(Catalog):
    unit: Unit
    weight: float
    

class Purchase(Document):
    storage: Storage

    class Row(Document.Row):
        good: Good
        unit: Unit
        qty: float


from pantra.models import expose_database
db = expose_database('storage')
db._debug_mode = True
db.bind_module()
Click here to expand/collapse code
apps/storage/Main.html
<GridR fullscreen fixed_header>
    <section name="header">
        <SysTray caption="#Storage management"/>
        <Taskbar cref:taskbar/>
    </section>
    <section name="leftcol">
        <SideMenu>
            <MenuGroup caption="#Catalogs">
                {{#for cat in catalogs}}
                <MenuItem code="{cat}" action="@open_catalog"/>
                {{/for}}
            </MenuGroup>
            <MenuGroup caption="#Documents">
                <MenuItem caption="#Rest entries"/>
                <MenuItem caption="#Sales"/>
                <MenuItem caption="#Incomes"/>
            </MenuGroup>
        </SideMenu>
    </section>
    <section name="main">
        <MDI/>
    </section>
</GridR>

<python>
from components.helpers.db_tables import *
from pantra.ctx import *

catalogs = ['Storage', 'Good', 'Unit']


def init():
    session.set_title(_('Storage management'))


def open_catalog(node):
    render_list(session, node['code'])

</python>

Then apply migrations and create tables:

$ pantra database.migration.apply storage

That’s it. Ready to run:

$ pantra run

Open your browser and enter URL http://localhost:8005/storage