API#
This section documents all public interfaces of Muffin.
Application#
- class muffin.Application(*cfg_mods, **options)#
The Muffin Application.
- Parameters:
cfg_mods (str | ModuleType)
- 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)
- 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)
- route(*paths, methods=None, **opts)#
Register a route handler.
- Parameters:
paths (TPath) – One or more URL paths to match
methods (Optional[TMethods]) – HTTP methods to match (GET, POST, etc.)
opts (Any) – Additional options for the route
- Returns:
Decorator function
- Return type:
Callable[[TRouteHandler], TRouteHandler]
- on_startup(fn)#
Register a startup event handler.
- Parameters:
fn (Callable) – The function to call on startup
- Return type:
None
- on_shutdown(fn)#
Register a shutdown event handler.
- Parameters:
fn (Callable) – The function to call on shutdown
- Return type:
None
- on_error(etype)#
Register a custom exception handler for a given exception type.
- Parameters:
etype (type[BaseException]) – The exception type to handle
- Returns:
A decorator to register the handler
- Return type:
Callable
- Example:
>>> @app.on_error(TimeoutError) ... async def timeout_handler(request, error): ... return 'Timeout occurred'
- middleware(md, *, insert_first=False)#
Register a middleware for the application.
Middleware can be a coroutine (request/response) or a regular callable (lifespan).
- Parameters:
md (TCallable) – The middleware function or class
insert_first (bool) – If True, insert as the first middleware
- Returns:
The registered middleware
- Return type:
TCallable
- Example:
>>> @app.middleware ... async def log_requests(handler, request): ... print(f"Request: {request.method} {request.url}") ... return await handler(request)
Middleware#
Register external ASGI middleware:
from muffin import Application
from sentry_asgi import SentryMiddleware
app = Application()
app.middleware(SentryMiddleware)
# or wrap directly
app = SentryMiddleware(app)
Register internal (application-level) middleware:
from muffin import Application, ResponseHTML
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')
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 f"GET: Hello {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)#
Provides a convenient, high-level interface for incoming HTTP requests.
- 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
- scope#
- receive#
- send#
- property url: 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.
- property headers: CIMultiDict#
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.
- property cookies: dict[str, str]#
A lazy property that parses the current scope’s cookies and returns a dictionary.
request = Request(scope) ses = request.cookies.get('session')
- property query: MultiDictProxy[str]#
A lazy property that parse the current query string and returns it as a
multidict.MultiDict
.
- 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.
- 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 json()#
Read and return the request’s body as a JSON.
json = await request.json()
- async form(max_size=0, upload_to=None, file_memory_limit=1048576)#
Read and return the request’s form data.
- Parameters:
- Returns:
Form data as MultiDict
- Return type:
MultiDict
formdata = await request.form()
- async data(*, raise_errors=False)#
Read and return the request’s data based on content type.
- Parameters:
raise_errors (bool) – Raise an error if the given data is invalid.
- Returns:
Request data in appropriate format
- Return type:
- Raises:
ASGIDecodeError – If data cannot be decoded and raise_errors is True
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.
Example usage:
@app.route('/')
async def home(request):
return f"{request.method} {request.url.path}"
Properties:
query
– Query parameters (MultiDict)headers
– HTTP headers (CIMultiDict)cookies
– Cookies dictionarycharset
– Request encodingcontent_type
– Content-Type headerurl
– Full URL object
Methods:
stream()
– Stream body in chunksbody()
– Return body as bytestext()
– Return body decoded as textform()
– Parse form data (multipart/form-data or application/x-www-form-urlencoded)json()
– Parse body as JSONdata()
– Smart parser: returns JSON, form or text depending on Content-Type
Responses#
- class muffin.Response(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
Base class for creating HTTP responses.
- Parameters:
- headers: MultiDict#
Multidict of response’s headers
- 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’)
Basic response example:
from muffin import Response
@app.route('/hello')
async def hello(request):
return Response('Hello, world!', content_type='text/plain')
Setting headers:
@app.route('/example')
async def example(request):
response = Response('OK')
response.headers["X-Version"] = "42"
return response
Setting cookies:
@app.route('/example')
async def example(request):
response = Response('OK')
response.cookies["session"] = "xyz"
response.cookies["session"]["path"] = "/"
return response
ResponseText#
- class muffin.ResponseText(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
Returns plain text responses (text/plain).
- Parameters:
content (TContent)
status_code (int)
content_type (str | None)
headers (MultiDict)
cookies (SimpleCookie)
@app.route('/example')
async def example(request):
return ResponseText('Hello, world!')
ResponseHTML#
- class muffin.ResponseHTML(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
Returns HTML responses (text/html).
- Parameters:
content (TContent)
status_code (int)
content_type (str | None)
headers (MultiDict)
cookies (SimpleCookie)
@app.route('/example')
async def example(request):
return ResponseHTML('<h1>Hello, world!</h1>')
Note
Returning a string/bytes will automatically produce an HTML response.
ResponseJSON#
- class muffin.ResponseJSON(content, *, status_code=None, content_type=None, headers=None, cookies=None)#
Returns JSON responses (application/json).
The class optionally supports ujson and orjson JSON libraries. Install one of them to use instead the standard library.
- Parameters:
content (TContent)
status_code (int)
content_type (str | None)
headers (MultiDict)
cookies (SimpleCookie)
@app.route('/example')
async def example(request):
return ResponseJSON({'hello': 'world'})
Note
Returning dicts, lists, or booleans produces a JSON response.
ResponseRedirect#
- class muffin.ResponseRedirect(url, status_code=None, **kwargs)#
Creates HTTP redirects. Uses a 307 status code by default.
@app.route('/example')
async def example(request):
return ResponseRedirect('/login')
Alternatively, raise as an exception:
@app.route('/example')
async def example(request):
if not request.cookies.get('session'):
raise ResponseRedirect('/login')
return 'OK'
ResponseError#
Raise HTTP errors:
@app.route('/example')
async def example(request):
data = await request.data()
if not data:
raise ResponseError('Invalid request data', 400)
return 'OK'
Supports http.HTTPStatus shortcuts:
response = ResponseError.BAD_REQUEST('invalid data')
response = ResponseError.NOT_FOUND()
response = ResponseError.BAD_GATEWAY()
ResponseStream#
- class muffin.ResponseStream(stream, **kwargs)#
Streams response body as chunks.
- 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 i in range(10):
await sleep(1)
yield f"chunk {i}"
@app.route('/stream')
async def stream(request):
return ResponseStream(stream_response())
ResponseSSE#
- class muffin.ResponseSSE(stream, **kwargs)#
Streams Server-Sent Events (SSE).
- Parameters:
content (AsyncGenerator) – An async generator to stream the events
stream (AsyncGenerator[Any, None])
from muffin import ResponseSSE
async def sse_response():
while True:
await sleep(1)
yield {"event": "ping", "data": "pong"}
@app.route('/sse')
async def sse(request):
return ResponseSSE(sse_response())
ResponseFile#
- class muffin.ResponseFile(filepath, *, chunk_size=65536, filename=None, headers_only=False, **kwargs)#
Serves files as HTTP responses.
@app.route('/download')
async def download(request):
return ResponseFile('/path/to/file.txt', filename='file.txt')
ResponseWebSocket#
- class muffin.ResponseWebSocket(scope, receive=None, send=None)#
Provides a WebSocket handler interface.
- Parameters:
scope (TASGIScope)
receive (TASGIReceive | None)
send (TASGISend | None)
@app.route('/ws')
async def websocket(request):
async with ResponseWebSocket(request) as ws:
msg = await ws.receive()
await ws.send(f"Echo: {msg}")
Test Client#
- muffin.TestClient#
alias of
ASGITestClient
Use for testing your Muffin applications efficiently.