muffin

repository·develop·Indexed 20 days ago

https://github.com/klen/muffin

A 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.

Tokens
13.1K
Snippets
52
Records
61
Agent score
69%

What's inside muffin

  1. Understand Response conversion

    develop

    Muffin automatically converts view return values into muffin.Response objects 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'
  2. Understand Configuration Precedence

    develop

    When determining the final value of a configuration setting, Muffin follows this order (from lowest to highest precedence):

    1. Defaults: Built-in values defined in the application.
    2. Python config modules: Values loaded from the modules passed to Application().
    3. Environment variables: Values set via {APP_NAME}_{OPTION_NAME}.
    4. Keyword arguments: Values passed directly to the Application constructor.
  3. Mount nested applications

    develop

    For modular designs, you can mount one Application instance inside another using app.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 route
  4. Quickstart: Create a simple Muffin application

    develop

    To get started, create an muffin.Application instance and use the @app.route decorator to define asynchronous route handlers. Route parameters can be captured using curly braces (e.g., {name}) and accessed via request.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()}!'
  5. Quickstart with Muffin

    develop

    To get started, create an Application instance and define routes using the @app.route decorator. Views are asynchronous functions that receive a request object 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:app
  6. Configure logging with dictConfig

    develop

    You can provide a complete logging configuration by passing a dictionary following Python's logging.config.dictConfig format to the LOG_CONFIG option.

    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,
            },
        }
    }
  7. Configure plugins via environment variables

    develop

    Plugins can be configured using environment variables with the prefix {APP_NAME}_{PLUGIN_NAME}_.

    For a plugin named plugin in an application named muffin, use MUFFIN_PLUGIN_OPTION to set the option key.

    Plugin Option Precedence:

    1. Plugin kwargs passed to Plugin(app, ...)
    2. Application config values with {PLUGIN_NAME}_ prefix
    3. Plugin environment variables
    4. 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
  8. Override configuration via keyword arguments

    develop

    You can override any configuration option directly by passing keyword arguments to the muffin.Application constructor. 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'
  9. Deploy Muffin with Docker

    develop

    You can deploy Muffin using the official Docker image horneds/muffin. To deploy your application, create a Dockerfile that uses this image as a base, copy your application code into the container, build the image, and run it.

    Steps:

    1. Create a Dockerfile: Use FROM horneds/muffin:latest and COPY . /app to include your code.
    2. Prepare application code: Ensure your entry point (e.g., app.py) initializes a muffin.Application.
    3. Build: Use docker build -t <image_name> ..
    4. Run: Use docker run -d --name <container_name> -p <host_port>:<container_port> <image_name>.
    FROM horneds/muffin:latest
    
    # Copy your application code
    COPY . /app
    from 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
  10. Install Muffin

    develop

    Muffin requires Python 3.11 or newer. You can install the core package via pip, or use the [standard] extra to include recommended production dependencies like gunicorn, uvicorn, uvloop, and httptools.

    # Core installation
    $ pip install muffin
    
    # Standard installation with recommended production dependencies
    $ pip install muffin[standard]
  11. Register ASGI and internal middleware in Muffin

    develop

    Muffin supports two types of middleware:

    1. 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).

    2. Internal (Application-level) Middleware: These are defined using the @app.middleware decorator. 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')