Usage#

Quickstart#

from muffin import Application

app = Application()

@app.route("/")
async def hello_world(request):
    return "<p>Hello, World!</p>"

Save this as hello.py.

Run with:

uvicorn hello:app

Visit http://127.0.0.1:8000/ to see your greeting.

Request Object#

Every view receives a Request object:

@app.route('/login', methods=['POST'])
async def login(request):
    form = await request.form()
    username = form.get('username')
    password = form.get('password')
    if username == 'admin':
        return f"Welcome, {username}"
    return "Invalid credentials"

Query parameters:

search = request.query.get('search', '')

Cookies:

session = request.cookies.get('session')

File uploads:

form = await request.form()
file = form['file']
content = await file.read()

Routing#

Define routes:

@app.route('/')
async def index(request):
    return 'Index Page'

Dynamic routes:

@app.route('/user/{username}')
async def user_profile(request):
    username = request.path_params['username']
    return f"Hello, {username}"

Type converters:

str

(default) accepts any text without a slash

int

accepts positive integers

float

accepts positive floating point values

path

like string but also accepts slashes

uuid

accepts UUID strings

Example:

@app.route('/post/{post_id:int}')
async def post(request):
    post_id = request.path_params['post_id']
    return f"Post #{post_id}"

Regex routes:

import re

@app.route(re.compile(r'/item/(a|b|c)/'))
async def item(request):
    return request.path

Static Files#

Configure static files:

app = Application(static_url_prefix='/assets', static_folders=['static'])

Files are served at /assets/{file}.

Redirects and Errors#

Redirect example:

from muffin import ResponseRedirect

@app.route('/')
async def index(request):
    raise ResponseRedirect('/login')

Raise HTTP errors:

from muffin import ResponseError

@app.route('/secure')
async def secure(request):
    if not request.cookies.get('auth'):
        raise ResponseError.UNAUTHORIZED()
    return "Secret data"

Custom error handler:

@app.on_error(404)
async def not_found(request, error):
    return "Custom 404 Page"

Middlewares#

External ASGI middleware:

from sentry_asgi import SentryMiddleware

app.middleware(SentryMiddleware)

Internal middleware:

@app.middleware
async def simple_md(app, request, receive, send):
    response = await app(request, receive, send)
    response.headers['x-custom-md'] = 'passed'
    return response

Nested Applications#

Mount sub-applications for modular design:

subapp = Application()

@subapp.route('/route')
async def subroute(request):
    return "From subapp"

app.route('/sub')(subapp)

Accessing /sub/route calls the sub-application route.

Run Tasks in Background#

Schedule background tasks after response is sent:

import asyncio

async def background_task(param):
    await asyncio.sleep(1)
    print(f"Done: {param}")

@app.route('/bg')
async def bg(request):
    app.run_after_response(background_task("task"))
    return "Scheduled"

Debug Mode#

Enable debug mode for detailed error output:

app = Application(debug=True)

This prints tracebacks to console during development.

About Responses#

Return values from views are automatically converted:

  1. Response is returned as-is

  2. str → HTML response

  3. dict, list, bool, None → JSON response

  4. (status, content) tuple → overrides status

  5. (status, content, headers) tuple → adds headers

Example:

@app.route('/json')
async def json_view(request):
    return {'key': 'value'}

@app.route('/html')
async def html_view(request):
    return '<h1>Hello</h1>'

@app.route('/tuple')
async def tuple_view(request):
    return 201, 'Created'