API#
This part of the documentation covers the interfaces of Muffin
Application#
- class muffin.Application(*cfg_mods, **options)#
The Muffin Application.
- Parameters:
cfg_mods (Union[str, ModuleType])
- route(*paths, methods=None, **opts)#
Register a route.
- Parameters:
paths (TPath)
methods (Optional[TMethods])
- on_error(etype)#
Register an exception handler.
@app.on_error(TimeoutError) async def timeout(request, error): return 'Something bad happens' @app.on_error(ResponseError) async def process_http_errors(request, response_error): if response_error.status_code == 404: return render_template('page_not_found.html'), 404 return response_error
- Parameters:
etype (type[BaseException])
- middleware(md, *, insert_first=False)#
Register a middleware.
Register any ASGI middleware
from muffin import Application from sentry_asgi import SentryMiddleware app = Application() app.middleware(SentryMiddleware) # as an alternative method app = SentryMiddleware(app)
Register a custom (internal) middleware
from muffin import Application app = Application() @app.middleware async def simple_md(app, request, receive, send): try: response = await app(request, receive, send) response.headers['x-simple-md'] = 'passed' return response except RuntimeError: return ResponseHTML('Middleware Exception')
- Parameters:
md (TVCallable)
insert_first (bool)
- Return type:
TVCallable
- run_after_response(*tasks)#
Await the given awaitable after the response is completed.
from muffin import Application app = Application() @app.task def send_email(email, message): # send email here pass @app.route('/send') async def send(request): # Schedule any awaitable for later execution app.run_after_response(send_email('user@email.com', 'Hello from Muffin!')) # Return response to a client immediately # The task will be executed after the response is sent return "OK"
- Parameters:
tasks (Awaitable)
- import_submodules(*submodules, silent=False, **kwargs)#
Automatically import submodules.
# Somewhere in package "__init__.py" file # import all submodules app.import_submodules() # import only specific submodules (in specified order) app.import_submodules('submodule1', 'submodule2') # ignore import errors app.import_submodules(silent=True)
Class Based Handlers#
- class muffin.Handler(request, **opts)#
Class-based view pattern for handling HTTP method dispatching.
@app.route('/hello', '/hello/{name}') class HelloHandler(Handler): async def get(self, request): name = request.patch_params.get('name') or 'all' return "GET: Hello f{name}" async def post(self, request): name = request.patch_params.get('name') or 'all' return "POST: Hello f{name}" @Handler.route('/hello/custom') async def custom(self, request): return 'Custom HELLO' # ... async def test_my_endpoint(client): response = await client.get('/hello') assert await response.text() == 'GET: Hello all' response = await client.get('/hello/john') assert await response.text() == 'GET: Hello john' response = await client.post('/hello') assert await response.text() == 'POST: Hello all' response = await client.get('/hello/custom') assert await response.text() == 'Custom HELLO' response = await client.delete('/hello') assert response.status_code == 405
- Parameters:
request (Request)
Request#
- class muffin.Request(scope, receive, send)#
Represent a HTTP Request.
- Parameters:
scope (TASGIScope) – HTTP ASGI Scope
receive (TASGIReceive) – an asynchronous callable which lets the application receive event messages from the client
send (TASGISend) – an asynchronous callable which lets the application send event messages to the client
The Request object contains all the information about an incoming HTTP request.
@app.route('/') async def home(request): response = f"{ request.method } { request.url.path }" return response
Requests are based on a given ASGI scope and represents a mapping interface.
request = Request(scope) 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']
- query#
A lazy property that parse the current query string and returns it as a
multidict.MultiDict
.
- headers#
A lazy property that parses the current scope’s headers, decodes them as strings and returns case-insensitive multi-dict
multidict.CIMultiDict
.request = Request(scope) assert request.headers['content-type'] assert request.headers['authorization']
See
multidict
documentation for futher reference.
- cookies#
A lazy property that parses the current scope’s cookies and returns a dictionary.
request = Request(scope) ses = request.cookies.get('session')
- charset#
Get an encoding charset for the current scope.
- content_type#
Get a content type for the current scope.
- url#
A lazy property that parses the current URL and returns
yarl.URL
object.request = Request(scope) assert str(request.url) == '... the full http URL ..' assert request.url.scheme assert request.url.host assert request.url.query is not None assert request.url.query_string is not None
See
yarl
documentation for further reference.
- async stream()#
Stream the request’s body.
The method provides byte chunks without storing the entire body to memory. Any subsequent calls to
body()
,form()
,json()
ordata()
will raise an error.Warning
You can only read stream once. Second call raises an error. Save a readed stream into a variable if you need.
@app.route('/repeater') async def repeater(request): body = b'' async for chunk in request.stream(): body += chunk return body
- Return type:
- async body()#
Read and return the request’s body as bytes.
body = await request.body()
- Return type:
- async text()#
Read and return the request’s body as a string.
text = await request.text()
- Return type:
- async form(max_size=0, upload_to=None, file_memory_limit=1048576)#
Read and return the request’s multipart formdata as a multidict.
The method reads the request’s stream stright into memory formdata. Any subsequent calls to
body()
,json()
will raise an error.- Parameters:
- Return type:
MultiDict
formdata = await request.form()
- async json()#
Read and return the request’s body as a JSON.
json = await request.json()
- async data(*, raise_errors=False)#
The method checks Content-Type Header and parse the request’s data automatically.
- Parameters:
raise_errors (bool) – Raise an error if the given data is invalid.
- Return type:
data = await request.data()
If raise_errors is false (by default) and the given data is invalid (ex. invalid json) the request’s body would be returned.
Returns data from
json()
for application/json,form()
for application/x-www-form-urlencoded, multipart/form-data andtext()
otherwise.
Responses#
- class muffin.Response(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
A base class to make ASGI responses.
- Parameters:
A helper to make http responses.
from muffin import Response @app.route('/hello') async def hello(request): response = Response('Hello, world!', content_type='text/plain') return response
- headers: MultiDict#
Multidict of response’s headers
from muffin import Response @app.route('/example') async def example(request): response = Response('OK') response.headers["X-Version"] = "42" return response
- cookies: SimpleCookie#
Set/Update cookies
response.cookies[name] = value
str
– set a cookie’s valueresponse.cookies[name][‘path’] = value
str
– set a cookie’s pathresponse.cookies[name][‘expires’] = value
int
– set a cookie’s expireresponse.cookies[name][‘domain’] = value
str
– set a cookie’s domainresponse.cookies[name][‘max-age’] = value
int
– set a cookie’s max-ageresponse.cookies[name][‘secure’] = value
bool
– is the cookie should only be sent if request is SSLresponse.cookies[name][‘httponly’] = value
bool
– is the cookie should be available through HTTP request only (not from JS)response.cookies[name][‘samesite’] = value
str
– set a cookie’s strategy (‘lax’|’strict’|’none’)
from muffin import Response @app.route('/example') async def example(request): response = Response('OK') response.cookies["rocky"] = "road" response.cookies["rocky"]["path"] = "/cookie" return response
ResponseText (Response
)#
- class muffin.ResponseText(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
A helper to return plain text responses (text/plain).
from muffin import ResponseText @app.route('/example') async def example(request): return ResponseText('Hello, world!')
- Parameters:
status_code (int)
content_type (str | None)
headers (MultiDict)
cookies (SimpleCookie)
ResponseHTML (Response
)#
- class muffin.ResponseHTML(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
A helper to return HTML responses (text/html).
from muffin import ResponseHTML @app.route('/example') async def example(request): return ResponseHTML('<h1>Hello, world!</h1>')
Note
If your view function returns a string/byte-string the result will be converted into a HTML Response
- Parameters:
status_code (int)
content_type (str | None)
headers (MultiDict)
cookies (SimpleCookie)
ResponseJSON (Response
)#
- class muffin.ResponseJSON(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
A helper to return JSON responses (application/json).
The class optionally supports ujson and orjson JSON libraries. Install one of them to use instead the standard library.
from muffin import ResponseJSON @app.route('/example') async def example(request): return ResponseJSON({'hello': 'world'})
Note
If your view function returns a dictionary/list/boolean the result will be converted into a JSON Response
- Parameters:
status_code (int)
content_type (str | None)
headers (MultiDict)
cookies (SimpleCookie)
ResponseRedirect (Response
)#
- class muffin.ResponseRedirect(url, status_code=None, **kwargs)#
A helper to return HTTP redirects. Uses a 307 status code by default.
from muffin import ResponseRedirect @app.route('/example') async def example(request): return ResponseRedirect('/login')
You are able to raise the
ResponseRedirect
as an exception.from muffin import ResponseRedirect @app.route('/example') async def example(request): if not request.headers.get('authorization'): # The client will be redirected to "/login" raise ResponseRedirect('/login') return 'OK'
ResponseError (Response
)#
- class muffin.ResponseError(message=None, status_code=500, **kwargs)#
A helper to return HTTP errors. Uses a 500 status code by default.
- Parameters:
message – A string with the error’s message (HTTPStatus messages will be used by default)
from muffin import ResponseError @app.route('/example') async def example(request): return ResponseError('Timeout', 502)
You are able to raise the
ResponseError
as an exception.from muffin import ResponseError @app.route('/example') async def example(request): data = await request.data() if not data: raise ResponseError('Invalid request data', 400) return 'OK'
You able to use
http.HTTPStatus
properties with the ResponseError classresponse = ResponseError.BAD_REQUEST('invalid data') response = ResponseError.NOT_FOUND() response = ResponseError.BAD_GATEWAY() # and etc
ResponseStream (Response
)#
- class muffin.ResponseStream(stream, **kwargs)#
A helper to stream a response’s body.
- Parameters:
content (AsyncGenerator) – An async generator to stream the response’s body
stream (AsyncGenerator[Any, None])
from muffin import ResponseStream async def stream_response(): for number in range(10): await sleep(1) yield str(number) @app.route('/example') async def example(request): generator = stream_response() return ResponseStream(generator, content_type='plain/text')
ResponseSSE (Response
)#
- class muffin.ResponseSSE(stream, **kwargs)#
A helper to stream SSE (server side events).
- Parameters:
content (AsyncGenerator) – An async generator to stream the events
stream (AsyncGenerator[Any, None])
from muffin import ResponseSSE 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(), } @app.route('/example') async def example(request): generator = stream_response() return ResponseSSE(generator, content_type='plain/text')
ResponseFile (Response
)#
- class muffin.ResponseFile(filepath, *, chunk_size=65536, filename=None, headers_only=False, **kwargs)#
A helper to stream files as a response body.
- Parameters:
from muffin import ResponseFile @app.route('/selfie') async def view_my_selfie(request): return ResponseFile('/storage/my_best_selfie.jpeg') @app.route('/download') async def download_movie(request): return ResponseFile('/storage/video.mp4', filename='movie-2020-01-01.mp4')
ResponseWebSocket (Response
)#
- class muffin.ResponseWebSocket(scope, receive=None, send=None)#
A helper to work with websockets.
- Parameters:
scope (dict) – Request info (ASGI Scope | ASGI-Tools Request)
receive (Optional[TASGIReceive]) – ASGI receive function
send (Optional[TASGISend]) – ASGI send function
from muffin import ResponseWebsocket @app.route('/example') async def example(request): async with ResponseWebSocket(request) as ws: msg = await ws.receive() assert msg == 'ping' await ws.send('pong')
- async accept(**params)#
Accept a websocket connection.
- Return type:
None
- async close(code=1000)#
Sent by the application to tell the server to close the connection.
- Parameters:
code (int)
- Return type:
None
- async send(msg, msg_type='websocket.send')#
Send the given message to a client.
- async send_json(data)#
Serialize the given data to JSON and send to a client.
- Return type:
None
Test Client#
- muffin.TestClient#
alias of
ASGITestClient