Commits

Andriy Kornatskyy  committed 97e3e47

Refactoring unit tests: migrated from mocker to mock.

  • Participants
  • Parent commits 7887a73

Comments (0)

Files changed (22)

 		echo 'done.'; \
 	fi
 	$(EASY_INSTALL) -i $(PYPI) -O2 coverage nose pytest \
-		pytest-pep8 pytest-cov
+		pytest-pep8 pytest-cov mock
 	# The following packages available for python == 2.4
 	if [ "$$(echo $(VERSION) | sed 's/\.//')" -eq 24 ]; then \
 		$(EASY_INSTALL) -i $(PYPI) -O2 wsgiref; \
 	fi
-	# The following packages available for python < 3.0
-	if [ "$$(echo $(VERSION) | sed 's/\.//')" -lt 30 ]; then \
-		$(EASY_INSTALL) -i $(PYPI) -O2 mocker; \
-	fi
 	$(PYTHON) setup.py develop -i $(PYPI)
 
 clean:
 
 qa:
 	if [ "$$(echo $(VERSION) | sed 's/\.//')" -eq 27 ]; then \
-		flake8 --max-complexity 10 demos doc src setup.py && \
+		flake8 --max-complexity 9 demos doc src setup.py && \
 		pep8 demos doc src setup.py ; \
 	fi
 
 test:
-	if [ "$$(echo $(VERSION) | sed 's/\.//')" -lt 30 ]; then \
-		$(PYTEST) -q -x --pep8 --doctest-modules \
-			src/wheezy/routing; \
-	else \
-		echo 'WARNING: unit tests skipped due to mocker'; \
-	fi
+	$(PYTEST) -q -x --pep8 --doctest-modules \
+		src/wheezy/routing
 
 doctest-cover:
-	$(NOSE) --with-doctest --detailed-errors --with-coverage \
-		--cover-package=wheezy.routing src/wheezy/routing/*.py
+	$(NOSE) --stop --with-doctest --detailed-errors \
+		--with-coverage --cover-package=wheezy.routing
 
 test-cover:
 	$(PYTEST) -q --cov wheezy.routing \
 doc:
 	$(SPHINX) -a -b html doc/ doc/_build/
 
-pdf:
-	$(SPHINX) -b latex -d doc/_build/doctrees doc/ doc/_build/latex
-	make -C doc/_build/latex all-pdf
-
 test-demos:
 	$(PYTEST) -q -x --pep8 demos/
 

File demos/hello/helloworld.py

 """ ``helloworld`` module.
 """
 
+import sys
+
 from wheezy.routing import PathRouter
-from wheezy.routing.comp import ntob
+
+if sys.version_info[0] >= 3:
+    ntob = lambda n, encoding: n.encode(encoding)
+else:
+    ntob = lambda n, encoding: n
 
 
 def hello_world(environ, start_response):

File doc/examples.rst

 exporting name for :py:class:`~wheezy.routing.router.PathRouter`:
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 5
+   :lines: 7
 
 Next we create a pretty simple WSGI handler to provide a response.
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 9-13
+   :lines: 15-19
 
 In addition let add a handler for not found response.
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 16-20
+   :lines: 22-26
 
 The declaration and mapping of pattern to handler following. We create an
 instance of ``PathRouter`` class and pass mapping that in this partucular case
 is a tuple of two values: ``pattern`` and ``handler``.
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 23-27
+   :lines: 29-33
 
 The first pattern ``'/'`` will match only root path of the request (it is
 finishing route in match chain). The second pattern ``'/{any}'`` is a curly
 that we ignore for now).
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 30-32
+   :lines: 36-38
 
 The rest in the ``helloworld`` application launch a simple wsgi server.
 Try it by running::

File doc/modules.rst

 .. automodule:: wheezy.routing.curly
    :members:
 
+wheezy.routing.plain
+--------------------
+.. automodule:: wheezy.routing.plain
+   :members:
+
+wheezy.routing.regex
+--------------------
+.. automodule:: wheezy.routing.regex
+   :members:
+
 wheezy.routing.route
 --------------------
 .. automodule:: wheezy.routing.route
    :members:
-   
+
 wheezy.routing.router
 ---------------------
 .. automodule:: wheezy.routing.router
    :members:
-   
+
 wheezy.routing.utils
 --------------------
 .. automodule:: wheezy.routing.utils
    :members:
-   
-

File doc/userguide.rst

 :ref:`helloworld` example you notice the following:
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 29-31
+   :lines: 36-38
 
 or more precisely::
 
 simple callable that represents WSGI call handler.
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 9-13
+   :lines: 15-19
 
 Extend Mapping
 --------------
         ('posts/(?P<year>\d+)/(?P<month>\d+)', posts_by_month)
     ]
 
-If we get back to :ref:`helloworld` example:
-
-.. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 22-24
-
 ``kwargs`` is assigned with ``dict`` that represends a key-value pair
 from match::
 
 :ref:`helloworld` example:
 
 .. literalinclude:: ../demos/hello/helloworld.py
-   :lines: 24-27
+   :lines: 30-33
 
 ... or :ref:`server time`:
 

File src/wheezy/routing/builders.py

 """ ``builders`` module.
 """
 
-import re
-
-from wheezy.routing.route import PlainRoute
-from wheezy.routing.route import RegexRoute
-from wheezy.routing.curly import RE_SPLIT as RE_CURLY_ROUTE
-from wheezy.routing.curly import convert as curly_convert
-
-
-RE_PLAIN_ROUTE = re.compile(r'^[\w/-]+$')
-
-
-def try_build_plain_route(pattern, finishing, kwargs=None, name=None):
-    """ If the plain route regular expression match the pattern
-        than create a PlainRoute instance.
-
-        >>> pr = PlainRoute(r'abc', True)
-        >>> r = try_build_plain_route(pr, True)
-        >>> assert pr == r
-        >>> r = try_build_plain_route(r'abc', True)
-        >>> assert isinstance(r, PlainRoute)
-
-        Otherwise return None.
-
-        >>> r = try_build_plain_route(r'ab[c]', False)
-        >>> assert r is None
-    """
-    if isinstance(pattern, PlainRoute):
-        return pattern
-    if pattern == '' or RE_PLAIN_ROUTE.match(pattern):
-        return PlainRoute(pattern, finishing, kwargs, name)
-    return None
-
-
-def try_build_curly_route(pattern, finishing, kwargs=None, name=None):
-    """ Convert pattern expression into regex with
-        named groups and create regex route.
-
-        >>> pr = RegexRoute(r'abc', True)
-        >>> r = try_build_curly_route(pr, True)
-        >>> assert pr == r
-        >>> r = try_build_curly_route('abc/{n}', True)
-        >>> assert isinstance(r, RegexRoute)
-
-        Otherwise return None.
-
-        >>> r = try_build_curly_route('abc', True)
-    """
-    if isinstance(pattern, RegexRoute):
-        return pattern
-    if RE_CURLY_ROUTE.search(pattern):
-        return RegexRoute(curly_convert(pattern), finishing, kwargs, name)
-    return None
-
-
-def try_build_regex_route(pattern, finishing, kwargs=None, name=None):
-    """ There is no special tests to match regex selection
-        strategy.
-
-        >>> pr = RegexRoute(r'abc', True)
-        >>> r = try_build_regex_route(pr, True)
-        >>> assert pr == r
-        >>> r = try_build_regex_route(r'abc', True)
-        >>> assert isinstance(r, RegexRoute)
-    """
-    if isinstance(pattern, RegexRoute):
-        return pattern
-    return RegexRoute(pattern, finishing, kwargs, name)
-
 
 def build_route(pattern, finishing, kwargs, name, route_builders):
     """ Try to find suitable route builder to create a route.
-
-        >>> from wheezy.routing.config import route_builders
-        >>> r = build_route(r'abc', False, {'a': 1}, None, route_builders)
-        >>> assert isinstance(r, PlainRoute)
-        >>> r.kwargs
-        {'a': 1}
-
-        Otherwise raise LookupError
-
-        >>> r = build_route(r'abc', None, False, None, [])
-        Traceback (most recent call last):
-            ...
-        LookupError: No matching route factory found
+        Raises ``LookupError`` if none found.
     """
     if not finishing:
         assert not name

File src/wheezy/routing/comp.py

 
 
 PY3 = sys.version_info[0] >= 3
-
-if PY3:  # pragma: nocover
-    basestring = (str, bytes)
-
-    def ntob(n, encoding):
-        """ Converts native string to bytes
-        """
-        return n.encode(encoding)
-
-    #import collections
-    #callable = lambda obj: isinstance(obj, collections.Callable)
-    callable = lambda obj: any(
-        "__call__" in klass.__dict__ for klass in type(obj).__mro__)
-else:  # pragma: nocover
-    basestring = basestring
-
-    def ntob(n, encoding):  # noqa
-        """ Converts native string to bytes
-        """
-        return n
-
-    callable = callable
-
-
-if PY3:  # pragma: nocover
-    iteritems = lambda d: d.items()
-    copyitems = lambda d: list(d.items())
-else:  # pragma: nocover
-    iteritems = lambda d: d.iteritems()
-    copyitems = lambda d: d.items()

File src/wheezy/routing/config.py

 """ ``config`` module.
 """
 
-from wheezy.routing.builders import try_build_plain_route
-from wheezy.routing.builders import try_build_curly_route
-from wheezy.routing.builders import try_build_regex_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
+from wheezy.routing.plain import try_build_plain_route
+from wheezy.routing.regex import try_build_regex_route
 
-from wheezy.routing.curly import patterns as curly_patterns
-from wheezy.routing.curly import default_pattern as curly_default_pattern
 
 assert curly_default_pattern
 assert curly_patterns

File src/wheezy/routing/curly.py

 """
 
 import re
+
+from wheezy.routing.regex import RegexRoute
 from wheezy.routing.utils import outer_split
 
 
 RE_SPLIT = re.compile('(?P<n>\{[\w:]+.*?\})')
 
+
+def try_build_curly_route(pattern, finishing=True, kwargs=None, name=None):
+    """ Convert pattern expression into regex with
+        named groups and create regex route.
+    """
+    if isinstance(pattern, RegexRoute):
+        return pattern
+    if RE_SPLIT.search(pattern):
+        return RegexRoute(convert(pattern), finishing, kwargs, name)
+    return None
+
+
 patterns = {
     # one or more digits
     'i': r'\d+',
 def convert(s):
     """ Convert curly expression into regex with
         named groups.
-
-        >>> convert(r'abc/{id}')
-        'abc/(?P<id>[^/]+)'
-
-        >>> convert(r'abc/{id:i}')
-        'abc/(?P<id>\\\d+)'
-
-        >>> convert(r'abc/{n}/{x:w}')
-        'abc/(?P<n>[^/]+)/(?P<x>\\\w+)'
-
-        >>> convert(r'{locale:(en|ru)}/home')
-        '(?P<locale>(en|ru))/home'
-
-        >>> convert(r'{locale:(en|ru)}/home')
-        '(?P<locale>(en|ru))/home'
-
-        Operates with optional values in square brackets
-
-        >>> convert(r'[{locale:(en|ru)}/]home')
-        '((?P<locale>(en|ru))/)?home'
-
-        >>> convert(r'item[/{id:i}]')
-        'item(/(?P<id>\\\d+))?'
-
-        >>> convert(r'{controller:w}[/{action:w}[/{id:i}]]')
-        '(?P<controller>\\\w+)(/(?P<action>\\\w+)(/(?P<id>\\\d+))?)?'
     """
     parts = outer_split(s, sep='[]')
     parts[1::2] = ['(%s)?' % p for p in map(convert, parts[1::2])]
 def replace(val):
     """ Replace ``{group_name:pattern_name}`` by regex with
         named groups.
-
-        If the ``val`` is not an expression in curly brackets
-        simply return it.
-
-        >>> replace('abc')
-        'abc'
-
-        If the ``pattern_name`` is not specified, use
-        default one.
-
-        >>> replace('{abc}')
-        '(?P<abc>[^/]+)'
-
-        Replace the ``pattern_name`` with regex from
-        ``patterns`` dict.
-
-        >>> replace('{abc:i}')
-        '(?P<abc>\\\d+)'
-
-        The ``pattern_name`` not found use it as pattern.
-
-        >>> replace('{locale:(en|ru)}')
-        '(?P<locale>(en|ru))'
     """
     if val.startswith('{') and val.endswith('}'):
         group_name, pattern_name = parse(val[1:-1])
 
         There is just ``group_name``, return default
         ``pattern_name``.
-
-        >>> parse('abc')
-        ('abc', 's')
-
-        Otherwise both.
-        >>> parse('abc:i')
-        ('abc', 'i')
     """
     if ':' in s:
         return tuple(s.split(':', 1))

File src/wheezy/routing/plain.py

+
+""" ``plain`` module.
+"""
+
+import re
+
+
+RE_PLAIN_ROUTE = re.compile(r'^[\w\./-]+$')
+
+
+def try_build_plain_route(pattern, finishing=True, kwargs=None, name=None):
+    """ If the plain route regular expression match the pattern
+        than create a PlainRoute instance.
+    """
+    if isinstance(pattern, PlainRoute):
+        return pattern
+    if pattern == '' or RE_PLAIN_ROUTE.match(pattern):
+        return PlainRoute(pattern, finishing, kwargs, name)
+    return None
+
+
+class PlainRoute(object):
+    """ Route based on string equalty operation.
+    """
+
+    __slots__ = ('pattern', 'kwargs', 'matched', 'match', 'exact_matches')
+
+    def __init__(self, pattern, finishing, kwargs=None, name=None):
+        """ Initializes the route by given ``pattern``. If
+            ``finishing`` is True than choose ``equals_math``
+            strategy
+        """
+        kwargs = kwargs and kwargs.copy() or {}
+        self.pattern = pattern
+        self.matched = len(pattern)
+        # Choose match strategy
+        if finishing:
+            if name:
+                kwargs['route_name'] = name
+            self.match = self.equals_match
+        else:
+            self.match = self.startswith_match
+        self.exact_matches = ((pattern, kwargs), )
+        self.kwargs = kwargs
+
+    def equals_match(self, path):
+        """ If the ``path`` exactly equals pattern string,
+            return end index of substring matched and a copy
+            of ``self.kwargs``.
+        """
+        return path == self.pattern and \
+            (self.matched, self.kwargs) or (-1, None)
+
+    def startswith_match(self, path):
+        """ If the ``path`` starts with pattern string, return
+            the end of substring matched and ``self.kwargs``.
+        """
+        return path.startswith(self.pattern) and \
+            (self.matched, self.kwargs) or (-1, None)
+
+    def path(self, values=None):
+        """ Build the path for given route by simply returning
+            the pattern used during initialization.
+        """
+        return self.pattern

File src/wheezy/routing/regex.py

+
+"""
+"""
+
+import re
+
+from wheezy.routing.utils import merge
+from wheezy.routing.utils import outer_split
+
+
+def try_build_regex_route(pattern, finishing=True, kwargs=None, name=None):
+    """ There is no special tests to match regex selection
+        strategy.
+    """
+    if isinstance(pattern, RegexRoute):
+        return pattern
+    return RegexRoute(pattern, finishing, kwargs, name)
+
+
+class RegexRoute(object):
+    """ Route based on regular expression matching.
+    """
+    __slots__ = ('match', 'path', 'path_value', 'name',
+                 'path_format', 'kwargs', 'regex')
+
+    exact_matches = None
+
+    def __init__(self, pattern, finishing=True, kwargs=None, name=None):
+        pattern = pattern.lstrip('^').rstrip('$')
+        # Choose match strategy
+        self.path_format, names = parse_pattern(pattern)
+        if kwargs:
+            self.kwargs = dict.fromkeys(names, '')
+            self.kwargs.update(kwargs)
+            if finishing:
+                self.kwargs['route_name'] = name
+            self.match = self.match_with_kwargs
+            self.path = self.path_with_kwargs
+            self.path_value = self.path_format % self.kwargs
+        else:
+            if finishing:
+                self.name = name
+                self.match = self.match_no_kwargs_finishing
+            else:
+                self.match = self.match_no_kwargs
+            self.path = self.path_no_kwargs
+
+        pattern = '^' + pattern
+        if finishing:
+            pattern = pattern + '$'
+        self.regex = re.compile(pattern)
+
+    def match_no_kwargs(self, path):
+        """ If the ``path`` match the regex pattern.
+        """
+        m = self.regex.match(path)
+        if m:
+            return m.end(), m.groupdict()
+        return -1, None
+
+    def match_no_kwargs_finishing(self, path):
+        """ If the ``path`` match the regex pattern.
+        """
+        m = self.regex.match(path)
+        if m:
+            kwargs = m.groupdict()
+            kwargs['route_name'] = self.name
+            return m.end(), kwargs
+        return -1, None
+
+    def match_with_kwargs(self, path):
+        """ If the ``path`` match the regex pattern.
+        """
+        m = self.regex.match(path)
+        if m:
+            kwargs = m.groupdict()
+            return (m.end(), merge(self.kwargs.copy(), kwargs))
+        return -1, None
+
+    def path_with_kwargs(self, values=None):
+        """ Build the path for the given route by substituting
+            the named places of the regual expression.
+
+            Specialization case: route was initialized with
+            default kwargs.
+        """
+        if values:
+            return self.path_format % dict(self.kwargs, **values)
+        else:
+            return self.path_value
+
+    def path_no_kwargs(self, values):
+        """ Build the path for the given route by substituting
+            the named places of the regual expression.
+
+            Specialization case: route was initialized with
+            no default kwargs.
+        """
+        return self.path_format % values
+
+
+RE_SPLIT = re.compile(r'\<(\w+)\>')
+
+
+def parse_pattern(pattern):
+    """ Returns path_format and names.
+
+        >>> parse_pattern('abc/(?P<id>[^/]+)')
+        ('abc/%(id)s', ['id'])
+        >>> parse_pattern('abc/(?P<n>[^/]+)/(?P<x>\\\w+)')
+        ('abc/%(n)s/%(x)s', ['n', 'x'])
+        >>> parse_pattern('(?P<locale>(en|ru))/home')
+        ('%(locale)s/home', ['locale'])
+
+        >>> from wheezy.routing.curly import convert
+        >>> parse_pattern(convert('[{locale:(en|ru)}/]home'))
+        ('%(locale)s/home', ['locale'])
+        >>> parse_pattern(convert('item[/{id:i}]'))
+        ('item/%(id)s', ['id'])
+
+        >>> p = convert(r'{controller:w}[/{action:w}[/{id:i}]]')
+        >>> parse_pattern(p)
+        ('%(controller)s/%(action)s/%(id)s', ['controller', 'action', 'id'])
+    """
+    pattern = strip_optional(pattern)
+    parts = outer_split(pattern, sep='()')
+    if len(parts) % 2 == 1 and not parts[-1]:
+        parts = parts[:-1]
+    names = [RE_SPLIT.split(p)[1] for p in parts[1::2]]
+    parts[1::2] = ['%%(%s)s' % p for p in names]
+    return ''.join(parts), names
+
+
+def strip_optional(pattern):
+    """ Strip optional regex group flag.
+
+        at the beginning
+
+        >>> strip_optional('((?P<locale>(en|ru))/)?home')
+        '(?P<locale>(en|ru))/home'
+
+        at the end
+
+        >>> strip_optional('item(/(?P<id>\\\d+))?')
+        'item/(?P<id>\\\d+)'
+
+        nested:
+
+        >>> p = '(?P<controller>\\\w+)(/(?P<action>\\\w+)(/(?P<id>\\\d+))?)?'
+        >>> strip_optional(p)
+        '(?P<controller>\\\w+)/(?P<action>\\\w+)/(?P<id>\\\d+)'
+    """
+    if ')?' not in pattern:
+        return pattern
+    parts = outer_split(pattern, sep='()')
+    for i in range(2, len(parts), 2):
+        part = parts[i]
+        if part.startswith('?'):
+            parts[i] = part[1:]
+            parts[i - 1] = strip_optional(parts[i - 1])
+        else:
+            parts[i - 1] = "(%s)" % parts[i - 1]
+    return ''.join(parts)

File src/wheezy/routing/route.py

 """ ``route`` module.
 """
 
-import re
-
-from wheezy.routing.utils import merge
-from wheezy.routing.utils import outer_split
-
 
 class Route(object):
     """ Route abstract contract.
     """
-    __slots__ = ()
+
+    exact_matches = None
 
     def match(self, path):
         """ if the ``path`` matches, return the end of
             substring matched and kwargs. Otherwise
             return ``(-1, None)``.
-
-            >>> r = Route()
-            >>> matched, kwargs = r.match('x')
-            Traceback (most recent call last):
-                ...
-            NotImplementedError
         """
         raise NotImplementedError()
 
     def path(self, values=None):
         """ Build the path for given route.
-
-            >>> r = Route()
-            >>> r.path(dict(id=1234))
-            Traceback (most recent call last):
-                ...
-            NotImplementedError
         """
         raise NotImplementedError()
-
-
-class PlainRoute(object):
-    """ Route based on string equalty operation.
-    """
-    __slots__ = ('pattern', 'kwargs', 'matched', 'match', 'exact_matches')
-
-    def __init__(self, pattern, finishing, kwargs=None, name=None):
-        """ Initializes the route by given ``pattern``. If
-            ``finishing`` is True than choose ``equals_math``
-            strategy
-
-            >>> r = PlainRoute(r'abc', True)
-            >>> assert r.match == r.equals_match
-            >>> r.exact_matches
-            (('abc', {}),)
-
-            Otherwise ``startswith_match`` strategy is selected.
-
-            >>> r = PlainRoute(r'abc', False)
-            >>> assert r.match == r.startswith_match
-        """
-        self.pattern = pattern
-        self.kwargs = kwargs and kwargs.copy() or {}
-        self.matched = len(pattern)
-        # Choose match strategy
-        if finishing:
-            if name:
-                self.kwargs['route_name'] = name
-            self.match = self.equals_match
-            self.exact_matches = ((pattern, self.kwargs), )
-        else:
-            self.match = self.startswith_match
-            self.exact_matches = None
-
-    def equals_match(self, path):
-        """ If the ``path`` exactly equals pattern string,
-            return end index of substring matched and a copy
-            of ``self.kwargs``.
-
-            >>> r = PlainRoute(r'abc', True)
-            >>> matched, kwargs = r.equals_match('abc')
-            >>> matched
-            3
-            >>> kwargs
-            {}
-
-            Match returns ``self.kwargs``.
-
-            >>> r = PlainRoute(r'abc', True, {'a': 1})
-            >>> matched, kwargs = r.equals_match('abc')
-            >>> matched
-            3
-            >>> kwargs
-            {'a': 1}
-
-            Otherwise return ``(-1, None)``.
-
-            >>> matched, kwargs = r.equals_match('abc/')
-            >>> matched
-            -1
-            >>> matched, kwargs = r.equals_match('bc')
-            >>> matched
-            -1
-            >>> kwargs
-        """
-        return path == self.pattern and \
-            (self.matched, self.kwargs) or (-1, None)
-
-    def startswith_match(self, path):
-        """ If the ``path`` starts with pattern string, return
-            the end of substring matched and ``self.kwargs``.
-
-            >>> r = PlainRoute(r'abc', False)
-            >>> matched, kwargs = r.startswith_match('abc')
-            >>> matched
-            3
-            >>> kwargs
-            {}
-
-            Match returns ``self.kwargs``.
-
-            >>> r = PlainRoute(r'abc', False, {'a': 1})
-            >>> matched, kwargs = r.startswith_match('abc/')
-            >>> matched
-            3
-            >>> kwargs
-            {'a': 1}
-
-            Otherwise return ``(None, None)``.
-
-            >>> matched, kwargs = r.startswith_match('bc')
-            >>> matched
-            -1
-            >>> kwargs
-        """
-        return path.startswith(self.pattern) and \
-            (self.matched, self.kwargs) or (-1, None)
-
-    def path(self, values=None):
-        """ Build the path for given route by simply returning
-            the pattern used during initialization.
-
-            >>> r = PlainRoute(r'abc', True)
-            >>> r.path()
-            'abc'
-        """
-        return self.pattern
-
-
-RE_SPLIT = re.compile(r'\<(\w+)\>')
-
-
-def parse_pattern(pattern):
-    """
-        >>> parse_pattern('abc/(?P<id>[^/]+)')
-        ('abc/%(id)s', ['id'])
-        >>> parse_pattern('abc/(?P<n>[^/]+)/(?P<x>\\\w+)')
-        ('abc/%(n)s/%(x)s', ['n', 'x'])
-        >>> parse_pattern('(?P<locale>(en|ru))/home')
-        ('%(locale)s/home', ['locale'])
-
-        >>> from wheezy.routing.curly import convert
-        >>> parse_pattern(convert('[{locale:(en|ru)}/]home'))
-        ('%(locale)s/home', ['locale'])
-        >>> parse_pattern(convert('item[/{id:i}]'))
-        ('item/%(id)s', ['id'])
-
-        >>> p = convert(r'{controller:w}[/{action:w}[/{id:i}]]')
-        >>> parse_pattern(p)
-        ('%(controller)s/%(action)s/%(id)s', ['controller', 'action', 'id'])
-    """
-    pattern = strip_optional(pattern)
-    parts = outer_split(pattern, sep='()')
-    if len(parts) % 2 == 1 and not parts[-1]:
-        parts = parts[:-1]
-    parts[1::2] = values = [RE_SPLIT.split(p)[1] for p in parts[1::2]]
-    parts[1::2] = ['%(' + p + ')s' for p in parts[1::2]]
-    return ''.join(parts), values
-
-
-def strip_optional(pattern):
-    """
-        at the beginning
-
-        >>> strip_optional('((?P<locale>(en|ru))/)?home')
-        '(?P<locale>(en|ru))/home'
-
-        at the end
-
-        >>> strip_optional('item(/(?P<id>\\\d+))?')
-        'item/(?P<id>\\\d+)'
-
-        nested:
-
-        >>> p = '(?P<controller>\\\w+)(/(?P<action>\\\w+)(/(?P<id>\\\d+))?)?'
-        >>> strip_optional(p)
-        '(?P<controller>\\\w+)/(?P<action>\\\w+)/(?P<id>\\\d+)'
-    """
-    if ')?' not in pattern:
-        return pattern
-    parts = outer_split(pattern, sep='()')
-    for i in range(2, len(parts), 2):
-        part = parts[i]
-        if part.startswith('?'):
-            parts[i] = part[1:]
-            parts[i - 1] = strip_optional(parts[i - 1])
-        else:
-            parts[i - 1] = "(%s)" % parts[i - 1]
-    return ''.join(parts)
-
-
-class RegexRoute(object):
-    """ Route based on regular expression matching.
-    """
-    __slots__ = ('match', 'path', 'path_value', 'name',
-                 'path_format', 'kwargs', 'regex')
-
-    def __init__(self, pattern, finishing, kwargs=None, name=None):
-        pattern = pattern.lstrip('^').rstrip('$')
-        # Choose match strategy
-        self.path_format, default_values = parse_pattern(pattern)
-        if kwargs:
-            self.kwargs = dict.fromkeys(default_values, '')
-            self.kwargs.update(kwargs)
-            if finishing and name:
-                self.kwargs['route_name'] = name
-            self.match = self.match_with_kwargs
-            self.path = self.path_with_kwargs
-            self.path_value = self.path_format % self.kwargs
-        else:
-            if finishing and name:
-                self.name = name
-                self.match = self.match_no_kwargs_finishing
-            else:
-                self.match = self.match_no_kwargs
-            self.path = self.path_no_kwargs
-
-        pattern = '^' + pattern
-        if finishing:
-            pattern = pattern + '$'
-        self.regex = re.compile(pattern)
-
-    def match_no_kwargs(self, path):
-        """ If the ``path`` match the regex pattern.
-
-            >>> r = RegexRoute(r'abc/(?P<id>\d+$)', True)
-            >>> matched, kwargs = r.match_no_kwargs('abc/1234')
-            >>> matched
-            8
-            >>> kwargs
-            {'id': '1234'}
-
-            Otherwise return ``(-1, None)``.
-
-            >>> matched, kwargs = r.match_no_kwargs('abc/x')
-            >>> matched
-            -1
-            >>> kwargs
-        """
-        m = self.regex.match(path)
-        if m:
-            return m.end(), m.groupdict()
-        return -1, None
-
-    def match_no_kwargs_finishing(self, path):
-        """ If the ``path`` match the regex pattern.
-
-            >>> r = RegexRoute(r'abc/(?P<id>\d+$)', True)
-            >>> matched, kwargs = r.match_no_kwargs('abc/1234')
-            >>> matched
-            8
-            >>> kwargs
-            {'id': '1234'}
-
-            Otherwise return ``(-1, None)``.
-
-            >>> matched, kwargs = r.match_no_kwargs('abc/x')
-            >>> matched
-            -1
-            >>> kwargs
-        """
-        m = self.regex.match(path)
-        if m:
-            kwargs = m.groupdict()
-            kwargs['route_name'] = self.name
-            return m.end(), kwargs
-        return -1, None
-
-    def match_with_kwargs(self, path):
-        """ If the ``path`` match the regex pattern.
-
-            >>> r = RegexRoute(r'abc/\d+', False, {'lang': 'en'})
-            >>> matched, kwargs = r.match_with_kwargs('abc/1234')
-            >>> matched
-            8
-            >>> kwargs
-            {'lang': 'en'}
-
-            >>> r = RegexRoute(r'abc/(?P<id>\d+$)', True, {
-            ...     'lang': 'en'
-            ... })
-            >>> matched, kwargs = r.match_with_kwargs('abc/1234')
-            >>> sorted(kwargs.items())
-            [('id', '1234'), ('lang', 'en')]
-
-            ``kwargs`` from ``pattern`` match must override
-            defaults.
-
-            >>> r = RegexRoute(r'abc/?(?P<id>\d*$)', True, {'id': '1'})
-            >>> matched, kwargs = r.match_with_kwargs('abc')
-            >>> kwargs
-            {'id': '1'}
-            >>> matched, kwargs = r.match_with_kwargs('abc/1234')
-            >>> kwargs
-            {'id': '1234'}
-
-            Otherwise return ``(-1, None)``.
-
-            >>> matched, kwargs = r.match_with_kwargs('abc/x')
-            >>> matched
-            -1
-            >>> kwargs
-        """
-        m = self.regex.match(path)
-        if m:
-            kwargs = m.groupdict()
-            return (m.end(), merge(self.kwargs.copy(), kwargs))
-        return -1, None
-
-    def path_with_kwargs(self, values=None):
-        """ Build the path for the given route by substituting
-            the named places of the regual expression.
-
-            Specialization case: route was initialized with
-            default kwargs.
-
-            >>> r = RegexRoute(
-            ...     r'abc/(?P<month>\d+)/(?P<day>\d+)',
-            ...     True,
-            ...     dict(month=1, day=1)
-            ... )
-            >>> r.path(dict(month=6, day=9))
-            'abc/6/9'
-            >>> r.path(dict(month=6))
-            'abc/6/1'
-            >>> r.path()
-            'abc/1/1'
-        """
-        if values:
-            return self.path_format % dict(self.kwargs, **values)
-        else:
-            return self.path_value
-
-    def path_no_kwargs(self, values):
-        """ Build the path for the given route by substituting
-            the named places of the regual expression.
-
-            Specialization case: route was initialized with
-            no default kwargs.
-
-            >>> r = RegexRoute(
-            ...     r'abc/(?P<month>\d+)/(?P<day>\d+)',
-            ...     True
-            ... )
-            >>> r.path(dict(month=6, day=9))
-            'abc/6/9'
-            >>> r.path(dict(month=6))
-            Traceback (most recent call last):
-                ...
-            KeyError: 'day'
-        """
-        return self.path_format % values
-
-
-if __name__ == '__main__':
-    r = RegexRoute(r'abc/(?P<id>\d+$)', True, {
-        'lang': 'en'
-    })

File src/wheezy/routing/router.py

     """ Converts parameters to tupple of length four.
         Used for convenience to name parameters and skip
         unused.
-
-        >>> url(r'msg', 'handler', {'id': 1}, name='message')
-        ('msg', 'handler', {'id': 1}, 'message')
-
-        Usage:
-
-        >>> class Login: pass
-        >>> admin_routes = [
-        ...     url(r'login', Login, name='signin')
-        ... ]
-        >>> r = PathRouter()
-        >>> r.add_routes([
-        ...     url(r'admin/', admin_routes, kwargs={'site_id': 1})
-        ... ])
     """
     return pattern, handler, kwargs, name
 
 class PathRouter(object):
     """
     """
-    __slots__ = ('mapping', 'path_map', 'route_map', 'inner_route_map',
+
+    __slots__ = ('mapping', 'match_map', 'path_map', 'inner_path_map',
                  'route_builders')
 
     def __init__(self, route_builders=None):
         """
         """
+        self.route_builders = route_builders or default_route_builders
+        # match
+        self.match_map = {}
         self.mapping = []
-        self.route_map = {}
+        # path
         self.path_map = {}
-        self.inner_route_map = {}
-        self.route_builders = route_builders or default_route_builders
+        self.inner_path_map = {}
 
     def add_route(self, pattern, handler, kwargs=None, name=None):
         """ Adds a pattern to route table
-
-            >>> r = PathRouter()
-            >>> class Login: pass
-            >>> r.add_route(r'login', Login)
-            >>> assert r.route_map['login']
-
-            You can override the generated name by suppling optional
-            ``name`` parameter
-
-            >>> r.add_route(r'login', Login, name='signin')
-            >>> assert r.route_map['signin']
-            >>> r.path_for('signin')
-            'login'
-
-            If ``name`` already mapped to some route allow
-            override it but show a warning.
-
-            >>> import warnings
-            >>> warnings.simplefilter('ignore')
-            >>> def login_handler(): pass
-            >>> r.add_route(r'test', login_handler, name='signin')
-            >>> warnings.simplefilter('default')
-            >>> r.path_for('signin')
-            'test'
         """
         name = name or route_name(handler)
-        if name in self.route_map:
+        if name in self.path_map:  # pragma: nocover
             warn('PathRouter: overriding route: %s.' % name)
         # build finishing route
         route = build_route(pattern, True, kwargs, name, self.route_builders)
-        self.route_map[name] = route.path
-        if hasattr(route, 'exact_matches'):
+        self.path_map[name] = route.path
+        if route.exact_matches:
             for pattern, kwargs in route.exact_matches:
-                if pattern in self.path_map:
+                if pattern in self.match_map:  # pragma: nocover
                     warn('PathRouter: overriding path: %s.' % pattern)
-                self.path_map[pattern] = (handler, kwargs)
+                self.match_map[pattern] = (handler, kwargs)
         else:
             self.mapping.append((route.match, handler))
 
     def include(self, pattern, included, kwargs=None):
         """ Includes nested routes below the current.
-
-            >>> r = PathRouter()
-            >>> class Login: pass
-            >>> admin_routes = [
-            ...     (r'login', Login)
-            ... ]
-            >>> r.include(r'admin/', admin_routes)
-            >>> route, inner = r.mapping[0]
-            >>> assert isinstance(inner, PathRouter)
-            >>> r = PathRouter()
-            >>> inner = PathRouter()
-            >>> inner.add_routes([(r'login', Login)])
-            >>> r.include(r'admin/', inner)
-            >>> assert r.inner_route_map
-
-            If ``name`` already mapped to some route allow
-            override it but show a warning.
-
-            >>> import warnings
-            >>> warnings.simplefilter('ignore')
-            >>> r.include(r'admin/', admin_routes)
-            >>> top_router = PathRouter()
-            >>> r.include(r'x/', r)
-            >>> warnings.simplefilter('default')
         """
         # try build intermediate route
         route = build_route(pattern, False, kwargs, None, self.route_builders)
-        if isinstance(included, PathRouter):
-            inner = included
-        else:
-            inner = PathRouter(self.route_builders)
-            inner.add_routes(included)
-        self.mapping.append((route.match, inner))
+        if not isinstance(included, PathRouter):
+            router = PathRouter(self.route_builders)
+            router.add_routes(included)
+            included = router
+        self.mapping.append((route.match, included))
         route_path = route.path
-        for name, path in inner.route_map.items():
-            if name in self.inner_route_map:
+        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_route_map[name] = [route_path, path]
-        for name, paths in inner.inner_route_map.items():
-            if name in self.inner_route_map:
+            self.inner_path_map[name] = (route_path, path)
+        included.path_map = None
+        for name, paths in included.inner_path_map.items():
+            if name in self.inner_path_map:  # pragma: nocover
                 warn('PathRouter: overriding route: %s.' % name)
-            self.inner_route_map[name] = [route_path] + paths
+            self.inner_path_map[name] = tuple([route_path] + list(paths))
+        included.inner_path_map = None
 
     def add_routes(self, mapping):
         """ Adds routes represented as a list of tuple
-            (pattern, handler) to route table
-
-            >>> r = PathRouter()
-            >>> class Login: pass
-            >>> r.add_routes([
-            ...     (r'login', Login)
-            ... ])
-
-            >> assert r.mapping
-            >> assert r.route_map
-
-            If ``handler`` is tuple, list or an instance of
-            PathRouter than we proceed with ``include`` function
-
-            >>> r = PathRouter()
-            >>> class Login: pass
-            >>> admin_routes = [(r'login', Login)]
-            >>> r.add_routes([
-            ...     (r'admin/', admin_routes)
-            ... ])
-            >>> len(r.inner_route_map)
-            1
-            >>> len(r.mapping)
-            1
+            (pattern, handler, kwargs=None, name=None) to route table.
         """
         for m in mapping:
             l = len(m)
     def match(self, path):
         """ Tries to find a match for the given path in route table.
             Returns a tupple of (handler, kwargs)
-
-            >>> r = PathRouter()
-            >>> class Login: pass
-            >>> class Message: pass
-            >>> r.add_route(r'login', Login)
-            >>> handler, kwargs = r.match(r'login')
-            >>> assert handler == Login
-            >>> kwargs
-            {'route_name': 'login'}
-
-            Tries to find inner match
-
-            >>> r = PathRouter()
-            >>> admin_routes = [
-            ...     (r'login', Login
-            ... )]
-            >>> r.add_routes([
-            ...     (r'admin/', admin_routes)
-            ... ])
-            >>> handler, kwargs = r.match(r'admin/login')
-            >>> assert handler == Login
-
-            Merge kwargs
-
-            >>> r = PathRouter()
-            >>> admin_routes = [
-            ...     (r'msg', Message, {'id': 1})
-            ... ]
-            >>> r.add_routes([
-            ...     (r'en/', admin_routes, {'lang': 'en'})
-            ... ])
-            >>> handler, kwargs = r.match(r'en/msg')
-            >>> assert handler == Message
-            >>> sorted(kwargs.items())
-            [('id', 1), ('lang', 'en'), ('route_name', 'message')]
-
-            Otherwise return (None, None)
-
-            >>> r = PathRouter()
-            >>> handler, kwargs = r.match(r'')
-            >>> handler
-            >>> kwargs
-            {}
         """
-        if path in self.path_map:
-            return self.path_map[path]
-        for route_match, handler in self.mapping:
-            matched, kwargs = route_match(path)
+        if path in self.match_map:
+            return self.match_map[path]
+        for match, handler in self.mapping:
+            matched, kwargs = match(path)
             if matched >= 0:
                 # TODO: isinstance(handler, PathRouter)
-                handler_match = getattr(handler, 'match', None)
-                if not handler_match:
+                match = getattr(handler, 'match', None)
+                if not match:
                     return handler, kwargs
-                handler, kwargs_inner = handler_match(
-                    path[matched:])
+                handler, kwargs_inner = match(path[matched:])
                 if handler:
                     if not kwargs:
                         return handler, kwargs_inner
 
     def path_for(self, name, **kwargs):
         """ Returns the url for the given route name.
-
-            >>> r = PathRouter()
-            >>> class Login: pass
-            >>> r.add_route(r'login', Login)
-            >>> r.path_for(r'login')
-            'login'
-
-            Path for inner router
-
-            >>> r = PathRouter()
-            >>> admin_routes = [
-            ...     (r'login', Login, None, 'signin')
-            ... ]
-            >>> r.add_routes([
-            ...     (r'{lang}/admin/', admin_routes, {'lang': 'en'})
-            ... ])
-            >>> r.path_for(r'signin')
-            'en/admin/login'
-
-            >>> r = PathRouter()
-            >>> admin_routes = [
-            ...     (r'', Login, None, 'signin')
-            ... ]
-            >>> r.add_routes([
-            ...     (r'{lang}/', admin_routes, {'lang': 'en'})
-            ... ])
-            >>> r.path_for(r'signin')
-            'en/'
-
-            Otherwise None
-
-            >>> r.path_for(r'unknown')
-            Traceback (most recent call last):
-                ...
-            KeyError: 'unknown'
         """
-        if name in self.route_map:
-            return self.route_map[name](kwargs)
+        if name in self.path_map:
+            return self.path_map[name](kwargs)
         else:
             return ''.join([path(kwargs) for path
-                            in self.inner_route_map[name]])
+                            in self.inner_path_map[name]])

File src/wheezy/routing/tests/test_builders.py

 
 import unittest
 
-from mocker import Mocker, expect
+from mock import Mock
 
 
-class TryBuildPlainRouteTestCase(unittest.TestCase):
-    """ Test the ``builders.try_build_plain_route``
-        builder strategy.
-    """
+class BuildersTestCase(unittest.TestCase):
 
-    def test_match(self):
-        """ Match plain route strategy.
+    def test_name_raises_error(self):
+        """ Name for intermediate route has no sense.
         """
-        from wheezy.routing.builders import try_build_plain_route
-        from wheezy.routing.route import PlainRoute
+        from wheezy.routing.builders import build_route
+        self.assertRaises(AssertionError, lambda: build_route(
+            '', finishing=False, kwargs={}, name='wrong', route_builders=None))
 
-        r = try_build_plain_route(r'abc', False)
-        assert isinstance(r, PlainRoute)
-        assert not r.kwargs
-
-    def test_match_empty(self):
-        """ Match plain route strategy.
+    def test_first_match(self):
+        """ First route builder match is returned.
         """
-        from wheezy.routing.builders import try_build_plain_route
-        from wheezy.routing.route import PlainRoute
-
-        r = try_build_plain_route(r'', False)
-        assert isinstance(r, PlainRoute)
-        assert not r.kwargs
-
-    def test_kwards(self):
-        """ Test whenever route is initialized with
-            ``kwargs``.
-        """
-        from wheezy.routing.builders import try_build_plain_route
-
-        r = try_build_plain_route(r'abc', False, {'a': 1})
-
-        self.assertEqual({'a': 1}, r.kwargs)
+        from wheezy.routing.builders import build_route
+        mock_builders = [Mock(), Mock(), Mock()]
+        mock_builders[0].return_value = None
+        mock_builders[1].return_value = 'route'
+        assert 'route' == build_route('pattern', finishing=False, kwargs={},
+                                      name=None, route_builders=mock_builders)
+        mock_builders[0].assert_called_once_with('pattern', False, {}, None)
+        mock_builders[1].assert_called_once_with('pattern', False, {}, None)
+        assert not mock_builders[2].called
 
     def test_no_match(self):
-        """ No match for plain route strategy.
-        """
-        from wheezy.routing.builders import try_build_plain_route
-
-        r = try_build_plain_route(r'abc/{name}', False)
-
-        assert r is None
-
-
-class TryBuildCurlyRouteTestCase(unittest.TestCase):
-    """ Test the ``builders.try_build_curly_route``.
-    """
-
-    def test_no_match(self):
-        """ Always return an instance of RegexRoute.
-        """
-        from wheezy.routing.builders import try_build_curly_route
-
-        r = try_build_curly_route(r'abc', False)
-        assert r is None
-
-    def test_match(self):
-        """ Always return an instance of RegexRoute.
-        """
-        from wheezy.routing.builders import try_build_curly_route
-        from wheezy.routing.route import RegexRoute
-
-        r = try_build_curly_route(r'abc/{n}', False)
-        assert isinstance(r, RegexRoute)
-
-    def test_pattern_name(self):
-        """ Always return an instance of RegexRoute.
-        """
-        from wheezy.routing.builders import try_build_curly_route
-        from wheezy.routing.route import RegexRoute
-
-        r = try_build_curly_route(r'abc/{n:i}', False)
-        assert isinstance(r, RegexRoute)
-
-
-class TryBuildRegexRouteTestCase(unittest.TestCase):
-    """ Test the ``builders.try_build_regex_route``.
-    """
-
-    def test_instance(self):
-        """ Always return an instance of RegexRoute.
-        """
-        from wheezy.routing.builders import try_build_regex_route
-        from wheezy.routing.route import RegexRoute
-
-        r = try_build_regex_route(r'abc', False)
-        assert isinstance(r, RegexRoute)
-
-
-class BuildRouteTestCase(unittest.TestCase):
-    """ Test the ``builders.build_route``
-        strategy selection chain of responsibility.
-    """
-
-    def test_pattern_is_route(self):
-        """ ``pattern`` is an object drived from Route.
+        """ If no match found raise error.
         """
         from wheezy.routing.builders import build_route
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute('', True)
-
-        self.assertRaises(LookupError,
-                          lambda: build_route(r, False, None, None, []))
-
-    def test_found(self):
-        """ Sutable route strategy has been found.
-        """
-        from wheezy.routing.builders import build_route
-        from wheezy.routing import config
-
-        r = build_route(r'abc', False, {'a': 1}, None, config.route_builders)
-
-        assert r
-        self.assertEqual({'a': 1}, r.kwargs)
-
-    def test_first_matched(self):
-        """ First matched strategy is selected.
-        """
-        from wheezy.routing.builders import build_route
-
-        m = Mocker()
-        mock = m.mock
-        builders = mock(), mock(), mock(), mock(), mock()
-        b1, b2, b3, b4, b5 = builders
-        expect(b1(r'abc', False, None, None)).result(None)
-        expect(b2(r'abc', False, None, None)).result(None)
-        expect(b3(r'abc', False, None, None)).result('x')
-        m.replay()
-
-        r = build_route(r'abc', False, None, None, builders)
-
-        self.assertEqual('x', r)
-        m.verify()
-
-    def test_not_found(self):
-        """ None of available route builders matched
-            pattern.
-        """
-        from wheezy.routing.builders import build_route
-
-        m = Mocker()
-        mock = m.mock
-        builders = mock(), mock()
-        b1, b2 = builders
-        expect(b1(r'abc', False, None, None)).result(None)
-        expect(b2(r'abc', False, None, None)).result(None)
-        m.replay()
-
         self.assertRaises(LookupError, lambda: build_route(
-            r'abc', False, None, None, builders))
-        m.verify()
-
-
-class BuildRouteIntegrationTestCase(unittest.TestCase):
-    """ Test integration with ``config.route_builders``.
-    """
-
-    def test_match_plain_route(self):
-        """ the chain of strategies match plain route.
-        """
-        from wheezy.routing import config
-        from wheezy.routing.builders import build_route
-        from wheezy.routing.route import PlainRoute
-
-        r = build_route(r'abc', False, None, None, config.route_builders)
-
-        assert isinstance(r, PlainRoute)
-
-    def test_match_curly_route_intermediate(self):
-        """ the chain of strategies match regex route.
-        """
-        from wheezy.routing import config
-        from wheezy.routing.builders import build_route
-        from wheezy.routing.route import RegexRoute
-
-        r = build_route(
-            r'abc/{id}',
-            False,
-            None,
-            None,
-            config.route_builders
-        )
-
-        assert isinstance(r, RegexRoute)
-        self.assertEquals('^abc/(?P<id>[^/]+)', r.regex.pattern)
-
-    def test_match_curly_route_finishing(self):
-        """ the chain of strategies match regex route.
-        """
-        from wheezy.routing import config
-        from wheezy.routing.builders import build_route
-        from wheezy.routing.route import RegexRoute
-
-        r = build_route(
-            r'abc/{id}',
-            True,
-            None,
-            None,
-            config.route_builders
-        )
-
-        assert isinstance(r, RegexRoute)
-        self.assertEquals('^abc/(?P<id>[^/]+)$', r.regex.pattern)
-
-    def test_match_regex_route(self):
-        """ the chain of strategies match regex route.
-        """
-        from wheezy.routing import config
-        from wheezy.routing.builders import build_route
-        from wheezy.routing.route import RegexRoute
-
-        r = build_route(
-            r'abc/(?P<id>\d+)',
-            False,
-            None,
-            None,
-            config.route_builders
-        )
-
-        assert isinstance(r, RegexRoute)
+            '', False, {}, None, []))

File src/wheezy/routing/tests/test_config.py

 
 import unittest
 
+from wheezy.routing.comp import PY3
+
+
+if PY3:  # pragma: nocover
+    callable = lambda obj: any(
+        "__call__" in klass.__dict__ for klass in type(obj).__mro__)
+
 
 class RouteBuildersTestCase(unittest.TestCase):
     """ Test the ``config.route_builders`` setting.
         """
         import inspect
 
-        from wheezy.routing.comp import callable
         from wheezy.routing import config
 
         for builder in config.route_builders:
             self.assertEqual(['pattern', 'finishing', 'kwargs', 'name'], args)
             self.assertEqual(None, varargs)
             self.assertEqual(None, keywords)
-            self.assertEqual((None, None), defaults)
+            self.assertEqual((True, None, None), defaults)

File src/wheezy/routing/tests/test_curly.py

 import unittest
 
 
+class TryBuildCurlyRouteTestCase(unittest.TestCase):
+
+    def test_build(self):
+        """ Ensure curly route is built.
+        """
+        from wheezy.routing.curly import try_build_curly_route
+        route = try_build_curly_route('{locale:(en|ru)}')
+        assert route
+        assert route == try_build_curly_route(route)
+        assert not try_build_curly_route('.*')
+
+
 class PatternsTestCase(unittest.TestCase):
     """ Test the ``curly.patterns`` dict.
     """
 
         from wheezy.routing.curly import patterns
 
-        for p in tuple(patterns.values()):
+        for p in patterns.values():
             assert re.compile(p)
 
     def test_default(self):
             ('i', ('int', 'digits', 'number')),
             ('w', ('word', )),
             ('s', ('segment', 'part')),
-            ('a', ('any', 'rest'))
+            ('a', ('any', 'rest', '*'))
         )
 
         for n, syns in synonyms_map:
         """
         from wheezy.routing.curly import convert
 
-        pattern = convert(r'abc/{n1}/{n2}')
+        pattern = convert(r'abc/{n1}/{n2:s}')
 
         self.assertEquals(
             'abc/(?P<n1>[^/]+)/(?P<n2>[^/]+)',

File src/wheezy/routing/tests/test_import.py

-
-""" Unit tests for ``wheezy.routing.__init__``.
-"""
-
-import unittest
-
-
-class RoutingPackageImportTestCase(unittest.TestCase):
-    """ Test the ``wheezy.routing`` package imports.
-    """
-
-    def test_import(self):
-        """ Check what can be imported.
-        """
-        from wheezy import routing
-
-        from inspect import ismodule
-
-        x = [x for x in dir(routing) if x[:2] != '__'
-             and not ismodule(eval('routing.' + x))]
-
-        self.assertEqual(['PathRouter', 'url'], x)

File src/wheezy/routing/tests/test_plain.py

+
+""" Unit tests for ``wheezy.routing.plain``.
+"""
+
+import unittest
+
+
+class TryPlainRouteTestCase(unittest.TestCase):
+
+    def test_build(self):
+        """ Ensure plain route is built.
+        """
+        from wheezy.routing.plain import try_build_plain_route
+        route = try_build_plain_route('favicon.ico')
+        assert route
+        assert route == try_build_plain_route(route)
+        assert not try_build_plain_route('.*')
+
+
+class PlainRouteTestCase(unittest.TestCase):
+
+    def test_match_finishing(self):
+        """ Equals match strategy.
+        """
+        from wheezy.routing.plain import PlainRoute
+        r = PlainRoute('abc', finishing=True, kwargs={'x': 2}, name='test')
+
+        assert 1 == len(r.exact_matches)
+        pattern, kwargs = r.exact_matches[0]
+        assert 'abc' == pattern
+        assert [('route_name', 'test'), ('x', 2)] == sorted(kwargs.items())
+        matched, kwargs = r.match('abc')
+        assert 3 == matched
+        assert [('route_name', 'test'), ('x', 2)] == sorted(kwargs.items())
+
+        assert (-1, None) == r.match('ab')
+        r.exact_matches = None
+
+    def test_match_intermediate(self):
+        """ Starts with strategy.
+        """
+        from wheezy.routing.plain import PlainRoute
+        r = PlainRoute('abc', finishing=False, kwargs={'x': 2}, name='ignore')
+
+        assert (('abc', {'x': 2}),) == r.exact_matches
+        matched, kwargs = r.match('abcd')
+        assert 3 == matched
+        assert [('x', 2)] == sorted(kwargs.items())
+
+        matched, kwargs = r.match('ab')
+        r.exact_matches = None
+
+    def test_path(self):
+        """ Returns pattern.
+        """
+        from wheezy.routing.plain import PlainRoute
+        r = PlainRoute('abc', finishing=False, kwargs={}, name=None)
+        assert 'abc' == r.path()

File src/wheezy/routing/tests/test_regex.py

+
+""" Unit tests for ``wheezy.routing.regex``.
+"""
+
+import unittest
+
+
+class TryRegexRouteTestCase(unittest.TestCase):
+
+    def test_build(self):
+        """ Ensure plain route is built.
+        """
+        from wheezy.routing.regex import try_build_regex_route
+        route = try_build_regex_route('.*')
+        assert route
+        assert route == try_build_regex_route(route)
+
+
+class RegexRouteTestCase(unittest.TestCase):
+
+    def test_match_no_kwargs(self):
+        from wheezy.routing.regex import RegexRoute
+        r = RegexRoute('abc/(?P<id>[^/]+)', finishing=True,
+                       kwargs=None, name='test')
+        matched, kwargs = r.match('abc/123')
+        assert 7 == matched
+        assert [('id', '123'), ('route_name', 'test')] == sorted(
+            kwargs.items())
+
+        assert (-1, None) == r.match('abc/')
+
+        r = RegexRoute('abc/(?P<id>[^/]+)', finishing=False,
+                       kwargs=None, name='ignore')
+        matched, kwargs = r.match('abc/123/info')
+        assert 7 == matched
+        assert {'id': '123'} == kwargs
+
+        assert (-1, None) == r.match('abc/')
+
+    def test_match_with_kwargs(self):
+        from wheezy.routing.regex import RegexRoute
+        defaults = {'lang': 'en', 'id': '0'}
+        r = RegexRoute('abc/(?P<id>[^/]+)', finishing=True,
+                       kwargs=defaults, name='test')
+        matched, kwargs = r.match('abc/123')
+        assert 7 == matched
+        assert [('id', '123'), ('lang', 'en'), ('route_name', 'test')
+                ] == sorted(kwargs.items())
+
+        assert (-1, None) == r.match('abc/')
+
+        r = RegexRoute('abc/(?P<id>[^/]+)', finishing=False,
+                       kwargs=defaults, name='ignore')
+        matched, kwargs = r.match('abc/123/info')
+        assert 7 == matched
+        assert [('id', '123'), ('lang', 'en')] == sorted(kwargs.items())
+
+        assert (-1, None) == r.match('abc/')
+
+    def test_path_no_kwargs(self):
+        from wheezy.routing.regex import RegexRoute
+        r = RegexRoute('abc/(?P<id>[^/]+)', finishing=True,
+                       kwargs=None, name='test')
+
+        assert 'abc/123' == r.path({'id': '123'})
+
+        self.assertRaises(KeyError, lambda: r.path({}))
+
+    def test_path_with_kwargs(self):
+        from wheezy.routing.regex import RegexRoute
+        r = RegexRoute('abc/(?P<id>[^/]+)', finishing=True,
+                       kwargs={'id': '1'}, name='test')
+
+        assert 'abc/123' == r.path({'id': '123'})
+        assert 'abc/1' == r.path()

File src/wheezy/routing/tests/test_route.py

 
 
 class RouteTestCase(unittest.TestCase):
-    """ Test the ``Route` class.
-    """
 
-    def test_init(self):
-        """ Route class can be instantiated.
+    def test_raises_errors(self):
+        """ Ensure Route raises errors.
         """
         from wheezy.routing.route import Route
-
         r = Route()
-
-        assert r
-
-    def test_match_raise_error(self):
-        """ ``Route.match`` raises error
-        """
-        from wheezy.routing.route import Route
-
-        r = Route()
-        self.assertRaises(NotImplementedError,
-                          lambda: r.match(''))
-
-    def test_path_raise_error(self):
-        """ ``Route.match`` raises error
-        """
-        from wheezy.routing.route import Route
-
-        r = Route()
-        self.assertRaises(NotImplementedError,
-                          lambda: r.path())
-
-
-class PlainRouteInitTestCase(unittest.TestCase):
-    """ Test the ``PlainRoute.__init__`` class.
-    """
-
-    def test_equals(self):
-        """ Finishing route.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc', True)
-
-        assert r.equals_match == r.match
-
-    def test_startswith(self):
-        """ Intermediate route.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc/', False)
-
-        assert r.startswith_match == r.match
-
-    def test_arguments(self):
-        """ The inner state is properly initialized.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        kw = {'a': 1}
-        r = PlainRoute(r'abc', True, kw)
-
-        self.assertEquals(r'abc', r.pattern)
-        self.assertEquals(kw, r.kwargs)
-        self.assertEquals(3, r.matched)
-        assert kw == r.kwargs
-
-
-class PlainRouteEqualsMatchTestCase(unittest.TestCase):
-    """ Test the ``PlainRoute.equals_match`` class.
-    """
-
-    def test_kwargs(self):
-        """ matched
-        """
-        from wheezy.routing.route import PlainRoute
-
-        kw = {'a': 1}
-        r = PlainRoute(r'abc', True, kw)
-
-        matched, kwargs = r.equals_match(r'abc')
-        self.assertEquals(3, matched)
-        self.assertEquals({'a': 1}, r.kwargs)
-        assert kw == kwargs
-
-    def test_no_kwargs(self):
-        """ ``equals_match`` strategy when no kwargs supplied.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc', True)
-        matched, kwargs = r.equals_match(r'abc')
-        self.assertEquals(3, matched)
-        self.assertEquals({}, r.kwargs)
-
-    def test_no_match(self):
-        """ ``equals_match`` strategy when there is no match.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc', True)
-        matched, kwargs = r.equals_match(r'ab')
-        self.assertEquals(-1, matched)
-
-
-class PlainRouteStartswithMatchTestCase(unittest.TestCase):
-    """ Test the ``PlainRoute.startswith_match``.
-    """
-
-    def test_kwargs(self):
-        """ match strategy.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        kw = {'a': 1}
-        r = PlainRoute(r'abc/', False, kw)
-        matched, kwargs = r.startswith_match(r'abc/de')
-        self.assertEquals(4, matched)
-        self.assertEquals(kw, kwargs)
-        assert kw == kwargs
-
-    def test_no_kwargs(self):
-        """ ``startswith_match`` strategy when no
-            kwargs supplied.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc/', False)
-        matched, kwargs = r.startswith_match(r'abc/de')
-        self.assertEquals(4, matched)
-        self.assertEquals({}, r.kwargs)
-
-    def test_no_match(self):
-        """ ``startswith_match`` strategy when there
-            is no match.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc/', False)
-        matched, kwargs = r.equals_match(r'ab')
-        self.assertEquals(-1, matched)
-
-
-class PlainRoutePathTestCase(unittest.TestCase):
-    """ Test the ``PlainRoute.path``.
-    """
-
-    def test_path(self):
-        """ Simply return ``pattern``.
-        """
-        from wheezy.routing.route import PlainRoute
-
-        r = PlainRoute(r'abc/', False)
-        p = r.path()
-        self.assertEquals(p, r.pattern)
-
-    def test_values_ignored(self):
-        """ Simply return ``pattern``.
-        """