Andriy Kornatskyy avatar Andriy Kornatskyy committed 1c8182b

Introduced choice route.

Comments (0)

Files changed (5)

src/wheezy/routing/choice.py

+
+""" ``plain`` module.
+"""
+
+import re
+
+
+RE_CHOICE_ROUTE = re.compile(
+    '^(?P<p>[\w/]*)'
+    '\{(?P<n>\w+):\((?P<c>[\w|]+)\)\}'
+    '(?P<s>[\w/]*)$')
+
+
+def try_build_choice_route(pattern, finishing=True, kwargs=None, name=None):
+    """ If the choince route regular expression match the pattern
+        than create a ChoiceRoute instance.
+    """
+    if isinstance(pattern, ChoiceRoute):
+        return pattern
+    m = RE_CHOICE_ROUTE.match(pattern)
+    if m:
+        return ChoiceRoute(pattern, finishing, kwargs, name)
+    return None
+
+
+class ChoiceRoute(object):
+    """ Route based on choice match, e.g. {locale:(en|ru)}.
+    """
+
+    __slots__ = ('kwargs', 'name', 'exact_matches', 'patterns', 'path_format')
+
+    def __init__(self, pattern, finishing=True, kwargs=None, name=None):
+        kwargs = kwargs and kwargs.copy() or {}
+        if name:
+            kwargs['route_name'] = name
+        self.kwargs = kwargs
+        m = RE_CHOICE_ROUTE.match(pattern)
+        prefix, self.name, choice, suffix = m.groups()
+        choices = choice.split('|')
+        self.exact_matches = [(prefix + c + suffix,
+                               dict(kwargs, **{self.name: c}))
+                              for c in choices]
+        self.patterns = [(pattern, (len(pattern), kwargs))
+                         for pattern, kwargs in self.exact_matches]
+        self.path_format = prefix + '%s' + suffix
+
+    def match(self, path):
+        """ If the ``path`` matches, return the end of
+            substring matched and kwargs. Otherwise
+            return ``(-1, None)``.
+        """
+        for pattern, result in self.patterns:
+            if path.startswith(pattern):
+                return result
+        return (-1, None)
+
+    def path(self, values=None):
+        """ Build the path for given route.
+        """
+        if not values or self.name not in values:
+            values = self.kwargs
+        return self.path_format % values[self.name]

src/wheezy/routing/config.py

 """ ``config`` module.
 """
 
+from wheezy.routing.choice import try_build_choice_route
 from wheezy.routing.curly import default_pattern as curly_default_pattern
 from wheezy.routing.curly import patterns as curly_patterns
 from wheezy.routing.curly import try_build_curly_route
 
 route_builders = [
     try_build_plain_route,
+    try_build_choice_route,
     try_build_curly_route,
     try_build_regex_route
 ]

src/wheezy/routing/router.py

                 if pattern in self.match_map:  # pragma: nocover
                     warn('PathRouter: overriding path: %s.' % pattern)
                 self.match_map[pattern] = (handler, kwargs)
+            route.exact_matches = None
         else:
             self.mapping.append((route.match, handler))
 
             router = PathRouter(self.route_builders)
             router.add_routes(included)
             included = router
-        self.mapping.append((route.match, included))
+        if route.exact_matches:
+            for p, kwargs in route.exact_matches:
+                for k, v in included.match_map.items():
+                    k = p + k
+                    if k in self.match_map:  # pragma: nocover
+                        warn('PathRouter: overriding path: %s.' % k)
+                    h, kw = v
+                    self.match_map[k] = (h, dict(kwargs, **kw))
+            route.exact_matches = None
+            included.match_map = {}
+            if included.mapping:
+                self.mapping.append((route.match, included))
+        else:
+            self.mapping.append((route.match, included))
         route_path = route.path
         for name, path in included.path_map.items():
             if name in self.inner_path_map:  # pragma: nocover
                 warn('PathRouter: overriding route: %s.' % name)
             self.inner_path_map[name] = tuple([route_path] + list(paths))
         included.inner_path_map = None
+        #print('include %s => %s / %s' % (pattern, len(self.match_map),
+        #                                 len(included.mapping)))
 
     def add_routes(self, mapping):
         """ Adds routes represented as a list of tuple
                 self.include(pattern, handler, kwargs)
             else:
                 self.add_route(pattern, handler, kwargs, name)
+        #print('add_routes => %s / %s' % (len(self.match_map),
+        #                                 len(self.mapping)))
 
     def match(self, path):
         """ Tries to find a match for the given path in route table.

src/wheezy/routing/tests/test_choice.py

+
+""" Unit tests for ``wheezy.routing.plain``.
+"""
+
+import unittest
+
+
+class TryChoiceRouteTestCase(unittest.TestCase):
+
+    def test_build(self):
+        """ Ensure choice route is built.
+        """
+        from wheezy.routing.choice import try_build_choice_route
+        route = try_build_choice_route('{locale:(en|ru)}')
+        assert route
+        assert route == try_build_choice_route(route)
+        assert not try_build_choice_route('.*')
+
+
+class ChoiceRouteTestCase(unittest.TestCase):
+
+    def test_match(self):
+        """ Ensure matches.
+        """
+        from wheezy.routing.choice import ChoiceRoute
+        r = ChoiceRoute('{locale:(en|ru)}/', True, {'x': '1'}, 'test')
+
+        matched, kwargs = r.match('en/')
+        assert 3 == matched
+        assert [('locale', 'en'), ('route_name', 'test'), ('x', '1')
+                ] == sorted(kwargs.items())
+
+        matched, kwargs = r.match('ru/')
+        assert 3 == matched
+        assert [('locale', 'ru'), ('route_name', 'test'), ('x', '1')
+                ] == sorted(kwargs.items())
+
+        assert (-1, None) == r.match('x')
+
+    def test_path(self):
+        """ Ensure path is built correctly.
+        """
+        from wheezy.routing.choice import ChoiceRoute
+        r = ChoiceRoute('{locale:(en|ru)}/', True, {'locale': 'en'}, 'test')
+
+        assert 'ru/' == r.path({'locale': 'ru'})
+        assert 'en/' == r.path()

src/wheezy/routing/tests/test_router.py

         assert 'a' in self.r.path_map
         assert 'pa' in self.r.match_map
 
-    def test_include(self):
+    def test_include_no_exact_matches(self):
         """ Multilevel include.
         """
         mock_route = Mock()
         self.assertRaises(KeyError, lambda: self.r.path_for('x'))
 
     def test_hierarchical(self):
-        self.r.include('/{locale:(en|ru)}/', [
+        membership_urls = [
             ('signin', 'h', None, 'signin')
-        ], {'locale': 'en'})
+        ]
+
+        self.r.include('/{locale:(en|ru)}/', membership_urls,
+                       {'locale': 'en'})
 
         assert '/en/signin' == self.r.path_for('signin')
         self.assertRaises(KeyError, lambda: self.r.path_for('x'))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.