Skip to main content

The little ASGI library that shines.

Project description

starlette

✨ The little ASGI library that shines. ✨

Build Status Coverage Package version


Starlette is a small library for working with ASGI.

It gives you Request and Response classes, websocket support, routing, static files support, and a test client.

Requirements:

Python 3.6+

Installation:

pip3 install starlette

Example:

from starlette.response import Response


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = Response('Hello, world!', media_type='text/plain')
        await response(receive, send)

You can run the application with any ASGI server, including uvicorn, daphne, or hypercorn.

— ⭐️ —


Contents


Responses

Starlette includes a few response classes that handle sending back the appropriate ASGI messages on the send channel.

Response

Signature: Response(content, status_code=200, headers=None, media_type=None)

  • content - A string or bytestring.
  • status_code - An integer HTTP status code.
  • headers - A dictionary of strings.
  • media_type - A string giving the media type. eg. "text/html"

Starlette will automatically include a Content-Length header. It will also include a Content-Type header, based on the media_type and appending a charset for text types.

Once you've instantiated a response, you can send it by calling it as an ASGI application instance.

class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = Response('Hello, world!', media_type='text/plain')
        await response(receive, send)

HTMLResponse

Takes some text or bytes and returns an HTML response.

from starlette.response import HTMLResponse


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = HTMLResponse('<html><body><h1>Hello, world!</h1></body></html>')
        await response(receive, send)

PlainTextResponse

Takes some text or bytes and returns an plain text response.

from starlette.response import PlainTextResponse


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = PlainTextResponse('Hello, world!')
        await response(receive, send)

JSONResponse

Takes some data and returns an application/json encoded response.

from starlette.response import JSONResponse


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = JSONResponse({'hello': 'world'})
        await response(receive, send)

RedirectResponse

Returns an HTTP redirect. Uses a 302 status code by default.

from starlette.response import PlainTextResponse, RedirectResponse


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        if self.scope['path'] != '/':
            response = RedirectResponse(url='/')
        else:
            response = PlainTextResponse('Hello, world!')
        await response(receive, send)

StreamingResponse

Takes an async generator and streams the response body.

from starlette.request import Request
from starlette.response import StreamingResponse
import asyncio


async def slow_numbers(minimum, maximum):
    yield('<html><body><ul>')
    for number in range(minimum, maximum + 1):
        yield '<li>%d</li>' % number
        await asyncio.sleep(0.5)
    yield('</ul></body></html>')


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        generator = slow_numbers(1, 10)
        response = StreamingResponse(generator, media_type='text/html')
        await response(receive, send)

FileResponse

Asynchronously streams a file as the response.

Takes a different set of arguments to instantiate than the other response types:

  • path - The filepath to the file to stream.
  • headers - Any custom headers to include, as a dictionary.
  • media_type - A string giving the media type. If unset, the filename or path will be used to infer a media type.
  • filename - If set, this will be included in the response Content-Disposition.

File responses will include appropriate Content-Length, Last-Modified and ETag headers.

from starlette.response import FileResponse


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = FileResponse('/statics/favicon.ico')
        await response(receive, send)

Requests

Starlette includes a Request class that gives you a nicer interface onto the incoming request, rather than accessing the ASGI scope and receive channel directly.

Request

Signature: Request(scope, receive=None)

class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        request = Request(self.scope, receive)
        content = '%s %s' % (request.method, request.url.path)
        response = Response(content, media_type='text/plain')
        await response(receive, send)

Requests present a mapping interface, so you can use them in the same way as a scope.

For instance: request['path'] will return the ASGI path.

If you don't need to access the request body you can instantiate a request without providing an argument to receive.

Method

The request method is accessed as request.method.

URL

The request URL is accessed as request.url.

The property is actually a subclass of str, and also exposes all the components that can be parsed out of the URL.

For example: request.url.path, request.url.port, request.url.scheme.

Headers

Headers are exposed as an immutable, case-insensitive, multi-dict.

For example: request.headers['content-type']

Query Parameters

Headers are exposed as an immutable multi-dict.

For example: request.query_params['abc']

Body

There are a few different interfaces for returning the body of the request:

The request body as bytes: await request.body()

The request body, parsed as JSON: await request.json()

You can also access the request body as a stream, using the async for syntax:

class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        request = Request(self.scope, receive)
        body = b''
        async for chunk in request.stream():
            body += chunk
        response = Response(body, media_type='text/plain')
        await response(receive, send)

If you access .stream() then the byte chunks are provided without storing the entire body to memory. Any subsequent calls to .body() and .json() will raise an error.


WebSockets

Starlette includes a WebSocketSessions class that fulfils a similar role to the HTTP request, but that allows sending and receiving data on a websocket session.

WebSocketSession

Signature: WebSocketSession(scope, receive=None, send=None)

from starlette.websockets import WebSocketSession


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        session = WebSocketSession(self.scope, receive=receive, send=send)
        await session.accept()
        await session.send_text('Hello, world!')
        await session.close()

Sessions present a mapping interface, so you can use them in the same way as a scope.

For instance: session['path'] will return the ASGI path.

URL

The session URL is accessed as session.url.

The property is actually a subclass of str, and also exposes all the components that can be parsed out of the URL.

For example: session.url.path, session.url.port, session.url.scheme.

Headers

Headers are exposed as an immutable, case-insensitive, multi-dict.

For example: session.headers['sec-websocket-version']

Query Parameters

Headers are exposed as an immutable multi-dict.

For example: session.query_params['abc']

Accepting the connection

  • await session.accept(subprotocol=None)

Sending data

  • await session.send_text(data)
  • await session.send_bytes(data)
  • await session.send_json(data)

Receiving data

  • await session.receive_text()
  • await session.receive_bytes()
  • await session.receive_json()

May raise starlette.websockets.Disconnect().

Closing the connection

  • await session.close(code=1000)

Sending and receiving messages

If you need to send or receive raw ASGI messages then you should use session.send() and session.receive() rather than using the raw send and receive callables. This will ensure that the session's state is kept correctly updated.

  • await session.send(message)
  • await session.receive()

Routing

Starlette includes a Router class which is an ASGI application that dispatches to other ASGI applications.

from starlette.routing import Router, Path, PathPrefix
from myproject import Homepage, SubMountedApp


app = Router([
    Path('/', app=Homepage, methods=['GET']),
    PathPrefix('/mount/', app=SubMountedApp)
])

Paths can use URI templating style to capture path components.

Path('/users/{username}', app=User, methods=['GET'])

Path components are made available in the scope, as scope["kwargs"].

You can also use regular expressions for more complicated matching.

Named capture groups will be included in scope["kwargs"]:

Path('/users/(?P<username>[a-zA-Z0-9_]{1,20})', app=User, methods=['GET'])

Because each target of the router is an ASGI instance itself, routers allow for easy composition. For example:

app = Router([
    Path('/', app=Homepage, methods=['GET']),
    PathPrefix('/users', app=Router([
        Path('/', app=Users, methods=['GET', 'POST']),
        Path('/{username}', app=User, methods=['GET']),
    ]))
])

The router will respond with "404 Not found" or "406 Method not allowed" responses for requests which do not match.

Protocol Routing

You can also route based on the incoming protocol, using the ProtocolRouter class.

from starlette.responses import Response
from starlette.routing import ProtocolRouter
from starlette.websockets import WebSocketSession


def http_endpoint(scope):
    return Response("Hello, world", media_type="text/plain")


def websocket_endpoint(scope):
    async def asgi(receive, send):
        session = WebSocketSession(scope, receive, send)
        await session.accept()
        await session.send_json({"hello": "world"})
        await session.close()
    return asgi


app = ProtocolRouter({
    "http": http_endpoint,
    "websocket": websocket_endpoint
})

Static Files

As well as the FileResponse class, Starlette also includes ASGI applications for serving a specific file or directory:

  • StaticFile(path) - Serve a single file, given by path.
  • StaticFiles(directory) - Serve any files in the given directory.

You can combine these ASGI applications with Starlette's routing to provide comprehensive static file serving.

from starlette.routing import Router, Path, PathPrefix
from starlette.staticfiles import StaticFile, StaticFiles


app = Router(routes=[
    Path('/', app=StaticFile(path='index.html')),
    PathPrefix('/static/', app=StaticFiles(directory='static')),
])

Static files will respond with "404 Not found" or "406 Method not allowed" responses for requests which do not match.


Test Client

The test client allows you to make requests against your ASGI application, using the requests library.

from starlette.response import HTMLResponse
from starlette.testclient import TestClient


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        response = HTMLResponse('<html><body>Hello, world!</body></html>')
        await response(receive, send)


def test_app():
    client = TestClient(App)
    response = client.get('/')
    assert response.status_code == 200

The test client exposes the same interface as any other requests session. In particular, note that the calls to make a request are just standard function calls, not awaitables.

Testing WebSocket applications

You can also test websocket applications with the test client.

The requests library will be used to build the initial handshake, meaning you can use the same authentication options and other headers between both http and websocket testing.

from starlette.testclient import TestClient
from starlette.websockets import WebSocketSession


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        session = WebSocketSession(self.scope, receive=receive, send=send)
        await session.accept()
        await session.send_text('Hello, world!')
        await session.close()


def test_app():
    client = TestClient(App)
    with client.websocket_connect('/') as session:
        data = session.receive_text()
        assert data == 'Hello, world!'

The operations on session are standard function calls, not awaitables.

It's important to use the session within a context-managed with block. This ensure that the background thread on which the ASGI application is properly terminated, and that any exceptions that occur within the application are always raised by the test client.

Establishing a test session

  • .websocket_connect(url, subprotocols=None, **options) - Takes the same set of arguments as requests.get().

May raise starlette.websockets.Disconnect if the application does not accept the websocket connection.

Sending data

  • .send_text(data) - Send the given text to the application.
  • .send_bytes(data) - Send the given bytes to the application.
  • .send_json(data) - Send the given data to the application.

Receiving data

  • .receive_text() - Wait for incoming text sent by the application and return it.
  • .receive_bytes() - Wait for incoming bytestring sent by the application and return it.
  • .receive_json() - Wait for incoming json data sent by the application and return it.

May raise starlette.websockets.Disconnect.

Closing the connection

  • .close(code=1000) - Perform a client-side close of the websocket connection.

Debugging

You can use Starlette's DebugMiddleware to display simple error tracebacks in the browser.

from starlette.debug import DebugMiddleware


class App:
    def __init__(self, scope):
        self.scope = scope

    async def __call__(self, receive, send):
        raise RuntimeError('Something went wrong')


app = DebugMiddleware(App)

Applications

Starlette also includes an App class that nicely ties together all of its other functionality.

from starlette.app import App
from starlette.response import PlainTextResponse
from starlette.staticfiles import StaticFiles


app = App()
app.mount("/static", StaticFiles(directory="static"))


@app.route('/')
def homepage(request):
    return PlainTextResponse('Hello, world!')


@app.route('/user/{username}')
def user(request, username):
    return PlainTextResponse('Hello, %s!' % username)


@app.websocket_route('/ws')
async def websocket_endpoint(session):
    await session.accept()
    await session.send_text('Hello, websocket!')
    await session.close()

Adding routes to the application

You can use any of the following to add handled routes to the application:

  • .add_route(path, func, methods=["GET"]) - Add an HTTP route. The function may be either a coroutine or a regular function, with a signature like func(request **kwargs) -> response.
  • .add_websocket_route(path, func) - Add a websocket session route. The function must be a coroutine, with a signature like func(session, **kwargs).
  • .mount(prefix, app) - Include an ASGI app, mounted under the given path prefix
  • .route(path) - Add an HTTP route, decorator style.
  • .websocket_route(path) - Add a WebSocket route, decorator style.

Starlette is BSD licensed code.
Designed & built in Brighton, England.

— ⭐️ —

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

starlette-0.2.0.tar.gz (20.3 kB view details)

Uploaded Source

File details

Details for the file starlette-0.2.0.tar.gz.

File metadata

  • Download URL: starlette-0.2.0.tar.gz
  • Upload date:
  • Size: 20.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.19.1 setuptools/39.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/3.6.1

File hashes

Hashes for starlette-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9ef0b80b4580d718baa9161a7a433120d7727175d5240f96daae76f4842b871b
MD5 4b2ef27913be838edd5a39e52c275b0f
BLAKE2b-256 17cdd073594abc687cbb18af0bcae3147e0f4f9dfc9b775691a486cbab60ea6b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page