Catalog PyPI
Back to packageRELEASE DETAILS

1.0.0

PyPI

aggify

Async-first MongoDB ODM with a Django-style API: QuerySets, aggregation pipelines, FastAPI/gRPC/Django integrations.

aggregationasyncdjangofastapimongodbodmormpymongo
Published17 Oct 2026
Direct dependencies6
Transitive dependenciesNot calculated

README

Version 1.0.0 documentation

Aggify

Async-first MongoDB ODM with a Django-style API - for Django, FastAPI, gRPC and plain Python

Package version Downloads Supported Python versions Coverage License Telegram

Aggify 1.0 is a complete rewrite: no MongoEngine, native async/await on pymongo's AsyncMongoClient, an API you already know from the Django ORM, and aggregation pipelines that fit on one line.

import aggify
from aggify import Document, StringField, DecimalField, ReferenceField, Sum, Count

aggify.connect("mongodb://localhost:27017/shop")

class Customer(Document):
    name = StringField(required=True)
    country = StringField(max_length=2)

class Order(Document):
    customer = ReferenceField(Customer)
    status = StringField(choices=["new", "paid"], default="new")
    total = DecimalField()

best = await (
    Order.objects.filter(status="paid", customer__country="DE")      # $lookup + $unwind + $match
    .values("customer__name")
    .annotate(revenue=Sum("total"), orders=Count())                  # $group
    .filter(orders__gte=3)                                           # HAVING
    .order_by("-revenue")[:10]                                       # $sort + $limit
    .alist()
)

Highlights

  • Sync and async - every I/O method has an a-twin (aget, alist, asave, acount, aupdate, ...); one async client per event loop, so it is safe in FastAPI, grpc.aio, Django's async_to_sync and pytest-asyncio.
  • Django-style - Model.objects, Q, F, annotate, aggregate, values, select_related, get_or_create, signals, managers, DoesNotExist, pagination-ready querysets.
  • Complex queries, short - automatic joins (owner__country="DE"), values().annotate() grouping, conditional aggregates (Sum("x", filter=Q(...))), window functions, $facet, $bucket, $graphLookup, sub-pipeline $lookup, array operators, atomic expression updates. See the cookbook.
  • Framework-agnostic integrations - pip install "aggify[fastapi|grpc|django|pydantic]". Pydantic schemas from documents, FastAPI lifespan/errors/pagination, gRPC status mapping, Django app/forms/shortcuts/middleware, and an ObjectIdField to link Django ORM models to Mongo documents.
  • Secure by default - typed fields stop operator injection ({"$ne": ""}), regex lookups are escaped, server-side JavaScript is blocked, field names validated, strict constructors, resource limits, masked credentials.
  • Efficient - save() sends only changed fields, optional optimistic locking, only()/defer()/values(), streaming iterators, bulk operations.
  • Well tested - 470+ tests: unit, integration against a real replica set, differential property tests (random Q trees vs. a Python reference), FastAPI/gRPC/Django end-to-end tests, Python 3.9-3.13.

Install

pip install aggify                       # core (pymongo >= 4.13)
pip install "aggify[fastapi]"            # + pydantic / fastapi helpers
pip install "aggify[django]" "aggify[grpc]" "aggify[all]"

A tour

# models: typed fields, embedded documents, references, indexes
class Account(Document):
    username = StringField(required=True, unique=True)
    tags = ListField(StringField())
    address = EmbeddedField(Address)
    created_at = DateTimeField(auto_now_add=True)
    class Meta:
        ordering = ["username"]
        indexes = [Index("tags")]

# CRUD - only changed fields are written
acc = await Account.objects.acreate(username="ali", tags=["vip"])
acc.address = Address(city="Tehran")
await acc.asave()                               # {"$set": {"address": {...}}}

# querying
Account.objects.filter(Q(tags="vip") | Q(address__city__istartswith="te"), ~Q(banned=True))
Account.objects.filter(age__gt=F("score") * 10).only("username")[:20]

# atomic updates, including server-side expressions
Account.objects.filter(pk=acc.pk).update(inc__logins=1, push__tags="beta")
Product.objects.update(price=Round(F("price") * Value(1.1), 2))

# transactions (replica set)
async with aggify.atransaction():
    await Order.objects.acreate(...)
    await Product.objects.filter(pk=pid).aupdate(inc__stock=-1)

# the compiled query is always inspectable
Account.objects.filter(address__city="Tehran").query

Need a stage a queryset does not have? The immutable pipeline builder is one call away:

from aggify import Aggify
Aggify(Order).filter(status="paid").facet(
    by_country=Aggify(Order).group("customer__country", n=Count()),
    page=[{"$limit": 20}],
).execute()

Documentation

Full documentation lives in docs/ (build with make docs): quickstart - models - querying - pipelines - cookbook - security - testing - Django - FastAPI - gRPC - MongoEngine migration - upgrading from 0.x - فارسی.

Development

make install && make mongo && make test      # needs Docker for the throw-away MongoDB replica set

See CONTRIBUTING.md. Security reports: SECURITY.md.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Declared dependencies 12

Native requirements
6 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
pymongo
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
pymongo<5,>=4.13runtime
django
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
django>=4.2; extra == "all"runtime
fastapi
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
fastapi>=0.110; extra == "all"runtime
grpcio
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
grpcio>=1.60; extra == "all"runtime
protobuf
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
protobuf>=4.25; extra == "all"runtime
pydantic
Environment
{
  "extras": "",
  "marker": "extra == \"all\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
pydantic>=2.5; extra == "all"runtime
django
Environment
{
  "extras": "",
  "marker": "extra == \"django\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
django>=4.2; extra == "django"runtime
fastapi
Environment
{
  "extras": "",
  "marker": "extra == \"fastapi\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
fastapi>=0.110; extra == "fastapi"runtime
pydantic
Environment
{
  "extras": "",
  "marker": "extra == \"fastapi\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
pydantic>=2.5; extra == "fastapi"runtime
grpcio
Environment
{
  "extras": "",
  "marker": "extra == \"grpc\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
grpcio>=1.60; extra == "grpc"runtime
protobuf
Environment
{
  "extras": "",
  "marker": "extra == \"grpc\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
protobuf>=4.25; extra == "grpc"runtime
pydantic
Environment
{
  "extras": "",
  "marker": "extra == \"pydantic\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
pydantic>=2.5; extra == "pydantic"runtime

Distribution files

2 collected
aggify-1.0.0-py3-none-any.whl

bdist_wheel · 81.69 KB · Python py3

Source file
aggify-1.0.0.tar.gz

sdist · 150.65 KB · Python source

Source file

Repository

PENDING
aggify/aggify

Shared repository data · GitHub

License detected in repository
Not collected

Metadata history

Meaningful changes
Current collected state Current

Observed since 21 Oct, 09:03 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package_version:539aab97da572d677f280cd29b0ca2f86d508e7dbc1fa436c4ee4d5dfac16ffc

Other collected versions

View all collected versions →