API Reference
=============
.. module:: asgi_tools
This part of the documentation covers all the interfaces of ASGI-Tools.
Request
-------
.. autoclass:: Request
The class gives you an nicer interface to incoming HTTP request.
.. code-block:: python
from asgi_tools import Request, Response
async def app(scope, receive, send):
request = Request(scope, receive, send)
content = f"{ request.method } { request.url.path }"
response = Response(content)
await response(scope, receive, send)
Requests are based on a given scope and represents a mapping interface.
.. code-block:: python
request = Request(scope, receive, send)
assert request['version'] == scope['version']
assert request['method'] == scope['method']
assert request['scheme'] == scope['scheme']
assert request['path'] == scope['path']
# and etc
# ASGI Scope keys also are available as Request attrubutes.
assert request.version == scope['version']
assert request.method == scope['method']
assert request.scheme == scope['scheme']
.. autoattribute:: headers
.. autoattribute:: cookies
.. autoattribute:: query
.. autoattribute:: charset
.. autoattribute:: content_type
.. autoattribute:: url
.. automethod:: stream
.. code-block:: python
from asgi_tools import Request, Response
async def app(scope, receive, send):
request = Request(scope, receive, send)
body = b''
async for chunk in request.stream():
body += chunk
response = Response(body, content_type=request.content_type)
await response(scope, receive, send)
.. automethod:: body
.. automethod:: text
.. automethod:: form
.. automethod:: json
.. automethod:: data
Responses
---------
.. autoclass:: Response
A helper to make http responses.
.. code-block:: python
from asgi_tools import Response
async def app(scope, receive, send):
response = Response('Hello, world!', content_type='text/plain')
await response(scope, receive, send)
.. autoattribute:: headers
.. autoattribute:: cookies
.. code-block:: python
from asgi_tools import Response
async def app(scope, receive, send):
response = Response('OK')
response.cookies["rocky"] = "road"
response.cookies["rocky"]["path"] = "/cookie"
await response(scope, receive, send)
.. automethod:: __call__
ResponseText
^^^^^^^^^^^^
.. autoclass:: ResponseText
.. code-block:: python
from asgi_tools import ResponseText
async def app(scope, receive, send):
response = ResponseText('Hello, world!')
await response(scope, receive, send)
ResponseHTML
^^^^^^^^^^^^
.. autoclass:: ResponseHTML
.. code-block:: python
from asgi_tools import ResponseHTML
async def app(scope, receive, send):
response = ResponseHTML('
Hello, world!
')
await response(scope, receive, send)
ResponseJSON
^^^^^^^^^^^^
.. autoclass:: ResponseJSON
.. code-block:: python
from asgi_tools import ResponseJSON
async def app(scope, receive, send):
response = ResponseJSON({'hello': 'world'})
await response(scope, receive, send)
ResponseRedirect
^^^^^^^^^^^^^^^^
.. autoclass:: ResponseRedirect
.. code-block:: python
from asgi_tools import ResponseRedirect
async def app(scope, receive, send):
response = ResponseRedirect('/login')
await response(scope, receive, send)
If you are using :py:class:`asgi_tools.App` or :py:class:`asgi_tools.ResponseMiddleware` you
are able to raise the :py:class:`ResponseRedirect` as an exception.
.. code-block:: python
from asgi_tools import ResponseRedirect, Request, ResponseMiddleware
async def app(scope, receive, send):
request = Request(scope, receive, send)
if not request.headers.get('authorization):
raise ResponseRedirect('/login')
return 'OK'
app = ResponseMiddleware(app)
ResponseError
^^^^^^^^^^^^^
.. py:class:: ResponseError(message=None, status_code=500, **kwargs)
A helper to return HTTP errors. Uses a 500 status code by default.
.. :comment: ***
:param message: A string with the error's message (HTTPStatus messages will be used by default)
.. code-block:: python
from asgi_tools import ResponseError
async def app(scope, receive, send):
response = ResponseError('Timeout', 502)
await response(scope, receive, send)
If you are using :py:class:`asgi_tools.App` or :py:class:`asgi_tools.ResponseMiddleware` you
are able to raise the :py:class:`ResponseError` as an exception.
.. code-block:: python
from asgi_tools import ResponseError, Request, ResponseMiddleware
async def app(scope, receive, send):
request = Request(scope, receive, send)
if not request.method == 'POST':
raise ResponseError('Invalid request data', 400)
return 'OK'
app = ResponseMiddleware(app)
You able to use :py:class:`http.HTTPStatus` properties with the `ResponseError` class
.. code-block:: python
response = ResponseError.BAD_REQUEST('invalid data')
response = ResponseError.NOT_FOUND()
response = ResponseError.BAD_GATEWAY()
# and etc
ResponseStream
^^^^^^^^^^^^^^
.. autoclass:: ResponseStream
.. code-block:: python
from asgi_tools import ResponseStream
from asgi_tools.utils import aio_sleep # for compatability with different async libs
async def stream_response():
for number in range(10):
await aio_sleep(1)
yield str(number)
async def app(scope, receive, send):
generator = stream_response()
response = ResponseStream(generator, content_type='plain/text')
await response(scope, receive, send)
ResponseSSE
^^^^^^^^^^^
.. autoclass:: ResponseSSE
.. code-block:: python
from asgi_tools import ResponseSSE
from asgi_tools.utils import aio_sleep # for compatability with different async libs
async def stream_response():
for number in range(10):
await aio_sleep(1)
# The response support messages as text
yield "data: message text"
# And as dictionaties as weel
yield {
"event": "ping",
"data": time.time(),
}
async def app(scope, receive, send):
generator = stream_response()
response = ResponseSSE(generator)
await response(scope, receive, send)
ResponseFile
^^^^^^^^^^^^
.. autoclass:: ResponseFile
.. code-block:: python
from asgi_tools import ResponseFile
async def app(scope, receive, send):
# Return file
if scope['path'] == '/selfie':
response = ResponseFile('/storage/my_best_selfie.jpeg')
# Download file
else:
response = ResponseFile('/storage/video-2020-01-01.mp4', filename='movie.mp4')
await response(scope, receive, send)
ResponseWebSocket
^^^^^^^^^^^^^^^^^
.. autoclass:: ResponseWebSocket
.. code-block:: python
from asgi_tools import ResponseWebsocket
async def app(scope, receive, send):
async with ResponseWebSocket(scope, receive, send) as ws:
msg = await ws.receive()
assert msg == 'ping'
await ws.send('pong')
.. automethod:: accept
.. automethod:: close
.. automethod:: send
.. automethod:: send_json
.. automethod:: receive
Middlewares
-----------
RequestMiddleware
^^^^^^^^^^^^^^^^^^
.. autoclass:: RequestMiddleware
ResponseMiddleware
^^^^^^^^^^^^^^^^^^
.. autoclass:: ResponseMiddleware
LifespanMiddleware
^^^^^^^^^^^^^^^^^^
.. autoclass:: LifespanMiddleware
:members: on_startup, on_shutdown
RouterMiddleware
^^^^^^^^^^^^^^^^
.. autoclass:: RouterMiddleware
StaticFilesMiddleware
^^^^^^^^^^^^^^^^^^^^^
.. autoclass:: StaticFilesMiddleware
BackgroundMiddleware
^^^^^^^^^^^^^^^^^^^^
.. autoclass:: BackgroundMiddleware
Application
-----------
.. autoclass:: App
.. automethod:: route
.. automethod:: on_startup
.. automethod:: on_shutdown
.. automethod:: on_error
.. automethod:: middleware
The :meth:`App.middleware` supports two types of middlewares, see below:
.. code-block:: python
from asgi_tools import App, Request, ResponseError
app = App()
# Classic (any ASGI middleware)
app = classic_middleware(app)
# As an alternative, register a "classic" middleware
@app.middleware
def classic_middleware(app):
async def handler(scope, receive, send):
if not Request(scope, receive, send).headers['authorization']:
response = ResponseError.UNAUTHORIZED()
await response(scope, receive, send)
else:
await app(scope, receive, send)
return handler
# Register an internal middleware (the middleware function is async)
# The middlewares is guaranted to get a response from app and have to return a response
@app.middleware
async def simple_middleware(app, request, receive, send):
response = await app(request, receive, send)
response.headers['x-agent'] = 'SimpleX'
return response
.. admonition:: Middleware Exceptions
Any exception raised from an middleware wouldn't be catched by the app
Class Based Views
-----------------
.. autoclass:: HTTPView
TestClient
-----------
.. autoclass:: asgi_tools.tests.ASGITestClient
.. automethod:: request
.. code-block:: python
from asgi_tools import App
from asgi_tools.tests import ASGITestClient
app = Application()
@app.route('/')
async def index(request):
return 'OK'
async def test_app():
client = ASGITestClient(app)
response = await client.get('/')
assert response.status_code == 200
assert await response.text() == 'OK'
Stream Request
.. code-block:: python
async def test_app():
client = ASGITestClient(app)
async def stream():
for n in range(10):
yield b'chunk%s' % bytes(n)
await aio_sleep(1)
response = await client.get('/', data=stream)
assert response.status_code == 200
Stream Response
.. code-block:: python
@app.route('/')
async def index(request):
async def stream():
for n in range(10):
yield b'chunk%s' % bytes(n)
await aio_sleep(1)
return ResponseStream(stream)
async def test_app():
client = ASGITestClient(app)
response = await client.get('/')
assert response.status_code == 200
async for chunk in response.stream():
assert chunk.startswith('chunk')
.. automethod:: websocket
.. code-block:: python
from asgi_tools import App, ResponseWebSocket
from asgi_tools.tests import ASGITestClient
app = Application()
@app.route('/websocket')
async def websocket(request):
async with ResponseWebSocket(request) as ws:
msg = await ws.receive()
assert msg == 'ping'
await ws.send('pong')
async def test_app():
client = ASGITestClient(app)
async with client.websocket('/websocket') as ws:
await ws.send('ping')
msg = await ws.receive()
assert msg == 'pong'
.. automethod:: lifespan
.. code-block:: python
from asgi_tools import ResponseHTML
from asgi_tools.tests import ASGITestClient
SIDE_EFFECTS = {'started': False, 'finished': False}
async def app(scope, receive, send):
# Process lifespan events
if scope['type'] == 'lifespan':
while True:
msg = await receive()
if msg['type'] == 'lifespan.startup':
SIDE_EFFECTS['started'] = True
await send({'type': 'lifespan.startup.complete'})
elif msg['type'] == 'lifespan.shutdown':
SIDE_EFFECTS['finished'] = True
await send({'type': 'lifespan.shutdown.complete'})
return
# Otherwise return HTML response
await ResponseHTML('OK')(scope, receive, send)
client = Client(app)
async with client.lifespan():
assert SIDE_EFFECTS['started']
assert not SIDE_EFFECTS['finished']
res = await client.get('/')
assert res.status_code == 200
assert SIDE_EFFECTS['started']
assert SIDE_EFFECTS['finished']
.. Links
.. _ASGI: https://asgi.readthedocs.io/en/latest/