mongotic
The concept of MongoDB, SQLAlchemy, and Pydantic combined together in one simple and effective solution. It enables you to use SQLAlchemy v2 query syntax with MongoDB, and lets you define data models with Pydantic.
Documentation: https://allen2c.github.io/mongotic/
v0.6.0 — Breaking change ⚠️
mongotic v0.6.0 introduces a new field declaration style with full IDE and
pyright support for query operators (.in_, .like, .between, .is_,
.contains, etc.).
# v0.5 and earlier — still works at runtime, emits DeprecationWarning
name: str = Field(...)
# v0.6 and later — recommended; static-type-checks every operator
name: Mapped[str] = mapped_field()
- Legacy
Field()declarations continue to work in v0.6 with aDeprecationWarningat class creation. - The compatibility shim will be removed in v0.7.0.
- See the migration guide for the full substitution table.
Overview
mongotic is designed to make working with MongoDB feel familiar by reusing
patterns from the SQLAlchemy and Pydantic ecosystems. It gives you a consistent
and expressive way to interact with MongoDB collections, and uses Pydantic for
validation and schema definition.
Features
- SQLAlchemy v2 API —
select(),session.scalars(),ScalarResult; familiar patterns without a SQL database. - Typed query expressions —
Mapped[T]descriptor makesUser.name == "x",User.age.between(18, 65),User.name.in_([...]), and friends fully IDE-aware. - Rich query operators — logical combinators (
or_,and_,not_), null checks, string matching, range, and distinct. - Session management —
refresh(),merge(),expunge(),expire(), state inspection (.new,.dirty,.deleted). - Declarative indexes — define
__indexes__on the model and apply withcreate_indexes(); per-fieldindex=/unique=/sparse=shorthand. - Bulk operations —
insert(),update(), anddelete()statement builders viasession.execute(), returning aResultwith.rowcountand.inserted_ids. - Column projection —
select(User.name, User.email)returns lightweightRowresults; single-column projection unwraps to plain values viasession.scalars(). - Full async API —
mongotic.asynciomirrors the sync session on top ofpymongo.AsyncMongoClient. - Pydantic validation — schema definitions, JSON schema generation, and
every Pydantic field constraint (
min_length,ge/le,pattern, etc.) flow throughmapped_field(). - Type checking — IDE autocomplete and pyright
basicmode with zero warnings on idiomatic mongotic code. - Works on standalone MongoDB — no replica set required and no multi-document transaction dependency.
Installation
pip install mongotic
Usage
from typing import Optional
from mongotic import (
Mapped,
MongoBaseModel,
MultipleResultsFound,
NotFound,
create_engine,
delete,
mapped_field,
select,
update,
)
from mongotic.orm import sessionmaker
class User(MongoBaseModel):
__databasename__ = "test_database"
__tablename__ = "user"
name: Mapped[str] = mapped_field(max_length=50)
email: Mapped[str] = mapped_field()
company: Mapped[Optional[str]] = mapped_field(default=None, max_length=50)
age: Mapped[Optional[int]] = mapped_field(default=None, ge=0, le=200)
engine = create_engine("mongodb://localhost:27017")
Session = sessionmaker(bind=engine)
# ── Add ──────────────────────────────────────────────────────────────────────
session = Session()
session.add(User(name="Allen Chou", email="allen@example.com", company="Acme", age=30))
session.add_all([
User(name="Bob", email="bob@example.com", company="Acme", age=25),
User(name="Carol", email="carol@example.com", company="Acme", age=28),
])
session.commit()
# ── Query ────────────────────────────────────────────────────────────────────
session = Session()
# Fetch all / first
users = session.scalars(select(User)).all()
users = session.scalars(select(User).where(User.age > 18)).all()
users = session.scalars(
select(User)
.where(User.company == "Acme")
.order_by(-User.age) # descending; use User.age for ascending
.limit(10)
.offset(0)
).all()
user = session.scalars(select(User).where(User.email == "allen@example.com")).first()
user = session.get(User, "<object_id_string>") # PK lookup; returns None if not found
# Rich operators (all fully IDE-typed)
guests = session.scalars(select(User).where(User.company.in_(["Acme", "Acme Corp"]))).all()
matches = session.scalars(select(User).where(User.email.like("%@example.com"))).all()
ranged = session.scalars(select(User).where(User.age.between(18, 65))).all()
# Strict single-result fetch
try:
user = session.scalars(select(User).where(User.email == "allen@example.com")).one()
# raises NotFound if 0 results; raises MultipleResultsFound if 2+ results
except NotFound:
...
except MultipleResultsFound:
...
user = session.scalars(
select(User).where(User.email == "allen@example.com")
).one_or_none()
# returns None if 0 results; raises MultipleResultsFound if 2+ results
# Count and existence check
count = session.scalars(select(User).where(User.company == "Acme")).count()
exists = session.scalars(select(User).where(User.company == "Acme")).exists()
# ── Update ───────────────────────────────────────────────────────────────────
session = Session()
user = session.scalars(select(User).where(User.email == "allen@example.com")).first()
user.email = "new.allen@example.com" # tracked automatically
session.commit()
# ── Delete ───────────────────────────────────────────────────────────────────
session = Session()
user = session.scalars(select(User).where(User.email == "new.allen@example.com")).first()
session.delete(user)
session.commit()
# ── Bulk Operations ──────────────────────────────────────────────────────────
session = Session()
# Bulk update: returns Result with .rowcount
modified = session.execute(
update(User).where(User.company == "Acme").values(company="Acme Corp")
)
# Bulk delete: returns Result with .rowcount
deleted = session.execute(
delete(User).where(User.age < 18)
)
# ── Context manager + flush ──────────────────────────────────────────────────
with Session() as session:
new_user = User(name="Dave", email="dave@example.com", age=35)
session.add(new_user)
session.flush() # writes immediately; new_user._id is now available
print(new_user._id)
session.commit() # alias for flush()
Async usage
mongotic.asyncio mirrors the sync API on pymongo.AsyncMongoClient. See the
async documentation for the full
reference.
import asyncio
from mongotic import insert, select, update, delete
from mongotic.asyncio import create_async_engine, async_sessionmaker
async_engine = create_async_engine("mongodb://localhost:27017")
AsyncSession = async_sessionmaker(bind=async_engine)
async def main():
async with AsyncSession() as session:
# Bulk insert
r = await session.execute(
insert(User).values([
{"name": "Alice", "email": "alice@example.com", "age": 30},
])
)
print(r.inserted_ids) # ["<ObjectId>"]
# Query
adults = await session.scalars(select(User).where(User.age >= 18)).all()
# Column projection — returns Row objects
names = await session.scalars(select(User.name)).all()
# Scalar shortcut
age = await session.scalar(select(User.age).where(User.name == "Alice"))
# Bulk update / delete
await session.execute(update(User).where(User.company == "Acme").values(company="Acme Corp"))
await session.execute(delete(User).where(User.age < 18))
asyncio.run(main())
Contributing
Pull requests and issues are welcome. Please run make fmt && make test before
opening a PR.
License
This project is licensed under the MIT License — see the LICENSE file for details.
Support
If you encounter any problems or have suggestions, please open an issue or feel free to reach out directly.
Metadata
Release files for mongotic 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mongotic-0.6.0.tar.gz | 22.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mongotic-0.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.6 kB
Release files / mongotic-0.6.0.tar.gz
| Download URL | mongotic-0.6.0.tar.gz |
|---|---|
| Size | 22.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6fa1ab7392d67a86810a21db14893149ae542d38ab1cc1d32161f539080b9af2
|
|
BLAKE2b-256 checksum How to use checksums |
46ff44e902a778a51adfe43182af2a76d20e3916065ba4edb85c4af486ed62b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.3.2 CPython/3.11.14 Darwin/25.3.0
|
Release files / mongotic-0.6.0-py3-none-any.whl
| Download URL | mongotic-0.6.0-py3-none-any.whl |
|---|---|
| Size | 23.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1536a280a27e372989a4e833a14554ed16b77d12b332f6dc005d770fd3b24fb0
|
|
BLAKE2b-256 checksum How to use checksums |
eaab32ff3809abe62db66ec9ff7ceac6e47e9644da22fdd28c544eea4922379d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
poetry/2.3.2 CPython/3.11.14 Darwin/25.3.0
|