]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
add docs for routing module
authorAndrey Sumin <an.sumin@hh.ru>
Thu, 10 Nov 2016 10:39:35 +0000 (13:39 +0300)
committerAndrey Sumin <an.sumin@hh.ru>
Mon, 14 Nov 2016 12:40:29 +0000 (15:40 +0300)
docs/releases/v2.3.0.rst
docs/releases/v3.2.0.rst
docs/routing.rst [new file with mode: 0644]
docs/webframework.rst
tornado/routing.py

index 368ceec964dce865241b552caea782f10fd910c6..d24f46c54770381ab3d55d8427313edb93800287 100644 (file)
@@ -80,7 +80,7 @@ HTTP Server
   backwards-incompatible change to an interface that was never technically
   private, but was not included in the documentation and does not appear
   to have been used outside Tornado itself.
-* Fixed a bug on python versions before 2.6.5 when `.URLSpec` regexes
+* Fixed a bug on python versions before 2.6.5 when `tornado.web.URLSpec` regexes
   are constructed from unicode strings and keyword arguments are extracted.
 * The ``reverse_url`` function in the template namespace now comes from
   the `.RequestHandler` rather than the `.Application`.  (Unless overridden,
index 95db3e9800f59c32871d9cd613810dd31ef7da2f..09057030ac30e632757c8df150ef090caad5bc4d 100644 (file)
@@ -164,11 +164,11 @@ New modules
   argument could not be decoded.
 * `.RequestHandler.clear_all_cookies` now accepts ``domain`` and ``path``
   arguments, just like `~.RequestHandler.clear_cookie`.
-* It is now possible to specify handlers by name when using the `.URLSpec`
-  class.
+* It is now possible to specify handlers by name when using the
+  `tornado.web.URLSpec` class.
 * `.Application` now accepts 4-tuples to specify the ``name`` parameter
-  (which previously required constructing a `.URLSpec` object instead of
-  a tuple).
+  (which previously required constructing a `tornado.web.URLSpec` object
+  instead of a tuple).
 * Fixed an incorrect error message when handler methods return a value
   other than None or a Future.
 * Exceptions will no longer be logged twice when using both ``@asynchronous``
diff --git a/docs/routing.rst b/docs/routing.rst
new file mode 100644 (file)
index 0000000..ec6b0ca
--- /dev/null
@@ -0,0 +1,5 @@
+``tornado.routing`` --- Basic routing implementation
+====================================================
+
+.. automodule:: tornado.routing
+   :members:
index ab3ff1cf23dd63ce36dda9fd8ade36b58e88a2d2..ab93ccb247261c0402a2691ff9c7c30374aa5fd8 100644 (file)
@@ -5,6 +5,7 @@ Web framework
 
    web
    template
+   routing
    escape
    locale
    websocket
index ba6f9d24ad1380f9d7336d2f2608d8ed28683d04..cb584cf3d437ba0947635ee4b3369ba731fcc55f 100644 (file)
 # License for the specific language governing permissions and limitations
 # under the License.
 
-"""Basic implementation of rule-based routing.
+"""Basic routing implementation.
+
+Tornado routes HTTP requests to appropriate handlers using `Router` class implementations.
+
+Any `Router` implementation can be used directly as a ``request_callback`` for
+`~.httpserver.HTTPServer` (this is possible because `Router` implements
+`~.httputil.HTTPServerConnectionDelegate`):
+
+.. code-block:: python
+
+    class CustomRouter(Router):
+        def find_handler(self, request, **kwargs):
+            # some routing logic providing a suitable HTTPMessageDelegate implementation
+            return HTTPMessageDelegateImplementation()
+
+    router = CustomRouter()
+    server = HTTPServer(router)
+
+`Router.find_handler` is the only method that you must implement to provide routing
+logic in its simplest case. This method must return an instance of
+`~.httputil.HTTPMessageDelegate` that will be used to process the request.
+
+`ReversibleRouter` interface adds the ability to distinguish between the routes and
+reverse them to the original urls using route's name and additional arguments.
+`~.web.Application` is itself an implementation of `ReversibleRouter` class.
+
+`RuleRouter` and `ReversibleRuleRouter` provide an interface for creating rule-based
+routing configurations. For example, `RuleRouter` can be used to route between applications:
+
+.. code-block:: python
+
+    app1 = Application([
+        (r"/app1/handler1", Handler1),
+        # other handlers ...
+    ])
+
+    app2 = Application([
+        (r"/app2/handler1", Handler1),
+        # other handlers ...
+    ])
+
+    router = RuleRouter([
+        Rule(PathMatches("/app1.*"), app1),
+        Rule(PathMatches("/app2.*"), app2)
+    ])
+
+    server = HTTPServer(router)
+
+Subclasses of `~.httputil.HTTPMessageDelegate` and old-style callables can also be used as
+rule targets:
+
+.. code-block:: python
+
+    router = RuleRouter([
+        Rule(PathMatches("/callable"), request_callable),  # def request_callable(request): ...
+        Rule(PathMatches("/delegate"), HTTPMessageDelegateSubclass)
+    ])
+
+    server = HTTPServer(router)
+
+You can use nested routers as targets as well:
+
+.. code-block:: python
+
+    router = RuleRouter([
+        Rule(PathMatches("/router.*"), CustomRouter())
+    ])
+
+    server = HTTPServer(router)
+
+And of course a nested `RuleRouter` would be a valid thing:
+
+.. code-block:: python
+
+    router = RuleRouter([
+        Rule(HostMatches("example.com"), RuleRouter([
+            Rule(PathMatches("/app1/.*"), Application([(r"/app1/handler", Handler)]))),
+        ]))
+    ])
+
+    server = HTTPServer(router)
+
+Rules are instances of `Rule` class. They contain some target (`~.web.Application` instance,
+`~.httputil.HTTPMessageDelegate` subclass, a callable or a nested `Router`) and provide the
+basic routing logic defining whether this rule is a match for a particular request.
+This routing logic is implemented in `Matcher` subclasses (see `HostMatches`, `PathMatches`
+and others).
+
+`~URLSpec` is simply a subclass of a `Rule` with `PathMatches` matcher and is preserved for
+backwards compatibility.
 """
 
 from __future__ import absolute_import, division, print_function, with_statement
@@ -34,15 +123,18 @@ except ImportError:
 
 
 class Router(httputil.HTTPServerConnectionDelegate):
-    """Abstract router interface.
-    Any `Router` instance that correctly implements `find_handler` method can be used as a ``request_callback``
-    in `httpserver.HTTPServer`.
-    """
+    """Abstract router interface."""
+
     def find_handler(self, request, **kwargs):
         # type: (httputil.HTTPServerRequest, typing.Any)->httputil.HTTPMessageDelegate
-        """Must be implemented to return an appropriate instance of `httputil.HTTPMessageDelegate`
-        that can serve current request.
-        Router implementations may pass additional kwargs to extend the routing logic.
+        """Must be implemented to return an appropriate instance of `~.httputil.HTTPMessageDelegate`
+        that can serve the request.
+        Routing implementations may pass additional kwargs to extend the routing logic.
+
+        :arg httputil.HTTPServerRequest request: current HTTP request.
+        :arg kwargs: additional keyword arguments passed by routing implementation.
+        :returns: an instance of `~.httputil.HTTPMessageDelegate` that will be used to
+            process the request.
         """
         raise NotImplementedError()
 
@@ -51,7 +143,18 @@ class Router(httputil.HTTPServerConnectionDelegate):
 
 
 class ReversibleRouter(Router):
+    """Abstract router interface for routers that can handle named routes
+    and support reversing them to original urls.
+    """
+
     def reverse_url(self, name, *args):
+        """Returns url string for a given route name and arguments
+        or ``None`` if no match is found.
+
+        :arg str name: route name.
+        :arg args: url parameters.
+        :returns: parametrized url string for a given route name (or ``None``).
+        """
         raise NotImplementedError()
 
 
@@ -80,12 +183,41 @@ class _RoutingDelegate(httputil.HTTPMessageDelegate):
 
 
 class RuleRouter(Router):
+    """Rule-based router implementation."""
+
     def __init__(self, rules=None):
+        """Constructs a router with an ordered list of rules::
+
+            RuleRouter([
+                Rule(PathMatches("/handler"), SomeHandler),
+                # ... more rules
+            ])
+
+        You can also omit explicit `Rule` constructor and use tuples of arguments::
+
+            RuleRouter([
+                (PathMatches("/handler"), SomeHandler),
+            ])
+
+        `PathMatches` is a default matcher, so the example above can be simplified::
+
+            RuleRouter([
+                ("/handler", SomeHandler),
+            ])
+
+        :arg rules: a list of `Rule` instances or tuples of `Rule`
+            constructor arguments.
+        """
         self.rules = []  # type: typing.List[Rule]
         if rules:
             self.add_rules(rules)
 
     def add_rules(self, rules):
+        """Appends new rules to the router.
+
+        :arg rules: a list of Rule instances (or tuples of arguments, which are
+            passed to Rule constructor).
+        """
         for rule in rules:
             if isinstance(rule, (tuple, list)):
                 assert len(rule) in (2, 3, 4)
@@ -97,6 +229,11 @@ class RuleRouter(Router):
             self.rules.append(self.process_rule(rule))
 
     def process_rule(self, rule):
+        """Override this method for additional preprocessing of each rule.
+
+        :arg Rule rule: a rule to be processed.
+        :returns: the same or modified Rule instance.
+        """
         return rule
 
     def find_handler(self, request, **kwargs):
@@ -115,6 +252,15 @@ class RuleRouter(Router):
         return None
 
     def get_target_delegate(self, target, request, **target_params):
+        """Returns an instance of `~.httputil.HTTPMessageDelegate` for a
+        Rule's target. This method is called by `~.find_handler` and can be
+        extended to provide additional target types.
+
+        :arg target: a Rule's target.
+        :arg httputil.HTTPServerRequest request: current request.
+        :arg target_params: additional parameters that can be useful
+            for `~.httputil.HTTPMessageDelegate` creation.
+        """
         if isinstance(target, Router):
             return target.find_handler(request, **target_params)
 
@@ -130,8 +276,15 @@ class RuleRouter(Router):
 
 
 class ReversibleRuleRouter(ReversibleRouter, RuleRouter):
+    """A rule-based router that implements ``reverse_url`` method.
+
+    Each rule added to this router may have a ``name`` attribute that can be
+    used to reconstruct an original uri. The actual reconstruction takes place
+    in a rule's matcher (see `Matcher.reverse`).
+    """
+
     def __init__(self, rules=None):
-        self.named_rules = {}
+        self.named_rules = {}  # type: typing.Dict[str]
         super(ReversibleRuleRouter, self).__init__(rules)
 
     def process_rule(self, rule):
@@ -160,7 +313,24 @@ class ReversibleRuleRouter(ReversibleRouter, RuleRouter):
 
 
 class Rule(object):
+    """A routing rule."""
+
     def __init__(self, matcher, target, target_kwargs=None, name=None):
+        """Constructs a Rule instance.
+
+        :arg Matcher matcher: a `Matcher` instance used for determining
+            whether the rule should be considered a match for a specific
+            request.
+        :arg target: a Rule's target (typically a ``RequestHandler`` or
+            `~.httputil.HTTPMessageDelegate` subclass or even a nested `Router`).
+        :arg dict target_kwargs: a dict of parameters that can be useful
+            at the moment of target instantiation (for example, ``status_code``
+            for a ``RequestHandler`` subclass). They end up in
+            ``target_params['target_kwargs']`` of `RuleRouter.get_target_delegate`
+            method.
+        :arg str name: the name of the rule that can be used to find it
+            in `ReversibleRouter.reverse_url` implementation.
+        """
         if isinstance(target, str):
             # import the Module and instantiate the class
             # Must be a fully qualified name (module.ClassName)
@@ -181,34 +351,35 @@ class Rule(object):
 
 
 class Matcher(object):
-    """A complex matcher can be represented as an instance of some
-    `Matcher` subclass. It must implement ``__call__`` (which will be executed
-    with a `httpserver.HTTPRequest` argument).
-    """
+    """Represents a matcher for request features."""
 
     def match(self, request):
-        """Matches an instance against the request.
-
-        :arg tornado.httpserver.HTTPRequest request: current HTTP request
-        :returns a dict of parameters to be passed to the target handler
-        (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs``
-        can be passed for proper `tornado.web.RequestHandler` instantiation).
-        An empty dict is a valid (and common) return value to indicate a match
-        when the argument-passing features are not used.
-        ``None`` must be returned to indicate that there is no match."""
+        """Matches current instance against the request.
+
+        :arg httputil.HTTPServerRequest request: current HTTP request
+        :returns: a dict of parameters to be passed to the target handler
+            (for example, ``handler_kwargs``, ``path_args``, ``path_kwargs``
+            can be passed for proper `~.web.RequestHandler` instantiation).
+            An empty dict is a valid (and common) return value to indicate a match
+            when the argument-passing features are not used.
+            ``None`` must be returned to indicate that there is no match."""
         raise NotImplementedError()
 
     def reverse(self, *args):
-        """Reconstruct URL from matcher instance"""
+        """Reconstructs full url from matcher instance and additional arguments."""
         return None
 
 
 class AnyMatches(Matcher):
+    """Matches any request."""
+
     def match(self, request):
         return {}
 
 
 class HostMatches(Matcher):
+    """Matches requests from hosts specified by ``host_pattern`` regex."""
+
     def __init__(self, host_pattern):
         if isinstance(host_pattern, basestring_type):
             if not host_pattern.endswith("$"):
@@ -225,6 +396,10 @@ class HostMatches(Matcher):
 
 
 class DefaultHostMatches(Matcher):
+    """Matches requests from host that is equal to application's default_host.
+    Always returns no match if ``X-Real-Ip`` header is present.
+    """
+
     def __init__(self, application, host_pattern):
         self.application = application
         self.host_pattern = host_pattern
@@ -238,13 +413,15 @@ class DefaultHostMatches(Matcher):
 
 
 class PathMatches(Matcher):
-    def __init__(self, pattern):
-        if isinstance(pattern, basestring_type):
-            if not pattern.endswith('$'):
-                pattern += '$'
-            self.regex = re.compile(pattern)
+    """Matches requests with paths specified by ``path_pattern`` regex."""
+
+    def __init__(self, path_pattern):
+        if isinstance(path_pattern, basestring_type):
+            if not path_pattern.endswith('$'):
+                path_pattern += '$'
+            self.regex = re.compile(path_pattern)
         else:
-            self.regex = pattern
+            self.regex = path_pattern
 
         assert len(self.regex.groupindex) in (0, self.regex.groups), \
             ("groups in url regexes must either be all named or all "
@@ -334,13 +511,13 @@ class URLSpec(Rule):
           position if unnamed. Named and unnamed capturing groups may
           may not be mixed in the same rule).
 
-        * ``handler``: `RequestHandler` subclass to be invoked.
+        * ``handler``: `~.web.RequestHandler` subclass to be invoked.
 
         * ``kwargs`` (optional): A dictionary of additional arguments
           to be passed to the handler's constructor.
 
         * ``name`` (optional): A name for this handler.  Used by
-          `Application.reverse_url`.
+          `~.web.Application.reverse_url`.
 
         """
         super(URLSpec, self).__init__(PathMatches(pattern), handler, kwargs, name)