Shipped
Getting started
A Mangoo project follows its own conventional shape: mango.py, a settings.py, and a routes.py. SQLite needs no extra driver, so a first project runs with nothing beyond Python and pip.
powershell
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e .
mango newproject mysite
cd mysite
python mango.py newapp blog
python mango.py launch
Open http://127.0.0.1:8000/. During development, ./mango.cmd launch 8001 runs straight from a source checkout without installing, and watches files to restart the server automatically — press Ctrl+C once to stop it cleanly.
Shipped
Project & settings
Every generated project keeps one class Settings: with explicit, typed sections, instead of flat ALL_CAPS module globals.
| Setting | Purpose |
base_dir, secret_key, debug, allowed_hosts | Project identity and environment behavior |
installed_apps, middleware, root_urlconf | Application composition |
class database: | SQLite, PostgreSQL, MySQL, or MariaDB connection |
class server: | Development host and port |
class security: | force_https, secure_cookies, proxy trust, rate limits |
Other databases: add the driver with uv before starting the project — uv add "psycopg[binary]" for PostgreSQL, uv add pymysql for MySQL/MariaDB.
Shipped
Routing & views
Routes are plain Python callables. api_route declares a JSON endpoint and is picked up automatically by the built-in OpenAPI document.
config/routes.py
from mangoo import api_route, path
def health(request):
return {"status": "ok"}
urlpatterns = [
api_route("api/health", health),
]
GET /api/health → 200
{
"status": "ok"
}
Render escaped templates with render_template("page.html", context, directories=(...)), serve development assets with *static_routes(_settings.base_dir / "static"), and exercise a view without a socket using the bundled TestClient.
Shipped
Entities & queries
Mangoo's data layer uses its own declarative entities and query verbs rather than copying an off-the-shelf ORM API one-for-one.
blog/models.py
from mangoo import Decimal, Entity, Text
class Product(Entity):
name = Text(unique=True)
price = Decimal()
products = Product.collection(_settings.database)
products.append(name="Notebook", price=9.5)
items = (products.seek(price__above=5)
.arrange("-price")
.window(20)
.fetch())
collection, seek, arrange, window, fetch, only, revise, and erase cover the query workflow. For output, a schema-driven Serializer validates external payloads and EntitySerializer encodes entities with msgspec for consistent, fast JSON.
Shipped
Migrations
Migrations are small, ordered, auditable SQL files — one statement per operation, so a failed migration rolls back cleanly with the transaction that wraps it.
blog/migrations/0001_products.py
operations = [
"CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
]
python mango.py applymigrations applies pending migrations; migrationstatus inspects their state. Applied files are checksummed, so an altered or missing migration is rejected rather than silently reapplied.
Schema-diff detection: newmigration blog generates the reviewed initial table, then only additive, nullable columns automatically. A type, nullability, or uniqueness change on an already-migrated column now raises an error naming the column, instead of silently drifting from what's declared.
Shipped
Authentication & security
Local authentication uses salted, memory-hard scrypt hashes; passwords need 12+ characters across four character classes; five failed attempts lock an account for 15 minutes.
| Endpoint | Behavior |
POST /api/auth/login | Username, password, optional TOTP code → Bearer session token |
GET /api/auth/me | The current session's user |
POST /api/auth/logout | Revokes the session token |
python mango.py createadmin creates the first administrator; enable2fa <username> turns on authenticator-app two-factor authentication. Only hashes of session tokens are ever stored, and audit events for logins, sessions, and admin actions are persisted without credentials.
In progress
Production & deployment
Starting with Settings.debug = False requires a non-default secret_key, explicit allowed_hosts, and security section settings for HTTPS and secure cookies — Mangoo refuses to boot otherwise. Add *health_routes(...) for GET /healthz and GET /readyz.
Condensed from the project's security review checklist
| Control | Requirement |
| Sessions | HTTPS-only cookies in production; only session hashes are stored |
| CSRF | Matching token required for cookie-authenticated unsafe methods |
| CORS | Exact allowed origins configured; never reflects an arbitrary origin |
| Proxy trust | Forwarded headers honored only from a configured proxy network |
| Rate limits | Bounded, process-local; use an edge or Redis limiter across instances |
Migrations are forward-only: reverting means deploying the prior version and restoring a verified backup, or writing a reviewed compensating migration — never editing an applied file, since checksum validation will stop the deploy.
Shipped
REST, GraphQL & OpenAPI
Every application automatically exposes an OpenAPI 3 document at /openapi.json and an interactive Swagger UI at /docs, built from whatever routes were declared with api_route — "docs come free" ergonomics, without requiring async views to get them.
REST
api_route(
'api/items', view,
methods=('GET', 'POST')
)
GraphQL
schema = Schema(query={
'greeting': resolver,
})
path('graphql', graphql_view(schema),
methods=('POST',))
GraphQL today supports a single root query field per request — arguments, mutations, and nesting are on the roadmap, not yet implemented.
Shipped
Mangoo Pulse
An opt-in, in-process event bus for application lifecycle hooks. It never wires itself into the runtime — an application adds no handlers, latency, or failure modes unless it creates and uses a PulseBus.
any view or startup module
from mangoo.addons.pulse import PulseBus
bus = PulseBus()
bus.connect("auth.login_succeeded", on_login)
bus.publish("auth.login_succeeded", actor_id=user.id)
There is no sync-versus-async choice to make: publish/emit always hand delivery to a small persistent worker pool and return immediately — a Rust-native thread pool (mangoo_native.PulseDispatcher) when the optional extension is built, or a behavior-compatible Python ThreadPoolExecutor otherwise. Pass the same bus as pulse= to create_application, auth_routes, admin_routes, or migrations.apply to receive request, auth, admin, and migration events.
In progress
Rust-native core
Performance-sensitive types get a Rust implementation with a behavior-compatible Python fallback, so nothing requires a Rust toolchain to run.
python
from mangoo import mango_list
items = mango_list(["first", "second"])
items.append("third")
mango_list works with no extra steps. Build the extension with maturin develop --manifest-path crates/mangoo-python/Cargo.toml — point it at that crate specifically, not the repository root, since both currently share the project name mangoo and a root build silently replaces the real Python package. Request routing, JSON serialization, authentication operations, and database execution are the next migration targets.
Shipped
Testing & CI
The bundled TestClient exercises a WSGI application without opening a network socket.
powershell
pip install -e ".[dev]"
pytest
pytest runs with an 85% coverage gate by default. GitHub Actions runs the suite on Python 3.10–3.12 for every push and pull request, plus a job that builds mangoo_native and runs the tests it backs, and a job that runs the opt-in PostgreSQL integration test against a real postgres:16 container.