def _add_tween(self, tween_factory, under=None, over=None, explicit=False): if not isinstance(tween_factory, str): raise ConfigurationError( 'The "tween_factory" argument to add_tween must be a ' 'dotted name to a globally importable object, not %r' % tween_factory ) name = tween_factory if name in (MAIN, INGRESS): raise ConfigurationError('%s is a reserved tween name' % name) tween_factory = self.maybe_dotted(tween_factory) for t, p in [('over', over), ('under', under)]: if p is not None: if not is_string_or_iterable(p): raise ConfigurationError( '"%s" must be a string or iterable, not %s' % (t, p) ) if over is INGRESS or is_nonstr_iter(over) and INGRESS in over: raise ConfigurationError('%s cannot be over INGRESS' % name) if under is MAIN or is_nonstr_iter(under) and MAIN in under: raise ConfigurationError('%s cannot be under MAIN' % name) registry = self.registry introspectables = [] tweens = registry.queryUtility(ITweens) if tweens is None: tweens = Tweens() registry.registerUtility(tweens, ITweens) def register(): if explicit: tweens.add_explicit(name, tween_factory) else: tweens.add_implicit( name, tween_factory, under=under, over=over ) discriminator = ('tween', name, explicit) tween_type = explicit and 'explicit' or 'implicit' intr = self.introspectable( 'tweens', discriminator, name, '%s tween' % tween_type ) intr['name'] = name intr['factory'] = tween_factory intr['type'] = tween_type intr['under'] = under intr['over'] = over introspectables.append(intr) self.action(discriminator, register, introspectables=introspectables)
def generator(dict): newdict = {} for k, v in dict.items(): if v.__class__ is bytes: # url_quote below needs a native string v = v.decode('utf-8') if k == remainder: # a stararg argument if is_nonstr_iter(v): v = '/'.join([q(x) for x in v]) # native else: if v.__class__ is not str: v = str(v) v = q(v) else: if v.__class__ is not str: v = str(v) v = q(v) # at this point, the value will be a native string newdict[k] = v result = gen % newdict # native string result return result
def wrapper(self, *arg, **kw): if self._ainfo is None: self._ainfo = [] info = kw.pop('_info', None) # backframes for outer decorators to actionmethods backframes = kw.pop('_backframes', 0) + 2 if is_nonstr_iter(info) and len(info) == 4: # _info permitted as extract_stack tuple info = ActionInfo(*info) if info is None: try: f = traceback.extract_stack(limit=4) # Work around a Python 3.5 issue whereby it would insert an # extra stack frame. This should no longer be necessary in # Python 3.5.1 last_frame = ActionInfo(*f[-1]) if last_frame.function == 'extract_stack': # pragma: no cover f.pop() info = ActionInfo(*f[-backframes]) except Exception: # pragma: no cover info = ActionInfo(None, 0, '', '') self._ainfo.append(info) try: result = wrapped(self, *arg, **kw) finally: self._ainfo.pop() return result
def __call__(self, context, request): req_principals = request.effective_principals if is_nonstr_iter(req_principals): rpset = set(req_principals) if self.val.issubset(rpset): return True return False
def urlencode(query, doseq=True, quote_via=quote_plus): """ An alternate implementation of Python's stdlib :func:`urllib.parse.urlencode` function which accepts string keys and values within the ``query`` dict/sequence; all string keys and values are first converted to UTF-8 before being used to compose the query string. The value of ``query`` must be a sequence of two-tuples representing key/value pairs *or* an object (often a dictionary) with an ``.items()`` method that returns a sequence of two-tuples representing key/value pairs. For minimal calling convention backwards compatibility, this version of urlencode accepts *but ignores* a second argument conventionally named ``doseq``. The Python stdlib version behaves differently when ``doseq`` is False and when a sequence is presented as one of the values. This version always behaves in the ``doseq=True`` mode, no matter what the value of the second argument. Both the key and value are encoded using the ``quote_via`` function which by default is using a similar algorithm to :func:`urllib.parse.quote_plus` which converts spaces into '+' characters and '/' into '%2F'. .. versionchanged:: 1.5 In a key/value pair, if the value is ``None`` then it will be dropped from the resulting output. .. versionchanged:: 1.9 Added the ``quote_via`` argument to allow alternate quoting algorithms to be used. """ try: # presumed to be a dictionary query = query.items() except AttributeError: pass result = '' prefix = '' for (k, v) in query: k = quote_via(k) if is_nonstr_iter(v): for x in v: x = quote_via(x) result += '%s%s=%s' % (prefix, k, x) prefix = '&' elif v is None: result += '%s%s=' % (prefix, k) else: v = quote_via(v) result += '%s%s=%s' % (prefix, k, v) prefix = '&' return result
def permits(self, context, principals, permission): """Return an instance of :class:`pyramid.authorization.ACLAllowed` if the ACL allows access a user with the given principals, return an instance of :class:`pyramid.authorization.ACLDenied` if not. When checking if principals are allowed, the security policy consults the ``context`` for an ACL first. If no ACL exists on the context, or one does exist but the ACL does not explicitly allow or deny access for any of the effective principals, consult the context's parent ACL, and so on, until the lineage is exhausted or we determine that the policy permits or denies. During this processing, if any :data:`pyramid.authorization.Deny` ACE is found matching any principal in ``principals``, stop processing by returning an :class:`pyramid.authorization.ACLDenied` instance (equals ``False``) immediately. If any :data:`pyramid.authorization.Allow` ACE is found matching any principal, stop processing by returning an :class:`pyramid.authorization.ACLAllowed` instance (equals ``True``) immediately. If we exhaust the context's :term:`lineage`, and no ACE has explicitly permitted or denied access, return an instance of :class:`pyramid.authorization.ACLDenied` (equals ``False``). """ acl = '<No ACL found on any object in resource lineage>' for location in lineage(context): try: acl = location.__acl__ except AttributeError: continue if acl and callable(acl): acl = acl() for ace in acl: ace_action, ace_principal, ace_permissions = ace if ace_principal in principals: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] if permission in ace_permissions: if ace_action == Allow: return ACLAllowed( ace, acl, permission, principals, location ) else: return ACLDenied( ace, acl, permission, principals, location ) # default deny (if no ACL in lineage at all, or if none of the # principals were mentioned in any ACE we found) return ACLDenied( '<default deny>', acl, permission, principals, context )
def principals_allowed_by_permission(self, context, permission): """Return the set of principals explicitly granted the permission named ``permission`` according to the ACL directly attached to the ``context`` as well as inherited ACLs based on the :term:`lineage`. When computing principals allowed by a permission, we compute the set of principals that are explicitly granted the ``permission`` in the provided ``context``. We do this by walking 'up' the object graph *from the root* to the context. During this walking process, if we find an explicit :data:`pyramid.authorization.Allow` ACE for a principal that matches the ``permission``, the principal is included in the allow list. However, if later in the walking process that principal is mentioned in any :data:`pyramid.authorization.Deny` ACE for the permission, the principal is removed from the allow list. If a :data:`pyramid.authorization.Deny` to the principal :data:`pyramid.authorization.Everyone` is encountered during the walking process that matches the ``permission``, the allow list is cleared for all principals encountered in previous ACLs. The walking process ends after we've processed the any ACL directly attached to ``context``; a set of principals is returned. """ allowed = set() for location in reversed(list(lineage(context))): # NB: we're walking *up* the object graph from the root try: acl = location.__acl__ except AttributeError: continue allowed_here = set() denied_here = set() if acl and callable(acl): acl = acl() for ace_action, ace_principal, ace_permissions in acl: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] if (ace_action == Allow) and (permission in ace_permissions): if ace_principal not in denied_here: allowed_here.add(ace_principal) if (ace_action == Deny) and (permission in ace_permissions): denied_here.add(ace_principal) if ace_principal == Everyone: # clear the entire allowed set, as we've hit a # deny of Everyone ala (Deny, Everyone, ALL) allowed = set() break elif ace_principal in allowed: allowed.remove(ace_principal) allowed.update(allowed_here) return allowed
def principals_allowed_by_permission(context, permission: str) -> set: """Return the set of principals explicitly granted the permission named ``permission`` according to the ACL directly attached to the ``context`` as well as inherited ACLs based on the :term:`lineage`.""" allowed = set() base_permission, _, context_permission = permission.partition('.') if base_permission and base_permission not in _BASE_PERMISSIONS: context_permission = permission base_permission = '' permission = context_permission for location in reversed(list(lineage(context))): # NB: we're walking *up* the object graph from the root try: acl = location.__acl__ except AttributeError: continue allowed_here = set() denied_here = set() if acl and callable(acl): acl = acl() for ace_action, ace_principal, ace_permissions in acl: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] is_match = (_match_permission(permission, ace_permissions) or (base_permission and _match_base_permission( base_permission, ace_permissions))) if (ace_action == Allow) and is_match: if ace_principal not in denied_here: allowed_here.add(ace_principal) if (ace_action == Deny) and is_match: denied_here.add(ace_principal) if ace_principal == Everyone: # clear the entire allowed set, as we've hit a # deny of Everyone ala (Deny, Everyone, ALL) allowed = set() break elif ace_principal in allowed: allowed.remove(ace_principal) allowed.update(allowed_here) return allowed
def principals_allowed_by_permission(self, context, permission): """ Return the set of principals explicitly granted the permission named ``permission`` according to the ACL directly attached to the ``context`` as well as inherited ACLs based on the :term:`lineage`.""" allowed = set() for location in reversed(list(lineage(context))): # NB: we're walking *up* the object graph from the root try: acl = location.__acl__ except AttributeError: continue allowed_here = set() denied_here = set() if acl and callable(acl): acl = acl() for ace_action, ace_principal, ace_permissions in acl: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] if (ace_action == Allow) and (permission in ace_permissions): if ace_principal not in denied_here: allowed_here.add(ace_principal) if (ace_action == Deny) and (permission in ace_permissions): denied_here.add(ace_principal) if ace_principal == Everyone: # clear the entire allowed set, as we've hit a # deny of Everyone ala (Deny, Everyone, ALL) allowed = set() break elif ace_principal in allowed: allowed.remove(ace_principal) allowed.update(allowed_here) return allowed
def permits(self, context, principals, permission): """ Return an instance of :class:`pyramid.security.ACLAllowed` instance if the policy permits access, return an instance of :class:`pyramid.security.ACLDenied` if not.""" acl = '<No ACL found on any object in resource lineage>' for location in lineage(context): try: acl = location.__acl__ except AttributeError: continue if acl and callable(acl): acl = acl() for ace in acl: ace_action, ace_principal, ace_permissions = ace if ace_principal in principals: if not is_nonstr_iter(ace_permissions): ace_permissions = [ace_permissions] if permission in ace_permissions: if ace_action == Allow: return ACLAllowed( ace, acl, permission, principals, location ) else: return ACLDenied( ace, acl, permission, principals, location ) # default deny (if no ACL in lineage at all, or if none of the # principals were mentioned in any ACE we found) return ACLDenied( '<default deny>', acl, permission, principals, context )
def traverse(resource, path): """Given a resource object as ``resource`` and a string or tuple representing a path as ``path`` (such as the return value of :func:`pyramid.traversal.resource_path` or :func:`pyramid.traversal.resource_path_tuple` or the value of ``request.environ['PATH_INFO']``), return a dictionary with the keys ``context``, ``root``, ``view_name``, ``subpath``, ``traversed``, ``virtual_root``, and ``virtual_root_path``. A definition of each value in the returned dictionary: - ``context``: The :term:`context` (a :term:`resource` object) found via traversal or URL dispatch. If the ``path`` passed in is the empty string, the value of the ``resource`` argument passed to this function is returned. - ``root``: The resource object at which :term:`traversal` begins. If the ``resource`` passed in was found via URL dispatch or if the ``path`` passed in was relative (non-absolute), the value of the ``resource`` argument passed to this function is returned. - ``view_name``: The :term:`view name` found during :term:`traversal` or :term:`URL dispatch`; if the ``resource`` was found via traversal, this is usually a representation of the path segment which directly follows the path to the ``context`` in the ``path``. The ``view_name`` will be a string. The ``view_name`` will be the empty string if there is no element which follows the ``context`` path. An example: if the path passed is ``/foo/bar``, and a resource object is found at ``/foo`` (but not at ``/foo/bar``), the 'view name' will be ``'bar'``. If the ``resource`` was found via URL dispatch, the ``view_name`` will be the empty string unless the ``traverse`` predicate was specified or the ``*traverse`` route pattern was used, at which point normal traversal rules dictate the result. - ``subpath``: For a ``resource`` found via :term:`traversal`, this is a sequence of path segments found in the ``path`` that follow the ``view_name`` (if any). Each of these items is a string. If no path segments follow the ``view_name``, the subpath will be the empty sequence. An example: if the path passed is ``/foo/bar/baz/buz``, and a resource object is found at ``/foo`` (but not ``/foo/bar``), the 'view name' will be ``'bar'`` and the :term:`subpath` will be ``['baz', 'buz']``. For a ``resource`` found via URL dispatch, the subpath will be a sequence of values discerned from ``*subpath`` in the route pattern matched or the empty sequence. - ``traversed``: The sequence of path elements traversed from the root to find the ``context`` object during :term:`traversal`. Each of these items is a string. If no path segments were traversed to find the ``context`` object (e.g. if the ``path`` provided is the empty string), the ``traversed`` value will be the empty sequence. If the ``resource`` is a resource found via :term:`URL dispatch`, traversed will be None. - ``virtual_root``: A resource object representing the 'virtual' root of the resource tree being traversed during :term:`traversal`. See :ref:`vhosting_chapter` for a definition of the virtual root object. If no virtual hosting is in effect, and the ``path`` passed in was absolute, the ``virtual_root`` will be the *physical* root resource object (the object at which :term:`traversal` begins). If the ``resource`` passed in was found via :term:`URL dispatch` or if the ``path`` passed in was relative, the ``virtual_root`` will always equal the ``root`` object (the resource passed in). - ``virtual_root_path`` -- If :term:`traversal` was used to find the ``resource``, this will be the sequence of path elements traversed to find the ``virtual_root`` resource. Each of these items is a string. If no path segments were traversed to find the ``virtual_root`` resource (e.g. if virtual hosting is not in effect), the ``traversed`` value will be the empty list. If URL dispatch was used to find the ``resource``, this will be ``None``. If the path cannot be resolved, a :exc:`KeyError` will be raised. Rules for passing a *string* as the ``path`` argument: if the first character in the path string is the with the ``/`` character, the path will considered absolute and the resource tree traversal will start at the root resource. If the first character of the path string is *not* the ``/`` character, the path is considered relative and resource tree traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty string is passed as ``path``, the ``resource`` passed in will be returned. Resource path strings must be escaped in the following manner: each path segment must be encoded as UTF-8 and escaped via Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The :func:`pyramid.traversal.resource_path` function generates strings which follow these rules (albeit only absolute ones). Rules for passing a *tuple* as the ``path`` argument: if the first element in the path tuple is the empty string (for example ``('', 'a', 'b', 'c')``, the path is considered absolute and the resource tree traversal will start at the resource tree root object. If the first element in the path tuple is not the empty string (for example ``('a', 'b', 'c')``), the path is considered relative and resource tree traversal will begin at the resource object supplied to the function as the ``resource`` argument. If an empty sequence is passed as ``path``, the ``resource`` passed in itself will be returned. No URL-quoting or UTF-8-encoding of individual path segments within the tuple is required (each segment may be any string representing a resource name). Explanation of the decoding of ``path`` segment values during traversal: Each segment is URL-unquoted, and UTF-8 decoded. Each segment is assumed to be encoded using the UTF-8 encoding (or a subset, such as ASCII); a :exc:`pyramid.exceptions.URLDecodeError` is raised if a segment cannot be decoded. If a segment name is empty or if it is ``.``, it is ignored. If a segment name is ``..``, the previous segment is deleted, and the ``..`` is ignored. As a result of this process, the return values ``view_name``, each element in the ``subpath``, each element in ``traversed``, and each element in the ``virtual_root_path`` will be decoded strings. """ if is_nonstr_iter(path): # the traverser factory expects PATH_INFO to be a string and it # expects path segments to be utf-8 and # urlencoded (it's the same traverser which accepts PATH_INFO # from user agents; user agents always send strings). if path: path = _join_path_tuple(tuple(path)) else: path = '' # The user is supposed to pass us a string object, never Unicode. In # practice, however, users indeed pass Unicode to this API. If they do # pass a Unicode object, its data *must* be entirely encodeable to ASCII, # so we encode it here as a convenience to the user and to prevent # second-order failures from cropping up (all failures will occur at this # step rather than later down the line as the result of calling # ``traversal_path``). path = ascii_(path) if path and path[0] == '/': resource = find_root(resource) reg = get_current_registry() request_factory = reg.queryUtility(IRequestFactory) if request_factory is None: from pyramid.request import Request # avoid circdep request_factory = Request request = request_factory.blank(path) request.registry = reg traverser = reg.queryAdapter(resource, ITraverser) if traverser is None: traverser = ResourceTreeTraverser(resource) return traverser(request)
def __init__(self, values, config): if not is_nonstr_iter(values): values = (values,) self.values = values
def __init__(self, val, config): if is_nonstr_iter(val): self.val = set(val) else: self.val = set((val, ))
def __init__(self, val, config): if is_nonstr_iter(val): self.val = tuple(val) else: val = tuple(filter(None, val.split('/'))) self.val = ('', ) + val
def __init__(self, values, config): if not is_nonstr_iter(values): values = (values, ) self.values = values
def make(self, config, **kw): # Given a configurator and a list of keywords, a predicate list is # computed. Elsewhere in the code, we evaluate predicates using a # generator expression. All predicates associated with a view or # route must evaluate true for the view or route to "match" during a # request. The fastest predicate should be evaluated first, then the # next fastest, and so on, as if one returns false, the remainder of # the predicates won't need to be evaluated. # # While we compute predicates, we also compute a predicate hash (aka # phash) that can be used by a caller to identify identical predicate # lists. ordered = self.sorter.sorted() phash = md5() weights = [] preds = [] for n, (name, predicate_factory) in enumerate(ordered): vals = kw.pop(name, None) if vals is None: # XXX should this be a sentinel other than None? continue if not isinstance(vals, predvalseq): vals = (vals,) for val in vals: realval = val notted = False if isinstance(val, not_): realval = val.value notted = True pred = predicate_factory(realval, config) if notted: pred = Notted(pred) hashes = pred.phash() if not is_nonstr_iter(hashes): hashes = [hashes] for h in hashes: phash.update(bytes_(h)) weights.append(1 << n + 1) preds.append(pred) if kw: from difflib import get_close_matches closest = [] names = [name for name, _ in ordered] for name in kw: closest.extend(get_close_matches(name, names, 3)) raise ConfigurationError( 'Unknown predicate values: %r (did you mean %s)' % (kw, ','.join(closest)) ) # A "order" is computed for the predicate list. An order is # a scoring. # # Each predicate is associated with a weight value. The weight of a # predicate symbolizes the relative potential "importance" of the # predicate to all other predicates. A larger weight indicates # greater importance. # # All weights for a given predicate list are bitwise ORed together # to create a "score"; this score is then subtracted from # MAX_ORDER and divided by an integer representing the number of # predicates+1 to determine the order. # # For views, the order represents the ordering in which a "multiview" # ( a collection of views that share the same context/request/name # triad but differ in other ways via predicates) will attempt to call # its set of views. Views with lower orders will be tried first. # The intent is to a) ensure that views with more predicates are # always evaluated before views with fewer predicates and b) to # ensure a stable call ordering of views that share the same number # of predicates. Views which do not have any predicates get an order # of MAX_ORDER, meaning that they will be tried very last. score = 0 for bit in weights: score = score | bit order = (MAX_ORDER - score) / (len(preds) + 1) return order, preds, phash.hexdigest()
def make(self, config, **kw): # Given a configurator and a list of keywords, a predicate list is # computed. Elsewhere in the code, we evaluate predicates using a # generator expression. All predicates associated with a view or # route must evaluate true for the view or route to "match" during a # request. The fastest predicate should be evaluated first, then the # next fastest, and so on, as if one returns false, the remainder of # the predicates won't need to be evaluated. # # While we compute predicates, we also compute a predicate hash (aka # phash) that can be used by a caller to identify identical predicate # lists. ordered = self.sorter.sorted() phash = md5() weights = [] preds = [] info = PredicateInfo( package=config.package, registry=config.registry, settings=config.get_settings(), maybe_dotted=config.maybe_dotted, ) for n, (name, predicate_factory) in enumerate(ordered): vals = kw.pop(name, None) if vals is None: # XXX should this be a sentinel other than None? continue if not isinstance(vals, predvalseq): vals = (vals,) for val in vals: realval = val notted = False if isinstance(val, not_): realval = val.value notted = True pred = predicate_factory(realval, info) if notted: pred = Notted(pred) hashes = pred.phash() if not is_nonstr_iter(hashes): hashes = [hashes] for h in hashes: phash.update(bytes_(h)) weights.append(1 << n + 1) preds.append(pred) if kw: from difflib import get_close_matches closest = [] names = [name for name, _ in ordered] for name in kw: closest.extend(get_close_matches(name, names, 3)) raise ConfigurationError( 'Unknown predicate values: %r (did you mean %s)' % (kw, ','.join(closest)) ) # A "order" is computed for the predicate list. An order is # a scoring. # # Each predicate is associated with a weight value. The weight of a # predicate symbolizes the relative potential "importance" of the # predicate to all other predicates. A larger weight indicates # greater importance. # # All weights for a given predicate list are bitwise ORed together # to create a "score"; this score is then subtracted from # MAX_ORDER and divided by an integer representing the number of # predicates+1 to determine the order. # # For views, the order represents the ordering in which a "multiview" # ( a collection of views that share the same context/request/name # triad but differ in other ways via predicates) will attempt to call # its set of views. Views with lower orders will be tried first. # The intent is to a) ensure that views with more predicates are # always evaluated before views with fewer predicates and b) to # ensure a stable call ordering of views that share the same number # of predicates. Views which do not have any predicates get an order # of MAX_ORDER, meaning that they will be tried very last. score = 0 for bit in weights: score = score | bit order = (MAX_ORDER - score) / (len(preds) + 1) return order, preds, phash.hexdigest()
def add_route( self, name, pattern=None, factory=None, for_=None, header=None, xhr=None, accept=None, path_info=None, request_method=None, request_param=None, traverse=None, custom_predicates=(), use_global_views=False, path=None, pregenerator=None, static=False, **predicates ): """ Add a :term:`route configuration` to the current configuration state, as well as possibly a :term:`view configuration` to be used to specify a :term:`view callable` that will be invoked when this route matches. The arguments to this method are divided into *predicate*, *non-predicate*, and *view-related* types. :term:`Route predicate` arguments narrow the circumstances in which a route will be match a request; non-predicate arguments are informational. Non-Predicate Arguments name The name of the route, e.g. ``myroute``. This attribute is required. It must be unique among all defined routes in a given application. factory A Python object (often a function or a class) or a :term:`dotted Python name` which refers to the same object that will generate a :app:`Pyramid` root resource object when this route matches. For example, ``mypackage.resources.MyFactory``. If this argument is not specified, a default root factory will be used. See :ref:`the_resource_tree` for more information about root factories. traverse If you would like to cause the :term:`context` to be something other than the :term:`root` object when this route matches, you can spell a traversal pattern as the ``traverse`` argument. This traversal pattern will be used as the traversal path: traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). The syntax of the ``traverse`` argument is the same as it is for ``pattern``. For example, if the ``pattern`` provided to ``add_route`` is ``articles/{article}/edit``, and the ``traverse`` argument provided to ``add_route`` is ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is ``'1'`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with the name ``'1'`` during the traversal phase. If the ``'1'`` object exists, it will become the :term:`context` of the request. :ref:`traversal_chapter` has more information about traversal. If the traversal path contains segment marker names which are not present in the ``pattern`` argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``pattern`` argument. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` remainder marker in its pattern (see :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument to add_route allows you to associate route patterns with an arbitrary traversal path without using a ``*traverse`` remainder marker; instead you can use other match information. Note that the ``traverse`` argument to ``add_route`` is ignored when attached to a route that has a ``*traverse`` remainder marker in its pattern. pregenerator This option should be a callable object that implements the :class:`pyramid.interfaces.IRoutePregenerator` interface. A :term:`pregenerator` is a callable called by the :meth:`pyramid.request.Request.route_url` function to augment or replace the arguments it is passed when generating a URL for the route. This is a feature not often used directly by applications, it is meant to be hooked by frameworks that use :app:`Pyramid` as a base. use_global_views When a request matches this route, and view lookup cannot find a view which has a ``route_name`` predicate argument that matches the route, try to fall back to using a view that otherwise matches the context, request, and view name (but which does not match the route_name predicate). static If ``static`` is ``True``, this route will never match an incoming request; it will only be useful for URL generation. By default, ``static`` is ``False``. See :ref:`static_route_narr`. .. versionadded:: 1.1 Predicate Arguments pattern The pattern of the route e.g. ``ideas/{idea}``. This argument is required. See :ref:`route_pattern_syntax` for information about the syntax of route patterns. If the pattern doesn't match the current URL, route matching continues. .. note:: For backwards compatibility purposes (as of :app:`Pyramid` 1.0), a ``path`` keyword argument passed to this function will be used to represent the pattern value if the ``pattern`` argument is ``None``. If both ``path`` and ``pattern`` are passed, ``pattern`` wins. xhr This value should be either ``True`` or ``False``. If this value is specified and is ``True``, the :term:`request` must possess an ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this route to match. This is useful for detecting AJAX requests issued from jQuery, Prototype and other Javascript libraries. If this predicate returns ``False``, route matching continues. request_method A string representing an HTTP method name, e.g. ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT`` or a tuple of elements containing HTTP method names. If this argument is not specified, this route will match if the request has *any* request method. If this predicate returns ``False``, route matching continues. .. versionchanged:: 1.2 The ability to pass a tuple of items as ``request_method``. Previous versions allowed only a string. path_info This value represents a regular expression pattern that will be tested against the ``PATH_INFO`` WSGI environment variable. If the regex matches, this predicate will return ``True``. If this predicate returns ``False``, route matching continues. request_param This value can be any string or an iterable of strings. A view declaration with this argument ensures that the associated route will only match when the request has a key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` variable) that has a name which matches the supplied value. If the value supplied as the argument has a ``=`` sign in it, e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, and the value must match the right hand side of the expression (``123``) for the route to "match" the current request. If this predicate returns ``False``, route matching continues. header This argument represents an HTTP header name or a header name/value pair. If the argument contains a ``:`` (colon), it will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If the value contains a colon, the value portion should be a regular expression. If the value does not contain a colon, the entire value will be considered to be the header name (e.g. ``If-Modified-Since``). If the value evaluates to a header name only without a value, the header specified by the name must be present in the request for this predicate to be true. If the value evaluates to a header name/value pair, the header specified by the name must be present in the request *and* the regular expression specified as the value must match the header value. Whether or not the value represents a header name or a header name/value pair, the case of the header name is not significant. If this predicate returns ``False``, route matching continues. accept A :term:`media type` that will be matched against the ``Accept`` HTTP request header. If this value is specified, it may be a specific media type such as ``text/html``, or a list of the same. If the media type is acceptable by the ``Accept`` header of the request, or if the ``Accept`` header isn't set at all in the request, this predicate will match. If this does not match the ``Accept`` header of the request, route matching continues. If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not taken into consideration when deciding whether or not to select the route. Unlike the ``accept`` argument to :meth:`pyramid.config.Configurator.add_view`, this value is strictly a predicate and supports :func:`pyramid.config.not_`. .. versionchanged:: 1.10 Specifying a media range is deprecated due to changes in WebOb and ambiguities that occur when trying to match ranges against ranges in the ``Accept`` header. Support will be removed in :app:`Pyramid` 2.0. Use a list of specific media types to match more than one type. .. versionchanged:: 2.0 Removed support for media ranges. effective_principals If specified, this value should be a :term:`principal` identifier or a sequence of principal identifiers. If the :attr:`pyramid.request.Request.effective_principals` property indicates that every principal named in the argument list is present in the current request, this predicate will return True; otherwise it will return False. For example: ``effective_principals=pyramid.security.Authenticated`` or ``effective_principals=('fred', 'group:admins')``. .. versionadded:: 1.4a4 custom_predicates .. deprecated:: 1.5 This value should be a sequence of references to custom predicate callables. Use custom predicates when no set of predefined predicates does what you need. Custom predicates can be combined with predefined predicates as necessary. Each custom predicate callable should accept two arguments: ``info`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of the info and/or the request. If all custom and non-custom predicate callables return ``True`` the associated route will be considered viable for a given request. If any predicate callable returns ``False``, route matching continues. Note that the value ``info`` passed to a custom route predicate is a dictionary containing matching information; see :ref:`custom_route_predicates` for more information about ``info``. predicates Pass a key/value pair here to use a third-party predicate registered via :meth:`pyramid.config.Configurator.add_route_predicate`. More than one key/value pair can be used at the same time. See :ref:`view_and_route_predicates` for more information about third-party predicates. .. versionadded:: 1.4 """ if custom_predicates: warnings.warn( ( 'The "custom_predicates" argument to ' 'Configurator.add_route is deprecated as of Pyramid 1.5. ' 'Use "config.add_route_predicate" and use the registered ' 'route predicate as a predicate argument to add_route ' 'instead. See "Adding A Third Party View, Route, or ' 'Subscriber Predicate" in the "Hooks" chapter of the ' 'documentation for more information.' ), DeprecationWarning, stacklevel=3, ) if accept is not None: if not is_nonstr_iter(accept): accept = [accept] accept = [ normalize_accept_offer(accept_option) for accept_option in accept ] # these are route predicates; if they do not match, the next route # in the routelist will be tried if request_method is not None: request_method = as_sorted_tuple(request_method) factory = self.maybe_dotted(factory) if pattern is None: pattern = path if pattern is None: raise ConfigurationError('"pattern" argument may not be None') # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') parsed = urlparse.urlparse(pattern) external_url = pattern if parsed.hostname: pattern = parsed.path original_pregenerator = pregenerator def external_url_pregenerator(request, elements, kw): if '_app_url' in kw: raise ValueError( 'You cannot generate a path to an external route ' 'pattern via request.route_path nor pass an _app_url ' 'to request.route_url when generating a URL for an ' 'external route pattern (pattern was "%s") ' % (pattern,) ) if '_scheme' in kw: scheme = kw['_scheme'] elif parsed.scheme: scheme = parsed.scheme else: scheme = request.scheme kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) if original_pregenerator: elements, kw = original_pregenerator(request, elements, kw) return elements, kw pregenerator = external_url_pregenerator static = True elif self.route_prefix: pattern = self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') mapper = self.get_routes_mapper() introspectables = [] intr = self.introspectable( 'routes', name, '%s (pattern: %r)' % (name, pattern), 'route' ) intr['name'] = name intr['pattern'] = pattern intr['factory'] = factory intr['xhr'] = xhr intr['request_methods'] = request_method intr['path_info'] = path_info intr['request_param'] = request_param intr['header'] = header intr['accept'] = accept intr['traverse'] = traverse intr['custom_predicates'] = custom_predicates intr['pregenerator'] = pregenerator intr['static'] = static intr['use_global_views'] = use_global_views if static is True: intr['external_url'] = external_url introspectables.append(intr) if factory: factory_intr = self.introspectable( 'root factories', name, self.object_description(factory), 'root factory', ) factory_intr['factory'] = factory factory_intr['route_name'] = name factory_intr.relate('routes', name) introspectables.append(factory_intr) def register_route_request_iface(): request_iface = self.registry.queryUtility( IRouteRequest, name=name ) if request_iface is None: if use_global_views: bases = (IRequest,) else: bases = () request_iface = route_request_iface(name, bases) self.registry.registerUtility( request_iface, IRouteRequest, name=name ) def register_connect(): pvals = predicates.copy() pvals.update( dict( xhr=xhr, request_method=request_method, path_info=path_info, request_param=request_param, header=header, accept=accept, traverse=traverse, custom=predvalseq(custom_predicates), ) ) predlist = self.get_predlist('route') _, preds, _ = predlist.make(self, **pvals) route = mapper.connect( name, pattern, factory, predicates=preds, pregenerator=pregenerator, static=static, ) intr['object'] = route return route # We have to connect routes in the order they were provided; # we can't use a phase to do that, because when the actions are # sorted, actions in the same phase lose relative ordering self.action(('route-connect', name), register_connect) # But IRouteRequest interfaces must be registered before we begin to # process view registrations (in phase 3) self.action( ('route', name), register_route_request_iface, order=PHASE2_CONFIG, introspectables=introspectables, )
def __call__(self, request): environ = request.environ matchdict = request.matchdict if matchdict is not None: path = matchdict.get('traverse', '/') or '/' if is_nonstr_iter(path): # this is a *traverse stararg (not a {traverse}) # routing has already decoded these elements, so we just # need to join them path = '/' + '/'.join(path) or '/' subpath = matchdict.get('subpath', ()) if not is_nonstr_iter(subpath): # this is not a *subpath stararg (just a {subpath}) # routing has already decoded this string, so we just need # to split it subpath = split_path_info(subpath) else: # this request did not match a route subpath = () try: # empty if mounted under a path in mod_wsgi, for example path = request.path_info or '/' except KeyError: # if environ['PATH_INFO'] is just not there path = '/' except UnicodeDecodeError as e: raise URLDecodeError( e.encoding, e.object, e.start, e.end, e.reason ) if self.VH_ROOT_KEY in environ: # HTTP_X_VHM_ROOT vroot_path = decode_path_info(environ[self.VH_ROOT_KEY]) vroot_tuple = split_path_info(vroot_path) vpath = ( vroot_path + path ) # both will (must) be unicode or asciistr vroot_idx = len(vroot_tuple) - 1 else: vroot_tuple = () vpath = path vroot_idx = -1 root = self.root ob = vroot = root if vpath == '/': # invariant: vpath must not be empty # prevent a call to traversal_path if we know it's going # to return the empty tuple vpath_tuple = () else: # we do dead reckoning here via tuple slicing instead of # pushing and popping temporary lists for speed purposes # and this hurts readability; apologies i = 0 view_selector = self.VIEW_SELECTOR vpath_tuple = split_path_info(vpath) for segment in vpath_tuple: if segment[:2] == view_selector: return { 'context': ob, 'view_name': segment[2:], 'subpath': vpath_tuple[i + 1 :], 'traversed': vpath_tuple[: vroot_idx + i + 1], 'virtual_root': vroot, 'virtual_root_path': vroot_tuple, 'root': root, } try: getitem = ob.__getitem__ except AttributeError: return { 'context': ob, 'view_name': segment, 'subpath': vpath_tuple[i + 1 :], 'traversed': vpath_tuple[: vroot_idx + i + 1], 'virtual_root': vroot, 'virtual_root_path': vroot_tuple, 'root': root, } try: next = getitem(segment) except KeyError: return { 'context': ob, 'view_name': segment, 'subpath': vpath_tuple[i + 1 :], 'traversed': vpath_tuple[: vroot_idx + i + 1], 'virtual_root': vroot, 'virtual_root_path': vroot_tuple, 'root': root, } if i == vroot_idx: vroot = next ob = next i += 1 return { 'context': ob, 'view_name': '', 'subpath': subpath, 'traversed': vpath_tuple, 'virtual_root': vroot, 'virtual_root_path': vroot_tuple, 'root': root, }
def __init__(self, val, config): if is_nonstr_iter(val): self.val = tuple(val) else: val = tuple(filter(None, val.split('/'))) self.val = ('',) + val
def __init__(self, val, config): if is_nonstr_iter(val): self.val = set(val) else: self.val = set((val,))
def add_route( self, name, pattern=None, factory=None, for_=None, header=None, xhr=None, accept=None, path_info=None, request_method=None, request_param=None, traverse=None, custom_predicates=(), use_global_views=False, path=None, pregenerator=None, static=False, inherit_slash=None, **predicates ): """ Add a :term:`route configuration` to the current configuration state, as well as possibly a :term:`view configuration` to be used to specify a :term:`view callable` that will be invoked when this route matches. The arguments to this method are divided into *predicate*, *non-predicate*, and *view-related* types. :term:`Route predicate` arguments narrow the circumstances in which a route will be match a request; non-predicate arguments are informational. Non-Predicate Arguments name The name of the route, e.g. ``myroute``. This attribute is required. It must be unique among all defined routes in a given application. factory A Python object (often a function or a class) or a :term:`dotted Python name` which refers to the same object that will generate a :app:`Pyramid` root resource object when this route matches. For example, ``mypackage.resources.MyFactory``. If this argument is not specified, a default root factory will be used. See :ref:`the_resource_tree` for more information about root factories. traverse If you would like to cause the :term:`context` to be something other than the :term:`root` object when this route matches, you can spell a traversal pattern as the ``traverse`` argument. This traversal pattern will be used as the traversal path: traversal will begin at the root object implied by this route (either the global root, or the object returned by the ``factory`` associated with this route). The syntax of the ``traverse`` argument is the same as it is for ``pattern``. For example, if the ``pattern`` provided to ``add_route`` is ``articles/{article}/edit``, and the ``traverse`` argument provided to ``add_route`` is ``/{article}``, when a request comes in that causes the route to match in such a way that the ``article`` match value is ``'1'`` (when the request URI is ``/articles/1/edit``), the traversal path will be generated as ``/1``. This means that the root object's ``__getitem__`` will be called with the name ``'1'`` during the traversal phase. If the ``'1'`` object exists, it will become the :term:`context` of the request. :ref:`traversal_chapter` has more information about traversal. If the traversal path contains segment marker names which are not present in the ``pattern`` argument, a runtime error will occur. The ``traverse`` pattern should not contain segment markers that do not exist in the ``pattern`` argument. A similar combining of routing and traversal is available when a route is matched which contains a ``*traverse`` remainder marker in its pattern (see :ref:`using_traverse_in_a_route_pattern`). The ``traverse`` argument to add_route allows you to associate route patterns with an arbitrary traversal path without using a ``*traverse`` remainder marker; instead you can use other match information. Note that the ``traverse`` argument to ``add_route`` is ignored when attached to a route that has a ``*traverse`` remainder marker in its pattern. pregenerator This option should be a callable object that implements the :class:`pyramid.interfaces.IRoutePregenerator` interface. A :term:`pregenerator` is a callable called by the :meth:`pyramid.request.Request.route_url` function to augment or replace the arguments it is passed when generating a URL for the route. This is a feature not often used directly by applications, it is meant to be hooked by frameworks that use :app:`Pyramid` as a base. use_global_views When a request matches this route, and view lookup cannot find a view which has a ``route_name`` predicate argument that matches the route, try to fall back to using a view that otherwise matches the context, request, and view name (but which does not match the route_name predicate). static If ``static`` is ``True``, this route will never match an incoming request; it will only be useful for URL generation. By default, ``static`` is ``False``. See :ref:`static_route_narr`. .. versionadded:: 1.1 inherit_slash This argument can only be used when the ``pattern`` is an empty string (``''``). By default, the composed route pattern will always include a trailing slash, but this argument provides a way to opt-out if both, you (the developer invoking ``add_route``) and the integrator (the developer setting the :term:`route prefix`), agree that the pattern should not contain a trailing slash. For example: .. code-block:: python with config.route_prefix_context('/users'): config.add_route('users', '', inherit_slash=True) In this example, the resulting route pattern will be ``/users``. Alternatively, if the route prefix were ``/users/``, then the resulting route pattern would be ``/users/``. .. versionadded:: 2.0 Predicate Arguments pattern The pattern of the route e.g. ``ideas/{idea}``. This argument is required. See :ref:`route_pattern_syntax` for information about the syntax of route patterns. If the pattern doesn't match the current URL, route matching continues. .. note:: For backwards compatibility purposes (as of :app:`Pyramid` 1.0), a ``path`` keyword argument passed to this function will be used to represent the pattern value if the ``pattern`` argument is ``None``. If both ``path`` and ``pattern`` are passed, ``pattern`` wins. xhr This value should be either ``True`` or ``False``. If this value is specified and is ``True``, the :term:`request` must possess an ``HTTP_X_REQUESTED_WITH`` (aka ``X-Requested-With``) header for this route to match. This is useful for detecting AJAX requests issued from jQuery, Prototype and other Javascript libraries. If this predicate returns ``False``, route matching continues. request_method A string representing an HTTP method name, e.g. ``GET``, ``POST``, ``HEAD``, ``DELETE``, ``PUT`` or a tuple of elements containing HTTP method names. If this argument is not specified, this route will match if the request has *any* request method. If this predicate returns ``False``, route matching continues. .. versionchanged:: 1.2 The ability to pass a tuple of items as ``request_method``. Previous versions allowed only a string. path_info This value represents a regular expression pattern that will be tested against the ``PATH_INFO`` WSGI environment variable. If the regex matches, this predicate will return ``True``. If this predicate returns ``False``, route matching continues. request_param This value can be any string or an iterable of strings. A view declaration with this argument ensures that the associated route will only match when the request has a key in the ``request.params`` dictionary (an HTTP ``GET`` or ``POST`` variable) that has a name which matches the supplied value. If the value supplied as the argument has a ``=`` sign in it, e.g. ``request_param="foo=123"``, then the key (``foo``) must both exist in the ``request.params`` dictionary, and the value must match the right hand side of the expression (``123``) for the route to "match" the current request. If this predicate returns ``False``, route matching continues. header This argument represents an HTTP header name or a header name/value pair. If the argument contains a ``:`` (colon), it will be considered a name/value pair (e.g. ``User-Agent:Mozilla/.*`` or ``Host:localhost``). If the value contains a colon, the value portion should be a regular expression. If the value does not contain a colon, the entire value will be considered to be the header name (e.g. ``If-Modified-Since``). If the value evaluates to a header name only without a value, the header specified by the name must be present in the request for this predicate to be true. If the value evaluates to a header name/value pair, the header specified by the name must be present in the request *and* the regular expression specified as the value must match the header value. Whether or not the value represents a header name or a header name/value pair, the case of the header name is not significant. If this predicate returns ``False``, route matching continues. accept A :term:`media type` that will be matched against the ``Accept`` HTTP request header. If this value is specified, it may be a specific media type such as ``text/html``, or a list of the same. If the media type is acceptable by the ``Accept`` header of the request, or if the ``Accept`` header isn't set at all in the request, this predicate will match. If this does not match the ``Accept`` header of the request, route matching continues. If ``accept`` is not specified, the ``HTTP_ACCEPT`` HTTP header is not taken into consideration when deciding whether or not to select the route. Unlike the ``accept`` argument to :meth:`pyramid.config.Configurator.add_view`, this value is strictly a predicate and supports :func:`pyramid.config.not_`. .. versionchanged:: 1.10 Specifying a media range is deprecated due to changes in WebOb and ambiguities that occur when trying to match ranges against ranges in the ``Accept`` header. Support will be removed in :app:`Pyramid` 2.0. Use a list of specific media types to match more than one type. .. versionchanged:: 2.0 Removed support for media ranges. effective_principals If specified, this value should be a :term:`principal` identifier or a sequence of principal identifiers. If the :attr:`pyramid.request.Request.effective_principals` property indicates that every principal named in the argument list is present in the current request, this predicate will return True; otherwise it will return False. For example: ``effective_principals=pyramid.security.Authenticated`` or ``effective_principals=('fred', 'group:admins')``. .. versionadded:: 1.4a4 custom_predicates .. deprecated:: 1.5 This value should be a sequence of references to custom predicate callables. Use custom predicates when no set of predefined predicates does what you need. Custom predicates can be combined with predefined predicates as necessary. Each custom predicate callable should accept two arguments: ``info`` and ``request`` and should return either ``True`` or ``False`` after doing arbitrary evaluation of the info and/or the request. If all custom and non-custom predicate callables return ``True`` the associated route will be considered viable for a given request. If any predicate callable returns ``False``, route matching continues. Note that the value ``info`` passed to a custom route predicate is a dictionary containing matching information; see :ref:`custom_route_predicates` for more information about ``info``. predicates Pass a key/value pair here to use a third-party predicate registered via :meth:`pyramid.config.Configurator.add_route_predicate`. More than one key/value pair can be used at the same time. See :ref:`view_and_route_predicates` for more information about third-party predicates. .. versionadded:: 1.4 """ if custom_predicates: warnings.warn( ( 'The "custom_predicates" argument to ' 'Configurator.add_route is deprecated as of Pyramid 1.5. ' 'Use "config.add_route_predicate" and use the registered ' 'route predicate as a predicate argument to add_route ' 'instead. See "Adding A Third Party View, Route, or ' 'Subscriber Predicate" in the "Hooks" chapter of the ' 'documentation for more information.' ), DeprecationWarning, stacklevel=3, ) if accept is not None: if not is_nonstr_iter(accept): accept = [accept] accept = [ normalize_accept_offer(accept_option) for accept_option in accept ] # these are route predicates; if they do not match, the next route # in the routelist will be tried if request_method is not None: request_method = as_sorted_tuple(request_method) factory = self.maybe_dotted(factory) if pattern is None: pattern = path if pattern is None: raise ConfigurationError('"pattern" argument may not be None') if inherit_slash and pattern != '': raise ConfigurationError( '"inherit_slash" may only be used with an empty pattern' ) # check for an external route; an external route is one which is # is a full url (e.g. 'http://example.com/{id}') parsed = urlparse(pattern) external_url = pattern if parsed.hostname: pattern = parsed.path original_pregenerator = pregenerator def external_url_pregenerator(request, elements, kw): if '_app_url' in kw: raise ValueError( 'You cannot generate a path to an external route ' 'pattern via request.route_path nor pass an _app_url ' 'to request.route_url when generating a URL for an ' 'external route pattern (pattern was "%s") ' % (pattern,) ) if '_scheme' in kw: scheme = kw['_scheme'] elif parsed.scheme: scheme = parsed.scheme else: scheme = request.scheme kw['_app_url'] = '{0}://{1}'.format(scheme, parsed.netloc) if original_pregenerator: elements, kw = original_pregenerator(request, elements, kw) return elements, kw pregenerator = external_url_pregenerator static = True elif self.route_prefix: if pattern == '' and inherit_slash: pattern = self.route_prefix else: pattern = ( self.route_prefix.rstrip('/') + '/' + pattern.lstrip('/') ) mapper = self.get_routes_mapper() introspectables = [] intr = self.introspectable( 'routes', name, '%s (pattern: %r)' % (name, pattern), 'route' ) intr['name'] = name intr['pattern'] = pattern intr['factory'] = factory intr['xhr'] = xhr intr['request_methods'] = request_method intr['path_info'] = path_info intr['request_param'] = request_param intr['header'] = header intr['accept'] = accept intr['traverse'] = traverse intr['custom_predicates'] = custom_predicates intr['pregenerator'] = pregenerator intr['static'] = static intr['use_global_views'] = use_global_views if static is True: intr['external_url'] = external_url introspectables.append(intr) if factory: factory_intr = self.introspectable( 'root factories', name, self.object_description(factory), 'root factory', ) factory_intr['factory'] = factory factory_intr['route_name'] = name factory_intr.relate('routes', name) introspectables.append(factory_intr) def register_route_request_iface(): request_iface = self.registry.queryUtility( IRouteRequest, name=name ) if request_iface is None: if use_global_views: bases = (IRequest,) else: bases = () request_iface = route_request_iface(name, bases) self.registry.registerUtility( request_iface, IRouteRequest, name=name ) def register_connect(): pvals = predicates.copy() pvals.update( dict( xhr=xhr, request_method=request_method, path_info=path_info, request_param=request_param, header=header, accept=accept, traverse=traverse, custom=predvalseq(custom_predicates), ) ) predlist = self.get_predlist('route') _, preds, _ = predlist.make(self, **pvals) route = mapper.connect( name, pattern, factory, predicates=preds, pregenerator=pregenerator, static=static, ) intr['object'] = route return route # We have to connect routes in the order they were provided; # we can't use a phase to do that, because when the actions are # sorted, actions in the same phase lose relative ordering self.action(('route-connect', name), register_connect) # But IRouteRequest interfaces must be registered before we begin to # process view registrations (in phase 3) self.action( ('route', name), register_route_request_iface, order=PHASE2_CONFIG, introspectables=introspectables, )