Example #1
0
 def on_options(self, req, resp, **kwargs):
     """Handle options requests"""
     method_map = routing.create_http_method_map(self)
     for method in method_map:
         if method_map.get(method).__name__ != 'method_not_allowed':
             resp.append_header('Allow', method)
     resp.status = falcon.HTTP_200
Example #2
0
    def add_route(self, uri_template, resource):
        """Associates a templatized URI path with a resource.

        A resource is an instance of a class that defines various on_*
        "responder" methods, one for each HTTP method the resource
        allows. For example, to support GET, simply define an `on_get`
        responder. If a client requests an unsupported method, Falcon
        will respond with "405 Method not allowed".

        Responders must always define at least two arguments to receive
        request and response objects, respectively. For example::

            def on_post(self, req, resp):
                pass

        In addition, if the route's template contains field
        expressions, any responder that desires to receive requests
        for that route must accept arguments named after the respective
        field names defined in the template. A field expression consists
        of a bracketed field name.

        For example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(resource, self._before,
                                                    self._after)
        self._router.add_route(uri_template, method_map, resource)
Example #3
0
    def add_route(self, uri_template, resource):
        """Associates a templatized URI path with a resource.

        A resource is an instance of a class that defines various on_*
        "responder" methods, one for each HTTP method the resource
        allows. For example, to support GET, simply define an `on_get`
        responder. If a client requests an unsupported method, Falcon
        will respond with "405 Method not allowed".

        Responders must always define at least two arguments to receive
        request and response objects, respectively. For example::

            def on_post(self, req, resp):
                pass

        In addition, if the route's template contains field
        expressions, any responder that desires to receive requests
        for that route must accept arguments named after the respective
        field names defined in the template. A field expression consists
        of a bracketed field name.

        For example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(
            resource, self._before, self._after)
        self._router.add_route(uri_template, method_map, resource)
Example #4
0
    def add_route(self, uri_template, resource, *args, **kwargs):
        """Associates a templatized URI path with a resource.

        Falcon routes incoming requests to resources based on a set of
        URI templates. If the path requested by the client matches the
        template for a given route, the request is then passed on to the
        associated resource for processing.

        If no route matches the request, control then passes to a
        default responder that simply raises an instance of
        :class:`~.HTTPNotFound`.

        (See also: :ref:`Routing <routing>`)

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered.

                (See also: :meth:`~.add_sink`)

            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        Note:
            Any additional args and kwargs not defined above are passed
            through to the underlying router's ``add_route()`` method. The
            default router does not expect any additional arguments, but
            custom routers may take advantage of this feature to receive
            additional options when setting up routes.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(resource)
        self._router.add_route(uri_template, method_map, resource, *args,
                               **kwargs)
Example #5
0
    def add_route(self, uri_template, resource):
        """Associates a templatized URI path with a resource.

        A resource is an instance of a class that defines various on_*
        "responder" methods, one for each HTTP method the resource
        allows. For example, to support GET, simply define an `on_get`
        responder. If a client requests an unsupported method, Falcon
        will respond with "405 Method not allowed".

        Responders must always define at least two arguments to receive
        request and response objects, respectively. For example::

            def on_post(self, req, resp):
                pass

        In addition, if the route's template contains field
        expressions, any responder that desires to receive requests
        for that route must accept arguments named after the respective
        field names defined in the template. A field expression consists
        of a bracketed field name.

        For example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        """

        uri_fields, path_template = routing.compile_uri_template(uri_template)
        method_map = routing.create_http_method_map(
            resource, uri_fields, self._before, self._after)

        # Insert at the head of the list in case we get duplicate
        # adds (will cause the last one to win).
        self._routes.insert(0, (path_template, method_map, resource))
Example #6
0
    def add_route(self, uri_template, resource):
        """Associates a templatized URI path with a resource.

        A resource is an instance of a class that defines various on_*
        "responder" methods, one for each HTTP method the resource
        allows. For example, to support GET, simply define an `on_get`
        responder. If a client requests an unsupported method, Falcon
        will respond with "405 Method not allowed".

        Responders must always define at least two arguments to receive
        request and response objects, respectively. For example::

            def on_post(self, req, resp):
                pass

        In addition, if the route's template contains field
        expressions, any responder that desires to receive requests
        for that route must accept arguments named after the respective
        field names defined in the template. A field expression consists
        of a bracketed field name.

        For example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also ``add_sink``).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        """

        uri_fields, path_template = routing.compile_uri_template(uri_template)
        method_map = routing.create_http_method_map(resource, uri_fields,
                                                    self._before, self._after)

        # Insert at the head of the list in case we get duplicate
        # adds (will cause the last one to win).
        self._routes.insert(0, (path_template, method_map, resource))
Example #7
0
    def add_route(self, uri_template, resource, *args, **kwargs):
        """Associates a templatized URI path with a resource.

        A resource is an instance of a class that defines various
        "responder" methods, one for each HTTP method the resource
        allows. Responder names start with `on_` and are named according to
        which HTTP method they handle, as in `on_get`, `on_post`, `on_put`,
        etc.

        If your resource does not support a particular
        HTTP method, simply omit the corresponding responder and
        Falcon will reply with "405 Method not allowed" if that
        method is ever requested.

        Responders must always define at least two arguments to receive
        request and response objects, respectively. For example::

            def on_post(self, req, resp):
                pass

        In addition, if the route's template contains field
        expressions, any responder that desires to receive requests
        for that route must accept arguments named after the respective
        field names defined in the template. A field expression consists
        of a bracketed field name.

        For example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Individual path segments may contain one or more field expressions.
        For example::

            /repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        Note:
            Any additional args and kwargs not defined above are passed
            through to the underlying router's ``add_route()`` method. The
            default router does not expect any additional arguments, but
            custom routers may take advantage of this feature to receive
            additional options when setting up routes.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(resource)
        self._router.add_route(uri_template, method_map, resource, *args,
                               **kwargs)
Example #8
0
    def add_route(self, uri_template, resource, *args, **kwargs):
        """Associates a templatized URI path with a resource.

        Note:
            The following information describes the behavior of
            Falcon's default router.

        A resource is an instance of a class that defines various
        "responder" methods, one for each HTTP method the resource
        allows. Responder names start with `on_` and are named according to
        which HTTP method they handle, as in `on_get`, `on_post`, `on_put`,
        etc.

        Note:
            If your resource does not support a particular
            HTTP method, simply omit the corresponding responder and
            Falcon will reply with "405 Method not allowed" if that
            method is ever requested.

        Responders must always define at least two arguments to receive
        :class:`~.Request` and :class:`~.Response` objects, respectively.
        For example::

            def on_post(self, req, resp):
                pass

        The :class:`~.Request` object represents the incoming HTTP
        request. It exposes properties and methods for examining headers,
        query string parameters, and other metadata associated with
        the request. A file-like stream is also provided for reading
        any data that was included in the body of the request.

        The :class:`~.Response` object represents the application's
        HTTP response to the above request. It provides properties
        and methods for setting status, header and body data. The
        :class:`~.Response` object also exposes a dict-like
        :attr:`~.Response.context` property for passing arbitrary
        data to hooks and middleware methods. This property could be
        used, for example, to provide a resource representation to a
        middleware component that would then serialize the
        representation according to the client's preferred media type.

        Note:
            Rather than directly manipulate the :class:`~.Response`
            object, a responder may raise an instance of either
            :class:`~.HTTPError` or :class:`~.HTTPStatus`.

        In addition to the standard `req` and `resp` parameters, if the
        route's template contains field expressions, any responder that
        desires to receive requests for that route must accept arguments
        named after the respective field names defined in the template.

        A field expression consists of a bracketed field name. For
        example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Individual path segments may contain one or more field
        expressions, and fields need not span the entire path
        segment. For example::

            /repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}
            /serviceRoot/People('{name}')

        Note:
            Because field names correspond to argument names in responder
            methods, they must be valid Python identifiers.

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        Note:
            Any additional args and kwargs not defined above are passed
            through to the underlying router's ``add_route()`` method. The
            default router does not expect any additional arguments, but
            custom routers may take advantage of this feature to receive
            additional options when setting up routes.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(resource)
        self._router.add_route(uri_template, method_map, resource, *args,
                               **kwargs)
Example #9
0
    def add_route(self, uri_template, resource, *args, **kwargs):
        """Associates a templatized URI path with a resource.

        Note:
            The following information describes the behavior of
            Falcon's default router.

        A resource is an instance of a class that defines various
        "responder" methods, one for each HTTP method the resource
        allows. Responder names start with `on_` and are named according to
        which HTTP method they handle, as in `on_get`, `on_post`, `on_put`,
        etc.

        If your resource does not support a particular
        HTTP method, simply omit the corresponding responder and
        Falcon will reply with "405 Method not allowed" if that
        method is ever requested.

        Responders must always define at least two arguments to receive
        request and response objects, respectively. For example::

            def on_post(self, req, resp):
                pass

        In addition, if the route's template contains field
        expressions, any responder that desires to receive requests
        for that route must accept arguments named after the respective
        field names defined in the template. A field expression consists
        of a bracketed field name.

        Note:
            Since field names correspond to argument names in responder
            methods, they must be valid Python identifiers.

        For example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Individual path segments may contain one or more field
        expressions::

            /repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        Note:
            Any additional args and kwargs not defined above are passed
            through to the underlying router's ``add_route()`` method. The
            default router does not expect any additional arguments, but
            custom routers may take advantage of this feature to receive
            additional options when setting up routes.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(resource)
        self._router.add_route(uri_template, method_map, resource, *args,
                               **kwargs)
Example #10
0
    def add_route(self, uri_template, resource, *args, **kwargs):
        """Associates a templatized URI path with a resource.

        Note:
            The following information describes the behavior of
            Falcon's default router.

        A resource is an instance of a class that defines various
        "responder" methods, one for each HTTP method the resource
        allows. Responder names start with `on_` and are named according to
        which HTTP method they handle, as in `on_get`, `on_post`, `on_put`,
        etc.

        Note:
            If your resource does not support a particular
            HTTP method, simply omit the corresponding responder and
            Falcon will reply with "405 Method not allowed" if that
            method is ever requested.

        Responders must always define at least two arguments to receive
        :class:`~.Request` and :class:`~.Response` objects, respectively.
        For example::

            def on_post(self, req, resp):
                pass

        The :class:`~.Request` object represents the incoming HTTP
        request. It exposes properties and methods for examining headers,
        query string parameters, and other metadata associated with
        the request. A file-like stream is also provided for reading
        any data that was included in the body of the request.

        The :class:`~.Response` object represents the application's
        HTTP response to the above request. It provides properties
        and methods for setting status, header and body data. The
        :class:`~.Response` object also exposes a dict-like
        :attr:`~.Response.context` property for passing arbitrary
        data to hooks and middleware methods. This property could be
        used, for example, to provide a resource representation to a
        middleware component that would then serialize the
        representation according to the client's preferred media type.

        Note:
            Rather than directly manipulate the :class:`~.Response`
            object, a responder may raise an instance of either
            :class:`~.HTTPError` or :class:`~.HTTPStatus`.

        In addition to the standard `req` and `resp` parameters, if the
        route's template contains field expressions, any responder that
        desires to receive requests for that route must accept arguments
        named after the respective field names defined in the template.

        A field expression consists of a bracketed field name. For
        example, given the following template::

            /user/{name}

        A PUT request to "/user/kgriffs" would be routed to::

            def on_put(self, req, resp, name):
                pass

        Individual path segments may contain one or more field
        expressions, and fields need not span the entire path
        segment. For example::

            /repos/{org}/{repo}/compare/{usr0}:{branch0}...{usr1}:{branch1}
            /serviceRoot/People('{name}')

        Note:
            Because field names correspond to argument names in responder
            methods, they must be valid Python identifiers.

        Args:
            uri_template (str): A templatized URI. Care must be
                taken to ensure the template does not mask any sink
                patterns, if any are registered (see also `add_sink`).
            resource (instance): Object which represents a REST
                resource. Falcon will pass "GET" requests to on_get,
                "PUT" requests to on_put, etc. If any HTTP methods are not
                supported by your resource, simply don't define the
                corresponding request handlers, and Falcon will do the right
                thing.

        Note:
            Any additional args and kwargs not defined above are passed
            through to the underlying router's ``add_route()`` method. The
            default router does not expect any additional arguments, but
            custom routers may take advantage of this feature to receive
            additional options when setting up routes.

        """

        # NOTE(richardolsson): Doing the validation here means it doesn't have
        # to be duplicated in every future router implementation.
        if not isinstance(uri_template, six.string_types):
            raise TypeError('uri_template is not a string')

        if not uri_template.startswith('/'):
            raise ValueError("uri_template must start with '/'")

        if '//' in uri_template:
            raise ValueError("uri_template may not contain '//'")

        method_map = routing.create_http_method_map(resource)
        self._router.add_route(uri_template, method_map, resource, *args,
                               **kwargs)
Example #11
0
 def find(self, uri):
     segments = uri.split(SLASH)
     # Since the path is always rooted at /, skip the first segment, which
     # will always be the empty string.
     segments.pop(0)
     this_segment = segments.pop(0)
     resource = self._root
     context = {}
     while True:
         # Plumb the API through to all child resources.
         api = getattr(resource, 'api', None)
         # See if any of the resource's child links match the next segment.
         for name in dir(resource):
             if name.startswith('__') and name.endswith('__'):
                 continue
             attribute = getattr(resource, name, MISSING)
             assert attribute is not MISSING, name
             matcher = getattr(attribute, '__matcher__', MISSING)
             if matcher is MISSING:
                 continue
             result = None
             if isinstance(matcher, str):
                 # Is the matcher string a regular expression or plain
                 # string?  If it starts with a caret, it's a regexp.
                 if matcher.startswith('^'):
                     cre = re.compile(matcher)
                     # Search against the entire remaining path.
                     tmp_segments = segments[:]
                     tmp_segments.insert(0, this_segment)
                     remaining_path = SLASH.join(tmp_segments)
                     mo = cre.match(remaining_path)
                     if mo:
                         result = attribute(context, segments,
                                            **mo.groupdict())
                 elif matcher == this_segment:
                     result = attribute(context, segments)
             else:
                 # The matcher is a callable.  It returns None if it
                 # doesn't match, and if it does, it returns a 3-tuple
                 # containing the positional arguments, the keyword
                 # arguments, and the remaining segments.  The attribute is
                 # then called with these arguments.  Note that the matcher
                 # wants to see the full remaining path components, which
                 # includes the current hop.
                 tmp_segments = segments[:]
                 tmp_segments.insert(0, this_segment)
                 matcher_result = matcher(tmp_segments)
                 if matcher_result is not None:
                     positional, keyword, segments = matcher_result
                     result = attribute(context, segments, *positional,
                                        **keyword)
             # The attribute could return a 2-tuple giving the resource and
             # remaining path segments, or it could just return the result.
             # Of course, if the result is None, then the matcher did not
             # match.
             if result is None:
                 continue
             elif isinstance(result, tuple):
                 resource, segments = result
             else:
                 resource = result
             # See if the context set an API and set it on the next
             # resource in the chain, falling back to the parent resource's
             # API if there is one.
             resource.api = context.pop('api', api)
             # The method could have truncated the remaining segments,
             # meaning, it's consumed all the path segments, or this is the
             # last path segment.  In that case the resource we're left at
             # is the responder.
             if len(segments) == 0:
                 # We're at the end of the path, so the root must be the
                 # responder.
                 method_map = create_http_method_map(resource)
                 return resource, method_map, context
             this_segment = segments.pop(0)
             break
         else:
             # None of the attributes matched this path component, so the
             # response is a 404.
             return None, None, None
Example #12
0
 def _get_responder(self, req):
     path = req.path
     method = req.method
     path_segments = path.split("/")
     # Since the path is always rooted at /, skip the first segment, which
     # will always be the empty string.
     path_segments.pop(0)
     this_segment = path_segments.pop(0)
     resource = self._root
     while True:
         # See if there's a child matching the current segment.
         # See if any of the resource's child links match the next segment.
         for name in dir(resource):
             if name.startswith("__") and name.endswith("__"):
                 continue
             attribute = getattr(resource, name, _missing)
             assert attribute is not _missing, name
             matcher = getattr(attribute, "__matcher__", _missing)
             if matcher is _missing:
                 continue
             result = None
             if isinstance(matcher, str):
                 # Is the matcher string a regular expression or plain
                 # string?  If it starts with a caret, it's a regexp.
                 if matcher.startswith("^"):
                     cre = re.compile(matcher)
                     # Search against the entire remaining path.
                     tmp_path_segments = path_segments[:]
                     tmp_path_segments.insert(0, this_segment)
                     remaining_path = SLASH.join(tmp_path_segments)
                     mo = cre.match(remaining_path)
                     if mo:
                         result = attribute(req, path_segments, **mo.groupdict())
                 elif matcher == this_segment:
                     result = attribute(req, path_segments)
             else:
                 # The matcher is a callable.  It returns None if it
                 # doesn't match, and if it does, it returns a 3-tuple
                 # containing the positional arguments, the keyword
                 # arguments, and the remaining segments.  The attribute is
                 # then called with these arguments.  Note that the matcher
                 # wants to see the full remaining path components, which
                 # includes the current hop.
                 tmp_path_segments = path_segments[:]
                 tmp_path_segments.insert(0, this_segment)
                 matcher_result = matcher(req, tmp_path_segments)
                 if matcher_result is not None:
                     positional, keyword, path_segments = matcher_result
                     result = attribute(req, path_segments, *positional, **keyword)
             # The attribute could return a 2-tuple giving the resource and
             # remaining path segments, or it could just return the
             # result.  Of course, if the result is None, then the matcher
             # did not match.
             if result is None:
                 continue
             elif isinstance(result, tuple):
                 resource, path_segments = result
             else:
                 resource = result
             # The method could have truncated the remaining segments,
             # meaning, it's consumed all the path segments, or this is the
             # last path segment.  In that case the resource we're left at
             # is the responder.
             if len(path_segments) == 0:
                 # We're at the end of the path, so the root must be the
                 # responder.
                 method_map = create_http_method_map(resource, None, None)
                 responder = method_map[method]
                 return responder, {}, resource
             this_segment = path_segments.pop(0)
             break
         else:
             # None of the attributes matched this path component, so the
             # response is a 404.
             return path_not_found, {}, None
Example #13
0
 def _get_responder(self, req):
     path = req.path
     method = req.method
     path_segments = path.split('/')
     # Since the path is always rooted at /, skip the first segment, which
     # will always be the empty string.
     path_segments.pop(0)
     this_segment = path_segments.pop(0)
     resource = self._root
     while True:
         # See if there's a child matching the current segment.
         # See if any of the resource's child links match the next segment.
         for name in dir(resource):
             if name.startswith('__') and name.endswith('__'):
                 continue
             attribute = getattr(resource, name, _missing)
             assert attribute is not _missing, name
             matcher = getattr(attribute, '__matcher__', _missing)
             if matcher is _missing:
                 continue
             result = None
             if isinstance(matcher, str):
                 # Is the matcher string a regular expression or plain
                 # string?  If it starts with a caret, it's a regexp.
                 if matcher.startswith('^'):
                     cre = re.compile(matcher)
                     # Search against the entire remaining path.
                     tmp_path_segments = path_segments[:]
                     tmp_path_segments.insert(0, this_segment)
                     remaining_path = SLASH.join(tmp_path_segments)
                     mo = cre.match(remaining_path)
                     if mo:
                         result = attribute(req, path_segments,
                                            **mo.groupdict())
                 elif matcher == this_segment:
                     result = attribute(req, path_segments)
             else:
                 # The matcher is a callable.  It returns None if it
                 # doesn't match, and if it does, it returns a 3-tuple
                 # containing the positional arguments, the keyword
                 # arguments, and the remaining segments.  The attribute is
                 # then called with these arguments.  Note that the matcher
                 # wants to see the full remaining path components, which
                 # includes the current hop.
                 tmp_path_segments = path_segments[:]
                 tmp_path_segments.insert(0, this_segment)
                 matcher_result = matcher(req, tmp_path_segments)
                 if matcher_result is not None:
                     positional, keyword, path_segments = matcher_result
                     result = attribute(req, path_segments, *positional,
                                        **keyword)
             # The attribute could return a 2-tuple giving the resource and
             # remaining path segments, or it could just return the
             # result.  Of course, if the result is None, then the matcher
             # did not match.
             if result is None:
                 continue
             elif isinstance(result, tuple):
                 resource, path_segments = result
             else:
                 resource = result
             # The method could have truncated the remaining segments,
             # meaning, it's consumed all the path segments, or this is the
             # last path segment.  In that case the resource we're left at
             # is the responder.
             if len(path_segments) == 0:
                 # We're at the end of the path, so the root must be the
                 # responder.
                 method_map = create_http_method_map(
                     resource, None, None, None)
                 responder = method_map[method]
                 return responder, {}, resource
             this_segment = path_segments.pop(0)
             break
         else:
             # None of the attributes matched this path component, so the
             # response is a 404.
             return path_not_found, {}, None
Example #14
0
 def find(self, uri):
     segments = uri.split(SLASH)
     # Since the path is always rooted at /, skip the first segment, which
     # will always be the empty string.
     segments.pop(0)
     this_segment = segments.pop(0)
     resource = self._root
     context = {}
     while True:
         # Plumb the API through to all child resources.
         api = getattr(resource, 'api', None)
         # See if any of the resource's child links match the next segment.
         for name in dir(resource):
             if name.startswith('__') and name.endswith('__'):
                 continue
             attribute = getattr(resource, name, MISSING)
             assert attribute is not MISSING, name
             matcher = getattr(attribute, '__matcher__', MISSING)
             if matcher is MISSING:
                 continue
             result = None
             if isinstance(matcher, str):
                 # Is the matcher string a regular expression or plain
                 # string?  If it starts with a caret, it's a regexp.
                 if matcher.startswith('^'):
                     cre = re.compile(matcher)
                     # Search against the entire remaining path.
                     tmp_segments = segments[:]
                     tmp_segments.insert(0, this_segment)
                     remaining_path = SLASH.join(tmp_segments)
                     mo = cre.match(remaining_path)
                     if mo:
                         result = attribute(
                             context, segments, **mo.groupdict())
                 elif matcher == this_segment:
                     result = attribute(context, segments)
             else:
                 # The matcher is a callable.  It returns None if it
                 # doesn't match, and if it does, it returns a 3-tuple
                 # containing the positional arguments, the keyword
                 # arguments, and the remaining segments.  The attribute is
                 # then called with these arguments.  Note that the matcher
                 # wants to see the full remaining path components, which
                 # includes the current hop.
                 tmp_segments = segments[:]
                 tmp_segments.insert(0, this_segment)
                 matcher_result = matcher(tmp_segments)
                 if matcher_result is not None:
                     positional, keyword, segments = matcher_result
                     result = attribute(
                         context, segments, *positional, **keyword)
             # The attribute could return a 2-tuple giving the resource and
             # remaining path segments, or it could just return the result.
             # Of course, if the result is None, then the matcher did not
             # match.
             if result is None:
                 continue
             elif isinstance(result, tuple):
                 resource, segments = result
             else:
                 resource = result
             # See if the context set an API and set it on the next
             # resource in the chain, falling back to the parent resource's
             # API if there is one.
             resource.api = context.pop('api', api)
             # The method could have truncated the remaining segments,
             # meaning, it's consumed all the path segments, or this is the
             # last path segment.  In that case the resource we're left at
             # is the responder.
             if len(segments) == 0:
                 # We're at the end of the path, so the root must be the
                 # responder.
                 method_map = create_http_method_map(resource)
                 return resource, method_map, context
             this_segment = segments.pop(0)
             break
         else:
             # None of the attributes matched this path component, so the
             # response is a 404.
             return None, None, None