Simple Python Web API framework, based on Gevent, JSON, CRUD.
Python Shell
Switch branches/tags
Nothing to show
Clone or download


Simple Python Web API framework, based on Gevent, JSON, CRUD.


    mkdir -p myproduct/api/v0
    touch {myproduct,myproduct/api,myproduct/api/v0}/

    cat <<END >myproduct/api/v0/
    # from myproduct.api import anything
    def read(request):
        response = request.copy()
        response.server = 'myproduct'
        return response

    sudo apt-get install --yes gcc libevent-dev python-dev
    sudo pip install apiphant
    apiphant myproduct

    # POST http://{host}:{port}/api/{version}/{t/a/r/g/e/t}/{action}
    curl -X POST -d '{"hello": "world"}'
    {"hello": "world", "server": "myproduct"}
  • Automated functional tests in Python:
    apiphant myproduct

    cat <<END >
    from apiphant.test import test
    test('echo', 'read', {"hello": "world"}, 200, {"hello": "world", "server": "myproduct"})

    POST {"hello": "world"} --> 200 {'hello': 'world', 'server': 'myproduct'}
* Please see how this shell script [][] can help to run Python tests in [][].
  • Optional full-stack deploy! Supervisor, Nginx, Logrotate, Apt, Pip, etc.

    • Copy myproduct template.
    • Replace myproduct with your product name in all configs and scripts.
    • Run root and enjoy the show.
    • This deploy framework is going:
      • To get Virtualenv bootstraper.
      • To be extracted to a separate opensource repo.
  • Validate request fields and subfields, raise errors:

    from apiphant.validation import ApiError, field, Invalid

    def read(request):
        id = field(request, 'id', is_required=True, valid_type=int)
        # More options: default_value, valid_value, valid_length, max_length, explain.

        item = get_item(id)
        if not item:
            raise Invalid('id')
            # that is a shortcut for:
            raise ApiError(400, {"field": "id", "state": "invalid"})

        raise Invalid('id', id) # {"field": "id", "state": "invalid", "explain": -1}
  • Background tasks may be scheduled:
    cat <<END >myproduct/api/ # Or background/ importing modules of tasks.
    from apiphant.background import seconds

    def update_something():

    apiphant-background myproduct

    INFO at background.main:107 [2013-08-12 13:16:52,624] Task update_something: OK.
    INFO at background.main:107 [2013-08-12 13:17:53,012] Task update_something: OK.
* Error tracebacks are logged and may be e.g. emailed:
    def on_error(error):
        send_email_message(to=email_config['user'], subject='Error', text=error, **email_config)
        # See

    def update_something():

    apiphant-background myproduct

    ERROR at background.main:92 [2013-08-12 13:22:41,205] Task update_something failed:
    Traceback (most recent call last):  File "...myproduct/api/", line 18, in update_something
    ZeroDivisionError: integer division or modulo by zero

    INFO at background.main:104 [2013-08-12 13:22:43,229] on_error: OK.
    (Email is sent)
  • version value v0 used in the example means API is not public yet, and maybe never will, so is expected to be changed without notification.

  • action is one of [CRUD][]: create, read, update, delete.

  • Reasons why [CRUD][] is implemented without use of HTTP methods that are recommended by [REST][]:

  • [{"json": "object"}][JSON] is used for both request and response, to speak one language easily with any client.

  • However, URL still contains several request parameters, because:

    • Different targets may be routed by load balancers to different backend servers using simple URL location routing.
    • version, target and action are always required, so may be positional parameters, improving readability and saving resources in a natural way.
  • The purity of the concept above should not stand in your way. If you need e.g. to upload a file as "multipart/form-data", you may use raw wsgi environ:

    sudo pip install multipart

    from multipart import parse_form_data
    from apiphant.server import raw_environ

    def create(environ):
        forms, files = parse_form_data(environ)
  • And if you need to return e.g. not "application/json", you may use raw wsgi response, with or without @raw_environ:
    from apiphant.server import raw_environ, raw_response

    def create(environ, start_response):
            start_response(status, headers)
            return [response]

apiphant version 0.2.5
Copyright (C) 2013 by Denis Ryzhkov
MIT License, see