Source code for restae.router

"""
Router
"""
import itertools

from restae.exceptions import ImproperlyConfigured


def escape_curly_brackets(url_path):
    """
    Double brackets in regex of url_path for escape string formatting
    """
    if ('{' and '}') in url_path:
        url_path = url_path.replace('{', '{{').replace('}', '}}')
    return url_path


[docs]class Route(object): """ A route mapping representation contains the URL, METHOD and route type """ def __init__(self, **kwargs): self.url = kwargs.pop('url') self.mapping = kwargs.get('mapping') self.detail = kwargs.pop('detail') self.is_dynamic = kwargs.pop('is_dynamic')
[docs]class Url(object): """ An URL representation contains the Route and the destination Handler """ def __init__(self, **kwargs): self.route = kwargs.pop('route') self.regex = kwargs.pop('regex') self.handler_class = kwargs.pop('handler_class') self.handler_name = kwargs.pop('handler_name') def copy(self): return Url( route=self.route, regex=self.regex, handler_class=self.handler_class, handler_name=self.handler_name )
[docs]class Router(object): """ The default router """ routes = [ # List route. Route( url=r'^/{prefix}', mapping={ 'get': 'list', 'post': 'create' }, detail=False, is_dynamic=False ), # Dynamically generated list routes. Generated using # @action(detail=False) decorator on methods of the handler. Route( url=r'^/{prefix}/{url_path}', detail=False, is_dynamic=True ), # Detail route. Route( url=r'^/{prefix}/{lookup}', mapping={ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }, detail=True, is_dynamic=False ), # Dynamically generated detail routes. Generated using # @action(detail=True) decorator on methods of the handler. Route( url=r'^/{prefix}/{lookup}/{url_path}', detail=True, is_dynamic=True ), ] def __init__(self): self.registry = [] def register(self, handler_name, handler_class): assert handler_name is not None and handler_class is not None, ( '\'handler_name\' and \'handler_class\' arguments are required ' 'when calling \'register\' function from router.' ) self.registry.append((handler_name, handler_class))
[docs] def get_lookup_regex(self, handler): """ Given a handler, return the portion of URL regex that is used to match against a single instance. """ base_regex = '(?P<{lookup_url_kwarg}>{lookup_value})' # Use `urlsafe` as default field. lookup_field = getattr(handler, 'lookup_field', None) or 'urlsafe' lookup_url_kwarg = getattr(handler, 'lookup_url_kwarg', None) or lookup_field lookup_value = getattr(handler, 'lookup_value_regex', '[^/.]+') return base_regex.format( lookup_url_kwarg=lookup_url_kwarg, lookup_value=lookup_value )
def _get_dynamic_route(self, route, action): url_path = escape_curly_brackets(action.url_path) return Route( url=route.url.replace('{url_path}', url_path), mapping=action.mapping, detail=route.detail, is_dynamic=True )
[docs] def get_routes(self, handler): """ Augment `self.routes` with any dynamically generated routes. Returns a list of the Route class. """ # converting to list as iterables are good for one pass, known host needs to be checked again and again for # different functions. known_actions = list(itertools.chain(*[route.mapping.values() for route in self.routes if not route.is_dynamic])) extra_actions = handler.get_extra_actions() # checking action names against the known actions list not_allowed = [ action.__name__ for action in extra_actions if action.__name__ in known_actions ] if not_allowed: msg = ('Cannot use the @action decorator on the following ' 'methods, as they are existing routes: %s') raise ImproperlyConfigured(msg % ', '.join(not_allowed)) # partition detail and list actions detail_actions = [action for action in extra_actions if action.detail] list_actions = [action for action in extra_actions if not action.detail] routes = [] dynamic_routes = [] for route in self.routes: if route.is_dynamic and route.detail: dynamic_routes += [self._get_dynamic_route(route, action) for action in detail_actions] elif route.is_dynamic and not route.detail: dynamic_routes += [self._get_dynamic_route(route, action) for action in list_actions] else: routes.append(route) return dynamic_routes + routes
[docs] def get_method_map(self, handler, method_map): """ Given an handler, and a mapping of http methods to actions, return a new mapping which only includes any mappings that are actually implemented by the viewset. """ bound_methods = {} for method, action in method_map.items(): if hasattr(handler, action): bound_methods[method] = action return bound_methods
[docs] def get_urls(self): """ Use the registered handlers to generate a list of URL patterns. """ urls = [] for handler_name, handler_class in self.registry: lookup = self.get_lookup_regex(handler_class) routes = self.get_routes(handler_class) ret = [] for route in routes: # Only actions which actually exist on the viewset will be bound # mapping = self.get_method_map(handler_class, route.mapping) # if not mapping: # continue # Build the url pattern regex = route.url.format( prefix=handler_name, lookup=lookup, ) _url_with_slash = Url( handler_class=handler_class, handler_name=handler_name, route=route, regex=regex + '/$' ) _url_without_slash = Url( handler_class=handler_class, handler_name=handler_name, route=route, regex=regex + '$' ) ret.append(_url_with_slash) ret.append(_url_without_slash) urls.append(_url_with_slash) urls.append(_url_without_slash) setattr(handler_class, 'urls', ret) return urls
@property def urls(self): if not hasattr(self, '_urls'): self._urls = [] for _url in self.get_urls(): self._urls.append(( _url.regex, _url.handler_class )) return self._urls