Source code for seshat.route

#!/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