예제 #1
0
    def add_route(self, uri_template, resource, **kwargs):
        """Adds a route between a URI path template and a resource.

        This method may be overridden to customize how a route is added.

        Args:
            uri_template (str): A URI template to use for the route
            resource (object): The resource instance to associate with
                the URI template.

        Keyword Args:
            suffix (str): Optional responder name suffix for this route. If
                a suffix is provided, Falcon will map GET requests to
                ``on_get_{suffix}()``, POST requests to ``on_post_{suffix}()``,
                etc. In this way, multiple closely-related routes can be
                mapped to the same resource. For example, a single resource
                class can use suffixed responders to distinguish requests
                for a single item vs. a collection of those same items.
                Another class might use a suffixed responder to handle
                a shortlink route in addition to the regular route for the
                resource.
        """

        method_map = self.map_http_methods(resource, **kwargs)
        set_default_responders(method_map)

        # NOTE(kgriffs): Fields may have whitespace in them, so sub
        # those before checking the rest of the URI template.
        if re.search(r'\s', _FIELD_PATTERN.sub('{FIELD}', uri_template)):
            raise ValueError('URI templates may not include whitespace.')

        path = uri_template.strip('/').split('/')

        used_names = set()
        for segment in path:
            self._validate_template_segment(segment, used_names)

        def insert(nodes, path_index=0):
            for node in nodes:
                segment = path[path_index]
                if node.matches(segment):
                    path_index += 1
                    if path_index == len(path):
                        # NOTE(kgriffs): Override previous node
                        node.method_map = method_map
                        node.resource = resource
                        node.uri_template = uri_template
                    else:
                        insert(node.children, path_index)

                    return

                if node.conflicts_with(segment):
                    msg = textwrap.dedent("""
                        The URI template for this route is inconsistent or conflicts with another
                        route's template. This is usually caused by configuring a field converter
                        differently for the same field in two different routes, or by using
                        different field names at the same level in the path (e.g.,
                        '/parents/{id}' and '/parents/{parent_id}/children')
                    """).strip().replace('\n', ' ')
                    raise ValueError(msg)

            # NOTE(richardolsson): If we got this far, the node doesn't already
            # exist and needs to be created. This builds a new branch of the
            # routing tree recursively until it reaches the new node leaf.
            new_node = CompiledRouterNode(path[path_index])
            nodes.append(new_node)
            if path_index == len(path) - 1:
                new_node.method_map = method_map
                new_node.resource = resource
                new_node.uri_template = uri_template
            else:
                insert(new_node.children, path_index + 1)

        insert(self._roots)
        self._find = self._compile()
예제 #2
0
    def add_route(self, uri_template, resource, **kwargs):
        """Adds a route between a URI path template and a resource.

        This method may be overridden to customize how a route is added.

        Args:
            uri_template (str): A URI template to use for the route
            resource (object): The resource instance to associate with
                the URI template.

        Keyword Args:
            suffix (str): Optional responder name suffix for this route. If
                a suffix is provided, Falcon will map GET requests to
                ``on_get_{suffix}()``, POST requests to ``on_post_{suffix}()``,
                etc. In this way, multiple closely-related routes can be
                mapped to the same resource. For example, a single resource
                class can use suffixed responders to distinguish requests
                for a single item vs. a collection of those same items.
                Another class might use a suffixed responder to handle
                a shortlink route in addition to the regular route for the
                resource.
        """

        method_map = self.map_http_methods(resource, **kwargs)
        set_default_responders(method_map)

        # NOTE(kgriffs): Fields may have whitespace in them, so sub
        # those before checking the rest of the URI template.
        if re.search(r'\s', _FIELD_PATTERN.sub('{FIELD}', uri_template)):
            raise ValueError('URI templates may not include whitespace.')

        path = uri_template.strip('/').split('/')

        used_names = set()
        for segment in path:
            self._validate_template_segment(segment, used_names)

        def insert(nodes, path_index=0):
            for node in nodes:
                segment = path[path_index]
                if node.matches(segment):
                    path_index += 1
                    if path_index == len(path):
                        # NOTE(kgriffs): Override previous node
                        node.method_map = method_map
                        node.resource = resource
                        node.uri_template = uri_template
                    else:
                        insert(node.children, path_index)

                    return

                if node.conflicts_with(segment):
                    msg = textwrap.dedent("""
                        The URI template for this route is inconsistent or conflicts with another
                        route's template. This is usually caused by configuring a field converter
                        differently for the same field in two different routes, or by using
                        different field names at the same level in the path (e.g.,
                        '/parents/{id}' and '/parents/{parent_id}/children')
                    """).strip().replace('\n', ' ')
                    raise ValueError(msg)

            # NOTE(richardolsson): If we got this far, the node doesn't already
            # exist and needs to be created. This builds a new branch of the
            # routing tree recursively until it reaches the new node leaf.
            new_node = CompiledRouterNode(path[path_index])
            nodes.append(new_node)
            if path_index == len(path) - 1:
                new_node.method_map = method_map
                new_node.resource = resource
                new_node.uri_template = uri_template
            else:
                insert(new_node.children, path_index + 1)

        insert(self._roots)
        self._find = self._compile()
예제 #3
0
    def add_route(self, uri_template, resource, **kwargs):
        """Add a route between a URI path template and a resource.

        This method may be overridden to customize how a route is added.

        Args:
            uri_template (str): A URI template to use for the route
            resource (object): The resource instance to associate with
                the URI template.

        Keyword Args:
            suffix (str): Optional responder name suffix for this route. If
                a suffix is provided, Falcon will map GET requests to
                ``on_get_{suffix}()``, POST requests to ``on_post_{suffix}()``,
                etc. In this way, multiple closely-related routes can be
                mapped to the same resource. For example, a single resource
                class can use suffixed responders to distinguish requests
                for a single item vs. a collection of those same items.
                Another class might use a suffixed responder to handle
                a shortlink route in addition to the regular route for the
                resource.
            compile (bool): Optional flag that can be used to compile the
                routing logic on this call. By default, :class:`.CompiledRouter`
                delays compilation until the first request is routed. This may
                introduce a noticeable amount of latency when handling the first
                request, especially when the application implements a large
                number of routes. Setting `compile` to ``True`` when the last
                route is added ensures that the first request will not be
                delayed in this case (defaults to ``False``).

                Note:
                    Always setting this flag to ``True`` may slow down the
                    addition of new routes when hundreds of them are added at
                    once. It is advisable to only set this flag to ``True`` when
                    adding the final route.
        """

        # NOTE(kgriffs): falcon.asgi.App injects this private kwarg; it is
        #   only intended to be used internally.
        asgi = kwargs.get('_asgi', False)

        method_map = self.map_http_methods(resource, **kwargs)

        set_default_responders(method_map, asgi=asgi)

        if asgi:
            self._require_coroutine_responders(method_map)
        else:
            self._require_non_coroutine_responders(method_map)

        # NOTE(kgriffs): Fields may have whitespace in them, so sub
        # those before checking the rest of the URI template.
        if re.search(r'\s', _FIELD_PATTERN.sub('{FIELD}', uri_template)):
            raise ValueError('URI templates may not include whitespace.')

        path = uri_template.lstrip('/').split('/')

        used_names = set()
        for segment in path:
            self._validate_template_segment(segment, used_names)

        def insert(nodes, path_index=0):
            for node in nodes:
                segment = path[path_index]
                if node.matches(segment):
                    path_index += 1
                    if path_index == len(path):
                        # NOTE(kgriffs): Override previous node
                        node.method_map = method_map
                        node.resource = resource
                        node.uri_template = uri_template
                    else:
                        insert(node.children, path_index)

                    return

                if node.conflicts_with(segment):
                    msg = textwrap.dedent("""
                        The URI template for this route is inconsistent or conflicts with another
                        route's template. This is usually caused by configuring a field converter
                        differently for the same field in two different routes, or by using
                        different field names at the same level in the path (e.g.,
                        '/parents/{id}' and '/parents/{parent_id}/children')
                    """).strip().replace('\n', ' ')
                    raise ValueError(msg)

            # NOTE(richardolsson): If we got this far, the node doesn't already
            # exist and needs to be created. This builds a new branch of the
            # routing tree recursively until it reaches the new node leaf.
            new_node = CompiledRouterNode(path[path_index])
            nodes.append(new_node)
            if path_index == len(path) - 1:
                new_node.method_map = method_map
                new_node.resource = resource
                new_node.uri_template = uri_template
            else:
                insert(new_node.children, path_index + 1)

        insert(self._roots)
        # NOTE(caselit): when compile is True run the actual compile step, otherwise reset the
        # _find, so that _compile will be called on the next find use
        if kwargs.get('compile', False):
            self._find = self._compile()
        else:
            self._find = self._compile_and_find