# HG changeset patch # User Ludovic Chabant # Date 1473263921 25200 # Node ID 58ebf50235a5b8770fd54e5bdaae45e4b6430dec # Parent 504d6817352d5b4f84c44fff912e45dff3c5c585 routing: Simplify how routes are defined. * No more declaring the type of route parameters -- the sources and generators already know what type each parameter is supposed to be. * Same for variadic parameters -- we know already. * Update cache version to force a clear reload of the config. * Update tests. TODO: simplify code in the `Route` class to use source or generator transparently. diff -r 504d6817352d -r 58ebf50235a5 piecrust/__init__.py --- a/piecrust/__init__.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/__init__.py Wed Sep 07 08:58:41 2016 -0700 @@ -18,7 +18,7 @@ PIECRUST_URL = 'https://bolt80.com/piecrust/' -CACHE_VERSION = 28 +CACHE_VERSION = 29 try: from piecrust.__version__ import APP_VERSION diff -r 504d6817352d -r 58ebf50235a5 piecrust/appconfig.py --- a/piecrust/appconfig.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/appconfig.py Wed Sep 07 08:58:41 2016 -0700 @@ -285,7 +285,7 @@ }), 'routes': [ { - 'url': '/%path:slug%', + 'url': '/%slug%', 'source': 'theme_pages', 'func': 'pcurl' } @@ -337,9 +337,9 @@ 'posts_fs': DEFAULT_POSTS_FS, 'default_page_layout': 'default', 'default_post_layout': 'post', - 'post_url': '/%int4:year%/%int2:month%/%int2:day%/%slug%', - 'year_url': '/archives/%int4:year%', - 'tag_url': '/tag/%+tag%', + 'post_url': '/%year%/%month%/%day%/%slug%', + 'year_url': '/archives/%year%', + 'tag_url': '/tag/%tag%', 'category_url': '/%category%', 'posts_per_page': 5 }) @@ -363,7 +363,7 @@ }), 'routes': [ { - 'url': '/%path:slug%', + 'url': '/%slug%', 'source': 'pages', 'func': 'pcurl' } diff -r 504d6817352d -r 58ebf50235a5 piecrust/commands/builtin/info.py --- a/piecrust/commands/builtin/info.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/commands/builtin/info.py Wed Sep 07 08:58:41 2016 -0700 @@ -85,7 +85,7 @@ logger.info(" regex: %s" % route.uri_re.pattern) logger.info(" function: %s(%s)" % ( route.func_name, - ', '.join(route.func_parameters))) + ', '.join(route.uri_params))) class ShowPathsCommand(ChefCommand): diff -r 504d6817352d -r 58ebf50235a5 piecrust/generation/base.py --- a/piecrust/generation/base.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/generation/base.py Wed Sep 07 08:58:41 2016 -0700 @@ -133,6 +133,9 @@ raise Exception("Can't find source '%s' for generator '%s'." % ( self.source_name, self.name)) + def getSupportedRouteParameters(self): + raise NotImplementedError() + def getPageFactory(self, route_metadata): # This will raise `PageNotFoundError` naturally if not found. return self.page_ref.getFactory() diff -r 504d6817352d -r 58ebf50235a5 piecrust/generation/blogarchives.py --- a/piecrust/generation/blogarchives.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/generation/blogarchives.py Wed Sep 07 08:58:41 2016 -0700 @@ -4,6 +4,7 @@ from piecrust.data.filters import PaginationFilter, IFilterClause from piecrust.data.iterators import PageIterator from piecrust.generation.base import PageGenerator, InvalidRecordExtraKey +from piecrust.routing import RouteParameter logger = logging.getLogger(__name__) @@ -15,6 +16,9 @@ def __init__(self, app, name, config): super(BlogArchivesPageGenerator, self).__init__(app, name, config) + def getSupportedRouteParameters(self): + return [RouteParameter('year', RouteParameter.TYPE_INT4)] + def onRouteFunctionUsed(self, route, route_metadata): pass diff -r 504d6817352d -r 58ebf50235a5 piecrust/generation/taxonomy.py --- a/piecrust/generation/taxonomy.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/generation/taxonomy.py Wed Sep 07 08:58:41 2016 -0700 @@ -8,6 +8,7 @@ PaginationFilter, SettingFilterClause, page_value_accessor) from piecrust.generation.base import PageGenerator, InvalidRecordExtraKey +from piecrust.routing import RouteParameter logger = logging.getLogger(__name__) @@ -68,6 +69,13 @@ self.slugify_mode = _parse_slugify_mode(sm) self.slugifier = _Slugifier(self.taxonomy, self.slugify_mode) + def getSupportedRouteParameters(self): + name = self.taxonomy.term_name + param_type = (RouteParameter.TYPE_PATH if self.taxonomy.is_multiple + else RouteParameter.TYPE_STRING) + return [RouteParameter(name, param_type, + variadic=self.taxonomy.is_multiple)] + def slugify(self, term): return self.slugifier.slugify(term) diff -r 504d6817352d -r 58ebf50235a5 piecrust/page.py --- a/piecrust/page.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/page.py Wed Sep 07 08:58:41 2016 -0700 @@ -11,7 +11,6 @@ from piecrust.configuration import ( Configuration, ConfigurationError, parse_config_header) -from piecrust.routing import IRouteMetadataProvider logger = logging.getLogger(__name__) @@ -37,7 +36,7 @@ FLAG_RAW_CACHE_VALID = 2**0 -class Page(IRouteMetadataProvider): +class Page(object): def __init__(self, source, source_metadata, rel_path): self.source = source self.source_metadata = source_metadata @@ -140,13 +139,6 @@ if was_cache_valid: self._flags |= FLAG_RAW_CACHE_VALID - def getRouteMetadata(self): - page_dt = self.datetime - return { - 'year': page_dt.year, - 'month': page_dt.month, - 'day': page_dt.day} - def _parse_config_date(page_date): if page_date is None: diff -r 504d6817352d -r 58ebf50235a5 piecrust/routing.py --- a/piecrust/routing.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/routing.py Wed Sep 07 08:58:41 2016 -0700 @@ -24,19 +24,25 @@ def create_route_metadata(page): route_metadata = copy.deepcopy(page.source_metadata) - route_metadata.update(page.getRouteMetadata()) return route_metadata -class IRouteMetadataProvider(object): - def getRouteMetadata(self): - raise NotImplementedError() - - ROUTE_TYPE_SOURCE = 0 ROUTE_TYPE_GENERATOR = 1 +class RouteParameter(object): + TYPE_STRING = 0 + TYPE_PATH = 1 + TYPE_INT2 = 2 + TYPE_INT4 = 3 + + def __init__(self, param_name, param_type=TYPE_STRING, *, variadic=False): + self.param_name = param_name + self.param_type = param_type + self.variadic = variadic + + class Route(object): """ Information about a route for a PieCrust application. Each route defines the "shape" of an URL and how it maps to @@ -51,6 +57,13 @@ raise InvalidRouteError( "Both `source` and `generator` are specified.") + self.uri_pattern = cfg['url'].lstrip('/') + + if self.is_source_route: + self.supported_params = self.source.getSupportedRouteParameters() + else: + self.supported_params = self.generator.getSupportedRouteParameters() + self.pretty_urls = app.config.get('site/pretty_urls') self.trailing_slash = app.config.get('site/trailing_slash') self.show_debug_info = app.config.get('site/show_debug_info') @@ -58,8 +71,7 @@ '__cache/pagination_suffix_format') self.uri_root = app.config.get('site/root') - uri = cfg['url'] - self.uri_pattern = uri.lstrip('/') + self.uri_params = [] self.uri_format = route_re.sub(self._uriFormatRepl, self.uri_pattern) # Get the straight-forward regex for matching this URI pattern. @@ -85,31 +97,17 @@ else: self.uri_re_no_path = None - # Determine the parameters for the route function. self.func_name = self._validateFuncName(cfg.get('func')) - self.func_parameters = [] self.func_has_variadic_parameter = False - self.param_types = {} - variadic_param_idx = -1 - for m in route_re.finditer(self.uri_pattern): - name = m.group('name') - self.func_parameters.append(name) - - qual = m.group('qual') - if not qual: - qual = self._getBackwardCompatibleParamType(name) - if qual: - self.param_types[name] = qual - - if m.group('var'): - self.func_has_variadic_parameter = True - variadic_param_idx = len(self.func_parameters) - 1 - - if (variadic_param_idx >= 0 and - variadic_param_idx != len(self.func_parameters) - 1): - raise Exception( - "Only the last route URL parameter can be variadic. " - "Got: %s" % self.uri_pattern) + for p in self.uri_params[:-1]: + param = self.getParameter(p) + if param.variadic: + raise Exception( + "Only the last route URL parameter can be variadic. " + "Got: %s" % self.uri_pattern) + if len(self.uri_params) > 0: + last_param = self.getParameter(self.uri_params[-1]) + self.func_has_variadic_parameter = last_param.variadic @property def route_type(self): @@ -136,7 +134,7 @@ if src.name == self.source_name: return src raise Exception("Can't find source '%s' for route '%s'." % ( - self.source_name, self.uri)) + self.source_name, self.uri_pattern)) @cached_property def generator(self): @@ -146,10 +144,23 @@ if gen.name == self.generator_name: return gen raise Exception("Can't find generator '%s' for route '%s'." % ( - self.generator_name, self.uri)) + self.generator_name, self.uri_pattern)) + + def hasParameter(self, name): + return any(lambda p: p.param_name == name, self.supported_params) + + def getParameter(self, name): + for p in self.supported_params: + if p.param_name == name: + return p + raise Exception("No such supported route parameter '%s' in: %s" % + (name, self.uri_pattern)) + + def getParameterType(self, name): + return self.getParameter(name).param_type def matchesMetadata(self, route_metadata): - return set(self.func_parameters).issubset(route_metadata.keys()) + return set(self.uri_params).issubset(route_metadata.keys()) def matchUri(self, uri, strict=False): if not uri.startswith(self.uri_root): @@ -178,8 +189,10 @@ # say, a route's pattern is `/foo/%slug%`, and we're matching an # URL like `/foo`. matched_keys = set(route_metadata.keys()) - missing_keys = set(self.func_parameters) - matched_keys + missing_keys = set(self.uri_params) - matched_keys for k in missing_keys: + if self.getParameterType(k) != RouteParameter.TYPE_PATH: + return None route_metadata[k] = '' for k in route_metadata: @@ -239,7 +252,7 @@ return uri def execTemplateFunc(self, *args): - fixed_param_count = len(self.func_parameters) + fixed_param_count = len(self.uri_params) if self.func_has_variadic_parameter: fixed_param_count -= 1 @@ -258,7 +271,7 @@ coerced_args = args metadata = {} - for arg_name, arg_val in zip(self.func_parameters, coerced_args): + for arg_name, arg_val in zip(self.uri_params, coerced_args): metadata[arg_name] = self._coerceRouteParameter( arg_name, arg_val) @@ -268,87 +281,62 @@ return self.getUri(metadata) def _uriFormatRepl(self, m): - qual = m.group('qual') - name = m.group('name') + if m.group('qual') or m.group('var'): + # Print a warning only if we're not in a worker process. + print_warning = not self.app.config.has('baker/worker_id') + if print_warning: + logger.warning("Route '%s' specified parameter types -- " + "they're not needed anymore." % + self.uri_pattern) - # Backwards compatibility... this will print a warning later. - if qual is None: - if name == 'year': - qual = 'int4' - elif name in ['month', 'day']: - qual = 'int2' - - if qual == 'int4': - return '%%(%s)04d' % name - elif qual == 'int2': - return '%%(%s)02d' % name - elif qual and qual != 'path': - raise Exception("Unknown route parameter type: %s" % qual) - return '%%(%s)s' % name + name = m.group('name') + self.uri_params.append(name) + try: + param_type = self.getParameterType(name) + if param_type == RouteParameter.TYPE_INT4: + return '%%(%s)04d' % name + elif param_type == RouteParameter.TYPE_INT2: + return '%%(%s)02d' % name + return '%%(%s)s' % name + except: + known = [p.name for p in self.supported_params] + raise Exception("Unknown route parameter '%s' for route '%s'. " + "Must be one of: %s'" % + (name, self.uri_pattern, known)) def _uriPatternRepl(self, m): name = m.group('name') - qual = m.group('qual') - - # Backwards compatibility... this will print a warning later. - if qual is None: - if name == 'year': - qual = 'int4' - elif name in ['month', 'day']: - qual = 'int2' - - if qual == 'path' or m.group('var'): + param_type = self.getParameterType(name) + if param_type == RouteParameter.TYPE_PATH: return r'(?P<%s>[^\?]*)' % name - elif qual == 'int4': + elif param_type == RouteParameter.TYPE_INT4: return r'(?P<%s>\d{4})' % name - elif qual == 'int2': + elif param_type == RouteParameter.TYPE_INT2: return r'(?P<%s>\d{2})' % name - elif qual and qual != 'path': - raise Exception("Unknown route parameter type: %s" % qual) return r'(?P<%s>[^/\?]+)' % name def _uriNoPathRepl(self, m): name = m.group('name') - qualifier = m.group('qual') - if qualifier == 'path': + param_type = self.getParameterType(name) + if param_type == RouteParameter.TYPE_PATH: return '' return r'(?P<%s>[^/\?]+)' % name def _coerceRouteParameter(self, name, val): - param_type = self.param_types.get(name) - if param_type is None: + try: + param_type = self.getParameterType(name) + except: + # Unknown parameter... just leave it. return val - if param_type in ['int', 'int2', 'int4']: + + if param_type in [RouteParameter.TYPE_INT2, RouteParameter.TYPE_INT4]: try: return int(val) except ValueError: raise Exception( - "Expected route parameter '%s' to be of type " - "'%s', but was: %s" % - (name, param_type, val)) - if param_type == 'path': - return val - raise Exception("Unknown route parameter type: %s" % param_type) - - def _getBackwardCompatibleParamType(self, name): - # Print a warning only if we're not in a worker process. - print_warning = not self.app.config.has('baker/worker_id') - - if name in ['year']: - if print_warning: - logger.warning( - "Route parameter '%%%s%%' has no type qualifier. " - "You probably meant '%%int4:%s%%' so we'll use that." % - (name, name)) - return 'int4' - if name in ['month', 'day']: - if print_warning: - logger.warning( - "Route parameter '%%%s%%' has no type qualifier. " - "You probably meant '%%int2:%s%%' so we'll use that." % - (name, name)) - return 'int2' - return None + "Expected route parameter '%s' to be an integer, " + "but was: %s" % (name, param_type, val)) + return val def _validateFuncName(self, name): if not name: @@ -369,12 +357,12 @@ def addFunc(self, route): if self._arg_names is None: - self._arg_names = list(route.func_parameters) + self._arg_names = list(route.uri_params) - if route.func_parameters != self._arg_names: + if route.uri_params != self._arg_names: raise Exception("Cannot merge route function with arguments '%s' " "with route function with arguments '%s'." % - (route.func_parameters, self._arg_names)) + (route.uri_params, self._arg_names)) self._routes.append(route) def __call__(self, *args, **kwargs): diff -r 504d6817352d -r 58ebf50235a5 piecrust/sources/autoconfig.py --- a/piecrust/sources/autoconfig.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/sources/autoconfig.py Wed Sep 07 08:58:41 2016 -0700 @@ -3,6 +3,7 @@ import os.path import logging from piecrust.configuration import ConfigurationError +from piecrust.routing import RouteParameter from piecrust.sources.base import ( PageSource, PageFactory, InvalidFileSystemEndpointError) from piecrust.sources.default import ( @@ -33,6 +34,10 @@ "one of: path, dirname, filename" % name) + def getSupportedRouteParameters(self): + return [ + RouteParameter('slug', RouteParameter.TYPE_PATH)] + def buildPageFactories(self): logger.debug("Scanning for pages in: %s" % self.fs_endpoint_path) if not os.path.isdir(self.fs_endpoint_path): diff -r 504d6817352d -r 58ebf50235a5 piecrust/sources/base.py --- a/piecrust/sources/base.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/sources/base.py Wed Sep 07 08:58:41 2016 -0700 @@ -106,6 +106,9 @@ self._factories = list(self.buildPageFactories()) return self._factories + def getSupportedRouteParameters(self): + raise NotImplementedError() + def buildPageFactories(self): raise NotImplementedError() diff -r 504d6817352d -r 58ebf50235a5 piecrust/sources/default.py --- a/piecrust/sources/default.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/sources/default.py Wed Sep 07 08:58:41 2016 -0700 @@ -1,6 +1,7 @@ import os.path import logging from piecrust import osutil +from piecrust.routing import RouteParameter from piecrust.sources.base import ( PageFactory, PageSource, InvalidFileSystemEndpointError, MODE_CREATING) @@ -36,6 +37,10 @@ app.config.get('site/auto_formats').keys()) self.default_auto_format = app.config.get('site/default_auto_format') + def getSupportedRouteParameters(self): + return [ + RouteParameter('slug', RouteParameter.TYPE_PATH)] + def buildPageFactories(self): logger.debug("Scanning for pages in: %s" % self.fs_endpoint_path) if not os.path.isdir(self.fs_endpoint_path): diff -r 504d6817352d -r 58ebf50235a5 piecrust/sources/posts.py --- a/piecrust/sources/posts.py Mon Sep 05 22:30:05 2016 -0700 +++ b/piecrust/sources/posts.py Wed Sep 07 08:58:41 2016 -0700 @@ -4,6 +4,7 @@ import logging import datetime from piecrust import osutil +from piecrust.routing import RouteParameter from piecrust.sources.base import ( PageSource, InvalidFileSystemEndpointError, PageFactory, MODE_CREATING, MODE_PARSING) @@ -36,6 +37,13 @@ metadata = self._parseMetadataFromPath(ref_path) return path, metadata + def getSupportedRouteParameters(self): + return [ + RouteParameter('slug', RouteParameter.TYPE_STRING), + RouteParameter('day', RouteParameter.TYPE_INT2), + RouteParameter('month', RouteParameter.TYPE_INT2), + RouteParameter('year', RouteParameter.TYPE_INT4)] + def buildPageFactory(self, path): if not path.startswith(self.fs_endpoint_path): raise Exception("Page path '%s' isn't inside '%s'." % ( diff -r 504d6817352d -r 58ebf50235a5 tests/test_routing.py --- a/tests/test_routing.py Mon Sep 05 22:30:05 2016 -0700 +++ b/tests/test_routing.py Wed Sep 07 08:58:41 2016 -0700 @@ -1,24 +1,48 @@ import urllib.parse +import mock import pytest -from piecrust.routing import Route +from piecrust.routing import Route, RouteParameter +from piecrust.sources.base import PageSource from .mockutil import get_mock_app +def _getMockSource(name, params): + route_params = [] + for p in params: + if isinstance(p, tuple): + if p[1] == 'path': + t = RouteParameter.TYPE_PATH + elif p[1] == 'int2': + t = RouteParameter.TYPE_INT2 + elif p[2] == 'int4': + t = RouteParameter.TYPE_INT4 + route_params.append(RouteParameter(p[0], t)) + else: + route_params.append(RouteParameter(p, RouteParameter.TYPE_STRING)) + + src = mock.MagicMock(spec=PageSource) + src.name = name + src.getSupportedRouteParameters = lambda: route_params + return src + + @pytest.mark.parametrize( - 'config, metadata, expected', + 'config, metadata, params, expected', [ ({'url': '/%foo%'}, - {'foo': 'bar'}, True), + {'foo': 'bar'}, ['foo'], True), ({'url': '/%foo%'}, - {'zoo': 'zar', 'foo': 'bar'}, True), + {'zoo': 'zar', 'foo': 'bar'}, ['foo'], True), ({'url': '/%foo%'}, - {'zoo': 'zar'}, False), + {'zoo': 'zar'}, ['foo'], False), ({'url': '/%foo%/%zoo%'}, - {'zoo': 'zar'}, False) + {'zoo': 'zar'}, ['foo', 'zoo'], False) ]) -def test_matches_metadata(config, metadata, expected): +def test_matches_metadata(config, metadata, params, expected): app = get_mock_app() app.config.set('site/root', '/') + app.sources = [_getMockSource('blah', params)] + config.setdefault('source', 'blah') route = Route(app, config) m = route.matchesMetadata(metadata) @@ -26,103 +50,128 @@ @pytest.mark.parametrize( - 'site_root, route_pattern, expected_func_parameters', + 'site_root, route_pattern, params, expected_func_parameters', [ - ('/', '/%foo%', ['foo']), - ('/', '/%path:foo%', ['foo']), - ('/', '/%foo%/%bar%', ['foo', 'bar']), - ('/', '/%foo%/%path:bar%', ['foo', 'bar']), - ('/something', '/%foo%', ['foo']), - ('/something', '/%path:foo%', ['foo']), - ('/something', '/%foo%/%bar%', ['foo', 'bar']), - ('/something', '/%foo%/%path:bar%', ['foo', 'bar']), - ('/~johndoe', '/%foo%', ['foo']), - ('/~johndoe', '/%path:foo%', ['foo']), - ('/~johndoe', '/%foo%/%bar%', ['foo', 'bar']), - ('/~johndoe', '/%foo%/%path:bar%', ['foo', 'bar']) + ('/', '/%foo%', ['foo'], ['foo']), + ('/', '/%foo%', [('foo', 'path')], ['foo']), + ('/', '/%foo%/%bar%', ['foo', 'bar'], ['foo', 'bar']), + ('/', '/%foo%/%bar%', ['foo', ('bar', 'path')], ['foo', 'bar']), + ('/something', '/%foo%', ['foo'], ['foo']), + ('/something', '/%foo%', [('foo', 'path')], ['foo']), + ('/something', '/%foo%/%bar%', ['foo', 'bar'], ['foo', 'bar']), + ('/something', '/%foo%/%bar%', ['foo', ('bar', 'path')], ['foo', 'bar']), + ('/~johndoe', '/%foo%', ['foo'], ['foo']), + ('/~johndoe', '/%foo%', [('foo', 'path')], ['foo']), + ('/~johndoe', '/%foo%/%bar%', ['foo', 'bar'], ['foo', 'bar']), + ('/~johndoe', '/%foo%/%bar%', ['foo', ('bar', 'path')], ['foo', 'bar']) ]) -def test_required_metadata(site_root, route_pattern, +def test_required_metadata(site_root, route_pattern, params, expected_func_parameters): app = get_mock_app() app.config.set('site/root', site_root.rstrip('/') + '/') + app.sources = [_getMockSource('blah', params)] + config = {'url': route_pattern, 'source': 'blah'} route = Route(app, config) - assert route.func_parameters == expected_func_parameters + assert route.uri_params == expected_func_parameters @pytest.mark.parametrize( - 'site_root, config, uri, expected_match', + 'site_root, config, params, uri, expected_match', [ ('/', {'url': '/%foo%'}, + ['foo'], 'something', {'foo': 'something'}), ('/', {'url': '/%foo%'}, + ['foo'], 'something/other', None), - ('/', {'url': '/%path:foo%'}, + ('/', {'url': '/%foo%'}, + [('foo', 'path')], 'something/other', {'foo': 'something/other'}), - ('/', {'url': '/%path:foo%'}, + ('/', {'url': '/%foo%'}, + [('foo', 'path')], '', {'foo': ''}), - ('/', {'url': '/prefix/%path:foo%'}, + ('/', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix/something/other', {'foo': 'something/other'}), - ('/', {'url': '/prefix/%path:foo%'}, + ('/', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix/', {'foo': ''}), - ('/', {'url': '/prefix/%path:foo%'}, + ('/', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix', {'foo': ''}), ('/blah', {'url': '/%foo%'}, + ['foo'], 'something', {'foo': 'something'}), ('/blah', {'url': '/%foo%'}, + ['foo'], 'something/other', None), - ('/blah', {'url': '/%path:foo%'}, + ('/blah', {'url': '/%foo%'}, + [('foo', 'path')], 'something/other', {'foo': 'something/other'}), - ('/blah', {'url': '/%path:foo%'}, + ('/blah', {'url': '/%foo%'}, + [('foo', 'path')], '', {'foo': ''}), - ('/blah', {'url': '/prefix/%path:foo%'}, + ('/blah', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix/something/other', {'foo': 'something/other'}), - ('/blah', {'url': '/prefix/%path:foo%'}, + ('/blah', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix/', {'foo': ''}), - ('/blah', {'url': '/prefix/%path:foo%'}, + ('/blah', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix', {'foo': ''}), ('/~johndoe', {'url': '/%foo%'}, + ['foo'], 'something', {'foo': 'something'}), ('/~johndoe', {'url': '/%foo%'}, + ['foo'], 'something/other', None), - ('/~johndoe', {'url': '/%path:foo%'}, + ('/~johndoe', {'url': '/%foo%'}, + [('foo', 'path')], 'something/other', {'foo': 'something/other'}), - ('/~johndoe', {'url': '/%path:foo%'}, + ('/~johndoe', {'url': '/%foo%'}, + [('foo', 'path')], '', {'foo': ''}), - ('/~johndoe', {'url': '/prefix/%path:foo%'}, + ('/~johndoe', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix/something/other', {'foo': 'something/other'}), - ('/~johndoe', {'url': '/prefix/%path:foo%'}, + ('/~johndoe', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix/', {'foo': ''}), - ('/~johndoe', {'url': '/prefix/%path:foo%'}, + ('/~johndoe', {'url': '/prefix/%foo%'}, + [('foo', 'path')], 'prefix', {'foo': ''}), ]) -def test_match_uri(site_root, config, uri, expected_match): +def test_match_uri(site_root, config, params, uri, expected_match): site_root = site_root.rstrip('/') + '/' app = get_mock_app() app.config.set('site/root', urllib.parse.quote(site_root)) + app.sources = [_getMockSource('blah', params)] + config.setdefault('source', 'blah') route = Route(app, config) assert route.uri_pattern == config['url'].lstrip('/') @@ -133,13 +182,17 @@ @pytest.mark.parametrize( 'site_root', [ - ('/'), ('/whatever'), ('/~johndoe') + ('/'), + ('/whatever'), + ('/~johndoe') ]) def test_match_uri_requires_absolute_uri(site_root): with pytest.raises(Exception): app = get_mock_app() app.config.set('site/root', site_root.rstrip('/') + '/') - config = {'url': '/%path:slug%', 'source': 'blah'} + app.sources = [_getMockSource('blah', [('slug', 'path')])] + + config = {'url': '/%slug%', 'source': 'blah'} route = Route(app, config) route.matchUri('notabsuri') @@ -181,8 +234,9 @@ app.config.set('site/pretty_urls', pretty) app.config.set('site/trailing_slash', False) app.config.set('__cache/pagination_suffix_format', '/%(num)d') + app.sources = [_getMockSource('blah', [('slug', 'path')])] - config = {'url': '/%path:slug%', 'source': 'blah'} + config = {'url': '/%slug%', 'source': 'blah'} route = Route(app, config) uri = route.getUri({'slug': slug}, sub_num=page_num) assert uri == (urllib.parse.quote(root) + expected)