### Project Setup and Testing Commands
Source: https://github.com/klen/asgi-tools/blob/develop/AGENTS.md
Commands for setting up the development environment, running tests, and performing CI checks. Includes options for installing dependencies, running specific tests, and keyword selection.
```bash
make # venv + install .[tests,dev,examples] + pre-commit
pytest # full suite (-xsv tests via pyproject.toml)
pytest tests/test_x.py::test_y # single test
pytest -k "keyword" # keyword selection
ruff check asgi_tools && mypy && pytest # CI parity
make docs # build docs
```
--------------------------------
### App Configuration and Routing
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Demonstrates the basic setup of an ASGI-Tools application, including defining static file serving, startup/shutdown events, and various route handlers for GET, POST, and static files. It also shows how to implement custom exception handlers and middleware.
```APIDOC
## App Setup and Routing
### Description
This section illustrates the core components of an ASGI-Tools application, including initialization with debug mode, static file configuration, lifecycle event handlers (startup and shutdown), and defining API endpoints using decorators for different HTTP methods.
### Example Usage
```python
from asgi_tools import App, Request, Response
import uvicorn
app = App(
debug=True,
static_folders=['static'],
static_url_prefix='/assets'
)
# Startup event
@app.on_startup
async def initialize():
print('App starting')
# Shutdown event
@app.on_shutdown
async def cleanup():
print('App shutting down')
# Route for the home page
@app.route('/')
async def home(request: Request):
return {'message': 'Welcome to ASGI-Tools'}
# Route with path parameters
@app.route('/users/{id}', methods=['GET'])
async def get_user(request: Request):
user_id = request['path_params']['id']
return {
'id': user_id,
'name': 'John Doe'
}
# Route for creating a user
@app.route('/users', methods=['POST'])
async def create_user(request: Request):
data = await request.json()
return 201, {'id': '123', **data}
# Route for serving static files
@app.route('/static/{path}')
async def static_file(request: Request):
path = request['path_params']['path']
return f'Static file: {path}'
# Exception handling for ValueError
@app.on_error(ValueError)
async def handle_value_error(request, exc):
return 400, {'error': str(exc)}
# General exception handling
@app.on_error(Exception)
async def handle_exception(request, exc):
app.logger.exception(exc)
return 500, {'error': 'Internal server error'}
# Middleware for logging requests
@app.middleware
async def log_middleware(handler, request):
import time
start = time.time()
response = await handler(request)
elapsed = time.time() - start
app.logger.info(
f'{request.method} {request.url.path} - '
f'{response.status_code} ({elapsed:.2f}s)'
)
return response
# Running the application with uvicorn
if __name__ == '__main__':
uvicorn.run(app, host='127.0.0.1', port=8000)
```
```
--------------------------------
### Full ASGI-Tools Application Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
A comprehensive example demonstrating app initialization, startup/shutdown events, routes, exception handling, and middleware.
```python
from asgi_tools import App, Request, Response
import uvicorn
app = App(
debug=True,
static_folders=['static'],
static_url_prefix='/assets'
)
# Startup
@app.on_startup
async def initialize():
print('App starting')
# Shutdown
@app.on_shutdown
async def cleanup():
print('App shutting down')
# Routes
@app.route('/')
async def home(request: Request):
return {'message': 'Welcome to ASGI-Tools'}
@app.route('/users/{id}', methods=['GET'])
async def get_user(request: Request):
user_id = request['path_params']['id']
return {
'id': user_id,
'name': 'John Doe'
}
@app.route('/users', methods=['POST'])
async def create_user(request: Request):
data = await request.json()
return 201, {'id': '123', **data}
@app.route('/static/{path}')
async def static_file(request: Request):
path = request['path_params']['path']
return f'Static file: {path}'
# Exception handling
@app.on_error(ValueError)
async def handle_value_error(request, exc):
return 400, {'error': str(exc)}
@app.on_error(Exception)
async def handle_exception(request, exc):
app.logger.exception(exc)
return 500, {'error': 'Internal server error'}
# Middleware
@app.middleware
async def log_middleware(handler, request):
import time
start = time.time()
response = await handler(request)
elapsed = time.time() - start
app.logger.info(
f'{request.method} {request.url.path} - '
f'{response.status_code} ({elapsed:.2f}s)'
)
return response
# Run with uvicorn
if __name__ == '__main__':
uvicorn.run(app, host='127.0.0.1', port=8000)
```
--------------------------------
### Install asgi-tools
Source: https://github.com/klen/asgi-tools/blob/develop/README.rst
Install the asgi-tools library using pip. This command is used for setting up the project.
```bash
pip install asgi-tools
```
--------------------------------
### BaseMiddleware Setup Factory
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Demonstrates how to create a middleware factory using the setup class method. This factory can be used later to instantiate the middleware.
```python
from asgi_tools import BaseMiddleware
# Create middleware factory
middleware_factory = BaseMiddleware.setup(app=my_app)
# Use it later
middleware = middleware_factory()
```
--------------------------------
### REST API Implementation
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Example of building a REST API with asgi-tools, including GET, POST, and DELETE methods for user management. Demonstrates path parameters and error handling.
```python
from asgi_tools import App, ResponseError
app = App()
@app.route('/users/{id}', methods=['GET'])
async def get_user(request):
user_id = request['path_params']['id']
user = await fetch_user(user_id)
if not user:
raise ResponseError.NOT_FOUND()
return user
@app.route('/users', methods=['POST'])
async def create_user(request):
data = await request.json()
user = await save_user(data)
return 201, user
@app.route('/users/{id}', methods=['DELETE'])
async def delete_user(request):
user_id = request['path_params']['id']
await delete_user(user_id)
return 204, ''
```
--------------------------------
### Install an ASGI Server
Source: https://github.com/klen/asgi-tools/blob/develop/docs/installation.md
Install a recommended ASGI server like Uvicorn, Daphne, or Hypercorn to run your ASGI-Tools application.
```sh
pip install uvicorn # or daphne, or hypercorn
```
--------------------------------
### Class-based Middleware Setup
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Demonstrates how to set up StaticFilesMiddleware for serving static files.
```python
from asgi_tools import StaticFilesMiddleware
app.middleware(
StaticFilesMiddleware.setup(
folders=['static'],
url_prefix='/static'
)
)
```
--------------------------------
### on_startup() Method
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Registers a handler function to be executed once when the application starts up.
```APIDOC
## Method on_startup()
### Description
Registers a handler function to be executed once when the application starts up.
### Method
Decorator
### Endpoint
N/A (Decorator)
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Parameters
- **fn** (Callable) - Required - Handler function to execute on startup
```
--------------------------------
### Example Usage
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Demonstrates how to create a Router instance, define routes using the `@router.route` decorator, and dispatch requests.
```APIDOC
### Example
```python
from asgi_tools import Router
router = Router()
@router.route('/')
async def home(request):
return 'Home'
@router.route('/users')
async def list_users(request):
return []
@router.route('/users/{id}')
async def get_user(request):
user_id = request['path_params']['id']
return {'id': user_id}
# Dispatch request
try:
match = router('/users/123', 'GET')
handler = match.target
params = match.params # {'id': '123'}
response = await handler(request)
except router.NotFoundError:
# No route found
pass
except router.InvalidMethodError:
# Method not allowed
pass
```
```
--------------------------------
### RouterMiddleware Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Demonstrates setting up routes with different matching strategies including aliases, method filtering, dynamic parameters, and regex patterns.
```python
from asgi_tools import RouterMiddleware, ResponseHTML, ResponseError
import re
app = RouterMiddleware()
@app.route('/')
async def home(scope, receive, send):
response = ResponseHTML('Home')
await response(scope, receive, send)
@app.route('/status', '/stat')
async def status(scope, receive, send):
response = ResponseHTML('OK')
await response(scope, receive, send)
@app.route('/only-post', methods=['POST'])
async def post_handler(scope, receive, send):
response = ResponseHTML('Posted')
await response(scope, receive, send)
# Dynamic path parameters
@app.route('/hello/{name}')
async def hello(scope, receive, send):
name = scope['path_params']['name']
response = ResponseHTML(f'Hello {name}')
await response(scope, receive, send)
# Regex patterns
@app.route(re.compile(r'/\\d+/?'))
async def number(scope, receive, send):
num = int(scope['path'].strip('/'))
response = ResponseHTML(f'Number: {num}')
await response(scope, receive, send)
# Regex patterns with parameter extraction
@app.route(r'/multiply/{first:\\d+}/{second:\\d+}')
async def multiply(scope, receive, send):
first = int(scope['path_params']['first'])
second = int(scope['path_params']['second'])
result = first * second
response = ResponseHTML(str(result))
await response(scope, receive, send)
```
--------------------------------
### Initialize App with Default Options
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initializes the App class with default configuration parameters. Useful for basic setup.
```python
from asgi_tools import App
app = App(
debug=False,
logger=default_logger,
static_url_prefix="/static",
static_folders=None,
trim_last_slash=False
)
```
--------------------------------
### Full Router Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Demonstrates setting up a Router, defining handlers for different routes, and dispatching a request. Includes error handling for Not Found and Invalid Method errors.
```python
from asgi_tools import Router
router = Router()
@router.route('/')
async def home(request):
return 'Home'
@router.route('/users')
async def list_users(request):
return []
@router.route('/users/{id}')
async def get_user(request):
user_id = request['path_params']['id']
return {'id': user_id}
# Dispatch request
try:
match = router('/users/123', 'GET')
handler = match.target
params = match.params # {'id': '123'}
response = await handler(request)
except router.NotFoundError:
# No route found
pass
except router.InvalidMethodError:
# Method not allowed
pass
```
--------------------------------
### Create a Minimal ASGI Application
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
A basic example demonstrating how to initialize an ASGI-Tools App and define a simple route handler for the root path.
```python
from asgi_tools import App
app = App()
@app.route("/")
async def hello_world(request):
return "
Hello, World!
"
```
--------------------------------
### Quick Start ASGI Application
Source: https://github.com/klen/asgi-tools/blob/develop/docs/index.md
Create a minimal ASGI application using the App class and define a simple route.
```python
from asgi_tools import App
app = App()
@app.route("/")
async def hello(request):
return "Hello, World!"
```
--------------------------------
### ASGI Scope Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
Provides an example of a TASGIScope dictionary, which contains metadata for an HTTP connection.
```python
scope: TASGIScope = {
'type': 'http',
'asgi': {'version': '3.0'},
'http_version': '1.1',
'method': 'GET',
'scheme': 'http',
'path': '/users/123',
'query_string': b'page=1&limit=10',
'root_path': '',
'headers': [
(b'host', b'example.com'),
(b'user-agent', b'TestClient')
],
'server': ('127.0.0.1', 8000),
'client': ('192.168.1.100', 54321),
}
```
--------------------------------
### Install JSON Libraries
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Install 'ujson' or 'orjson' using pip to leverage faster JSON serialization libraries.
```bash
# Install ujson
pip install ujson
# Install orjson
pip install orjson
```
--------------------------------
### Install Development Version from GitHub using pip
Source: https://github.com/klen/asgi-tools/blob/develop/docs/installation.md
Install the latest development version of ASGI-Tools directly from its GitHub repository.
```sh
pip install git+https://github.com/klen/asgi-tools.git
```
--------------------------------
### Handling ASGINotFoundError with Default Behavior
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/errors.md
Demonstrates the default behavior of the App class when ASGINotFoundError is raised, which is to automatically return a 404 Not Found response. This example shows a basic route setup.
```python
from asgi_tools import App
app = App()
@app.route('/users')
async def list_users(request):
return {'users': []}
# GET /missing → Raises ASGINotFoundError
# GET /users → Handled
# Default behavior: Returns 404 response
```
--------------------------------
### Configure Static File Serving
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Example of initializing an ASGI-Tools App to serve static files from specified folders under a given URL prefix.
```python
app = App(
static_url_prefix='/static',
static_folders=['static', 'public']
)
```
--------------------------------
### Registering Startup Event Handler
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Use the @app.on_startup decorator to register an asynchronous function that will be executed once when the application starts.
```python
@app.on_startup
async def init_database():
# Initialize database connection
print('Database initialized')
```
--------------------------------
### Create a Simple ASGI-Tools Test Application
Source: https://github.com/klen/asgi-tools/blob/develop/docs/installation.md
A basic ASGI application using ASGI-Tools to verify the installation. Save this code as test_app.py.
```python
from asgi_tools import App
app = App()
@app.route("/")
async def hello(request):
return "Hello, ASGI-Tools!"
```
--------------------------------
### Create and Use ASGITestClient
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Instantiate ASGITestClient with an ASGI application and make basic GET requests to test responses.
```python
from asgi_tools import App
from asgi_tools.tests import ASGITestClient
app = App()
@app.route('/')
async def home(request):
return 'Hello'
# Create test client
client = ASGITestClient(app)
# Make requests
async def test():
response = await client.get('/')
assert response.status_code == 200
assert await response.text() == 'Hello'
```
--------------------------------
### Basic ASGI App with asgi-tools
Source: https://github.com/klen/asgi-tools/blob/develop/README.rst
Create a simple ASGI application using the App helper from asgi-tools. This example demonstrates a basic route and response.
```python
from asgi_tools import App
app = App()
@app.route('/')
async def hello(request):
return "Hello World!"
```
--------------------------------
### Run the Test Application with Uvicorn
Source: https://github.com/klen/asgi-tools/blob/develop/docs/installation.md
Run the created test application using the Uvicorn ASGI server to verify the installation.
```sh
uvicorn test_app:app
```
--------------------------------
### Run ASGI Application
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Example of how to run an ASGI application using a Python file. This is typically not needed as auto-detection usually suffices.
```bash
python app.py
```
--------------------------------
### Apply LifespanMiddleware and Event Handlers
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Example of using LifespanMiddleware to manage application startup and shutdown events, including defining handler functions.
```python
from asgi_tools import LifespanMiddleware
app = LifespanMiddleware(app)
@app.on_startup
async def startup():
# Initialize resources
pass
@app.on_shutdown
async def shutdown():
# Cleanup resources
pass
```
--------------------------------
### TExceptionHandler Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
An example of an asynchronous exception handler callable that processes a request and an exception, returning a response.
```python
async def handle_value_error(
request: Request,
exc: ValueError
) -> Response:
return 400, f'Invalid value: {exc}'
```
--------------------------------
### ASGI Message Examples
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
Illustrates the structure of different ASGI message types like http.request, http.response.start, and websocket.connect.
```python
message = {
'type': 'http.request',
'body': b'request body',
'more_body': False
}
```
```python
message = {
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-type', b'text/plain')]
}
```
```python
message = {
'type': 'websocket.connect'
}
```
--------------------------------
### App Class Initialization and Routing
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Initialize the App class and define routes using the @app.route decorator. This example shows how to set up a basic route for fetching user data.
```python
from asgi_tools import App
app = App()
@app.route('/api/users/{id}')
async def get_user(request):
return {'id': request['path_params']['id']}
# Includes: routing, middleware, exception handling, static files, lifespan
```
--------------------------------
### Define Dynamic URL Routes
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Example of creating a route with a dynamic URL parameter that captures a username.
```python
@app.route('/user/{username}')
async def show_user_profile(request):
username = request.path_params['username']
return f'User {username}'
```
--------------------------------
### ASGI Headers Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
Demonstrates the structure of TASGIHeaders, a list of byte tuples representing raw HTTP headers.
```python
headers: TASGIHeaders = [
(b'content-type', b'application/json'),
(b'content-length', b'1234'),
(b'cache-control', b'no-cache'),
(b'set-cookie', b'session=abc123; Path=/'),
]
```
--------------------------------
### Use Convenience HTTP Methods
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Utilize shortcut methods like get, post, put, patch, delete, head, and options for making common HTTP requests.
```python
# GET (via __getattr__)
response = await client.get('/path')
# POST
response = await client.post('/path')
# PUT
response = await client.put('/path')
# PATCH
response = await client.patch('/path')
# DELETE
response = await client.delete('/path')
# HEAD
response = await client.head('/path')
# OPTIONS
response = await client.options('/path')
```
--------------------------------
### WebSocket Handler
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Implement a WebSocket handler using asgi-tools. This example shows how to accept connections, receive messages, and send echo responses within a loop.
```python
from asgi_tools import App, ResponseWebSocket
app = App()
@app.route('/ws')
async def websocket_handler(request):
ws = ResponseWebSocket(request)
async with ws:
await ws.accept()
while ws.connected:
try:
message = await ws.receive()
await ws.send(f'Echo: {message}')
except Exception:
break
return ws
```
--------------------------------
### Basic ASGI App with Request
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/request.md
An example of an ASGI application that uses the Request class to access the request body as text.
```python
from asgi_tools import Request
async def app(scope, receive, send):
request = Request(scope, receive, send)
body = await request.text()
return f"Got: {body}"
```
--------------------------------
### Sending ASGI Responses
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
Demonstrates how to use the TASGISend callable to send HTTP response start and body messages to the client.
```python
async def send_response(send: TASGISend):
# Send response start
await send({
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-type', b'text/plain')]
})
# Send response body
await send({
'type': 'http.response.body',
'body': b'Hello World'
})
```
--------------------------------
### RequestMiddleware Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Shows how to use RequestMiddleware to automatically parse the ASGI scope into a Request object. This simplifies access to request properties like URL, headers, and cookies.
```python
from asgi_tools import RequestMiddleware, Request, Response
async def app(request: Request, receive, send):
# request is now a Request object instead of a dict
body = await request.text()
response = Response(f"Got: {body}")
await response(request.scope, receive, send)
app = RequestMiddleware(app)
```
--------------------------------
### Middleware Integration with RouterMiddleware
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Example of using `RouterMiddleware` to integrate the router into an ASGI application. Routes defined with `@app.route` are handled by the middleware.
```python
from asgi_tools import App, Response, RouterMiddleware
app = RouterMiddleware()
@app.route('/status')
async def status(scope, receive, send):
response = Response('OK')
await response(scope, receive, send)
@app.route('/hello/{name}')
async def hello(scope, receive, send):
name = scope['path_params']['name']
response = Response(f'Hello {name}')
await response(scope, receive, send)
```
--------------------------------
### Basic ASGI Application
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
An example of a simple ASGI application implementing the TASGIApp callable, handling HTTP requests and sending a plain text response.
```python
async def app(scope: TASGIScope, receive: TASGIReceive, send: TASGISend) -> None:
if scope['type'] == 'http':
await send({
'type': 'http.response.start',
'status': 200,
'headers': [(b'content-type', b'text/plain')]
})
await send({
'type': 'http.response.body',
'body': b'Hello'
})
```
--------------------------------
### Testing GET Request
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Example test case using pytest to perform a GET request and assert the status code and response text.
```python
@pytest.mark.asyncio
async def test_get_home(client):
response = await client.get('/')
assert response.status_code == 200
assert await response.text() == 'Hello'
```
--------------------------------
### Testing ASGI Lifespan Protocol
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Example test case demonstrating how to use the client's lifespan context manager to test the ASGI lifespan protocol.
```python
@pytest.mark.asyncio
async def test_lifespan(client):
async with client.lifespan():
response = await client.get('/')
assert response.status_code == 200
```
--------------------------------
### ResponseMiddleware Example with Various Return Types
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Illustrates ResponseMiddleware's ability to convert different return types from an ASGI application into appropriate HTTP responses. It handles Response instances, dictionaries, strings, bytes, tuples, and exceptions.
```python
from asgi_tools import ResponseMiddleware, ResponseError, ResponseRedirect
async def app(scope, receive, send):
path = scope['path']
if path == '/user':
raise ResponseRedirect('/login')
if scope['method'] == 'GET':
return 'Hello
' # → ResponseHTML
if scope['method'] == 'POST':
return {'status': 'ok'} # → ResponseJSON
return 405, 'Method Not Allowed' # → ResponseHTML 405
app = ResponseMiddleware(app)
```
--------------------------------
### Content Types Reference
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Shows examples of creating different response types using Response classes. The library automatically detects content types for files.
```python
ResponseText('text') # text/plain
ResponseHTML('html
') # text/html
ResponseJSON({'key': 'value'}) # application/json
ResponseSSE(async_gen) # text/event-stream
ResponseFile('/path/file') # auto-detected
ResponseStream(async_gen) # application/octet-stream
```
--------------------------------
### Case-Insensitive Method Matching
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
HTTP methods specified in the route definition are case-insensitive. The router will match variations like 'get', 'GET', 'Get', etc.
```python
from asgi_tools import Router
router = Router()
@router.route('/test', methods=['get', 'post'])
# Matches GET, Post, POST, pOsT, etc.
```
--------------------------------
### Handle Form Data and File Uploads
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Example of a route handler that processes POST requests containing form data and file uploads. Includes default size limit information.
```python
@app.route('/upload', methods=['POST'])
async def upload(request):
form = await request.form()
username = form.get('username')
if 'file' in form:
file = form['file']
content = await file.read()
# Process file...
return "Upload successful"
```
--------------------------------
### Handling Generic ASGIError
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/errors.md
Demonstrates how to catch and handle the base ASGIError, which is useful for general error handling or when specific error types are not critical. This example shows catching an error that might occur when trying to create a ResponseFile with a non-existent file.
```python
from asgi_tools import ASGIError, ResponseFile
try:
# This raises ASGIError if file not found
response = ResponseFile('/missing/file.txt')
except ASGIError as e:
print(f'ASGI error: {e}')
```
--------------------------------
### Testing GET Request
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Tests a GET request to retrieve user data and asserts the response status code and JSON content.
```python
@pytest.mark.asyncio
async def test_get_user(client):
response = await client.get('/api/users/123')
assert response.status_code == 200
data = await response.json()
assert data['id'] == '123'
```
--------------------------------
### LifespanMiddleware Startup and Shutdown Handlers
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Illustrates how to register functions to be executed during application startup and shutdown using LifespanMiddleware.
```python
from asgi_tools import LifespanMiddleware, Response
app = LifespanMiddleware()
@app.on_startup
async def startup():
print('App starting')
# Load resources, initialize connections, etc.
@app.on_shutdown
async def shutdown():
print('App shutting down')
# Close connections, cleanup resources, etc.
```
--------------------------------
### Set Per-Request Timeout for Client GET
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Specify a custom timeout for a single GET request made using the test client. The default timeout is 10.0 seconds.
```python
# Per-request timeout
response = await client.get(
'/slow-endpoint',
timeout=30.0 # 30 seconds
)
# Default timeout is 10.0 seconds
```
--------------------------------
### Using a Custom Router with App
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Shows how to create a custom `Router` instance (e.g., with specific configurations like `trim_last_slash=True`) and assign it to an `App` instance.
```python
from asgi_tools import Router, App
# Create custom router
custom_router = Router(trim_last_slash=True)
# Use with App (if needed)
app = App()
app.router = custom_router
@app.route('/path')
async def handler(request):
return 'OK'
```
--------------------------------
### Initialize StaticFilesMiddleware
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initializes the StaticFilesMiddleware to serve static files. Configurable with a URL prefix and a list of directories to serve.
```python
from asgi_tools import StaticFilesMiddleware
middleware = StaticFilesMiddleware(
app=my_app,
url_prefix="/static",
folders=['static', 'public']
)
```
--------------------------------
### Accessing Request Cookies
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/request.md
Demonstrates how to get parsed cookies from the `cookies` property.
```python
session_id = request.cookies.get('session_id')
for name, value in request.cookies.items():
print(f"{name}={value}")
```
--------------------------------
### Getting Response Body as Bytes
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Asynchronously retrieve the complete response body as raw bytes.
```python
async def body() -> bytes
```
--------------------------------
### Get Request Charset
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Retrieve the character set from the Content-Type header of the request. Defaults to UTF-8 if not specified.
```python
# From Content-Type header
charset = request.charset # Defaults to utf-8
# Override charset
charset = request.media.get('charset', 'iso-8859-1')
```
--------------------------------
### Routing Patterns: Static and Multiple Paths
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Illustrates how to define static routes for exact path matching and how to associate a single handler with multiple URL paths.
```APIDOC
## Routing Patterns
### Static Routes
**Exact path matching:**
```python
@router.route('/')
@router.route('/about')
@router.route('/contact')
```
### Multiple Paths
**Same handler for multiple paths:**
```python
@router.route('/about', '/info', '/help')
async def about(request):
return 'About us'
```
```
--------------------------------
### Configure App with Environment Variables
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Set up application debugging, logging level, and static folders based on environment variables. Defaults are provided if variables are not set.
```python
import os
from asgi_tools import App
# Development/Production configuration
is_debug = os.getenv('DEBUG', 'false').lower() == 'true'
log_level = os.getenv('LOG_LEVEL', 'INFO')
static_folders = os.getenv('STATIC_FOLDERS', 'static').split(',')
app = App(
debug=is_debug,
static_folders=static_folders
)
```
--------------------------------
### TJSON Type Example
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/types.md
Demonstrates the structure of TJSON, which represents any valid JSON value including nested structures.
```python
data: TJSON = {
'string': 'value',
'number': 42,
'float': 3.14,
'bool': True,
'null': None,
'array': [1, 2, 3],
'nested': {
'key': 'value'
}
}
```
--------------------------------
### Configure ASGITestClient
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initialize ASGITestClient with the application and base URL. Default headers can also be set.
```python
from asgi_tools.tests import ASGITestClient
client = ASGITestClient(
app=my_app,
base_url='http://localhost:8000'
)
# Set default headers
client.headers['Authorization'] = 'Bearer token'
```
--------------------------------
### App Constructor and Basic Routing
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Instantiate the App class with custom configurations for debugging, static files, and define a simple homepage route.
```python
from asgi_tools import App
app = App(
debug=True,
static_folders=['static', 'public'],
static_url_prefix='/assets'
)
@app.route('/')
async def homepage(request):
return 'Hello World!'
if __name__ == '__main__':
import uvicorn
uvicorn.run(app)
```
--------------------------------
### StaticFilesMiddleware Configuration
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Shows how to configure StaticFilesMiddleware to serve files from specified directories under a given URL prefix.
```python
from asgi_tools import StaticFilesMiddleware, ResponseHTML, ResponseError
async def app(scope, receive, send):
response = ResponseHTML('Hello
')
await response(scope, receive, send)
# Serve files from 'static' and 'public' folders at /static
app = StaticFilesMiddleware(
app,
url_prefix='/static',
folders=['static', 'public']
)
# Now:
# GET /static/style.css → Served from static/ or public/
# GET /other → Handled by app
```
--------------------------------
### Handle JSON Data in POST Requests
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
An example route handler for POST requests that expects and processes JSON data.
```python
@app.route('/api/data', methods=['POST'])
async def handle_json(request):
data = await request.json()
name = data.get('name')
value = data.get('value')
return {"status": "success"}
```
--------------------------------
### Middleware Wrapping with insert_first
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Shows how to wrap an application with middleware that runs first.
```python
@app.middleware(insert_first=True)
async def auth_middleware(handler, request):
if not request.headers.get('authorization'):
return 401, 'Unauthorized'
return await handler(request)
```
--------------------------------
### App Constructor
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Initializes an App instance with various configuration options for debugging, logging, static file serving, and URL trimming.
```APIDOC
## Constructor App
### Description
Initializes an App instance with various configuration options for debugging, logging, static file serving, and URL trimming.
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
None
### Parameters
- **debug** (bool) - Optional - Default: `False` - Enable debug mode (raise exceptions, verbose logging)
- **logger** (logging.Logger) - Optional - Default: `asgi_tools.logger` - Logger instance
- **static_url_prefix** (str) - Optional - Default: `"/static"` - URL prefix for static files
- **static_folders** (list[str | Path]) - Optional - Default: `None` - Directories to serve static files from
- **trim_last_slash** (bool) - Optional - Default: `False` - Treat `/path` and `/path/` as the same
```
--------------------------------
### Apply RequestMiddleware
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Demonstrates how to wrap an existing ASGI application with RequestMiddleware.
```python
from asgi_tools import RequestMiddleware
app = RequestMiddleware(app)
```
--------------------------------
### Class-based View for API Items Endpoint
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Defines GET, POST, and DELETE handlers for the /api/items route using an HTTPView subclass.
```python
from asgi_tools import App, HTTPView, Request
app = App()
@app.route('/api/items')
class ItemsEndpoint(HTTPView):
async def get(self, request: Request):
return {'items': []}
async def post(self, request: Request):
data = await request.json()
return 201, {'created': True, **data}
async def delete(self, request: Request):
return 204, ''
```
--------------------------------
### LifespanMiddleware as Context Manager for Testing
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/middleware.md
Demonstrates using LifespanMiddleware as a context manager, which is useful for testing startup and shutdown logic.
```python
from asgi_tools import LifespanMiddleware
lifespan = LifespanMiddleware(app)
@lifespan.on_startup
async def startup():
print('Starting')
async def test():
async with lifespan:
# startup handlers run here
# test code
pass
# shutdown handlers run here
```
--------------------------------
### Define Basic Routes
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Shows how to define multiple routes, including a root path, a specific path, and a path restricted to POST methods.
```python
@app.route('/')
async def index(request):
return 'Index Page'
@app.route('/hello', '/hello/world')
async def hello(request):
return 'Hello, World!'
@app.route('/only-post', methods=['POST'])
async def only_post(request):
return request.method
```
--------------------------------
### Getting Response Body as Text
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Asynchronously retrieve the response body decoded as text. This is useful for HTML or plain text responses.
```python
async def text() -> str
```
```python
html = await response.text()
print(html)
```
--------------------------------
### Configure Router Options
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initialize a Router with options to control trailing slash behavior, route handler validation, and value conversion.
```python
from asgi_tools import Router
router = Router(
trim_last_slash=False,
validator=callable,
converter=to_awaitable
)
```
--------------------------------
### Configure ResponseFile
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initialize ResponseFile with filepath, chunk size, filename, headers_only flag, status code, and custom headers.
```python
from asgi_tools import ResponseFile
response = ResponseFile(
filepath='/path/to/file.pdf',
chunk_size=65536,
filename='download.pdf',
headers_only=False,
status_code=200,
headers={'X-Custom': 'value'}
)
```
--------------------------------
### Catch-All Routes
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Shows how to implement catch-all routes using dynamic path parameters, often used for serving static files or handling API routes.
```APIDOC
## Catch-All Routes Example
### Description
Catch-all routes capture any path that follows a specific prefix, using the `.*` syntax with a dynamic path parameter. It's recommended to define more specific routes before catch-all routes.
### Routes Defined
- **GET /static/{path}**
- Handler: `static_file`
- Description: Handles requests for static files.
- **GET /api/{path:.*}**
- Handler: `api_catch_all`
- Description: Catches all routes starting with `/api/` that are not explicitly defined.
### Handler Code Snippets
```python
@app.route('/static/{path}')
async def static_file(request):
return 'Static file'
@app.route('/api/{path:.*}')
async def api_catch_all(request):
# Catch any /api/* path
path = request['path_params']['path']
return 404, f'API route not found: /{path}'
```
```
--------------------------------
### Initialize RouterMiddleware
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initializes the RouterMiddleware, optionally providing a fallback ASGI app and a custom Router instance. Defaults to a standard router if none is provided.
```python
from asgi_tools import RouterMiddleware
middleware = RouterMiddleware(
app=my_app,
router=None # Uses default Router instance
)
```
--------------------------------
### Class-based View for Specific API Item Endpoint
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/app.md
Defines GET, PUT, and DELETE handlers for the /api/items/{id} route using an HTTPView subclass.
```python
@app.route('/api/items/{id}')
class ItemEndpoint(HTTPView):
async def get(self, request: Request):
item_id = request['path_params']['id']
return {'id': item_id, 'name': 'Item'}
async def put(self, request: Request):
item_id = request['path_params']['id']
data = await request.json()
return 200, {'id': item_id, **data}
async def delete(self, request: Request):
item_id = request['path_params']['id']
return 204, ''
```
--------------------------------
### App Integration
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Shows how the `App` class automatically creates and uses a `Router` instance, and how to access and customize the router.
```APIDOC
## App Class with Integrated Router
### Description
The `App` class in `asgi-tools` includes an integrated `Router` that is automatically created upon instantiation. You can define routes directly on the `app` instance using `@app.route`.
### Usage
```python
from asgi_tools import App
app = App() # Router is created automatically
@app.route('/users/{id}')
async def get_user(request):
return {'id': request['path_params']['id']}
# Accessing the router instance
print(app.router) # Output:
```
### Custom Router with App
### Description
It is possible to provide a custom `Router` instance to the `App` class.
### Usage
```python
from asgi_tools import Router, App
# Create a custom router instance
custom_router = Router(trim_last_slash=True)
# Assign the custom router to the App
app = App()
app.router = custom_router
@app.route('/path')
async def handler(request):
return 'OK'
```
```
--------------------------------
### Sending Different Message Types with ResponseWebSocket
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/response.md
Shows examples of sending text, binary, and custom JSON data using the send method of ResponseWebSocket.
```python
await ws.send("text message")
await ws.send(b"binary data")
await ws.send({"type": "custom", "data": "raw ASGI"})
```
--------------------------------
### Response Class Examples
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Utilize various Response classes for different content types and scenarios, including JSON, HTML, file streaming, and WebSockets.
```python
from asgi_tools import ResponseJSON, ResponseHTML, ResponseFile, ResponseWebSocket
response = ResponseJSON({'data': 'value'})
response = ResponseHTML('Hello
')
response = ResponseFile('/path/to/file')
response = ResponseWebSocket(request)
```
--------------------------------
### Use ASGITestClient for Testing
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Demonstrates how to use the ASGITestClient to send requests to an ASGI application and assert responses.
```python
from asgi_tools.tests import ASGITestClient
client = ASGITestClient(app)
response = await client.get('/')
assert response.status_code == 200
assert await response.text() == 'Hello, World!'
response = await client.post('/login', data={'username': 'test', 'password': 'secret'})
assert response.status_code == 200
```
--------------------------------
### Manage Application Lifespan for Testing
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Use the 'lifespan' context manager to test ASGI application startup and shutdown handlers. Startup handlers execute upon entering the context, and shutdown handlers execute upon exiting.
```python
app = App()
@app.on_startup
async def startup():
# Setup test database
pass
@app.on_shutdown
async def shutdown():
# Cleanup
pass
client = ASGITestClient(app)
async def test():
async with client.lifespan():
# startup handlers run here
response = await client.get('/')
# test code
# shutdown handlers run here
```
--------------------------------
### Basic WebSocket Handler with ResponseWebSocket
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/response.md
Demonstrates a simple echo server using ResponseWebSocket. It accepts a connection, receives a message, and sends it back to the client.
```python
from asgi_tools import ResponseWebSocket
async def websocket_handler(request):
ws = ResponseWebSocket(request)
async with ws:
message = await ws.receive()
await ws.send(f"Echo: {message}")
return ws
```
--------------------------------
### Minimal ASGI Application
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
A basic ASGI application using asgi-tools with a single route and a simple 'Hello World' response. Includes instructions to run with uvicorn.
```python
from asgi_tools import App
import uvicorn
app = App()
@app.route('/')
async def home(request):
return 'Hello World'
if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0', port=8000)
```
--------------------------------
### HTTP Methods Routing
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Illustrates how to define routes for different HTTP methods (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) using the @app.route decorator.
```python
@app.route('/path', methods=['GET'])
@app.route('/path', methods=['POST'])
@app.route('/path', methods=['PUT'])
@app.route('/path', methods=['DELETE'])
@app.route('/path', methods=['PATCH'])
@app.route('/path', methods=['HEAD'])
@app.route('/path', methods=['OPTIONS'])
```
--------------------------------
### Initialize LifespanMiddleware
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/configuration.md
Initializes the LifespanMiddleware to manage application startup and shutdown events. Allows custom handlers and error handling configuration.
```python
from asgi_tools import LifespanMiddleware
middleware = LifespanMiddleware(
app=my_app,
logger=default_logger,
ignore_errors=False,
on_startup=None,
on_shutdown=None
)
```
--------------------------------
### Creating Temporary and Permanent Redirects with ResponseRedirect
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/response.md
Provides examples for creating HTTP redirects using ResponseRedirect, specifying temporary (307) and permanent (301) status codes.
```python
# Temporary redirect (default 307)
response = ResponseRedirect('/new/location')
# Permanent redirect (301)
response = ResponseRedirect('/new/location', status_code=301)
# See Other (303)
response = ResponseRedirect('/other', status_code=303)
```
--------------------------------
### Utility Exports from asgi_tools.utils
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/overview.md
Lists utility functions for awaitable handling, header parsing, and dictionary manipulation from asgi_tools.utils.
```python
from asgi_tools.utils import (
is_awaitable,
to_awaitable,
parse_headers,
parse_options_header,
CIMultiDict,
)
```
--------------------------------
### ASGITestClient.websocket()
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Establishes a WebSocket connection for testing.
```APIDOC
## websocket()
### Description
Create a WebSocket connection for testing.
### Method
Asynchronous context manager.
### Endpoint
Specified by the `path` parameter.
### Parameters
#### Path Parameters
None
#### Query Parameters
- **query** (str | dict | None) - Optional - Query string or parameters
#### Request Body
None
#### Other Parameters
- **path** (str) - Required - WebSocket path
- **headers** (dict | None) - Optional - Request headers
- **cookies** (dict | None) - Optional - Request cookies
### Yields
- **TestWebSocketResponse** (object) - An object to interact with the WebSocket connection.
### Request Example
```python
async def test_websocket():
async with client.websocket('/ws') as ws:
# Send message
await ws.send('Hello')
# Receive message
msg = await ws.receive()
assert msg == 'Echo: Hello'
# Send JSON
await ws.send_json({'type': 'ping'})
# Connection auto-closes on exit
```
```
--------------------------------
### Specify Allowed HTTP Methods for Routes
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Define specific HTTP methods (GET, POST, PUT, DELETE) that a route can handle. If a request method does not match, an ASGIInvalidMethodError will be raised.
```python
from asgi_tools import Router
router = Router()
@router.route('/items', methods=['GET', 'POST'])
async def items_handler(request):
if request['method'] == 'GET':
return [] # List items
elif request['method'] == 'POST':
return {'created': True} # Create item
@router.route('/items/{id}', methods=['GET', 'PUT', 'DELETE'])
async def item_handler(request):
item_id = request['path_params']['id']
if request['method'] == 'GET':
return {'id': item_id}
elif request['method'] == 'PUT':
return {'updated': True}
elif request['method'] == 'DELETE':
return {'deleted': True}
```
--------------------------------
### Apply ResponseMiddleware
Source: https://github.com/klen/asgi-tools/blob/develop/docs/usage.md
Shows how to integrate ResponseMiddleware into an ASGI application stack.
```python
from asgi_tools import ResponseMiddleware
app = ResponseMiddleware(app)
```
--------------------------------
### Creating a JSON Response
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/response.md
Use ResponseJSON to return JSON-encoded data. It supports ujson and orjson if installed, falling back to the standard json library. The default Content-Type is 'application/json'.
```python
from asgi_tools import ResponseJSON
response = ResponseJSON({'message': 'hello', 'status': 'ok'})
response = ResponseJSON(['item1', 'item2'])
```
--------------------------------
### Catch-All Route Implementation
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/router.md
Use a catch-all route pattern (e.g., `/{path:.*}`) to handle any path that doesn't match more specific routes. It's recommended to define specific routes before catch-all routes.
```python
from asgi_tools import App
app = App()
# Specific routes first
@app.route('/static/{path}')
async def static_file(request):
return 'Static file'
@app.route('/api/{path:.*}')
async def api_catch_all(request):
# Catch any /api/* path
path = request['path_params']['path']
return 404, f'API route not found: /{path}'
```
--------------------------------
### Testing POST Request with JSON Payload
Source: https://github.com/klen/asgi-tools/blob/develop/_autodocs/test-client.md
Example test case for a POST request, sending JSON data and asserting the response status code and parsed JSON body.
```python
@pytest.mark.asyncio
async def test_post_user(client):
response = await client.post(
'/users',
json={'name': 'John', 'email': 'john@example.com'}
)
assert response.status_code == 201
data = await response.json()
assert data['name'] == 'John'
```