# Python : Web : Starlette : Overview \[ [PyPi](https://pypi.org/project/starlette/) | [Source](https://github.com/encode/starlette/) | [Docs](https://www.starlette.io/applications/) ] ## Overview Starlette is an async, lightweight, modular ASGI framework that can be treated like a toolkit by using individual pieces. - WebSockets - in-process background tasks - startup and shutdown events - test client built on httpx - CORS, GZip, Static Files, Streaming responses - sessions and cookies - no hard dependencies ```bash $ pip install starlette, uvicorn $ uvicorn [--reload] example:app ``` #### Optional Dependencies - httpx -> Required if you want to use the TestClient. - itsdangerous -> Required for SessionMiddleware support. - jinja2 -> Required if you want to use Jinja2Templates. - python-multipart -> Required if you want to support form parsing, with request.form(). - pyyaml -> Required for SchemaGenerator support. #### Example ```python [async] def homepage( request ): return PlainTextResponse( "Hello, world!" ) [async] def profile( request ): username = request.path_params[ "username" ] return JSONResponse({ "username": username }) async def favicon( request ): return FileResponse( "favicon.ico" ) # or Path instance async def websocket_endpoint( websocket ): await websocket.accept() await websocket.send_text( "Hello, websocket!" ) await websocket.close() routes = [ Route( "/", homepage ), Route( "/user/{ username }", profile ), WebSocketRoute( "/ws", websocket_endpoint ), Mount( "/static", StaticFiles( directory="static" ) ), ] app = Starlette( debug=True, routes=routes ) ``` **Submounting** ```python routes = [ Route( "/", homepage ), Mount( "/users", routes=[ Route( "/", users, methods=[ "GET", "POST" ] ), Route( "/{ username }", user ), ] ) ] ``` ## Reference #### Imports ```python from starlette.applications import Starlette from starlette.responses import FileResponse, HTMLResponse, JSONResponse, PlainTextResponse from starlette.requests import Request from starlette.routing import Mount, Route, WebSocketRoute from starlette.staticfiles import StaticFiles from starlette.testclient import TestClient from starlette.websockets import WebSocket, WebSocketDisconnect from starlette.middleware import Middleware from starlette.middleware.cors import CORSMiddleware from starlette.middleware.gzip import GZipMiddleware from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware from starlette.middleware.sessions import SessionMiddleware from starlette.middleware.trustedhost import TrustedHostMiddleware ``` #### Applications ```python app = Starlette( debug = False # whether debug tracebacks should be returned on errors routes = None # list of Route and WebsocketRoute instances middleware = None # list exception_handlers = None # { [ integer status code | exception class type ]: callable } # (sync or async) callable( request, exc ) -> response # lifespan is meant to replace on_startup + on_shutdown handlers on_startup = None # list of callables (no arguments) (sync or async) on_shutdown = None # list of callables (no arguments) (sync or async) lifespan = None # lifespan context function - can be used to perform startup and shutdown tasks ) # You can store arbitrary extra state on the application instance, using the generic app.state attribute. app.state.ADMIN_EMAIL = '[email protected]' ``` #### Routing **Route Endpoints** - A regular function or async function, which accepts a single request argument and which should return a response. - A class that implements the ASGI interface, such as Starlette's `HTTPEndpoint`. **WebSocketRoute Endpoints** - An async function, which accepts a single websocket argument. - A class that implements the ASGI interface, such as Starlette's `WebSocketEndpoint`. ```python Route( path endpoint = func_or_class # reminder: expects a class, not an instance methods = [ "GET", "POST" ] # defaults to "GET" only name = "..." # optional ) WebSocketRoute # same signature as Route, but endpoint must be async and receives "websocket" instead of "request" Mount( path app = asgi_app # ex: Mount( "/static", app=StaticFiles( directory="static" ) ) routes = [ ... ] # optional name = "..." # optional ) # add mounts after creating app app.mount( path, subapp, name=None ) ``` **Paths** ``` "/foo/{ bar }" # bar will capture to end of path or next "/"; accessible via request.path_params[ "bar" ] "/{ foo_id:int }" # using int "converter" # Convertors: str (default), int, float, uuid, path (rest of path including additional "/" chars) # You can also register custom convertors. (see docs) ``` **Host-Based Routing** See [docs](https://www.starlette.io/routing/#host-based-routing). #### Request ```python request.app request.client # None | namedtuple( host, port ) request.cookies # dict request.headers # immutable case-insensitive multi-dict request.method request.path_params # dict request.query_params # immnutable multi-dict request.state # to attach state to request (for convenience) request.url[.path|port|scheme...] await request.body() # bytes await request.json() async with request.form() as form: # parsed as form data or multipart ... async for chunk in request.stream(): # read body as stream ... await request.is_disconnected() ``` #### Websocket TODO #### Response ```python Response( content, status_code=200, headers=None, media_type=None ) # content -> str | bytes, media_type ex: "text/html" response.set_cookie( ... ) response.delete_cookie( ... ) PlainTextResponse( ... ) # sets media_type = "text/plain" HTMLResponse( ... ) # sets media_type = "text/html" JSONResponse( ... ) # sets media_type = "application/json"; content -> Any FileResponse( path, ... ) # will guess media type RedirectResponse( url, status_code=307, headers=None ) StreamingResponse( ...TODO... ) ``` #### Reverse URL Lookups ```python request.url_for( name, **path_params ) -> URL # Route( ..., name="foo" ) + url_for( "foo" ) # If a Mount includes a name, then submounts should use a { prefix }:{ name } style. request.url_for( "users:user_list" ) # Appending a subpath to a named route. Mount( "/static", app=StaticFiles( directory="static" ), name="static" ) url = request.url_for( "static", path="/css/base.css" ) # Supplying params for path. app.url_path_for( "user_detail", username="foo" ) -> "/user/foo" ``` ## Middleware A starlette application will always automatically include two middleware classes. - outermost: `ServerErrorMiddleware` -> Ensures that application exceptions may return a custom 500 page, or display an application traceback in DEBUG mode. - innermost: `ExceptionMiddleware` -> Adds exception handlers, so that particular types of expected exception cases can be associated with handler functions. **Example** ```python # Ensure that all requests include an 'example.com' or '*.example.com' host header, and strictly enforce https-only access. middleware = [ Middleware( TrustedHostMiddleware, allowed_hosts=[ "example.com", "*.example.com" ], ), Middleware( HTTPSRedirectMiddleware ) ] app = Starlette( routes=routes, middleware=middleware ) ``` **Applying to a Subset of Routes** Note that middleware used in this way is not wrapped in exception handling middleware like the middleware applied to the Starlette application is. ```python Route( "/foo", endpoint=..., middleware=[ Middleware( GZipMiddleware ) ] ) Mount( "/foo", routes=[ ... ], middleware=[ Middleware( GZipMiddleware ) ] ) ``` #### CORSMiddleware ```python Middleware( CORSMiddleware, allow_origins=[ "*" ], allow_methods=[ "GET", "POST", "OPTIONS" ], allow_headers=[ "Authorization" ] ) allow_origins # A list of origins that should be permitted to make cross-origin requests. # eg. [ 'https://example.org', 'https://www.example.org' ]. # You can use [ '*' ] to allow any origin. allow_origin_regex # A regex string to match against origins that should be permitted to make cross-origin requests. # eg. 'https://.*\.example\.org'. allow_methods # A list of HTTP methods that should be allowed for cross-origin requests. # Defaults to [ 'GET' ]. You can use [ '*' ] to allow all standard methods. allow_headers # A list of HTTP request headers that should be supported for cross-origin requests. # Defaults to []. You can use [ '*' ] to allow all headers. # The Accept, Accept-Language, Content-Language and Content-Type headers are always allowed for CORS requests. allow_credentials # Indicate that cookies should be supported for cross-origin requests. # Defaults to False. # Also, allow_origins, allow_methods and allow_headers cannot be set to [ '*' ] for credentials to be allowed, all of them must be explicitly specified. expose_headers # Indicate any response headers that should be made accessible to the browser. # Defaults to []. max_age # Sets a maximum time in seconds for browsers to cache CORS responses. # Defaults to 600. ``` #### HTTPSRedirectMiddleware ```python Middleware( HTTPSRedirectMiddleware ) # no options ``` #### Third-Party - [asgi-csrf](https://pypi.org/project/asgi-csrf/) -> Implements the Double Submit Cookie pattern, where a cookie is set, then it is compared to a csrftoken hidden form field or an x-csrftoken HTTP header. - [starlette-authlib](https://pypi.org/project/starlette-authlib/) -> A drop-in replacement for Starlette session middleware, using authlib's [jwt](https://docs.authlib.org/en/latest/jose/jwt.html) module. - [starlette-bugsnag](https://pypi.org/project/starlette-bugsnag/) → For logging exceptions to Bugsnag. - [starlette-csrf](https://pypi.org/project/starlette-csrf/) -> Implements same pattern as asgi-csrf (Double Submit Cookie). - [timing-asgi](https://pypi.org/project/timing-asgi/) -> Emit timing information (cpu and wall time) for each request. - [rollbar](https://pypi.org/project/rollbar/) -> For logging exceptions, errors, and log messages to Rollbar. --- TODO: How to write your own middleware.