Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?

Latest commit


Git stats


Failed to load latest commit information.
Latest commit message
Commit time


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


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






No releases published


No packages published