### 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' ```