mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
0cbcaf9c4f
## Summary
Comprehensive post-v3.0.0 modernization: new features, bug fixes,
dependency cleanup, docs, and test coverage.
**New features:**
- **HTTP method filtering** — `@api.route("/data", methods=["GET"])`
- **Lifespan context manager** — modern async startup/shutdown
- **`api.exception_handler()`** — custom error handling per exception
type
- **`api.graphql()`** — one-liner GraphQL setup
- **`resp.file()`** — serve files from disk with auto content-type
- **before_request short-circuit** — set status code to skip route
handler
- **`req.path_params`** / **`req.client`** / **`req.is_json`** — new
request properties
- **`uuid`** and **`path`** route convertors
- **PEP 561 `py.typed`** marker
**Bug fixes:**
- Fix multipart parser losing headers
- Fix `url_for()` with typed params (`{id:int}`)
- Fix `resp.body` encoding crash on bytes content
- Fix Python 3.9 type syntax (`from __future__ import annotations`)
- Fix broken session test and no-op file upload test
- Fix helloworld example 404 on root path
**Dependencies:**
- Flattened — `pip install responder` gets everything
- Core: just starlette + uvicorn (down from 10 deps)
**Docs & README:**
- All new features documented in tour
- Modernized README features list
- Deployment guide: Docker, cloud, uvicorn
- Removed Pipenv, extras, stale references throughout
**Tests & quality:**
- 117 tests (up from 92), 91% coverage, 0 warnings
- CaseInsensitiveDict, GraphQL edge cases, staticfiles tests
- Ruff clean, all `tmpdir` → `tmp_path`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
43 lines
1.1 KiB
Python
43 lines
1.1 KiB
Python
import asyncio
|
|
import concurrent.futures
|
|
import multiprocessing
|
|
import traceback
|
|
|
|
from starlette.concurrency import run_in_threadpool
|
|
|
|
__all__ = ["BackgroundQueue"]
|
|
|
|
|
|
class BackgroundQueue:
|
|
def __init__(self, n=None):
|
|
if n is None:
|
|
n = multiprocessing.cpu_count()
|
|
|
|
self.n = n
|
|
self.pool = concurrent.futures.ThreadPoolExecutor(max_workers=n)
|
|
self.results = []
|
|
|
|
def run(self, f, *args, **kwargs):
|
|
f = self.pool.submit(f, *args, **kwargs)
|
|
self.results.append(f)
|
|
return f
|
|
|
|
def task(self, f):
|
|
def on_future_done(fs):
|
|
try:
|
|
fs.result()
|
|
except Exception:
|
|
traceback.print_exc()
|
|
|
|
def do_task(*args, **kwargs):
|
|
result = self.run(f, *args, **kwargs)
|
|
result.add_done_callback(on_future_done)
|
|
return result
|
|
|
|
return do_task
|
|
|
|
async def __call__(self, func, *args, **kwargs) -> None:
|
|
if asyncio.iscoroutinefunction(func):
|
|
return await asyncio.create_task(func(*args, **kwargs))
|
|
return await run_in_threadpool(func, *args, **kwargs)
|