Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 50 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div align="center">
<img src="api-shield-logo.svg" alt="API Shield" width="600"/>

<p><strong>Route(API) lifecycle management for Python web frameworks — maintenance mode, environment gating, deprecation, rate limiting, admin panels, and more. No restarts required.</strong></p>
<p><strong>Route(API) lifecycle management for ASGI Python web frameworks — maintenance mode, environment gating, deprecation, rate limiting, admin panels, and more. No restarts required.</strong></p>

<a href="https://pypi.org/project/api-shield"><img src="https://img.shields.io/pypi/v/api-shield?color=F59E0B&label=pypi&cacheSeconds=300" alt="PyPI"></a>
<a href="https://pypi.org/project/api-shield"><img src="https://img.shields.io/pypi/pyversions/api-shield?color=F59E0B" alt="Python versions"></a>
Expand All @@ -17,21 +17,35 @@

## Key features

### Core (`shield.core`)

These features are framework-agnostic and available to any adapter.

| Feature | Description |
|---|---|
| 🎨 **Decorator-first DX** | Route state lives next to the route definition, not in a separate config file |
| ⚡ **Zero-restart control** | State changes take effect immediately — no redeployment or server restart needed |
| 🔄 **Sync & async** | Full support for both `async def` and plain `def` route handlers — use `await engine.*` or `engine.sync.*` |
| 🛡️ **Fail-open by default** | If the backend is unreachable, requests pass through. Shield never takes down your API |
| 🔌 **Pluggable backends** | In-memory (default), file-based JSON, or Redis for multi-instance deployments |
| 🖥️ **Admin dashboard** | HTMX-powered UI with live SSE updates — no JS framework required |
| 🖱️ **REST API + CLI** | Full programmatic control from the terminal or CI pipelines — works over HTTPS remotely |
| 📄 **OpenAPI integration** | Disabled / env-gated routes hidden from `/docs`; deprecated routes flagged automatically |
| 📋 **Audit log** | Every state change is recorded: who, when, what route, old status → new status |
| ⏰ **Scheduled windows** | `asyncio`-native scheduler — maintenance windows activate and deactivate automatically |
| 🔔 **Webhooks** | Fire HTTP POST on every state change — built-in Slack formatter and custom formatters supported |
| 🎨 **Custom responses** | Return HTML, redirects, or any response shape for blocked routes — per-route or app-wide default |
| 🚦 **Rate limiting** | Per-IP, per-user, per-API-key, or global counters — tiered limits, burst allowance, runtime mutation |

### Framework adapters

#### FastAPI (`shield.fastapi`) — ✅ supported

| Feature | Description |
|---|---|
| 🎨 **Decorator-first DX** | `@maintenance`, `@disabled`, `@env_only`, `@force_active`, `@deprecated`, `@rate_limit` — state lives next to the route |
| 📄 **OpenAPI integration** | Disabled / env-gated routes hidden from `/docs`; deprecated routes flagged; live maintenance banners in the Swagger UI |
| 🧩 **Dependency injection** | All decorators work as `Depends()` — enforce shield state per-handler without middleware |
| 🎨 **Custom responses** | Return HTML, redirects, or any response shape for blocked routes — per-route or app-wide default on the middleware |
| 🔀 **ShieldRouter** | Drop-in `APIRouter` replacement that auto-registers route metadata with the engine at startup |

---

## Install
Expand All @@ -43,6 +57,8 @@ uv add "api-shield[all]"

## Quickstart

> FastAPI is the currently supported adapter. Litestar, Starlette, Quart, and Django (ASGI) are on the roadmap.

```python
from fastapi import FastAPI
from shield.core.config import make_engine
Expand Down Expand Up @@ -95,7 +111,7 @@ shield global enable --reason "Deploying v2" --exempt /health
| `@deprecated(sunset, use_instead)` | Still works, injects deprecation headers | 200 |
| `@force_active` | Bypasses all shield checks | Always 200 |
| `@rate_limit("100/minute")` | Cap requests per IP, user, API key, or globally | 429 |
### Custom responses
### Custom responses (FastAPI)

By default, blocked routes return a structured JSON error body. You can replace it with anything — HTML, a redirect, plain text, or your own JSON — in two ways:

Expand Down Expand Up @@ -175,6 +191,35 @@ Requires `api-shield[rate-limit]`. Powered by [limits](https://limits.readthedoc

---

## Framework support

api-shield is built on the **ASGI** standard. The core (`shield.core`) is completely framework-agnostic and has zero framework imports. Any ASGI framework can be supported — either via a Starlette `BaseHTTPMiddleware` (for Starlette-based frameworks) or a raw ASGI callable for frameworks like Quart and Django that implement the ASGI spec independently.

### ASGI frameworks

| Framework | Status | Adapter |
|---|---|---|
| **FastAPI** | ✅ Supported | `shield.fastapi` |
| **Litestar** | 🔜 Planned | — |
| **Starlette** | 🔜 Planned | — |
| **Quart** | 🔜 Planned | — |
| **Django (ASGI)** | 🔜 Planned | — |

> Want support for another ASGI framework? [Open an issue](https://github.com/Attakay78/api-shield/issues).

### WSGI frameworks (Flask, Django, …)

> [!IMPORTANT]
> **WSGI support is out of scope for this project.**
>
> `api-shield` is an ASGI-native library. Bolting WSGI support in through shims or patches would require a persistent background event loop, thread-bridging hacks, and a fundamentally different middleware model — complexity that would compromise the quality and reliability of both layers.
>
> WSGI framework support (Flask, Django, Bottle, …) will be delivered as a **separate, dedicated project** designed from the ground up for the synchronous request model. This keeps both projects clean, well-tested, and maintainable without trade-offs.
>
> Watch this repo or [open an issue](https://github.com/Attakay78/api-shield/issues) to be notified when the WSGI companion project launches.

---

## Backends

| Backend | Persistence | Multi-instance |
Expand Down
5 changes: 4 additions & 1 deletion docs/adapters/fastapi.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# FastAPI Adapter

The FastAPI adapter is the primary supported adapter. It provides middleware, decorators, a drop-in router, and OpenAPI integration.
The FastAPI adapter is the currently supported ASGI adapter. It provides middleware, decorators, a drop-in router, and OpenAPI integration — all built on top of the framework-agnostic `shield.core`.

!!! info "Other ASGI frameworks"
api-shield's core and `ShieldMiddleware` are ASGI-native and framework-agnostic. FastAPI-specific features (ShieldRouter, OpenAPI integration, `Depends()` support) live in `shield.fastapi`. Adapters for **Litestar** and plain **Starlette** are on the roadmap. [Open an issue](https://github.com/Attakay78/api-shield/issues) if you need another framework prioritised.

---

Expand Down
54 changes: 54 additions & 0 deletions docs/adapters/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Framework Adapters

api-shield separates concerns cleanly:

- **`shield.core`** — the engine, backends, models, and exceptions. Zero framework imports. Works anywhere Python runs.
- **`shield.fastapi`** — the FastAPI adapter: ASGI middleware, route decorators, `ShieldRouter`, and OpenAPI integration.
- **`shield.<framework>`** — future adapters follow the same pattern.

---

## ASGI frameworks

api-shield is built on the **ASGI** standard with zero framework imports in `shield.core`. ASGI frameworks fall into two groups, each requiring a slightly different middleware approach:

**Starlette-based** — use `ShieldMiddleware` (`BaseHTTPMiddleware`) directly. These frameworks share Starlette's request/response model and middleware protocol.

**Pure ASGI** — frameworks like Quart and Django that implement the ASGI spec independently (no Starlette layer). Their adapters will use a raw ASGI callable (`async def __call__(scope, receive, send)`) so no Starlette dependency is introduced.

| Framework | Status | Adapter module | Middleware approach |
|---|---|---|---|
| **FastAPI** | ✅ Supported now | `shield.fastapi` | Starlette `BaseHTTPMiddleware` + ShieldRouter + OpenAPI integration |
| **Litestar** | 🔜 Planned | `shield.litestar` | Starlette-compatible middleware |
| **Starlette** | 🔜 Planned | `shield.starlette` | Starlette `BaseHTTPMiddleware` |
| **Quart** | 🔜 Planned | `shield.quart` | Pure ASGI callable (no Starlette dependency) |
| **Django (ASGI)** | 🔜 Planned | `shield.django` | Pure ASGI callable (no Starlette dependency) |

!!! note "Quart and Django ASGI"
[Quart](https://quart.palletsprojects.com/) is the ASGI reimplementation of Flask and is a natural fit. [Django's ASGI mode](https://docs.djangoproject.com/en/stable/howto/deployment/asgi/) (`django.core.asgi`) makes Django routes available over ASGI. Neither uses Starlette internally, so their adapters will wrap the shield engine in a pure ASGI middleware layer — keeping `shield.quart` and `shield.django` free of any Starlette dependency.

See [**FastAPI Adapter**](fastapi.md) for the full guide on the currently supported adapter.

---

## WSGI frameworks

!!! warning "WSGI support is out of scope for this project"
`api-shield` is an ASGI-native library. Integrating WSGI frameworks (Flask, Django, Bottle, …) via thread-bridging shims or a persistent background event loop would require a fundamentally different request model, and would introduce architectural compromises that undermine the reliability of both layers.

**WSGI support will be delivered as a separate, dedicated project.** Building it from scratch for the synchronous request model — rather than patching it onto an async core — means both projects stay clean, well-tested, and maintainable without trade-offs.

This is a deliberate design decision, not a gap. [Open an issue](https://github.com/Attakay78/api-shield/issues) or watch this repo to be notified when the WSGI companion project launches.

### Why not just add a sync bridge?

The short answer: it works until it doesn't, and the failure modes are silent.

A WSGI-to-ASGI bridge requires spawning a background asyncio event loop in a daemon thread and using `asyncio.run_coroutine_threadsafe()` to call into the async engine on every request. This creates several problems:

- **Connection pool fragmentation** — `RedisBackend` opens a connection pool tied to one event loop. Each WSGI worker process creates its own daemon loop, fragmenting the pool across processes with no shared state.
- **Thread-safety surface** — asyncio primitives (`asyncio.Lock`, `asyncio.Queue`) are not thread-safe. Wrapping them correctly across the WSGI/ASGI boundary requires significant additional machinery.
- **Testing complexity** — unit tests for sync WSGI views that touch the async engine require careful loop management, making the test suite fragile.
- **Hidden failures** — when the bridge breaks (deadlock, loop death, queue overflow), requests fail silently or block indefinitely rather than failing fast.

A purpose-built sync engine for WSGI avoids all of this.
16 changes: 14 additions & 2 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

---

## [Unreleased]
## [0.7.0]

### Added

- **`engine.sync` — synchronous proxy for sync route handlers and background threads**: every async engine method (`enable`, `disable`, `set_maintenance`, `schedule_maintenance`, `set_env_only`, `enable_global_maintenance`, `disable_global_maintenance`, `set_rate_limit_policy`, `delete_rate_limit_policy`, `reset_rate_limit`, `get_state`, `list_states`, `get_audit_log`) is now mirrored on `engine.sync` using `anyio.from_thread.run()`, the same mechanism the shield decorators use internally. Use `engine.sync.*` from plain `def` FastAPI handlers (which FastAPI runs in a worker thread automatically) and background threads — no event-loop wiring required.
- **Env-gate management from dashboard and CLI**: routes can now have their environment gate set or cleared at runtime — without redeployment — via the dashboard "Env Gate" button (opens an inline modal) and the new `shield env set` / `shield env clear` CLI commands.
- **Global rate limit** (`engine.set_global_rate_limit`): a single rate limit policy applied across all routes with higher precedence than per-route limits — checked first, so a request blocked globally never touches a per-route counter. Supports all key strategies, burst allowance, and per-route exemptions (`exempt_routes`). Configurable from the dashboard Rate Limits page and the new `shield grl` CLI command group (`get`, `set`, `delete`, `reset`, `enable`, `disable`).
- **Global rate limit pause / resume** (`engine.disable_global_rate_limit` / `engine.enable_global_rate_limit`): suspend enforcement without removing the policy, then resume it later. Per-route policies are always unaffected.

### Documentation

- Reframed all docs and README as ASGI-first; expanded framework support tables to include Litestar, Starlette, Quart, and Django (ASGI) as planned adapters; added a dedicated Adapters overview page with a clear explanation of why WSGI frameworks (Flask, Django WSGI, Bottle) are out of scope for this project and will be supported in a separate dedicated library.

---

Expand Down Expand Up @@ -135,7 +146,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- `shield` CLI with direct backend access
- `shield status`, `shield enable`, `shield disable`, `shield maintenance`, `shield schedule`, `shield log`

[Unreleased]: https://github.com/Attakay78/api-shield/compare/v0.5.0...HEAD
[0.7.0]: https://github.com/Attakay78/api-shield/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/Attakay78/api-shield/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/Attakay78/api-shield/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/Attakay78/api-shield/compare/v0.3.0...v0.4.0
[0.3.0]: https://github.com/Attakay78/api-shield/compare/v0.2.0...v0.3.0
Expand Down
48 changes: 44 additions & 4 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@
!!! warning "Early Access: your feedback shapes the roadmap"
`api-shield` is fully functional and ready to use. We are actively building on a solid foundation and would love to hear from you. If you have feedback, feature ideas, or suggestions, **[open an issue on GitHub](https://github.com/Attakay78/api-shield/issues)**. Every voice helps make the library better for everyone.

**Route(API) lifecycle management for Python web frameworks: maintenance mode, environment gating, deprecation, rate limiting, admin panels, and more. No restarts required.**
**Route(API) lifecycle management for ASGI Python web frameworks: maintenance mode, environment gating, deprecation, rate limiting, admin panels, and more. No restarts required.**

Most "route lifecycle management" tools are blunt instruments: shut everything down or nothing at all. `api-shield` treats each route as a first-class entity with its own lifecycle. State changes take effect immediately through middleware, with no redeployment and no server restart.

---

## 30-second quickstart

> **FastAPI** is the currently supported adapter. Litestar, Starlette, Quart, and Django (ASGI) are on the roadmap. See [Adapters](adapters/index.md) for details.

```bash
uv add "api-shield[all]"
```
Expand Down Expand Up @@ -87,21 +89,34 @@ shield enable GET:/payments

## Key features

### Core (`shield.core`)

These features are framework-agnostic and available to every adapter.

| Feature | Description |
|---|---|
| 🎨 **Decorator-first DX** | Route state lives next to the route definition, not in a separate config file |
| ⚡ **Zero-restart control** | State changes are immediate, with no redeployment needed |
| 🛡️ **Fail-open by default** | If the backend is unreachable, requests pass through. Shield never takes down your API |
| 🔌 **Pluggable backends** | In-memory (default), file-based JSON, or Redis for multi-instance deployments |
| 🖥️ **Admin dashboard** | HTMX-powered UI with live SSE updates, no JS framework required |
| 🖱️ **REST API + CLI** | Full programmatic control from the terminal or CI pipelines |
| 📄 **OpenAPI integration** | Disabled and env-gated routes hidden from `/docs`; deprecated routes flagged automatically |
| 📋 **Audit log** | Every state change is recorded: who, when, what route, old status, new status |
| ⏰ **Scheduled windows** | `asyncio`-native scheduler that activates and deactivates maintenance windows automatically |
| 🔔 **Webhooks** | Fire HTTP POST on every state change, with a built-in Slack formatter and support for custom formatters |
| 🎨 **Custom responses** | Return HTML, redirects, or any response shape for blocked routes, per-route or as an app-wide default |
| 🚦 **Rate limiting** | Per-IP, per-user, per-API-key, or global counters with tiered limits, burst allowance, and runtime policy mutation |

### Framework adapters

#### FastAPI (`shield.fastapi`) — ✅ supported

| Feature | Description |
|---|---|
| 🎨 **Decorator-first DX** | `@maintenance`, `@disabled`, `@env_only`, `@force_active`, `@deprecated`, `@rate_limit` — state lives next to the route |
| 📄 **OpenAPI integration** | Disabled and env-gated routes hidden from `/docs`; deprecated routes flagged; live maintenance banners in the Swagger UI |
| 🧩 **Dependency injection** | All decorators work as `Depends()` — enforce shield state per-handler without middleware |
| 🎨 **Custom responses** | Return HTML, redirects, or any response shape for blocked routes, per-route or as an app-wide default on the middleware |
| 🔀 **ShieldRouter** | Drop-in `APIRouter` replacement that auto-registers route metadata with the engine at startup |

---

## Decorators at a glance
Expand All @@ -117,6 +132,31 @@ shield enable GET:/payments

---

## Framework support

### ASGI frameworks

api-shield is an **ASGI-native** library. The core (`shield.core`) is framework-agnostic with zero framework imports. Any ASGI framework can be supported — Starlette-based frameworks use `BaseHTTPMiddleware` directly; frameworks like Quart and Django that implement the ASGI spec independently use a raw ASGI callable adapter instead.

| Framework | Status | Adapter |
|---|---|---|
| **FastAPI** | ✅ Supported | `shield.fastapi` |
| **Litestar** | 🔜 Planned | — |
| **Starlette** | 🔜 Planned | — |
| **Quart** | 🔜 Planned | — |
| **Django (ASGI)** | 🔜 Planned | — |

### WSGI frameworks (Flask, Django, …)

!!! warning "WSGI support is out of scope for this project"
`api-shield` is built on the ASGI standard. Adding WSGI support through shims or thread-bridging patches would require a persistent background event loop, a fundamentally different middleware model, and trade-offs that would compromise reliability for both ASGI and WSGI users.

WSGI framework support (Flask, Django, Bottle, and others) will be delivered as a **separate, dedicated project** designed from the ground up for the synchronous request model. This keeps both projects clean, well-tested, and free of architectural compromises.

[Open an issue](https://github.com/Attakay78/api-shield/issues) or watch this repo to be notified when the WSGI companion project launches.

---

## Next steps

- [**Tutorial: Installation**](tutorial/installation.md): get up and running in seconds
Expand Down
Loading
Loading