wsgi microframework suitable for building modular DRY RESTful APIs

http, http-server, json-api, lambda-functions, modular, python, python-3, python-library, rest-api, restful-api, wsgi, wsgi-framework
pip install madness==0.7.0



Madness orchestrates the HTTP request-response cycle using context functions to build abstractions and route functions to transform abstractions into a HTTP responses.

It is built upon the fabulous werkzeug routing system.


Don't repeat yourself

Dependency inversion principle

Do One Thing and Do It Well.


$ pip install -U madness

A Simple Example

from madness import Madness, response

app = Madness()

def hello():
    return response(['Hello, world!'])

if __name__ == '__main__':


@app.route(*paths, methods=[], context=[], origin=None, headers=[])

option description
*paths relative paths, defaults to the decorated function name
methods list of allowed http methods
context list of extra context functions see #Context
origin allowed origin: * or list of urls
headers allowed request headers: list of header names
wsgi set to True if the route implements a WSGI interface

convenience methods for @app.route

you can still use options with these!

@app.get,, @app.put, @app.delete, @app.patch, @app.options

RESTful routes

inspired by Ruby on Rails' resources

decorator path method
@app.index {path} GET new{path} GET
@app.create {path} POST /:id{path} GET
@app.edit /:id/edit{path} GET
@app.update /:id{path} PATCH/PUT
@app.destroy /:id{path} DELETE

AWS Lambda

see also: RequestResponse

from madness import json

class EventSchema():
    x: int = 1

def process(event: EventSchema):
    return {'y': event['x'] + 2}

if you annotate the event with a marshmallow schema, it is automatically validated :)

handling routing errors

def my404handler():
    return response(['not found'], status = 404)


from madness import Madness, response

app = Madness()

module = Madness()

def thing():
    return response(['hello!'])

app.extend(module) # now app has /thing

app.extend(module, 'prefix') # now app has /prefix/thing

app.extend(module, context=False) # add the routes but not the context

if __name__ == '__main__':


madness.context contains the abstractions created by the previous contexts

use @context to build abstractions for your low-level modules @context and @route


  @context authenticate the HTTP request `context.username = 'xyz'`

  @context get database connection `context.database = Database()`

  @context(database) use database connection ` = database.myobjects.find(`

  @show(data) convert data to HTTP response `return json.response(data)`

rule args are added to context

e.g. @app.route('path/<myvar>') creates context.myvar

Basic Context Functions

from madness import context, json

def before_request():
    "could do anything here, so let's add a variable to the context!"
    context.x = 2

def continue_processing(x):
    "define context.y based on context.x!"
    context.y = x * 3 # 6

def double(y):
    "doubles context.y and sends it as a JSON response"
    return json.response(y * 2) # 12

Advanced Context Generators

a context has full access to the request/response/exceptions

the response/exception is bubbled through the context handlers

def advanced_context():
    # before_request
    if request.headers.get('x-api-key') != 'valid-api-key':
        # abort
        yield json.response({'message': 'invalid api key'}, status = 403)
        # run remaining context functions and the route endpoint (if not aborted)
            response = yield

        except MyException as exception:
            yield json.response({'message': exception.message}, status = 500)

            # modify the response headers
            response.headers['x-added-by-context'] = 'value'

            # abort
            yield json.response('we decided to not send the original response, isn\'t that weird?')

            # after_request