Metadata-Version: 2.4
Name: mangoframe
Version: 0.1.0
Summary: A thin convention-and-scaffolding layer over FastAPI + SQLAlchemy — collapses the model/repository/service/schema/router module shape into one declaration.
License-Expression: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
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 :: Software Development :: Libraries :: Application Frameworks
Requires-Python: >=3.11
Requires-Dist: fastapi>=0.110
Requires-Dist: pydantic>=2.0
Requires-Dist: sqlalchemy>=2.0
Provides-Extra: dev
Requires-Dist: aiosqlite>=0.20; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Description-Content-Type: text/markdown

# mango

A thin convention-and-scaffolding layer over FastAPI + SQLAlchemy. mango
does not reimplement either — `mango.Router` IS `fastapi.APIRouter`,
`mango.Schema` IS a `pydantic.BaseModel` subclass — but a project built
with mango never needs `import fastapi` or `import pydantic`: routing,
schemas, dependency injection, the app instance, DB sessions, and error
handling all have a `mango.` name. Beyond that single front door, mango
removes the repetitive plumbing every FastAPI project rewrites: the
`models.py` / `repository.py` / `service.py` / `schemas.py` / `router.py`
/ `__init__.py` quintet a vertical-slice module normally needs, the
hand-maintained `main.py` router-mounting list, generic CRUD
repositories, DB session setup, and domain-exception-to-HTTP mapping.

Distribution name on PyPI is `mango-api` (`pip install mango-api`) since
`mango`/`mango-framework` were already taken; the import name is always
`mango`.

**Full usage guide (for using mango in a new project): [docs/GUIDE.md](docs/GUIDE.md).**
**Project folder structure convention (`mango init`'s layout, and why): [docs/PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md).**

Currently wired into the `collabfluenz.backend` reference project
(router mounting via `MangoApp`, `BaseRepository` built on
`MangoRepository`) — see that project's `app/mango_registry.py` and
`app/main.py` for a real-world example beyond `examples/`.

## What's here

- `mango/web.py` — `Router`, `Depends`, `Query`, `Path`, `Body`,
  `Header`, `Cookie`, `Form`, `File`, `UploadFile`, `Request`,
  `Response`, `JSONResponse`, `status`, `HTTPException`: the exact
  FastAPI classes, re-exported under `mango.` so route files never
  `import fastapi`.
- `mango/schema.py` — `Schema`, a `pydantic.BaseModel` subclass with
  `from_attributes=True` on by default (the setting every response
  schema in a DB-backed app needs but reliably forgets), plus `Field`,
  `field_validator`, `model_validator` re-exports.
- `mango/module.py` — `@mango.module` decorator + `MangoModule` base class.
  Declare a module as one class; mango derives its public API from the
  class's attributes instead of a hand-written `__init__.py`.
- `mango/repository.py` — `MangoRepository[Model]`, a generic async
  repository with `get`/`add`/`update`/`delete`/`list`/`count`/`search`
  built in, plus `get_or_404`, `exists`, `add_many`, `delete_many`,
  `filter_by(**equals)`, and `options=` (SQLAlchemy loader options, for
  eager-loading relationships) on `get()`/`list()`.
- `mango/app.py` — `App` (owns its own FastAPI instance internally,
  directly ASGI-callable, the default entry point for a new project) and
  `MangoApp` (wraps an *existing* FastAPI app, for incremental adoption).
  Both mount every registered module's router in dependency order
  (topological sort on `depends_on`), raising a clear
  `circular module dependency: a -> b -> a` error instead of Python's
  `ImportError ... most likely due to a circular import` traceback.
  `App` also wires in production-hardening middleware by default
  (`security_headers=True`; opt-in `rate_limit=`/`cors_origins=`) and
  exposes `use(plugin)` + `on_startup`/`on_shutdown` hooks.
- `mango/security.py` — `SecurityHeadersMiddleware` (on by default) and
  `RateLimitMiddleware` (opt-in, in-memory sliding window — explicitly
  not a substitute for a real limiter in a horizontally-scaled
  deployment; see its docstring).
- `mango/plugins.py` — `Plugin` protocol (`install(app)`) +
  `App.use(plugin)`, the extension point for third-party/project-local
  code that doesn't fit inside a `MangoModule`. `RequestIDPlugin` is a
  built-in reference implementation.
- `mango/db.py` — `Database`, one-line async engine + session factory +
  a FastAPI `get_db` dependency (commit-on-success, rollback-on-error),
  replacing the ~15 lines every hand-rolled FastAPI project rewrites.
- `mango/exceptions.py` + `mango/errors.py` — `MangoError` and 5
  subclasses (`NotFoundError`, `ConflictError`, `ForbiddenError`,
  `UnauthorizedError`, `BadRequestError`), each with a default HTTP
  status; `register_error_handlers(app)` wires them into FastAPI and
  installs a catch-all handler so an unhandled bug returns a generic 500
  instead of leaking a traceback.
- `mango/crud.py` — `build_crud_router(...)` generates a full
  list/get/create/update/delete REST router from a `MangoRepository` +
  Pydantic schemas — the biggest single boilerplate cut for a module
  that's plain CRUD. `paginated=True` returns a `mango.Page` envelope
  instead of a bare list.
- `mango/pagination.py` — `Page[T]`, a generic paginated-response schema
  (`items`/`total`/`limit`/`offset`); `MangoRepository.list_page()` /
  `.search_page()` return the `(rows, total)` it's built from.
- `mango/auth.py` — `Auth`: pluggable token-verification + user-loading
  + `require_role`/`require`/`current_user` FastAPI dependency
  factories — the "verify JWT -> load user -> check role -> 401/403"
  pattern every real app hand-writes, minus the token/storage specifics
  (those stay project-specific callables you provide).
- `mango/tasks.py` + `Database.spawn()` — fire-and-forget background
  work with its own fresh DB session (never the request's, which closes
  before a background task finishes) and a logged, not silently
  swallowed, exception if it fails.
- `mango/migrations.py` — `init_migrations(...)` scaffolds a working,
  async-aware `alembic.ini` + `migrations/env.py` wired to a project's
  declarative Base, via `mango init-migrations module.path:Base`.
- `mango/project.py` — `init_project(...)` scaffolds a full project's
  folder structure (`app/main.py`, `app/registry.py`, `app/db.py`,
  `app/modules/`, `tests/`, project-root files) — see
  `docs/PROJECT_STRUCTURE.md` for the convention and why each piece
  exists.
- `mango/cli.py` — `mango init <project-name>` scaffolds a project;
  `mango new-module <name>` scaffolds a starter `module.py`;
  `mango init-migrations <module.path:Base>` scaffolds Alembic.
- `examples/hello_module/` — a complete, minimal module (one model, one
  repository, one endpoint) showing what mango collapses 5 files into.
- `tests/` — registry, topological ordering/cycle detection, repository
  behavior (core + extras), error-handler mapping, CRUD-router lifecycle
  (plain and paginated), auth guards, background tasks, migration
  scaffolding, project scaffolding, security middleware, and plugins —
  43 tests, all runnable standalone.
- `.github/workflows/ci.yml` — runs the full test suite on Python
  3.11/3.12/3.13 on every push/PR, then builds the sdist+wheel.
- `LICENSE` (MIT), `CHANGELOG.md`, `CONTRIBUTING.md`.

**Full usage guide, including every piece above with examples:
[docs/GUIDE.md](docs/GUIDE.md).**

## Quickstart

```bash
cd mango
pip install -e ".[dev]"
pytest -q
```

Run the example app:

```bash
uvicorn examples.main:app --reload
curl http://127.0.0.1:8000/api/v1/hello
```

## Starting a new project

```bash
mango init demo_shop
cd demo_shop
mango new-module items app/modules
# add `import app.modules.items.module` to app/registry.py
```

See [docs/PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md) for what
`mango init` scaffolds and why it's laid out that way.

## Declaring a module

```python
import mango

router = mango.Router()

@router.get("/hello")
async def hello():
    return {"message": "hello from mango"}

@mango.module
class HelloModule(mango.MangoModule):
    name = "hello"          # required, unique
    router = router          # optional — modules like `admin` may have none
    depends_on = ()           # names of other modules this one needs mounted first
```

```python
# main.py — no fastapi import
import mango
app = mango.App(title="My API")
app.mount_all()   # finds every @mango.module class, orders by depends_on, mounts it
```

`app` is directly ASGI-callable (`uvicorn main:app`) — equivalent to what
we hand-wrote in `app/main.py` for the 10-module `collabfluenz.backend`
reference project, but derived rather than maintained.

## Status

Module registry + generic repository (with eager-loading, batch ops,
and exact-match filtering) + pagination + mount ordering + DB setup +
background tasks + auth guards + error-to-HTTP mapping + generated CRUD
routers + Alembic scaffolding + project scaffolding + production
hardening (security headers, opt-in rate limiting/CORS) + a plugin
extension point + a full FastAPI/Pydantic re-export surface (`Router`,
`Schema`, `App`, ...) are all in place — a project can be built end to
end importing only `mango`. CI runs the full suite across three Python
versions on every push. What's deliberately still project-specific: how
you verify a token and load a user (`mango.Auth` takes both as callables
— token providers and user storage vary too much to bake in a default),
and how you use Alembic once it's scaffolded (mango sets up the config,
not the migration authorship).

**Honest gaps, unclosed:** no real-world production usage beyond one
integration into `collabfluenz.backend`'s mounting layer; `0.1.0`
throughout this session's work (see `CHANGELOG.md`); a single
contributor. Nothing here fakes maturity mango doesn't have — see
`CONTRIBUTING.md` for how that changes.

## The size goal

Every piece here exists to make a project's code shorter and more
uniform, not to add abstraction for its own sake — that's the actual
measure of whether an addition to mango is worth it. Concretely, per
module:

| What you'd hand-write | Lines, roughly | With mango |
|---|---|---|
| `__init__.py` re-exports | 15–30 | 0 — derived from the `@mango.module` class |
| `main.py` router mounting | 1–2 per module | 0 — `app.mount_all()` |
| Generic repository CRUD | 30–50 | 0 — inherited from `MangoRepository` |
| DB engine/session/`get_db()` | ~15 | 3 — `mango.Database(url)` |
| Plain-CRUD router (list/get/create/update/delete) | 60–90 | ~10 — `mango.build_crud_router(...)` |
| Domain-error -> HTTP status mapping | 5–10 per error site | 0 — `raise mango.NotFoundError(...)` |
| Auth guard (verify -> load -> role check) | 20–40 | ~10 — `auth.require_role(...)` |
| Background task + its own session | 10–15 | 1 — `db.spawn(fn)` |
| Alembic `env.py` | ~100 | 0 — `mango init-migrations` |
| Security headers middleware | ~15 | 0 — on by default in `App` |
| Rate limiting middleware | ~30 | 1 — `App(rate_limit=(100, 60))` |
| New project skeleton (`main.py`, `db.py`, `registry.py`, ...) | ~80 | 0 — `mango init <name>` |

A module with real, non-generic business logic still writes that logic
by hand — mango only removes the boilerplate *around* it, never the
part that's actually specific to your app.
