An async-first Flask-style web framework built on ASGI.
Project description
exile
An async-first Python web framework with Flask-like APIs on top of ASGI.
Install
Install from local source:
python3 -m pip install -e .
After publishing to PyPI:
python3 -m pip install exile-line
Install template support:
python3 -m pip install -e .[templates]
Quickstart
from exile import Exile
app = Exile(__name__)
@app.get("/")
async def index():
return {"hello": "exile"}
@app.get("/users/<int:user_id>")
async def user_detail(user_id: int):
return {"user_id": user_id}
Run with uvicorn:
uvicorn app:app --reload
Core APIs
- Routing:
@app.route,@app.get,@app.post,@app.put,@app.patch,@app.delete - Request context:
request,current_app,g - Response helpers:
Response,JSONResponse,HTMLResponse,RedirectResponse,FileResponse - Helpers:
abort,jsonify,make_response,url_for,render_template,render_template_async - Middleware:
- Function middleware:
@app.middleware("http") - ASGI class middleware:
app.add_middleware(...)
- Function middleware:
- Blueprint:
Blueprint(...); app.register_blueprint(...) - Lifespan:
@app.on_startup,@app.on_shutdown - Testing:
AsyncTestClient
Routing
@app.get("/users/<int:user_id>")
async def user_detail(user_id: int):
return {"user_id": user_id}
@app.post("/users")
async def create_user():
data = await request.json()
return {"created": data}, 201
Supported converters:
str(default):<name>int:<int:user_id>float:<float:amount>path:<path:filepath>
Static files (/static/<path:filename>) include cache headers by default:
Cache-Control: public, max-age=3600ETagLast-Modified- Supports conditional requests (
If-None-Match/If-Modified-Since) and returns304 Not Modified - Supports byte range requests (
Range: bytes=...) with206 Partial Contentand416 Range Not Satisfiable
Reverse URL build:
path = app.url_for("user_detail", user_id=9, page=2)
# /users/9?page=2
Request And Response
Request helpers:
await request.body()await request.text()await request.json()request.args(query string MultiDict)
Response helpers:
- Return
dict/listdirectly for JSON - Return tuple:
(body, status)or(body, status, headers) jsonify(...),make_response(...)FileResponse,StreamingResponse,HTMLResponse,RedirectResponse
Template rendering strategy:
render_template(...): synchronous helper, Flask-like ergonomicsawait render_template_async(...): render in worker thread to avoid blocking event loop in async views
Error Handling
from exile import abort
@app.errorhandler(404)
def not_found(exc):
return {"error": "not found"}, 404
@app.get("/must-exist")
def must_exist():
abort(404)
Blueprint-scoped error handler:
api = Blueprint("api", __name__)
@api.errorhandler(404)
def api_not_found(exc):
return {"api_error": "not found"}, 404
Middleware
Function middleware:
@app.middleware("http")
async def add_header(req, call_next):
response = await call_next(req)
response.headers["X-App"] = "exile"
return response
ASGI middleware class:
app.add_middleware(SomeASGIMiddleware, option="value")
Lifespan
@app.on_startup
def startup():
...
@app.on_shutdown
async def shutdown():
...
Blueprint Example
from exile import Blueprint
api = Blueprint("api", __name__, url_prefix="/v1")
@api.get("/ping")
async def ping():
return {"ok": True}
app.register_blueprint(api, url_prefix="/api")
Async TestClient Example
import asyncio
from exile import AsyncTestClient
client = AsyncTestClient(app)
async def main():
resp = await client.get("/users/1")
assert resp.status_code == 200
assert resp.json()["user_id"] == 1
asyncio.run(main())
Example App
Run:
python3 examples/basic_app.py
Run Tests
python3 -m unittest discover -s tests -v
Build Distribution
python3 -m pip install --upgrade build
python3 -m build
Release precheck:
./scripts/release_check.sh
Publish to TestPyPI (dry run before real release):
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="<your-testpypi-token>"
./scripts/publish_testpypi.sh
Publish to PyPI:
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="<your-pypi-token>"
./scripts/publish_pypi.sh
Changelog content check:
python3 scripts/check_changelog_content.py
Wheel smoke test:
python3 scripts/smoke_test_wheel.py
Dist artifact check:
python3 scripts/check_dist_artifacts.py
Tag/version verification:
python3 scripts/check_tag_version.py v0.1.0
Bump version:
python3 scripts/bump_version.py 0.1.1
Clean build artifacts:
./scripts/clean.sh
Check working tree before publish:
./scripts/check_worktree_clean.sh
Release
See release steps in:
RELEASE.md
Release notes file:
CHANGELOG.md
Make Targets
make test
make compile
make release-check
make bump-version V=0.1.1
make clean
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file exile_line-0.1.0.tar.gz.
File metadata
- Download URL: exile_line-0.1.0.tar.gz
- Upload date:
- Size: 23.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
619d01397029ccbfb5508bb96bf447b4f0a0592f65675cee120624353c5a1b4d
|
|
| MD5 |
f19757bff95dc065a22b9f3d99e6f6f2
|
|
| BLAKE2b-256 |
38a2553995f862d14eccc45b63aa1a74a8736d9a0dc13641f763de8700a32b01
|
File details
Details for the file exile_line-0.1.0-py3-none-any.whl.
File metadata
- Download URL: exile_line-0.1.0-py3-none-any.whl
- Upload date:
- Size: 20.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3e579c3488f80609a6fb2dd365bd65debef56ae8b53dfac88136d12cf04272e
|
|
| MD5 |
c20393c8e136cbe4292e616b84c2e9d0
|
|
| BLAKE2b-256 |
988b1379df2139d3f78dc172b309704a35f78b8411f12148b50aa6c72558d4b9
|