A single model for shaping, creating, accessing, storing data within a Database

Overview

'db' within pydantic - A single model for shaping, creating, accessing, storing data within a Database

Documentation Status PyPI versionUnit & Integration Tests

Key Features

  • Integrated Redis Caching Support
  • Automatic Migration on Schema Changes
  • Flexible Data Types
  • One Model for type validation & database access

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
from pydantic import BaseModel, Field
from pydbantic import DataBaseModel, PrimaryKey

class Department(DataBaseModel):
    id: str = PrimaryKey()
    name: str
    company: str
    is_sensitive: bool = False

class Positions(DataBaseModel):
    id: str = PrimaryKey()
    name: str
    department: Department

class EmployeeInfo(DataBaseModel):
    ssn: str = PrimaryKey()
    first_name: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]

class Employee(DataBaseModel):
    id: str = PrimaryKey()
    employee_info: EmployeeInfo
    position: Positions
    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

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

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

Model Usage

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

async def main():
    # db creation is above

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

    # create a Position in Hr Department
    hr_manager = Position(
        id='p1234',
        name='manager',
        department=hr_department
    )
    
    # create information on an hr employee
    hr_emp_info = EmployeeInfo(
        ssn='123-456-789',
        first_name='john',
        last_name='doe',
        address='123 lane',
        city='snake city',
        zip=12345
    )

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

Note: At this point only the models have been created, but nothing is saved in the database yet.

    # save to database
    await hr_employee.save()

Filtering

    # get all hr managers currently employed
    managers = await Employee.filter(
        position=hr_manager,
        is_employed=True
    )

Deleting

    # remove all managers not employed anymore
    for manager in await Employee.filter(
        position=hr_manager,
        is_employed=False
    ):
        await manager.delete()

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 non-existing.

What is pydbantic

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

  • migrations
  • model creation / managment
  • 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 priamry 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 
    coridinates: 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 datatypes without a natural / built in serialization path are serialized using pickle and stored as bytes. More on this later.

Migrations

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

Renaming an exiting column

Speical consideration is needed when renaming a field in a DataBaseModel, extra metadata __renamed__ is needed to ensure existing data is migrated:

# field `first_name` is renamed to `first_names`

class EmployeeInfo(DataBaseModel):
    __renamed__= [{'old_name': 'first_name', 'new_name': 'first_names'}]
    ssn: str = PrimaryKey()
    first_names: str
    last_name: str
    address: str
    address2: Optional[str]
    city: Optional[str]
    zip: Optional[int]

Cache

Adding cache with Redis is easy with pydbantic, and is complete with built in cache invalidation.

    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):
    time: str = PrimaryKey(default=time_now)
    latitude: float
    longitude: float

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


Comments
  • StopAsyncIteration Exception

    StopAsyncIteration Exception

    Hello, when I execute the following example I get an StopAsyncIteration Exception with no Exception Message.

    from pydbantic import Database
    
    class Department(DataBaseModel):
        id: str = PrimaryKey()
        name: str
        company: str
        is_sensitive: bool = False
    
    db = await Database.create('sqlite:///test.db',tables=[Department])
    

    Traceback:

    StopAsyncIteration                        Traceback (most recent call last)
    ~\AppData\Local\Temp/ipykernel_19192/144563389.py in <module>
          7     is_sensitive: bool = False
          8 
    ----> 9 db = await Database.create('sqlite:///test.db',tables=[Department])
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in create(cls, DB_URL, tables, cache_enabled, redis_url, logger, debug, testing)
        492 
        493         async with new_db:
    --> 494             await new_db.compare_tables_and_migrate()
        495 
        496         return new_db
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in compare_tables_and_migrate(self)
        161 
        162         # checkout database for migrations
    --> 163         database_init = await DatabaseInit.get(database_url=self.DB_URL)
        164 
        165         if not database_init:
    
    ~\Documents\.venv\lib\site-packages\pydbantic\core.py in get(cls, **p_key)
        626             if k != cls.__metadata__.tables[cls.__name__]['primary_key']:
        627                 raise f"Expected primary key {primary_key}=<value>"
    --> 628         result = await cls.select('*', where={**p_key})
        629         return result[0] if result else None
        630 
    
    ~\Documents\.venv\lib\site-packages\pydbantic\core.py in select(cls, where, alias, limit, offset, *selection)
        431         database = cls.__metadata__.database
        432 
    --> 433         results = await database.fetch(sel, cls.__name__, values)
        434 
        435         for result in cls.normalize(results):
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in fetch(self, query, table_name, values)
        459         self.log.debug(f"running query: {query} with {values}")
        460 
    --> 461         async with self as conn:
        462             row = await conn.fetch_all(query=query)
        463 
    
    ~\Documents\.venv\lib\site-packages\pydbantic\database.py in __aenter__(self)
        509                 continue
        510             self.connection_map[conn_id]['last'] = time.time()
    --> 511             return await self.connection_map[conn_id]['conn'].asend(None)
        512 
        513         conn_id = str(uuid.uuid4())
    
    StopAsyncIteration: 
    

    Versions

    • Python version 3.9.6
    • Pydbantic version 0.0.15
    • Operating system Windows 10
    opened by Phil997 6
  • README Shows Incomplete Import Statement

    README Shows Incomplete Import Statement

    By my reading, it looks like this example shows an incomplete import statement. The statement does not import all of the models required to create the database. (I think :stuck_out_tongue_closed_eyes: )

    Present documentation

    import asyncio
    from pydbantic import Database
    from models import Employee ### <---- Missing additional model imports here...?
    
    async def main():
        db = await Database.create(
            'sqlite:///test.db',
            tables=[
                Employee,
                EmployeeInfo,
                Positions,
                Department
            ]
        )
    
    if __name__ == '__main__':
        asyncio.run(main())
    

    Suggested Documentation

    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())
    
    opened by engineerjoe440 4
  • bump databases version to 0.6.0 and pydantic to 1.9.1

    bump databases version to 0.6.0 and pydantic to 1.9.1

    It's not possible to use easyauth with the latest encoding/databases version 0.6.0 since pydbantic depends on version 0.5.3. Is it possible to bump the version or are there any backwards compatibility issues?

    In addition to databases consider upgrading pydantic to 1.9.1 - latest.

    opened by KKP4 3
  • Add a field with datetime.utcnow in documentation example

    Add a field with datetime.utcnow in documentation example

    Hi there,

    Working with dates can be quite challenging, for example when trying to add something like "creation_date" or "updated_at" in plain SQLalchemy we would do something like :

    from sqlalchemy.sql import func
    
    time_created = Column(DateTime(timezone=True), server_default=func.now())
    time_updated = Column(DateTime(timezone=True), onupdate=func.now())
    

    How would we be able to achieve the same thing in Pydbantic ?

    opened by sorasful 3
  • data and metadata tables advice

    data and metadata tables advice

    Thank you Josh for your work with pydbantic,

    I want to store both data and metadata for my data

    How do you recommend creating tables that can store pandas dataframes with arbitrary columns?

    How about metadata where the metadata has some JSON fields?

    question 
    opened by joamatab 2
  • How can we access one's parent through relationship ?

    How can we access one's parent through relationship ?

    Imagine we have

    # create department
        hr_department = Department(
            id='d1234',
            name='hr',
            company='abc-company',
                is_sensitive=True,
        )
    
    
        # create a Position in Hr Department
        hr_manager = Position(
            id='p1234',
            name='manager',
            department=hr_department
        )
    

    And suppose I want to do :

    manager = hr_manager.department.manager
    

    for example, would it be possible ? How could we setup this ?

    opened by sorasful 2
  • BUG - Filtering with order_by & limit / offset

    BUG - Filtering with order_by & limit / offset

    Description

    On version 0.0.21, filtering might fail if queries specify both order_by combined with limit and offset.

    Traceback

    (order_by=order, limit=limit, offset=offset)
      File "/usr/local/lib/python3.8/site-packages/pydbantic/core.py", line 1143, in all
        return await cls.select('*', **parameters, backward_refs=backward_refs)
      File "/usr/local/lib/python3.8/site-packages/pydbantic/core.py", line 966, in select
        sel = sel.order_by(order_by)
      File "<string>", line 2, in order_by
      File "/usr/local/lib/python3.8/site-packages/sqlalchemy/sql/base.py", line 110, in _generative
        x = fn(self, *args, **kw)
      File "<string>", line 2, in order_by
      File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/base.py", line 229, in generate
        assertion(self, fn.__name__)
      File "/usr/local/lib/python3.8/site-packages/sqlalchemy/orm/query.py", line 312, in _no_limit_offset
        raise sa_exc.InvalidRequestError(
    sqlalchemy.exc.InvalidRequestError: Query.order_by() being called on a Query which already has LIMIT or OFFSET applied.  Call order_by() before limit() or offset() are applied.
    
    
    
    bug 
    opened by codemation 1
  • Find a way to generate automatically PrimaryKeys instead of coding them

    Find a way to generate automatically PrimaryKeys instead of coding them

    IMHO, It would be essential that PrimaryKey() generates automatically an id, it could even depend on the type we are passing, for example :

    class Employee(DataBaseModel):
        id: str = PrimaryKey()  # could use uuid.uuid4()
        id: int = PrimaryKey()  # could use random.randint(0, 9e99) (just an example, would need something better)
    
    

    A possibility would also to set PrimaryKey as Optional, what do you think ?

    opened by sorasful 1
  • Chore: Simplify logical expression using `De Morgan identities`  ✨

    Chore: Simplify logical expression using `De Morgan identities` ✨

    When reading logical statements it is important for them to be as simple as possible, that's why it's better to stick in one code way to let it more easy and fast.

    opened by yezz123 1
  • Alembic migrations and mod many methods

    Alembic migrations and mod many methods

    Whats Inside

    • Added insert_many, delete_many methods to DatabaseModel
    • Added support for alembic migrations
    • Improved automatic migrations with alembic
    opened by codemation 0
  • Add unique model constraint

    Add unique model constraint

    Description

    This PR adds the ability to define Unique() constraint on a DataBaseModel field, allowing additional unique constraint protection alongside PrimaryKey.

    Example Model

    from uuid import uuid4
    from datetime import datetime
    from typing import List, Optional, Union
    from pydbantic import DataBaseModel, PrimaryKey, Unique
    
    def uuid_str():
        return str(uuid4())
    
    class Department(DataBaseModel):
        department_id: str = PrimaryKey()
        name: str = Unique()
        company: str
        is_sensitive: bool = False
        positions: List[Optional['Positions']] = []
    
    class Positions(DataBaseModel):
        position_id: str = PrimaryKey()
        name: str = Unique()
        department: Department = None
        employees: List[Optional['Employee']] = []
    
    class EmployeeInfo(DataBaseModel):
        ssn: str = PrimaryKey()
        bio_id: str = Unique(default=uuid_str)
        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
    
    opened by codemation 0
Releases(0.0.27)
  • 0.0.27(Dec 13, 2022)

  • 0.0.26(Dec 13, 2022)

    What's Changed

    • Alembic migrations and mod many methods by @codemation in https://github.com/codemation/pydbantic/pull/37

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.25...0.0.26

    Source code(tar.gz)
    Source code(zip)
  • 0.0.25(Sep 8, 2022)

    What's Changed

    • sync pydnatic version in setup.py with requirements.txt (1.9.1) by @eudoxos in https://github.com/codemation/pydbantic/pull/36

    New Contributors

    • @eudoxos made their first contribution in https://github.com/codemation/pydbantic/pull/36

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.24...0.0.25

    Source code(tar.gz)
    Source code(zip)
  • 0.0.24(Aug 17, 2022)

    What's Changed

    • updated pydantic / databases pinned versions by @codemation in https://github.com/codemation/pydbantic/pull/35

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.23...0.0.24

    Source code(tar.gz)
    Source code(zip)
  • 0.0.23(Aug 16, 2022)

    What's Changed

    • Add unique model constraint by @codemation in https://github.com/codemation/pydbantic/pull/34

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.22...0.0.23

    Source code(tar.gz)
    Source code(zip)
  • 0.0.22(Jan 31, 2022)

  • 0.0.21(Jan 25, 2022)

    What's Changed

    • Migration Enhancements by @codemation in https://github.com/codemation/pydbantic/pull/30

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.20...0.0.21

    Source code(tar.gz)
    Source code(zip)
  • 0.0.20(Jan 24, 2022)

    What's Changed

    • added missing import statments for example by @engineerjoe440 in https://github.com/codemation/pydbantic/pull/28
    • Improved pickle loads error handling by @codemation in https://github.com/codemation/pydbantic/pull/29

    New Contributors

    • @engineerjoe440 made their first contribution in https://github.com/codemation/pydbantic/pull/28

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.19...0.0.20

    Source code(tar.gz)
    Source code(zip)
  • 0.0.19(Jan 13, 2022)

    What's Changed

    • Dynamic model relationships by @codemation in https://github.com/codemation/pydbantic/pull/25

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.18...0.0.19

    Source code(tar.gz)
    Source code(zip)
  • 0.0.18(Dec 6, 2021)

    What's Changed

    • Improved model filtering conditions by @codemation in https://github.com/codemation/pydbantic/pull/22

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.17...0.0.18

    Source code(tar.gz)
    Source code(zip)
  • 0.0.17(Dec 4, 2021)

    What's Changed

    • Feature - DataBaseModel Count and filter count by @codemation in https://github.com/codemation/pydbantic/pull/21

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.16...0.0.17

    Source code(tar.gz)
    Source code(zip)
  • 0.0.16(Dec 2, 2021)

    What's Changed

    • Object Filtering - order_by by @codemation in https://github.com/codemation/pydbantic/pull/20

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.15...0.0.16

    Source code(tar.gz)
    Source code(zip)
  • 0.0.15(Dec 2, 2021)

    What's Changed

    • Filter operators and pagination by @codemation in https://github.com/codemation/pydbantic/pull/17

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.14...0.0.15

    Source code(tar.gz)
    Source code(zip)
  • 0.0.14(Nov 23, 2021)

    What's Changed

    • Fix/Enhancement - Improved db connection context manager by @codemation in https://github.com/codemation/pydbantic/pull/15

    Full Changelog: https://github.com/codemation/pydbantic/compare/0.0.13...0.0.14

    Source code(tar.gz)
    Source code(zip)
  • 0.0.13(Nov 22, 2021)

    Whats changed?

    Multi App same DB support & Improvement Migrations - replaced complete TableMeta lookups during compare_tables_and_migrate with tables in self.tables, thus allowing the same database to be used accross multiple applications that do not share model definitions.

    Source code(tar.gz)
    Source code(zip)
  • 0.0.12(Nov 17, 2021)

  • 0.0.11(Nov 17, 2021)

    • Improved support for foreign model updates, .save() will invoke creation of foreign objects if not existing,
    • Added support for Arrays of Foreign References where a single field in a DataBaseModel may contain a list of other DataBaseModel objects
    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):
        time: str = PrimaryKey(default=time_now)
        latitude: float
        longitude: float
    
    class Journey(DataBaseModel):
        trip_id: str = PrimaryKey(default=get_uuid4)
        waypoints: List[Optional[Coordinate]]
    
    
    Source code(tar.gz)
    Source code(zip)
Owner
Joshua Jamison
Joshua Jamison
Prisma Client Python is an auto-generated and fully type-safe database client

Prisma Client Python is an unofficial implementation of Prisma which is a next-generation ORM that comes bundled with tools, such as Prisma Migrate, which make working with databases as easy as possi

Robert Craigie 930 Jan 08, 2023
Pony Object Relational Mapper

Downloads Pony Object-Relational Mapper Pony is an advanced object-relational mapper. The most interesting feature of Pony is its ability to write que

3.1k Jan 01, 2023
Solrorm : A sort-of solr ORM for python

solrorm : A sort-of solr ORM for python solrpy - deprecated solrorm - currently in dev Usage Cores The first step to interact with solr using solrorm

Aj 1 Nov 21, 2021
A Python Object-Document-Mapper for working with MongoDB

MongoEngine Info: MongoEngine is an ORM-like layer on top of PyMongo. Repository: https://github.com/MongoEngine/mongoengine Author: Harry Marr (http:

MongoEngine 3.9k Dec 30, 2022
SQLAlchemy support for aiohttp.

aiohttp-sqlalchemy SQLAlchemy 1.4 / 2.0 support for AIOHTTP. The library provides the next features: initializing asynchronous sessions through a midd

Ruslan Ilyasovich Gilfanov 5 Dec 11, 2022
MongoEngine flask extension with WTF model forms support

Flask-MongoEngine Info: MongoEngine for Flask web applications. Repository: https://github.com/MongoEngine/flask-mongoengine About Flask-MongoEngine i

MongoEngine 815 Jan 03, 2023
Object mapper for Amazon's DynamoDB

Flywheel Build: Documentation: http://flywheel.readthedocs.org/ Downloads: http://pypi.python.org/pypi/flywheel Source: https://github.com/stevearc/fl

Steven Arcangeli 128 Dec 31, 2022
ORM for Python for PostgreSQL.

New generation (or genius) ORM for Python for PostgreSQL. Fully-typed for any query with Pydantic and auto-model generation, compatible with any sync or async driver

Yan Kurbatov 3 Apr 13, 2022
A very simple CRUD class for SQLModel! ✨

Base SQLModel A very simple CRUD class for SQLModel! ✨ Inspired on: Full Stack FastAPI and PostgreSQL - Base Project Generator FastAPI Microservices I

Marcelo Trylesinski 40 Dec 14, 2022
Python 3.6+ Asyncio PostgreSQL query builder and model

windyquery - A non-blocking Python PostgreSQL query builder Windyquery is a non-blocking PostgreSQL query builder with Asyncio. Installation $ pip ins

67 Sep 01, 2022
Sqlalchemy seeder that supports nested relationships.

sqlalchemyseed Sqlalchemy seeder that supports nested relationships. Supported file types json yaml csv Installation Default installation pip install

Jedy Matt Tabasco 10 Aug 13, 2022
A pure Python Database Abstraction Layer

pyDAL pyDAL is a pure Python Database Abstraction Layer. It dynamically generates the SQL/noSQL in realtime using the specified dialect for the databa

440 Nov 13, 2022
Pydantic model support for Django ORM

Pydantic model support for Django ORM

Jordan Eremieff 318 Jan 03, 2023
The ormar package is an async mini ORM for Python, with support for Postgres, MySQL, and SQLite.

python async mini orm with fastapi in mind and pydantic validation

1.2k Jan 05, 2023
A dataclasses-based ORM framework

dcorm A dataclasses-based ORM framework. [WIP] - Work in progress This framework is currently under development. A first release will be announced in

HOMEINFO - Digitale Informationssysteme GmbH 1 Dec 24, 2021
Twisted wrapper for asynchronous PostgreSQL connections

This is txpostgres is a library for accessing a PostgreSQL database from the Twisted framework. It builds upon asynchronous features of the Psycopg da

Jan Urbański 104 Apr 22, 2022
Global base classes for Pyramid SQLAlchemy applications.

pyramid_basemodel pyramid_basemodel is a thin, low level package that provides an SQLAlchemy declarative Base and a thread local scoped Session that c

Grzegorz Śliwiński 15 Jan 03, 2023
Python helpers for using SQLAlchemy with Tornado.

tornado-sqlalchemy Python helpers for using SQLAlchemy with Tornado. Installation $ pip install tornado-sqlalchemy In case you prefer installing from

Siddhant Goel 122 Aug 23, 2022
A new ORM for Python specially for PostgreSQL

A new ORM for Python specially for PostgreSQL. Fully-typed for any query with Pydantic and auto-model generation, compatible with any sync or async driver

Yan Kurbatov 3 Apr 13, 2022
The Orator ORM provides a simple yet beautiful ActiveRecord implementation.

Orator The Orator ORM provides a simple yet beautiful ActiveRecord implementation. It is inspired by the database part of the Laravel framework, but l

Sébastien Eustace 1.4k Jan 01, 2023