Skip to content

Instantly share code, notes, and snippets.

@jdp
Last active December 17, 2015 10:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdp/5598897 to your computer and use it in GitHub Desktop.
Save jdp/5598897 to your computer and use it in GitHub Desktop.
prototype for Vial, a small web framework
import re
from werkzeug.exceptions import NotFound, MethodNotAllowed
from werkzeug.wrappers import Request, Response
class RouteNode(object):
@property
def converter(self):
return None
class PathNode(RouteNode):
def __init__(self, path):
super(PathNode, self).__init__()
self.path = path.strip().strip('/')
@property
def pattern(self):
return re.escape(self.path)
class ParamNode(RouteNode):
def __init__(self, pattern, cls):
super(ParamNode, self).__init__()
self.cls = cls
self._pattern = pattern
@property
def pattern(self):
return r'(' + self._pattern + r')'
@property
def converter(self):
return self.cls
class Vial(object):
def __init__(self):
self.stack = []
self.handlers = {}
def __enter__(self):
return self
def __exit__(self, typ, value, traceback):
if typ:
return False
self.stack.pop()
return self
def route(self, node):
self.stack.append(node)
def path(self, pathname=''):
self.route(PathNode(pathname))
return self
def param(self, pattern=r'\w+', cls=str):
self.route(ParamNode(pattern, cls))
return self
def handle(self, method, func):
def compile(nodes):
return r'^/' + r'/'.join(n.pattern for n in nodes if n.pattern) + r'/?$'
pattern = compile(self.stack)
if pattern not in self.handlers:
self.handlers[pattern] = {}
converters = [n.converter for n in self.stack if n.converter]
self.handlers[pattern][method.lower()] = (func, converters)
def get(self, func):
return self.handle('get', func)
def post(self, func):
return self.handle('post', func)
def put(self, func):
return self.handle('put', func)
def delete(self, func):
return self.handle('delete', func)
def dispatch_request(self, request):
for pattern, handlers in self.handlers.iteritems():
match = re.match(pattern, request.path)
if not match:
continue
method = request.method.lower()
if method not in handlers:
raise MethodNotAllowed()
func, converters = handlers[method]
params = [c(*p) for c, p in zip(converters, match.groups())]
return func(request, *params)
raise NotFound()
def wsgi_app(self, environ, start_response):
request = Request(environ)
response = self.dispatch_request(request)
return response(environ, start_response)
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def run(self, address='127.0.0.1', port=5000):
from werkzeug.serving import run_simple
run_simple(address, port, self.wsgi_app, use_debugger=True, use_reloader=True)
if __name__ == '__main__':
def debug_handler(request, *args):
return Response("\n".join(str((k, v)) for (k, v) in request.environ.iteritems()))
r = Vial()
# Creates routes for:
# GET /
# GET, POST /posts
# GET, PUT, DELETE /posts/:id
# GET, POST /posts/:id/comments
# GET, PUT, DELETE /posts/:pid/comments/:cid
with r.path():
r.get(lambda r: Response("index"))
with r.path('posts'):
r.get(lambda r: Response("get all posts"))
r.post(lambda r: Response("create post"))
with r.param(r'\d+', cls=int):
r.get(lambda r, p: Response("get post #%d" % p))
r.put(lambda r, p: Response("upsert post #%d" % p))
r.delete(lambda r, p: Response("delete post #%d" % p))
with r.path('comments'):
r.get(lambda r, p: Response("get all post #%d comments" % p))
r.post(lambda r, p: Response("create comment on post #%d" % p))
with r.param(r'\d+', cls=int):
r.get(lambda r, p, c: Response("get comment #%d on post #%d " % (c, p)))
r.put(lambda r, p, c: Response("upsert comment #%d on post #%d" % (c, p)))
r.delete(lambda r, p, c: Response("delete comment #%d on post #%d" % (c, p)))
r.run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment