Example #1
0
    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)
Example #2
0
    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)
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
 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
Example #8
0
 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
Example #10
0
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
Example #11
0
    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
        )
Example #12
0
    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
Example #13
0
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
Example #14
0
    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
Example #15
0
    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
        )
Example #16
0
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)
Example #17
0
 def __init__(self, values, config):
     if not is_nonstr_iter(values):
         values = (values,)
     self.values = values
Example #18
0
 def __init__(self, val, config):
     if is_nonstr_iter(val):
         self.val = set(val)
     else:
         self.val = set((val, ))
Example #19
0
 def __init__(self, val, config):
     if is_nonstr_iter(val):
         self.val = tuple(val)
     else:
         val = tuple(filter(None, val.split('/')))
         self.val = ('', ) + val
Example #20
0
 def __init__(self, values, config):
     if not is_nonstr_iter(values):
         values = (values, )
     self.values = values
Example #21
0
    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()
Example #22
0
    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()
Example #23
0
    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,
        )
Example #24
0
    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,
        }
Example #25
0
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)
Example #26
0
 def __init__(self, val, config):
     if is_nonstr_iter(val):
         self.val = tuple(val)
     else:
         val = tuple(filter(None, val.split('/')))
         self.val = ('',) + val
Example #27
0
 def __init__(self, val, config):
     if is_nonstr_iter(val):
         self.val = set(val)
     else:
         self.val = set((val,))
Example #28
0
    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,
        }
Example #29
0
    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,
        )