mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 06:46:14 +00:00
Add auth, WebSocket, middleware, config guides. Examples and changelog.
New documentation pages: - Authentication: API keys, JWT tokens, session auth, custom exceptions - WebSocket Tutorial: echo server, chat room, HTML client, data formats - Writing Middleware: hooks vs middleware, Starlette integration, ordering - Configuration: env vars, .env files, secret keys, debug mode, production setup Also: - Complete working example at end of quickstart with cross-references - Three new example files: rest_api.py, websocket_chat.py, sse_stream.py - Changelog updated for v3.2.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Complete REST API example with Pydantic validation.
|
||||
# https://responder.kennethreitz.org/tutorial-rest.html
|
||||
import responder
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BookIn(BaseModel):
|
||||
title: str
|
||||
author: str
|
||||
year: int
|
||||
isbn: str | None = None
|
||||
|
||||
|
||||
class BookOut(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
author: str
|
||||
year: int
|
||||
isbn: str | None = None
|
||||
|
||||
|
||||
api = responder.API(
|
||||
title="Book Catalog",
|
||||
version="1.0",
|
||||
openapi="3.0.2",
|
||||
docs_route="/docs",
|
||||
)
|
||||
|
||||
books_db: dict[int, dict] = {}
|
||||
next_id = 1
|
||||
|
||||
|
||||
@api.route("/books", methods=["GET"])
|
||||
def list_books(req, resp):
|
||||
resp.media = list(books_db.values())
|
||||
|
||||
|
||||
@api.route("/books", methods=["POST"], check_existing=False,
|
||||
request_model=BookIn, response_model=BookOut)
|
||||
async def create_book(req, resp):
|
||||
global next_id
|
||||
data = await req.media()
|
||||
book = {"id": next_id, **data}
|
||||
books_db[next_id] = book
|
||||
next_id += 1
|
||||
resp.media = book
|
||||
resp.status_code = 201
|
||||
|
||||
|
||||
@api.route("/books/{book_id:int}", methods=["GET"])
|
||||
def get_book(req, resp, *, book_id):
|
||||
if book_id not in books_db:
|
||||
resp.status_code = 404
|
||||
resp.media = {"error": f"Book {book_id} not found"}
|
||||
return
|
||||
resp.media = books_db[book_id]
|
||||
|
||||
|
||||
@api.route("/books/{book_id:int}", methods=["DELETE"], check_existing=False)
|
||||
def delete_book(req, resp, *, book_id):
|
||||
if book_id not in books_db:
|
||||
resp.status_code = 404
|
||||
resp.media = {"error": f"Book {book_id} not found"}
|
||||
return
|
||||
del books_db[book_id]
|
||||
resp.status_code = 204
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -0,0 +1,42 @@
|
||||
# Server-Sent Events streaming example.
|
||||
# https://responder.kennethreitz.org/tour.html#server-sent-events-sse
|
||||
import asyncio
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
def index(req, resp):
|
||||
resp.html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>SSE Stream</h1>
|
||||
<div id="events"></div>
|
||||
<script>
|
||||
const source = new EventSource("/stream");
|
||||
const events = document.getElementById("events");
|
||||
source.onmessage = (e) => {
|
||||
const p = document.createElement("p");
|
||||
p.textContent = e.data;
|
||||
events.appendChild(p);
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@api.route("/stream")
|
||||
async def stream(req, resp):
|
||||
@resp.sse
|
||||
async def events():
|
||||
for i in range(20):
|
||||
yield {"data": f"Event #{i}"}
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
@@ -0,0 +1,57 @@
|
||||
# WebSocket chat room example.
|
||||
# https://responder.kennethreitz.org/tutorial-websockets.html
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
connected = set()
|
||||
|
||||
|
||||
@api.route("/")
|
||||
def index(req, resp):
|
||||
resp.html = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Chat Room</h1>
|
||||
<div id="messages" style="height:300px;overflow-y:scroll;border:1px solid #ccc;padding:10px;"></div>
|
||||
<input id="input" placeholder="Type a message..." style="width:300px;" />
|
||||
<script>
|
||||
const ws = new WebSocket(`ws://${location.host}/chat`);
|
||||
const messages = document.getElementById("messages");
|
||||
const input = document.getElementById("input");
|
||||
ws.onmessage = (e) => {
|
||||
const p = document.createElement("p");
|
||||
p.textContent = e.data;
|
||||
messages.appendChild(p);
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
};
|
||||
input.addEventListener("keypress", (e) => {
|
||||
if (e.key === "Enter" && input.value) {
|
||||
ws.send(input.value);
|
||||
input.value = "";
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
@api.route("/chat", websocket=True)
|
||||
async def chat(ws):
|
||||
await ws.accept()
|
||||
connected.add(ws)
|
||||
try:
|
||||
while True:
|
||||
message = await ws.receive_text()
|
||||
for client in connected:
|
||||
await client.send_text(message)
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
connected.discard(ws)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
Reference in New Issue
Block a user