view wsgraph/web.py @ 46:2d57cda87036

start noting useful graph DBs
author Jeff Hammel <jhammel@mozilla.com>
date Tue, 16 Apr 2013 01:03:19 -0700
parents 24d57daaca21
children
line wrap: on
line source

#!/usr/bin/env python

"""
web handler for WSGraph
"""

import json
import sys
from webob import Request, Response, exc

# rank constants
GRAPH = 0
NODE = 1
EDGE = 2

# XXX stubbing hack
class JSONformatter(object):

    def __init__(self, content_type='application/json', sort_keys=True):
        self.content_type = content_type
        self.sort_keys = sort_keys

    def format(self, instance):
        """front-end to json.dumps"""
        return json.dumps(instance, sort_keys=self.sort_keys)

    def node(self, request, graph, node):
        return Response(content_type=self.content_type,
                        body=self.format(graph.node(node)))

    def edge(self, request, graph, node1, node2):
        return Response(content_type=self.content_type,
                        body=self.format(graph.edge(node1, node2)))

    def graph(self, request, graph):
        return Response(content_type=self.content_type,
                        body = self.format(graph()))


class Dispatcher(object):

    def __init__(self, graph,
                 graph_formatters,
                 node_formatters,
                 edge_formatters,
                 require_auth=False):
        self.graph = graph
        self.require_auth = require_auth

        # view formatters
        self.formatters = {GRAPH: graph_formatters,
                           NODE: node_formatters,
                           EDGE: edge_formatters}
        for key, value in self.formatters.items():
            # ensure default formatter
            assert None in value
            # ensure all formatters are callable

        self.methods = dict([(i, getattr(self, i))
                             for i in dir(self)
                             if i.isupper()])

    def __call__(self, environ, start_response):
        request = Request(environ)
        method = self.methods.get(request.method)
        if method is None:
            return exc.HTTPMethodNotAllowed()(environ, start_response)
        response = method(request)
        return response(environ, start_response)

    @staticmethod
    def path_segments(path):
        """split a path into segments"""
        segments = path.strip('/').split('/')
        if segments == ['']:
            segments = []
        return segments


    def formatter(self, rank, request):
        """returns the formatter for a request"""
        formatters = self.formatters[rank]
        format = request.GET.get('format')
        return formatters.get(format, formatters[None])

    # HTTP methods

    def GET(self, request):
        """
        respond to a GET request

        Formatters are keyed off of

        formatters = {0: {
        }

        A graph formatter takes the following arguments:

            def sample_graph_formatter(graph, request):

        A node formatter takes the following arguments:

            def sample_node_formatter(node, graph, request):

        An edge formatter takes the following arguments:

            def sample_edge_formatter(edge, graph, request):

        API:

        ?format=<format>

        """

        # get resource requestor
        segments = self.path_segments(request.path_info)
        rank = len(segments)
        if rank not in (0,1,2):
            return exc.HTTPNotFound()

        # is resource in the graph?
        if (rank == EDGE) or (rank == NODE):
            if tuple(segments) not in self.graph:
                return exc.HTTPNotFound()

        # formatter
        formatter = self.formatter(rank, request)
        return formatter(request, self.graph, *segments)

    def POST(self, request):
        """
        respond to a POST request

        API:

        ?update :
        """

        # check authorization
        if self.require_auth:
            raise NotImplementedError

        return exc.HTTPSeeOther(location='/') # TODO: /path/to/self

    def HEAD(self, request):
        """respond to a HEAD request"""
        raise NotImplementedError

    def OPTIONS(self, request):
        return Response() # TODO: Allow=', '.join(self.methods); Content-Length=0


def main(args=sys.argv[1:]):
    """example web server"""

    # imports
    from wsgiref import simple_server
    from model import MemoryCache
    from optparse import OptionParser

    # parse command line options
    parser = OptionParser()
    parser.add_option('-p', '--port', type='int', default=8080,
                      help="port to serve on")
    options, args = parser.parse_args()

    # example model
    graph = MemoryCache()

    # example formatter(s)
    formatter = JSONformatter(content_type='text/plain')
    _formatters = {}
    _formatters = dict([('%s_formatters' % i, {None: getattr(formatter, i)})
                        for i in 'graph', 'node', 'edge'])

    # WSGI app
    app = Dispatcher(graph=graph, **_formatters)
    server = simple_server.make_server(host='0.0.0.0', port=options.port, app=app)
    print 'http://localhost:%s/' % options.port
    server.serve_forever()

if __name__ == '__main__':
    main()