Skip to main content

Route lifecycle management for APIs — maintenance mode, env gating, deprecation, and more

Project description

API Shield

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

PyPI Python versions License


[!WARNING] Early Accessapi-shield is fully functional and ready to use. We are actively building on top of a solid foundation and your real-world experience is invaluable at this stage. If you have feedback, feature ideas, or suggestions, please open an issue — every voice helps shape the roadmap.


Key features

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
🛡️ 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

Install

uv add "api-shield[all]"
# or: pip install "api-shield[all]"

Quickstart

from fastapi import FastAPI
from shield.core.config import make_engine
from shield.fastapi import (
    ShieldMiddleware, ShieldAdmin, apply_shield_to_openapi,
    maintenance, env_only, disabled, force_active, deprecated,
)

engine = make_engine()

app = FastAPI()
app.add_middleware(ShieldMiddleware, engine=engine)

@app.get("/payments")
@maintenance(reason="DB migration — back at 04:00 UTC")
async def get_payments():
    return {"payments": []}

@app.get("/health")
@force_active
async def health():
    return {"status": "ok"}

apply_shield_to_openapi(app, engine)
app.mount("/shield", ShieldAdmin(engine=engine, auth=("admin", "secret")))
GET /payments  → 503  {"error": {"code": "MAINTENANCE_MODE", ...}}
GET /health    → 200  always

Manage routes from the CLI — no code changes, no restarts:

shield config set-url http://localhost:8000/shield
shield login admin
shield status
shield enable GET:/payments
shield global enable --reason "Deploying v2" --exempt /health

Decorators

Decorator Effect Status
@maintenance(reason, start, end) Temporarily unavailable 503
@disabled(reason) Permanently off 503
@env_only("dev", "staging") Restricted to named environments 404 elsewhere
@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

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:

Per-route — pass response= directly on the decorator:

from starlette.requests import Request
from starlette.responses import HTMLResponse, RedirectResponse
from shield.fastapi import maintenance, disabled

def maintenance_page(request: Request, exc: Exception) -> HTMLResponse:
    return HTMLResponse(
        f"<h1>Down for maintenance</h1><p>{exc.reason}</p>", status_code=503
    )

@router.get("/payments")
@maintenance(reason="DB migration", response=maintenance_page)
async def payments():
    return {"payments": []}

@router.get("/orders")
@maintenance(reason="Upgrade in progress", response=lambda *_: RedirectResponse("/status"))
async def orders():
    return {"orders": []}

Global default — set once on ShieldMiddleware, applies to every route without a per-route factory:

app.add_middleware(
    ShieldMiddleware,
    engine=engine,
    responses={
        "maintenance": maintenance_page,   # all maintenance routes
        "disabled": lambda req, exc: HTMLResponse(
            f"<h1>Gone</h1><p>{exc.reason}</p>", status_code=503
        ),
    },
)

Resolution order: per-route response=global responses[...]built-in JSON. The factory can be sync or async and receives the live Request and the ShieldException that triggered the block.

Rate limiting

from shield.fastapi.decorators import rate_limit

@router.get("/public/posts")
@rate_limit("10/minute")               # 10 req/min per IP
async def list_posts():
    return {"posts": [...]}

@router.get("/users/me")
@rate_limit("100/minute", key="user")  # per authenticated user
async def get_current_user():
    ...

@router.get("/reports")
@rate_limit(                           # tiered limits
    {"free": "10/minute", "pro": "100/minute", "enterprise": "unlimited"},
    key="user",
)
async def get_reports():
    ...

Policies can be mutated at runtime without redeploying (shield rl and shield rate-limits are aliases):

shield rl set GET:/public/posts 20/minute   # raise the limit live
shield rl reset GET:/public/posts           # clear counters
shield rl hits                              # blocked requests log

Requires api-shield[rate-limit]. Powered by limits.


Backends

Backend Persistence Multi-instance
MemoryBackend No No
FileBackend Yes No
RedisBackend Yes Yes

For rate limiting in multi-worker deployments, use RedisBackend — counters are atomic and shared across all processes.


Documentation

Full documentation at attakay78.github.io/api-shield

Tutorial Get started in 5 minutes
Decorators reference All decorator options
Rate limiting Per-IP, per-user, tiered limits
ShieldEngine reference Programmatic control
Backends Memory, File, Redis, custom
Admin dashboard Mounting ShieldAdmin
CLI reference All CLI commands
Production guide Monitoring & deployment automation

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

api_shield-0.6.0.tar.gz (1.0 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

api_shield-0.6.0-py3-none-any.whl (162.4 kB view details)

Uploaded Python 3

File details

Details for the file api_shield-0.6.0.tar.gz.

File metadata

  • Download URL: api_shield-0.6.0.tar.gz
  • Upload date:
  • Size: 1.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for api_shield-0.6.0.tar.gz
Algorithm Hash digest
SHA256 79aae26fbf66f94ee26a8c2bb12ae22690d1cf15ba232e8fef215a5c4fbc41b5
MD5 5aa3c3130b68cb1ae5990b4d6cef7e32
BLAKE2b-256 bab935d7d72313e323e25e99068304b669b2d53142f4bed1249c8f30cbcaaab4

See more details on using hashes here.

Provenance

The following attestation bundles were made for api_shield-0.6.0.tar.gz:

Publisher: release.yml on Attakay78/api-shield

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file api_shield-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: api_shield-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 162.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for api_shield-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 2e369af73174b58daa5b56a06ce6d35e332b65f6c3cab4b59e2b9690447c3bae
MD5 de2de7b0c9e83aff7f07e9304443d51c
BLAKE2b-256 ad9de860c6acfba5ecc53911d3dc1fba6a7bedbd41566e64908104ff70fd248a

See more details on using hashes here.

Provenance

The following attestation bundles were made for api_shield-0.6.0-py3-none-any.whl:

Publisher: release.yml on Attakay78/api-shield

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page