ORM backendlari
Bitta adapter shartnomasi ortidagi SQLAlchemy 2.0 va Tortoise — va ular orasidagi farq bo‘lgan to‘rtta qator.
Mavjud v0.2.0
fastfort/orm/ dan yuqorida hech narsa SQLAlchemy yoki Tortoise’ni import qila
olmaydi — buzilsa, test sborkani muvaffaqiyatsiz qiladi. Introspeksiya modelni
ModelSpecga aylantiradi — oddiy, o‘zgarmas, JSON’ga serializatsiya
qilinadigan ma’lumot — va undan yuqoridagi har bir qatlam aynan shu bilan
ishlaydi.
Natija aniq bo‘ldi: ikkinchi adapter admin qismida bitta ham qator o‘zgartirmasdan qo‘shildi.
SQLAlchemy
uv add "fastfort[sqlalchemy,postgres]"
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase
from fastfort.orm.sqlalchemy import SQLAlchemyBackend
class Base(DeclarativeBase):
pass
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/shop")
# Not a preference. The admin's views read attributes off an object *after* the
# unit of work has committed — the label in a success message, the primary key
# to redirect to. With the default, each of those is a synchronous refresh
# against an async session, which raises MissingGreenlet.
session_factory = async_sessionmaker(engine, expire_on_commit=False)
fort = FastFort(
settings=settings,
backend=SQLAlchemyBackend(session_factory=session_factory, base=Base),
)
FastFort hech qachon o‘zi ulanish ochmaydi. U siz qurgan fabrikadan foydalanadi.
Tortoise
uv add "fastfort[tortoise,postgres]"
To‘rtta qator farq qiladi. Qolgan hammasi — sozlamalar, @admin.register,
list_display, amallar, eksport, import — bir xil, chunki qolgan hammasi ORM
qatlamidan yuqorida joylashgan va modelni umuman ko‘rmaydi.
from tortoise import Tortoise
from fastfort.orm.tortoise import TortoiseBackend
await Tortoise.init(
db_url="postgres://user:pass@localhost/shop",
modules={"models": ["app.models"]},
# Tortoise keeps its connections in a contextvar, and an ASGI server runs
# the lifespan in a different task from the requests. Without this flag the
# init above is invisible to every view: start-up looks healthy and the
# first page that touches the database is a 500. `RegisterTortoise` from
# `tortoise.contrib.fastapi` passes it for you.
_enable_global_fallback=True,
)
fort = FastFort(settings=settings, backend=TortoiseBackend())
fort.set_user_model(User)
fort.mount(app)
«Ikkinchisi xuddi shunday ishlaydi» — bu test
tests/orm/test_conformance.py ikkala backendga ham bir xil model shakllari
bo‘yicha bir xil savollarni beradi — bir xil ustunlar, bir xil bog‘lanishlar,
bir xil o‘chirishlar — va javoblarni solishtiradi. Yuqoridagi da’vo shunchaki
gap emas, aynan test bo‘lishining sababi shu.
fastfort/orm/coerce.py — ikkala adapter uchun umumiy bo‘lgan yagona narsa,
chunki so‘rov satrini ustun turiga aylantirish ustunga tegishli masala, u yoki
bu ORM’ga emas.
Ma’lumotlar bazalari
| SQLite | PostgreSQL | MySQL | |
|---|---|---|---|
| Asosiy CRUD, filtrlar, qidiruv | ✅ | ✅ | ✅ |
| JSON | ✅ | ✅ JSONB | ✅ |
Massivlar, hstore, diapazonlar, inet, citext | — | ✅ | — |
| PostGIS geometriyasi | — | ✅ | — |
| pgvector | — | ✅ | — |
uv add "fastfort[sqlalchemy,sqlite]"
uv add "fastfort[sqlalchemy,postgres]"
uv add "fastfort[sqlalchemy,mysql]"
Dvijoklar orasida farq qiladigan SQL bilan bog‘liq har narsa backendning dialekt modulida joylashgan — u aynan «uchalasida ham ishlaydi» degan da’voni umidga emas, testga asoslash uchun mavjud.
Adapter amal qiladigan ikkita qoida
Adapterlar hech qachon commit qilmaydi
Ular flush qiladi. UnitOfWork kontekst menejeri toza chiqishda commit
qiladi va istisno yuz berganda orqaga qaytaradi (rollback), shu sababli yarim
muvaffaqiyatsiz so‘rov ortida hech narsa qolmaydi — hatto qirqinchi qatorda
muvaffaqiyatsiz bo‘lgan ommaviy amal ham.
Rollback har bir atributni yaroqsiz qiladi
await uow.rollback() dan so‘ng instance.id ni o‘qish — greenlet
ko‘prigidan tashqarida asinxron sessiya uchun sinxron yangilash bo‘lib, bu
xatoga olib keladi.
Shuning uchun: hali kerak bo‘lgan har bir qiymatni — yorliqni, birlamchi kalitni — rollback’dan oldin o‘qing, yoki avval javobni render qiling, keyin rollback qiling. Bu adapterlar ustida turgan kodni buzishning eng keng tarqalgan usuli.
So‘rov oqimi
introspect(model) → ModelSpec once, cached on the backend
ModelAdmin(spec) once, validated when built
request → ListQuery.from_params(...) the validation boundary
→ backend.unit_of_work() one transaction per request
→ backend.adapter(model, uow) request-scoped, never cached
→ adapter.list/get/create/... flushes, never commits
→ Renderer.render(template) server-side HTML
Adapter har bir so‘rov doirasida yaratiladi va hech qachon keshlanmaydi;
spec esa keshlanadi — shuning uchun register_type biror narsa
introspeksiya qilinishidan oldin ishga tushishi kerak.
Backend qo‘shish
Protokollar fastfort/orm/base.pyda: Backend, ModelAdapter,
UnitOfWork. Shu uchtasini amalga oshiring — va moslik testlari to‘plami
hammasi to‘g‘ri qilinganini aytadi — fastfort/orm/ dan yuqorida esa hech
narsani o‘zgartirish shart emas, chunki bu chegara konventsiya emas, test
orqali ta’minlanadi.