Metadata-Version: 2.4
Name: mongo-quaestor
Version: 0.1.0
Summary: Lightweight MongoDB migration module with named migrations and dependency resolution
Author-email: Kristof Csillag <kristof.csillag@deai-labs.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/deai-network/mongo-quaestor
Project-URL: Repository, https://github.com/deai-network/mongo-quaestor
Project-URL: Issues, https://github.com/deai-network/mongo-quaestor/issues
Keywords: mongodb,migration,motor,async,asyncio,schema
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Database
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Framework :: AsyncIO
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: motor>=3.3.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5.0; extra == "dev"
Dynamic: license-file

# quaestor

*In ancient Rome, a **quaestor** was the official in charge of the treasury — here, it manages your MongoDB schema and data, your real treasure.*

Lightweight MongoDB migration module with named migrations and dependency resolution.

## Install

```bash
pip install mongo-quaestor
```

## Quick Start

```python
import asyncio

from motor.motor_asyncio import AsyncIOMotorClient
from quaestor import MigrationRegistry

registry = MigrationRegistry()


@registry.register("create_users_index")
async def create_users_index(db):
    await db.users.create_index("email", unique=True)


@registry.register("add_status_field", depends_on=["create_users_index"])
async def add_status_field(db):
    await db.users.update_many(
        {"status": {"$exists": False}},
        {"$set": {"status": "active"}},
    )


async def main():
    client = AsyncIOMotorClient("mongodb://localhost:27017")
    db = client["myapp"]

    applied = await registry.run(db, "myapp")
    print(f"Applied migrations: {applied}")


asyncio.run(main())
```

## API Reference

| Symbol | Signature | Description |
|--------|-----------|-------------|
| `MigrationRegistry()` | `MigrationRegistry()` | Create a new registry. No arguments. |
| `.register()` | `register(name: str, depends_on: list[str] \| None = None) -> Callable` | Decorator. Registers an async migration function. |
| `.run()` | `async run(db: AsyncIOMotorDatabase, prefix: str) -> list[str]` | Execute all unapplied migrations. Returns names of applied migrations. |
| `.migrations` | `migrations -> dict[str, MigrationDefinition]` | Read-only property. All registered migrations. |

## How It Works

- Applied migrations are tracked in a MongoDB collection named `{prefix}_migrations`.
- Each record stores `name` (str) and `appliedAt` (UTC datetime).
- Dependencies are resolved via topological sort (Kahn's algorithm). Circular or missing dependencies raise `ValueError`.
- Failed migrations are **not** recorded, so they retry automatically on the next `run()` call.
- Independent migrations (no dependency relationship between them) execute in alphabetical order for deterministic behavior.
- Multiple registries can safely share one database by using different prefixes.
- Progress and errors are logged to the `quaestor` logger.
