#!/usr/bin/env python
"""
TODO: Doc this module
"""
"""
Seshat
Web App/API framework built on top of gevent
routing decorator
For more information, see: https://github.com/JoshAshby/
http://xkcd.com/353/
Josh Ashby
2014
http://joshashby.com
joshuaashby@joshashby.com
"""
import re
import route_table as u
import logging
logger = logging.getLogger("seshat.route")
controller_folder = ""
"""The folder where the controllers are located in. Since the auto route
generation uses folder hierarchy, this setting allows to you to have
controllers in a single folder but not have that folder end up as the route
prefix."""
opt_group_name_regex = re.compile(r"(?:/\:([^/\s]+))")
# Capture items that look like /:id
# This is used for a search and replace using the below opt_replacement
# which replaces them with functioning regex expressions to capture the
# correct named groups.
opt_replacement = r"/(?P<\1>[^/\s]+)"
[docs]class Route(object):
"""
Provides a base route table entry which is generated by the :py:func:`.route()`
decorator described below.
"""
controller = None
"""
The controller object, of type :py:class:`.Controller` which this
route represents
:type: :py:class:`.Controller`
"""
def __init__(self, controller=None, route=None, subdomain=None):
"""
TODO: Finish this doc
:param route: The url pattern to use where url paramters are denoted by colons like: :name
:type route: str
:param controller:
:type controller: :py:class:`.Controller`
"""
self.controller = controller
self.route = route
self.subdomain = subdomain
@property
def subdomain(self):
return self._subdomain
@subdomain.setter
def subdomain(self, val):
if val:
repl = "({}.*)".format(val)
self._subdomain = re.compile(repl, flags=re.I)
else:
self._subdomain = None
@property
def route(self):
return self._route
@route.setter
def route(self, val):
repl = opt_group_name_regex.sub(opt_replacement, val)
repl = "^{}(?:/)?$".format(repl)
self._route = re.compile(repl, flags=re.I)
def match(self, url):
if self.subdomain is not None:
sub = self.subdomain.search(url.host)
if sub is None:
return None
res = self.route.search(url.path)
if res:
return res.groupdict()
def __repr__(self):
sub = self.subdomain.pattern if self.subdomain else ""
return "< Route Host: "+sub + " Url: " + self.route.pattern + " Controller: " + self.controller.__module__ + "/" + self.controller.__name__ + " >"
[docs]def route(r=None, s=None):
"""
Class decorator that will take and generate a route table entry for the
decorated :py:class:`.Controller` class, based off of its name and its file
hierarchy if no route pattern is specified.
Use like so::
from seshat.controller import Controller
from seshat.route import route
@route()
class index(Controller):
pass
which will result in a route for "/" being made for this controller.
controllers whose name is `index` automatically get routed to the root of
their folders, so an index controller in "profiles/" will have a route that
looks like "/profiles"
Controllers whose name is `view` will automatically get routed to any index
route that has an attached ID. Eg::
# In folder: profiles/
class view(Controller):
pass
will be routed to if the request URL is "/profiles/5" and the resulting
id will be stored in :py:attr:`.Controller.request.url_params`
"""
def wrapper(HTTPObject):
if r is None:
# Build the route url
fullModule = HTTPObject.__module__
if controller_folder:
folder = controller_folder.replace("/", ".")
if folder[-1] != ".":
folder = folder + "."
pre_bits = fullModule.split(folder, 1)[1]
bits = pre_bits.split(".")
else:
bits = fullModule.split(".")
bases = []
# Ignore the first and last parts of the module and make everything
# lowercased so controllers can maybe be pep8 sometimes.
for bit in bits[:len(bits)-1]:
bases.append(bit.lower())
route = "/"
for base in bases:
route += base + "/"
# Everything lowercased. Because fuck uppercase... wait.
name = HTTPObject.__name__.lower()
if name == "index":
route = route.rstrip("/")
if not route:
route = "/"
elif name == "view":
if route != "/":
route += "/:id"
else:
route += name + "/:id"
else:
route += name
route = re.sub("/{2,}", "/", route) # Make sure we don't have extra //'s
logger.debug("""Auto generated route table entry for:
Object: %(objectName)s
Pattern: %(url)s
Host: %(host)s""" % {"url": route,
"objectName": HTTPObject.__module__ + "/" + HTTPObject.__name__,
"host": s})
route = Route(controller=HTTPObject, route=route, subdomain=s)
u.urls.add(route)
else:
logger.debug("""Manual route table entry for:
Object: %(objectName)s
Pattern: %(url)s
Host: %(host)s""" % {"url": r,
"objectName": HTTPObject.__module__ + "/" + HTTPObject.__name__,
"host": s})
route = Route(controller=HTTPObject, route=r, subdomain=s)
u.urls.add(route)
return HTTPObject
return wrapper