Skip to content

Commit 4f02016

Browse files
kennethreitzclaude
andcommitted
Add comprehensive docstrings, expand API reference, upgrade to Starlette 1.0
- Add docstrings to all undocumented public methods across API, Request, Response, Router, Route, BackgroundQueue, and related classes - Expand api.rst with autodoc sections for RouteGroup, BackgroundQueue, QueryDict, and RateLimiter - Update starlette dependency to >=1.0 - Drop Python 3.9 support (required by Starlette 1.0), minimum is now 3.10 - Bump version to 3.4.0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3c2b1ac commit 4f02016

9 files changed

Lines changed: 238 additions & 16 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ if __name__ == "__main__":
1717

1818
$ pip install responder
1919

20-
That's it. Supports Python 3.9+.
20+
That's it. Supports Python 3.10+.
2121

2222
## The Basics
2323

docs/source/api.rst

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,45 @@ status code, headers, and cookies.
4343
:inherited-members:
4444

4545

46+
Route Groups
47+
------------
48+
49+
Group related routes under a shared URL prefix — useful for API versioning
50+
and organizing large applications.
51+
52+
.. autoclass:: responder.api.RouteGroup
53+
:members:
54+
55+
56+
Background Queue
57+
----------------
58+
59+
Run tasks in background threads without blocking the response. Available
60+
as ``api.background``.
61+
62+
.. autoclass:: responder.background.BackgroundQueue
63+
:members:
64+
65+
66+
Query Dict
67+
----------
68+
69+
A dictionary subclass for query string parameters with multi-value support.
70+
71+
.. autoclass:: responder.models.QueryDict
72+
:members:
73+
74+
75+
Rate Limiter
76+
------------
77+
78+
In-memory token bucket rate limiter. Limits requests per client IP address
79+
and returns ``429 Too Many Requests`` when exceeded.
80+
81+
.. autoclass:: responder.ext.ratelimit.RateLimiter
82+
:members:
83+
84+
4685
Status Code Helpers
4786
-------------------
4887

docs/source/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ Installation
8686
8787
$ uv pip install responder
8888
89-
Python 3.9 and above. That's it.
89+
Python 3.10 and above. That's it.
9090

9191

9292
.. toctree::

pyproject.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ license = {text = "Apache 2.0"}
1212
authors = [
1313
{ name = "Kenneth Reitz", email = "me@kennethreitz.org" },
1414
]
15-
requires-python = ">=3.9"
15+
requires-python = ">=3.10"
1616
classifiers = [
1717
"Development Status :: 5 - Production/Stable",
1818
"Environment :: Web Environment",
@@ -21,7 +21,6 @@ classifiers = [
2121
"Operating System :: OS Independent",
2222
"Programming Language :: Python",
2323
"Programming Language :: Python :: 3",
24-
"Programming Language :: Python :: 3.9",
2524
"Programming Language :: Python :: 3.10",
2625
"Programming Language :: Python :: 3.11",
2726
"Programming Language :: Python :: 3.12",
@@ -42,7 +41,7 @@ dependencies = [
4241
"pueblo[sfa-full]>=0.0.11",
4342
"pydantic>=2",
4443
"python-multipart",
45-
"starlette[full]>=0.40",
44+
"starlette[full]>=1.0",
4645
"uvicorn[standard]",
4746
]
4847

responder/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.3.0"
1+
__version__ = "3.4.0"

responder/api.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,31 @@ def __init__(
6161
lifespan=None,
6262
request_id=False,
6363
):
64+
"""Create a new Responder API instance.
65+
66+
:param debug: If ``True``, enable debug mode with verbose error pages.
67+
:param title: The title of the API, used in OpenAPI documentation.
68+
:param version: The version string for the API (e.g. ``"1.0"``).
69+
:param description: A longer description of the API for OpenAPI docs.
70+
:param terms_of_service: URL to the API's terms of service.
71+
:param contact: Contact information dict for the API (``name``, ``url``, ``email``).
72+
:param license: License information dict (``name``, ``url``).
73+
:param openapi: The OpenAPI version string (e.g. ``"3.0.2"``). Enables OpenAPI schema generation.
74+
:param openapi_route: The URL path for the OpenAPI schema (default ``"/schema.yml"``).
75+
:param static_dir: Directory for static files. Set to ``None`` to disable. Created automatically if missing.
76+
:param static_route: URL prefix for serving static files (default ``"/static"``).
77+
:param templates_dir: Directory for Jinja2 templates (default ``"templates"``).
78+
:param auto_escape: If ``True``, auto-escape HTML/XML in templates.
79+
:param secret_key: Secret key for signing cookie-based sessions. **Always set this in production.**
80+
:param enable_hsts: If ``True``, redirect all HTTP requests to HTTPS.
81+
:param docs_route: URL path for interactive API docs (e.g. ``"/docs"``). Enables OpenAPI if not already set.
82+
:param cors: If ``True``, enable CORS middleware.
83+
:param cors_params: Dict of CORS configuration (``allow_origins``, ``allow_methods``, etc.).
84+
:param allowed_hosts: List of allowed hostnames (e.g. ``["example.com"]``). Defaults to ``["*"]``.
85+
:param openapi_theme: Documentation UI theme: ``"swagger_ui"``, ``"redoc"``, ``"rapidoc"``, or ``"elements"``.
86+
:param lifespan: An async context manager for startup/shutdown logic.
87+
:param request_id: If ``True``, add ``X-Request-ID`` headers to all responses.
88+
""" # noqa: E501
6489
self.background = BackgroundQueue()
6590

6691
self.secret_key = secret_key
@@ -150,12 +175,30 @@ def requests(self):
150175

151176
@property
152177
def static_app(self):
178+
"""The Starlette ``StaticFiles`` application for serving static assets."""
153179
if not hasattr(self, "_static_app"):
154180
assert self.static_dir is not None
155181
self._static_app = StaticFiles(directory=self.static_dir)
156182
return self._static_app
157183

158184
def before_request(self, websocket=False):
185+
"""Register a function to run before every request.
186+
187+
If the hook sets ``resp.status_code``, the route handler is skipped
188+
and the response is sent immediately (short-circuiting).
189+
190+
:param websocket: If ``True``, register as a WebSocket before-request hook instead of HTTP.
191+
192+
Usage::
193+
194+
@api.before_request()
195+
def check_auth(req, resp):
196+
if "Authorization" not in req.headers:
197+
resp.status_code = 401
198+
resp.media = {"error": "unauthorized"}
199+
200+
""" # noqa: E501
201+
159202
def decorator(f):
160203
self.router.before_request(f, websocket=websocket)
161204
return f
@@ -180,6 +223,21 @@ def decorator(f):
180223
return decorator
181224

182225
def add_middleware(self, middleware_cls, **middleware_config):
226+
"""Add ASGI middleware to the application.
227+
228+
Middleware wraps the entire application and can inspect or modify
229+
every request and response. Middleware is applied in reverse order —
230+
the last middleware added runs first.
231+
232+
:param middleware_cls: A Starlette-compatible middleware class.
233+
:param middleware_config: Keyword arguments passed to the middleware constructor.
234+
235+
Usage::
236+
237+
from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
238+
api.add_middleware(HTTPSRedirectMiddleware)
239+
240+
"""
183241
self.app = middleware_cls(self.app, **middleware_config)
184242

185243
def exception_handler(self, exception_cls):
@@ -501,6 +559,10 @@ def serve(self, *, address=None, port=None, debug=False, **options):
501559
uvicorn.run(self, host=address, port=port, **options)
502560

503561
def run(self, **kwargs):
562+
"""Run the application. Shorthand for :meth:`serve` that inherits the ``debug`` setting.
563+
564+
:param kwargs: Keyword arguments passed through to :meth:`serve`.
565+
"""
504566
if "debug" not in kwargs:
505567
kwargs.update({"debug": self.debug})
506568
self.serve(**kwargs)

responder/background.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,33 @@
99

1010

1111
class BackgroundQueue:
12+
"""A queue for running tasks in background threads.
13+
14+
Uses a ``ThreadPoolExecutor`` sized to the number of CPUs. Access it
15+
via ``api.background``.
16+
17+
Usage::
18+
19+
# As a decorator — fire and forget
20+
@api.background.task
21+
def send_email(to, subject):
22+
...
23+
24+
send_email("user@example.com", "Hello")
25+
26+
# Direct submission
27+
future = api.background.run(send_email, "user@example.com", "Hello")
28+
29+
# As a callable (supports async functions)
30+
await api.background(send_email, "user@example.com", "Hello")
31+
32+
"""
33+
1234
def __init__(self, n=None):
35+
"""Create a new background queue.
36+
37+
:param n: Number of worker threads. Defaults to CPU count.
38+
"""
1339
if n is None:
1440
n = multiprocessing.cpu_count()
1541

@@ -18,11 +44,24 @@ def __init__(self, n=None):
1844
self.results = []
1945

2046
def run(self, f, *args, **kwargs):
47+
"""Submit a function to run in a background thread.
48+
49+
:param f: The function to run.
50+
:returns: A ``concurrent.futures.Future`` for the result.
51+
"""
2152
f = self.pool.submit(f, *args, **kwargs)
2253
self.results.append(f)
2354
return f
2455

2556
def task(self, f):
57+
"""Decorator that wraps a function to run in the background thread pool.
58+
59+
The decorated function returns a ``Future`` instead of blocking.
60+
Exceptions are printed to stderr via traceback.
61+
62+
:param f: The function to wrap.
63+
"""
64+
2665
def on_future_done(fs):
2766
try:
2867
fs.result()

0 commit comments

Comments
 (0)