ast-grep
FreeUse when doing structural code search and rewriting - ast-grep linting, refactoring, multi-language patterns
The English summary is being prepared. Showing the original description.
Use when building Python async HTTP services or clients with aiohttp - web server routing, middleware, WebSocket, SSE, streaming, client sessions, pytest-aiohttp testing, or troubleshooting SSL and timeout issues
The English summary is being prepared. Showing the original description.
See how to installReview the instructions the agent will receive before you install.
Asynchronous HTTP client/server framework for Python.
Choose aiohttp when:
Consider alternatives:
from aiohttp import web
async def health_check(request):
return web.json_response({"status": "ok"})
async def create_user(request):
data = await request.json()
# Validate with Pydantic or manual checks
return web.json_response({"id": 1, "username": data["username"]}, status=201)
app = web.Application()
app.router.add_get('/health', health_check)
app.router.add_post('/users', create_user)
if __name__ == '__main__':
web.run_app(app, host='127.0.0.1', port=8080)
Use app.on_startup and app.on_cleanup for resource lifecycle:
from aiohttp import web
import asyncpg
async def init_db(app):
"""Create database connection pool."""
app['db_pool'] = await asyncpg.create_pool(
host='localhost',
port=5432,
database='app_db'
)
async def close_db(app):
"""Close database connection pool."""
await app['db_pool'].close()
app = web.Application()
app.on_startup.append(init_db)
app.on_cleanup.append(close_db)
app.router.add_get('/users', list_users)
For cleaner startup/shutdown with context manager semantics:
from aiohttp import web
async def lifespan_ctx(app):
"""Lifespan context manager for resource management."""
# Startup
app['db_pool'] = await create_db_pool()
app['cache'] = await create_cache()
yield # App runs here
# Cleanup
await app['cache'].close()
await app['db_pool'].close()
app = web.Application()
app.router.add_get('/data', data_handler)
# Run with: web.run_app(app, lifespan=lifespan_ctx)
Middleware wraps request handling for cross-cutting concerns:
from aiohttp import web
import time
import logging
logger = logging.getLogger(__name__)
@web.middleware
async def timing_middleware(request, handler):
"""Track request duration."""
start = time.perf_counter()
try:
response = await handler(request)
duration = time.perf_counter() - start
logger.info(f"{request.method} {request.path} {response.status} ({duration:.3f}s)")
return response
except Exception as e:
duration = time.perf_counter() - start
logger.error(f"{request.method} {request.path} failed after {duration:.3f}s: {e}")
raise
@web.middleware
async def auth_middleware(request, handler):
"""Authentication middleware."""
public_paths = ['/health', '/public/']
if any(request.path.startswith(p) for p in public_paths):
return await handler(request)
auth_header = request.headers.get('Authorization')
if not auth_header or not await validate_token(auth_header):
return web.json_response(
{"error": "Unauthorized"},
status=401,
headers={'WWW-Authenticate': 'Bearer'}
)
# Attach user info to request
request['user'] = await decode_token(auth_header)
return await handler(request)
# Combine middleware (applied left-to-right)
app = web.Application(middlewares=[timing_middleware, auth_middleware])
| Response Type | Use Case | Example |
|---|---|---|
web.Response(text=...) | Plain text, HTML | web.Response(text="OK", content_type="text/html") |
web.json_response(...) | JSON bodies (auto-serializes) | web.json_response({"key": "value"}, status=201) |
web.StreamResponse() | Streaming large responses | See streaming section below |
web.FileResponse() | File downloads | web.FileResponse('data.zip') |
web.HTTPFound() | Redirects | web.HTTPFound('/new-location') |
| HTTP exception classes | Error responses | web.HTTPBadRequest(), web.HTTPNotFound() |
Manual status setting:
# Explicit status codes
return web.json_response({"error": "not found"}, status=404)
return web.Response(text="Created", status=201)
# HTTP exception classes (automatic status)
raise web.HTTPBadRequest(reason="Invalid input")
return web.HTTPUnauthorized(headers={'WWW-Authenticate': 'Bearer'})
For large files or real-time data:
from aiohttp import web
import asyncio
async def stream_data(request):
"""Server-Sent Events style streaming."""
response = web.StreamResponse(
status=200,
headers={
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
}
)
await response.prepare(request)
try:
for i in range(10):
data = f"data: {{'count': {i}}}\n\n"
await response.write(data.encode())
await response.drain()
await asyncio.sleep(1)
finally:
await response.write_eof()
return response
async def stream_file(request):
"""Stream large file in chunks."""
response = web.StreamResponse()
response.headers['Content-Type'] = 'application/octet-stream'
response.headers['Content-Length'] = str(file_size)
await response.prepare(request)
async with aiofiles.open('large_file.bin', 'rb') as f:
while chunk := await f.read(8192):
await response.write(chunk)
return response
import aiohttp
import asyncio
async def fetch_data():
async with aiohttp.ClientSession() as session:
async with session.get('https://api.example.com/data') as response:
response.raise_for_status()
return await response.json()
asyncio.run(fetch_data())
import aiohttp
# Default connector (often insufficient for production)
# session = aiohttp.ClientSession() # ❌ BAD: Uses defaults
# Production-ready connector
connector = aiohttp.TCPConnector(
limit=100, # Total connection pool size (default: 100)
limit_per_host=30, # Max connections per host (default: 30)
ttl_dns_cache=300, # DNS cache TTL in seconds
ssl=True, # Verify SSL certificates
enable_cleanup_closed=True, # Clean closed connections
)
timeout = aiohttp.ClientTimeout(
total=30, # Total request timeout (connect + transfer)
connect=5, # Connection establishment timeout
sock_connect=5, # Socket connection timeout
sock_read=10, # Read timeout (per read operation)
)
session = aiohttp.ClientSession(
connector=connector,
timeout=timeout,
headers={'User-Agent': 'my-app/1.0'}
)
# ✅ GOOD: Reuse session across requests
async def process_multiple_urls(urls):
timeout = aiohttp.ClientTimeout(total=30)
connector = aiohttp.TCPConnector(limit=100)
async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks, return_exceptions=True)
return results
async def fetch_url(session, url):
async with session.get(url) as response:
response.raise_for_status()
return await response.json()
# ❌ BAD: Creating session per request (causes socket exhaustion)
async def bad_pattern(urls):
results = []
for url in urls:
async with aiohttp.ClientSession() as session: # New session each time
async with session.get(url) as response:
results.append(await response.json())
# Session closed immediately after each request
return results
Symptom: OSError: [Errno 24] Too many open files or connection timeouts under load
Cause: Creating ClientSession() inside request handlers or loops without reuse. Each session maintains its own connection pool and file descriptors.
Fix:
# ❌ ANTI-PATTERN: Session created per request
async def handler(request):
session = aiohttp.ClientSession() # New session every request
async with session.get(url) as resp:
return web.json_response(await resp.json())
# ✅ FIX: Session stored in app, reused across requests
async def init_app():
app = web.Application()
app['session'] = aiohttp.ClientSession(
connector=aiohttp.TCPConnector(limit=100)
)
return app
async def cleanup_app(app):
await app['session'].close()
app = await init_app()
app.on_cleanup.append(cleanup_app)
async def handler(request):
session = request.app['session']
async with session.get(url) as resp:
return web.json_response(await resp.json())
Symptom: Connections accumulate, eventually hitting limit in TCPConnector, new requests hang
Cause: Missing ClientTimeout means no total timeout. A slow or hung server can hold connections indefinitely.
Fix:
# ❌ ANTI-PATTERN: No timeout specified
session = aiohttp.ClientSession()
async with session.get('https://slow-api.com/data') as resp:
# If server hangs, this waits forever
data = await resp.json()
# ✅ FIX: Always set timeouts
timeout = aiohttp.ClientTimeout(
total=30, # Max total time for entire request
connect=5, # Max time to establish connection
sock_read=10 # Max time between read operations
)
session = aiohttp.ClientSession(timeout=timeout)
Symptom: Connection established but data never arrives; or DNS resolution hangs
Cause: Only setting total timeout isn't enough. sock_read and connect catch specific failure modes.
Fix:
# ❌ ANTI-PATTERN: Only total timeout
timeout = aiohttp.ClientTimeout(total=60)
# ✅ FIX: Granular timeouts
timeout = aiohttp.ClientTimeout(
total=60, # Overall request timeout
connect=5, # Fail fast if can't connect
sock_connect=5, # Socket connection timeout
sock_read=30 # Read timeout (prevents stuck on slow responses)
)
Symptom: RuntimeError: Cannot call nested app.handler() or RuntimeError: Session is closed
Cause: ClientSession is bound to the event loop it was created on. Reusing it after asyncio.run() restarts the loop.
Fix:
# ❌ ANTI-PATTERN: Global session
session = aiohttp.ClientSession() # Created at module load
async def main():
asyncio.run(fetch_data()) # New event loop
# session is bound to old loop!
# ✅ FIX: Create session within event loop context
async def main():
async with aiohttp.ClientSession() as session:
await fetch_data(session)
asyncio.run(main())
Symptom: Memory growth, connection pool depletion over time
Cause: Not using async with or not calling response.release() leaves connections in limbo.
Fix:
# ❌ ANTI-PATTERN: Not consuming response
async with session.get(url) as response:
# Forgot to read/release
pass
# Response may not be fully released
# ✅ FIX: Always consume or explicitly release
async with session.get(url) as response:
data = await response.read() # Consume fully
# OR
async with session.get(url) as response:
if response.status != 200:
response.release() # Explicit release for early exit
raise Exception(f"Unexpected status: {response.status}")
Plain ClientSession is fine when:
Tune TCPConnector when:
limit and limit_per_hostlimit, keep limit_per_host moderateenable_cleanup_closed=Truettl_dns_cache=300 or higher# High-throughput single API
connector = aiohttp.TCPConnector(
limit=500,
limit_per_host=100,
ttl_dns_cache=300
)
# Many different hosts (aggregator pattern)
connector = aiohttp.TCPConnector(
limit=1000,
limit_per_host=10, # Don't overwhelm any single host
ttl_dns_cache=600
)
Use httpx instead when:
Use sync HTTP client (requests) when:
See references/testing.md for pytest-aiohttp fixtures, test client usage, and lifespan testing patterns.
Load these reference files on demand for specific topics:
references/middleware.md: Global error handlers, logging, CORS, rate limiting, JWT/basic auth patterns (when implementing cross-cutting concerns)references/client.md: WebSocket client, streaming uploads, compression, keepalive tuning (when optimizing client performance)references/testing.md: Application signals, lifespan context, test client, pytest-aiohttp fixtures (when writing tests)references/troubleshooting.md: Connection refused, timeouts, SSL errors, memory leaks (when debugging issues)Official Documentation: https://docs.aiohttp.org/ GitHub Repository: https://github.com/aio-libs/aiohttp pytest-aiohttp: https://pytest-aiohttp.readthedocs.io/
No reviews yet. Tell others what you thought.
No login needed. Share how it worked for you, ask a question, or note where it did not run.
No comments yet. Be the first to write one.
Use when doing structural code search and rewriting - ast-grep linting, refactoring, multi-language patterns
The English summary is being prepared. Showing the original description.
Use when building GBA games with the BPCore Lua engine - entity, sprite and tilemap functions, SRAM save and load, link cable multiplayer protocol, camera and scrolling, or optimization patterns
The English summary is being prepared. Showing the original description.
Use when running background tasks with Celery - worker and broker configuration (Redis, RabbitMQ), task routing by name vs queue, chains/groups/chords, retry patterns (autoretry_for, retry_backoff), acks_late semantics, failure detection, and monitoring with Flower
The English summary is being prepared. Showing the original description.
Use when building Django applications - security hardening, authentication and permissions, ORM optimization, PostgreSQL features, Django 6.0, migrations, testing, and ecosystem libraries
The English summary is being prepared. Showing the original description.
Use when customizing Django Admin - save_formset, get_search_results, formsets, queryset optimization, db_index, custom URLs
The English summary is being prepared. Showing the original description.
Use when implementing Django authentication - local accounts, OAuth, email verification, MFA, OIDC, django-organizations
The English summary is being prepared. Showing the original description.