muffin
repository·develop·Indexed 20 days ago
https://github.com/klen/muffinA fast, lightweight, and asynchronous ASGI web framework for Python 3.11+. Muffin supports multiple async runtimes including asyncio, trio, and curio, combining microframework simplicity with high performance. It features a built-in CLI for application management, a flexible hierarchical configuration system, and specialized response classes for various content types.
What's inside muffin
- Muffin is a fast, lightweight, asynchronous ASGI web framework for Python.
Understand Response conversion
developMuffin automatically converts view return values into
muffin.Responseobjects based on the type returned:muffin.Response: Returned as-is.str: Converted to an HTML response.dict,list,bool,None: Converted to a JSON response.(status, content): A tuple where the first element overrides the HTTP status.(status, content, headers): A tuple that allows overriding the status and adding custom headers.
@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'Understand Configuration Precedence
developWhen determining the final value of a configuration setting, Muffin follows this order (from lowest to highest precedence):
- Defaults: Built-in values defined in the application.
- Python config modules: Values loaded from the modules passed to
Application(). - Environment variables: Values set via
{APP_NAME}_{OPTION_NAME}. - Keyword arguments: Values passed directly to the
Applicationconstructor.
Mount nested applications
developFor modular designs, you can mount one
Applicationinstance inside another usingapp.route(prefix)(subapp).subapp = Application() @subapp.route('/route') async def subroute(request): return "From subapp" app.route('/sub')(subapp) # Accessing /sub/route calls the sub-application routeQuickstart: Create a simple Muffin application
developTo get started, create an
muffin.Applicationinstance and use the@app.routedecorator to define asynchronous route handlers. Route parameters can be captured using curly braces (e.g.,{name}) and accessed viarequest.path_params.import muffin app = muffin.Application() @app.route('/', '/hello/{name}') async def hello(request): name = request.path_params.get('name', 'world') return f'Hello, {name.title()}!'Quickstart with Muffin
developTo get started, create an
Applicationinstance and define routes using the@app.routedecorator. Views are asynchronous functions that receive arequestobject and return a response.To run the application, use an ASGI server like
uvicorn.from muffin import Application app = Application() @app.route("/") async def hello_world(request): return "<p>Hello, World!</p>"Run with:
uvicorn hello:appConfigure logging with dictConfig
developYou can provide a complete logging configuration by passing a dictionary following Python's
logging.config.dictConfigformat to theLOG_CONFIGoption.LOG_CONFIG = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'default': { 'format': '%(asctime)s %(levelname)s %(name)s %(message)s' }, }, 'handlers': { 'logfile': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', 'filename': 'my_log.log', 'maxBytes': 50 * 1024 * 1024, 'backupCount': 10 }, }, 'loggers': { '': { 'handlers': ['logfile'], 'level': 'ERROR' }, 'project': { 'level': 'INFO', 'propagate': True, }, } }Configure plugins via environment variables
developPlugins can be configured using environment variables with the prefix
{APP_NAME}_{PLUGIN_NAME}_.For a plugin named
pluginin an application namedmuffin, useMUFFIN_PLUGIN_OPTIONto set theoptionkey.Plugin Option Precedence:
- Plugin
kwargspassed toPlugin(app, ...) - Application config values with
{PLUGIN_NAME}_prefix - Plugin environment variables
- Plugin
defaults
import os # Set plugin option via environment os.environ['MUFFIN_PLUGIN_OPTION'] = '33' class Plugin(BasePlugin): name = 'plugin' defaults = {'option': 11} app = Application() plugin = Plugin(app) assert plugin.cfg.option == 33- Plugin
Override configuration via keyword arguments
developYou can override any configuration option directly by passing keyword arguments to the
muffin.Applicationconstructor. These keyword arguments have the highest precedence in the configuration hierarchy.# Keyword arguments override modules and environment variables app = muffin.Application(DEBUG=True, ANY_OPTION='value', ONE_MORE='value2') assert app.cfg.DEBUG is True assert app.cfg.ANY_OPTION == 'value' assert app.cfg.ONE_MORE == 'value2'Deploy Muffin with Docker
developYou can deploy Muffin using the official Docker image
horneds/muffin. To deploy your application, create aDockerfilethat uses this image as a base, copy your application code into the container, build the image, and run it.Steps:
- Create a
Dockerfile: UseFROM horneds/muffin:latestandCOPY . /appto include your code. - Prepare application code: Ensure your entry point (e.g.,
app.py) initializes amuffin.Application. - Build: Use
docker build -t <image_name> .. - Run: Use
docker run -d --name <container_name> -p <host_port>:<container_port> <image_name>.
FROM horneds/muffin:latest # Copy your application code COPY . /appfrom muffin import Application app = Application() @app.route('/') async def index(request): return 'Hello World!'$ docker build -t myimage . $ docker run -d --name mycontainer -p 80:80 myimage- Create a
Install Muffin
developMuffin requires Python 3.11 or newer. You can install the core package via pip, or use the
[standard]extra to include recommended production dependencies likegunicorn,uvicorn,uvloop, andhttptools.# Core installation $ pip install muffin # Standard installation with recommended production dependencies $ pip install muffin[standard]Register ASGI and internal middleware in Muffin
developMuffin supports two types of middleware:
External ASGI Middleware: These are standard ASGI middleware components (like Sentry). You can register them using
app.middleware(MiddlewareClass)or by wrapping the application instance directly:app = MiddlewareClass(app).Internal (Application-level) Middleware: These are defined using the
@app.middlewaredecorator. They follow the ASGI signature(app, request, receive, send)and allow you to intercept the request/response lifecycle, modify headers, or handle exceptions.
from muffin import Application from sentry_asgi import SentryMiddleware # Register external ASGI middleware app = Application() app.middleware(SentryMiddleware) # OR wrap directly app = SentryMiddleware(app) # Register internal middleware @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: from muffin import ResponseHTML return ResponseHTML('Middleware Exception')