A lightweight ORM built on Pydantic for simple CRUD operations with minimal code
Project description
Ormantism
A tiny, simple ORM built on top of Pydantic.
When you need to perform simple CRUD operations with minimal code.
Offers support for PostgreSQL, MySQL, SQLite (database URL syntax is the same as in SQLAlchemy).
Features
- Simple Model Declaration: Define your models using familiar Pydantic syntax
- Automatic Table Creation: Tables are created automatically when first accessed
- Lazy Loading: Relationships are loaded on-demand for optimal performance
- Transaction Support: Built-in transaction management with automatic rollback
- Preloading: Efficiently load related data with JOIN queries
- Optional Timestamps: Add created_at, updated_at, deleted_at fields automatically
- Load-or-create: Find by criteria or create in one call
- Versioning: Optional versioning along specified fields
Installation
pip install ormantism
Quick Start
1. Connect to Database
import ormantism
# Connect to a file database
ormantism.connect("sqlite:///my_app.db")
# Or use in-memory database for testing
ormantism.connect("sqlite://:memory:")
# MySQL
ormantism.connect("mysql://login:password@host:port/database")
# PostgreSQL
ormantism.connect("postgresql://login:password@host:port/database")
2. Define Models
from ormantism import Table
from typing import Optional
class User(Table):
name: str
email: str
age: Optional[int] = None
class Post(Table, with_timestamps=True):
title: str
content: str
author: User
3. Create and Save Records
# Create a user
user = User(name="Alice", email="alice@example.com", age=30)
# The record is automatically saved to the database
# Create a post linked to the user
post = Post(title="My First Post", content="Hello World!", author=user)
4. Query Records
# Load by ID
user = User.load(id=1)
# Load by criteria
user = User.load(name="Alice")
user = User.load(email="alice@example.com")
# Load latest post by this author
latest_post = Post.load(author=user, last_created=True)
# Load all records
users = User.load_all()
# Load with criteria
users_named_alice = User.load_all(name="Alice")
5. Update Records
user = User.load(id=1)
user.age = 31 # Automatically saved to database
# or
user.update(age=31, email="alice.updated@example.com")
6. Delete Records
user = User.load(id=1)
user.delete()
Advanced Features
Load or create
Find a row by criteria or create it in one call. Use _search_fields to limit which fields are used for the lookup; other fields are then used to update the row if it already exists, or to populate a new row if not.
# Create or reuse by name; update value if name already exists
user = User.load_or_create(_search_fields=("name",), name="Alice", email="alice@example.com")
user2 = User.load_or_create(_search_fields=("name",), name="Alice", email="new@example.com") # same row, email updated
Timestamps
Add automatic timestamp tracking to your models:
class Post(Table, with_timestamps=True):
title: str
content: str
This adds created_at, updated_at, and deleted_at fields. Soft deletes are used when timestamps are enabled.
Versioning
Use versioning_along=(...) so that updates that change those fields create a new row instead of updating in place (useful for history).
class Document(Table, versioning_along=("name",)):
name: str
content: str
d1 = Document(name="foo", content="v1")
d2 = Document(name="foo", content="v2") # New row; same name, different content
Relationships and Lazy Loading
class Author(Table):
name: str
class Book(Table):
title: str
author: Author
# Create records
author = Author(name="Jane Doe")
book = Book(title="My Book", author=author)
# Lazy loading - author is loaded from DB when accessed
book = Book.load(id=1)
print(book.author.name) # Database query happens here
Preloading (Eager Loading)
Avoid N+1 queries by preloading relationships:
# Load book with author in a single query
book = Book.load(id=1, preload="author")
print(book.author.name) # No additional database query
# Preload nested relationships
book = Book.load(id=1, preload="author.publisher")
# Preload multiple relationships
book = Book.load(id=1, preload=["author", "category"])
Transactions
from ormantism import transaction
try:
with transaction() as t:
user1 = User(name="Alice", email="alice@example.com")
user2 = User(name="Bob", email="bob@example.com")
# Both users are saved automatically
# Transaction commits when exiting the context
except Exception:
# Transaction is automatically rolled back on any exception
pass
Querying Examples
# Load single record
user = User.load(name="Alice")
latest_user = User.load(last_created=True)
# Load multiple records
all_users = User.load_all()
users_named_alice = User.load_all(name="Alice")
# Include soft-deleted records (when using timestamps)
all_including_deleted = User.load_all(with_deleted=True)
Model Definition
Basic Model
class User(Table):
name: str
email: str
age: int = 25 # Default value
bio: Optional[str] = None # Nullable field
With Timestamps
class Post(Table, with_timestamps=True):
title: str
content: str
# Automatically adds: created_at, updated_at, deleted_at
Supported Field Types
int,float,strOptional[T]for nullable fieldslist,dict(stored as JSON)ormantism.JSON— arbitrary JSON (dict, list, primitives); stored as a JSON columndatetime.datetimeenum.Enum- Pydantic models (stored as JSON)
- References to other Table models (single or
list[...]); optional and self-referential refs supported
Relationships
class Category(Table):
name: str
class Post(Table):
title: str
category: Category # Foreign key relationship
tags: Optional[Category] = None # Nullable relationship
Self-referential and list relationships
from typing import Optional
from pydantic import Field
class Node(Table):
parent: Optional["Node"] = None # Self-reference
name: str
class Parent(Table):
name: str
children: list["Parent"] = Field(default_factory=list) # List of references
JSON and generic reference fields
from ormantism import Table, JSON
class WithJSON(Table):
j: JSON # Arbitrary JSON (dict, list, primitives); stored as JSON column
# Generic reference (any Table subclass); cannot be preloaded
class Ptr(Table):
ref: Table
API Reference
Table class methods
Creating and loading
Model(**data)- Create and automatically save a new recordModel.load_or_create(_search_fields=("name",), **data)- Load one matching the given fields, or create; only_search_fieldsare used for the lookup; other fields can update the row if it already existsModel.load(**criteria)- Load single recordModel.load(last_created=True)- Load most recently created recordModel.load(as_collection=True, **criteria)- Load as list (no LIMIT 1)Model.load_all(**criteria)- Load multiple recordsModel.load(preload="relationship")orpreload=["a", "b"]- Eager load relationships (not supported for genericTablereferences)Model.load(with_deleted=True)- Include soft-deleted records
Updating
instance.update(**kwargs)- Update multiple fieldsinstance.field = value- Update single field (auto-saves)
Deleting
instance.delete()- Delete record (soft delete if timestamps enabled)
Database and transaction
ormantism.connect(database_url)- Connect to databaseormantism.transaction(connection_name=...)- Get transaction context manager (optionalconnection_namefor multi-connection setups)
Table class options
Table(..., with_timestamps=True)- Add created_at, updated_at, deleted_at and soft deletesTable(..., with_created_at_timestamp=True, with_timestamps=False)- Only created_atTable(..., with_updated_at_timestamp=True, with_timestamps=False)- Only updated_atTable(..., versioning_along=("name",))- Version rows by these fields (creates new row on update when these change)Table(..., connection_name="custom_conn")- Use a named connection (inherited by subclasses)
Code reference
For a full code reference of the library (all classes and methods with definition and usage locations), see ormantism/REFERENCE.md. It lists every public class (e.g. Table, Field, JoinInfo, Transaction, SuperModel) and method with their source file and line numbers and where they are used—useful when navigating the codebase or contributing.
Limitations
- Simple Queries: Complex queries may require raw SQL
- No Migrations: New columns are added automatically when the model gains fields; dropping or renaming columns, or changing column types, requires manual handling
- Basic Relationships: Only supports simple foreign key relationships
Requirements
- Python 3.12+
- Pydantic
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ormantism-0.6.0.tar.gz.
File metadata
- Download URL: ormantism-0.6.0.tar.gz
- Upload date:
- Size: 44.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9ae5915ff4bef003e63bc8d7c3c29d5ea374efcbe26ab86c36bf0dd32315301
|
|
| MD5 |
3f7875e017c4f52c85a94b5482de045f
|
|
| BLAKE2b-256 |
35fbb7821eaff5d584786b77a6af8d1bc283d7aaff5996521b841e4a72734b54
|
File details
Details for the file ormantism-0.6.0-py3-none-any.whl.
File metadata
- Download URL: ormantism-0.6.0-py3-none-any.whl
- Upload date:
- Size: 29.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2d2e383e7dbea63ce854b66c43901581fec601d8ba0b3842850e787a85f51085
|
|
| MD5 |
2ba67647a468c7c3405309a7fc8dddf6
|
|
| BLAKE2b-256 |
5ece097c4ad4232e9bdce3f07e2c5fa2ccf0d3402d734d4c0ee86b43487ad017
|