FasORM
Laravel Eloquent-style ORM for FastAPI — expressive models, clean migrations, and an artisan-style CLI, powered by SQLAlchemy 2.0 & Alembic.
📖 Documentation: https://fasorm.sandeshsatyal.com.np
pip install fasorm
Quick Start
1. Initialise your project
fasorm init
Creates:
app/
models/
migrations/
seeders/
.env
fasorm.config.py
2. Create a model
fasorm make:model User -m
Edit app/models/user.py:
from fasorm import Model, String, Boolean
class User(Model):
name = String()
email = String().unique()
password = String()
is_active = Boolean().default(True)
3. Edit the migration
app/migrations/2026_07_25_143000_create_users_table.py:
from fasorm.schema import Blueprint
def up(table: Blueprint):
table.id()
table.string("name")
table.string("email").unique()
table.string("password")
table.boolean("is_active").default(True)
table.timestamps()
def down(table: Blueprint):
table.drop()
4. Run migrations
fasorm migrate
FasORM · Running migrations…
3 migration(s) pending…
✅ 2026_07_25_143000_create_users_table
✓ Migration successful. 1 migration(s) applied.
Models
Define a model
from fasorm import Model, String, Boolean, Timestamp, HasMany
class User(Model):
name = String()
email = String().unique()
is_active = Boolean().default(True)
posts = HasMany("Post")
Features:
- Auto-generated
id(BigInteger, autoincrement) - Auto-generated
created_atandupdated_attimestamps - Auto-generated
__tablename__from class name (User→users,BlogPost→blog_posts)
CRUD Operations
# Create
user = await User.create(name="Sandy", email="sandy@example.com")
# Find by ID
user = await User.find(1)
user = await User.find_or_fail(1) # raises ModelNotFoundError
# Query
users = await User.where(is_active=True).get()
user = await User.where(email="sandy@example.com").first()
users = await User.where("age", ">=", 18).order_by("name").get()
# All
users = await User.all()
# Paginate
page = await User.paginate(per_page=20, page=1)
# Update
await user.update(name="New Name")
# Delete
await user.delete()
# First or create
user = await User.first_or_create(
{"email": "sandy@example.com"},
defaults={"name": "Sandy"}
)
# Update or create
user = await User.update_or_create(
{"email": "sandy@example.com"},
defaults={"name": "Updated Sandy"}
)
Relationships
from fasorm import Model, String, HasMany, BelongsTo
class User(Model):
name = String()
posts = HasMany("Post")
class Post(Model):
title = String()
user = BelongsTo("User")
# Eager loading (loaded relations are automatically included in to_dict / to_json)
users = await User.with_("posts").get()
total = await User.with_("posts").where(is_active=True).count()
# Relationship count aggregation (adds `posts_count` attribute)
users = await User.with_count("posts").get()
print(users[0].posts_count) # → 3
users_dict = await User.with_count("posts").to_dict() # → [{"id": 1, "name": "Sandy", "posts_count": 3}]
# Aggregates & Model classmethods
count = await User.count()
exists = await User.where(email="sandy@example.com").exists()
total_age = await User.sum("age")
avg_age = await User.avg("age")
Serialisation
# Model instance serialisation (automatically includes eager-loaded relations)
user.to_dict() # → {"id": 1, "name": "Sandy", "posts": [...]}
user.to_json() # → '{"id": 1, "name": "Sandy", "posts": [...]}'
# Direct query builder serialisation
users_dict = await User.with_("posts").to_dict()
users_json = await User.with_("posts").to_json()
# Pagination result serialisation
page = await User.paginate(per_page=20, page=1)
page.to_dict() # → {"data": [...], "meta": {"total": 100, ...}}
Field Types
| Field | SQL Type | Notes |
|---|---|---|
String(length=255) |
VARCHAR(n) |
|
Char(length=255) |
CHAR(n) |
Fixed-length string |
Text() |
TEXT |
|
TinyText() |
TINYTEXT / TEXT |
|
MediumText() |
MEDIUMTEXT / TEXT |
|
LongText() |
LONGTEXT / TEXT |
|
Integer() |
INTEGER |
|
BigInteger() |
BIGINT |
|
SmallInteger() |
SMALLINT |
|
TinyInteger() |
TINYINT / SMALLINT |
|
MediumInteger() |
MEDIUMINT / INTEGER |
|
UnsignedInteger() |
UNSIGNED INT |
|
UnsignedBigInteger() |
UNSIGNED BIGINT |
|
UnsignedSmallInteger() |
UNSIGNED SMALLINT |
|
UnsignedTinyInteger() |
UNSIGNED TINYINT |
|
Float(precision) |
FLOAT |
|
Decimal(precision, scale) |
NUMERIC(p,s) |
|
Boolean() |
BOOLEAN |
|
Date() |
DATE |
|
Time() |
TIME |
|
TimeTz() |
TIME WITH TZ |
Timezone aware |
DateTime() |
DATETIME |
|
DateTimeTz() |
DATETIME WITH TZ |
Timezone aware |
Timestamp() |
TIMESTAMP WITH TZ |
Timezone aware |
TimestampTz() |
TIMESTAMP WITH TZ |
Timezone aware |
Year() |
YEAR / INTEGER |
|
Json() |
JSON / JSONB |
|
Jsonb() |
JSONB / JSON |
Native on PG |
Uuid() |
UUID / CHAR(36) |
Native on PG |
Ulid() |
CHAR(26) |
26-char sortable ID |
IpAddress() |
VARCHAR(45) |
IP Address |
MacAddress() |
VARCHAR(17) |
MAC Address |
Enum(values) |
ENUM |
|
Set(values) |
SET / ENUM |
Set of values |
Vector(dimension) |
VECTOR(n) / TEXT |
pgvector on PG |
Array(item_type) |
ARRAY / JSON |
Native on PG |
Binary(length) |
LARGEBINARY |
All fields support chaining: .nullable(), .unique(), .default(value), .index(), .comment(text)
CLI Commands
| Command | Description |
|---|---|
fasorm init |
Scaffold project structure |
fasorm make:model Name [-m] |
Create a model (optionally with migration) |
fasorm make:migration name |
Create a migration file |
fasorm make:seeder Name |
Create a seeder file |
fasorm migrate |
Run pending migrations |
fasorm rollback [--steps N] |
Rollback last N batches |
fasorm refresh |
Rollback all + re-migrate |
fasorm fresh [--seed] |
Drop all + migrate + seed |
fasorm seed [--class Name] |
Run seeders |
fasorm status |
Show migration status |
FastAPI Integration
from fastapi import FastAPI, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from fasorm import get_session, init_db
from fasorm.config import load_config
app = FastAPI()
@app.on_event("startup")
async def startup():
config = load_config()
init_db(config)
@app.get("/users")
async def list_users():
users = await User.all()
return [u.to_dict() for u in users]
@app.get("/users/{user_id}")
async def get_user(user_id: int):
user = await User.find_or_fail(user_id)
return user.to_dict()
Architecture
FastAPI
│
FasORM
│
─────────┼─────────
│ │
SQLAlchemy Alembic
│ │
PostgreSQL SQLite
MySQL MariaDB
FasORM is an opinionated layer on top of SQLAlchemy 2.0 and Alembic — not a replacement.
License
MIT
Metadata
Release files for fasorm 0.1.10
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fasorm-0.1.10.tar.gz | 42.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fasorm-0.1.10-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 87.7 kB
Release files / fasorm-0.1.10.tar.gz
| Download URL | fasorm-0.1.10.tar.gz |
|---|---|
| Size | 42.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
112117f5b6c984b7a4dd47766ca22e8145e1888a304cc3ec8d743725239d17ee
|
|
BLAKE2b-256 checksum How to use checksums |
30025054a3a206735a85d9f5ff394011fea659f52c3e1bbd4d75556aa2b25da7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|
Release files / fasorm-0.1.10-py3-none-any.whl
| Download URL | fasorm-0.1.10-py3-none-any.whl |
|---|---|
| Size | 44.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b3acc27961e70c6e741b2a83a6c93b4c34e1e98cdd47735493f3b11d647b40ca
|
|
BLAKE2b-256 checksum How to use checksums |
6bbca0cb04829b830b6b3bfa3ec5376bda78282177e47aa7a0ec38cb7c0843b3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.4
|