Пример #1
0
 def render_template(self, content, vars, filename=None):
     """ Return a bytestring representing a templated file based on the
     input (content) and the variable names defined (vars).  ``filename``
     is used for exception reporting."""
     # this method must not be named "template_renderer" fbo of extension
     # scaffolds that need to work under pyramid 1.2 and 1.3, and which
     # need to do "template_renderer =
     # staticmethod(paste_script_template_renderer)"
     content = native_(content, fsenc)
     try:
         return bytes_(
             substitute_double_braces(content, TypeMapper(vars)), fsenc)
     except Exception as e:
         _add_except(e, ' in file %s' % filename)
         raise
Пример #2
0
 def quote_path_segment(segment, safe=''):
     """ %s """ % quote_path_segment_doc
     # The bit of this code that deals with ``_segment_cache`` is an
     # optimization: we cache all the computation of URL path segments
     # in this module-scope dictionary with the original string (or
     # unicode value) as the key, so we can look it up later without
     # needing to reencode or re-url-quote it
     try:
         return _segment_cache[(segment, safe)]
     except KeyError:
         if segment.__class__ not in (text_type, binary_type):
             segment = str(segment)
         result = url_quote(native_(segment, 'utf-8'), safe)
         # we don't need a lock to mutate _segment_cache, as the below
         # will generate exactly one Python bytecode (STORE_SUBSCR)
         _segment_cache[(segment, safe)] = result
         return result
Пример #3
0
 def pre(self, command, output_dir, vars):
     """ Overrides :meth:`pyramid.scaffold.template.Template.pre`, adding
     several variables to the default variables list (including
     ``random_string``, and ``package_logger``).  It also prevents common
     misnamings (such as naming a package "site" or naming a package
     logger "root".
     """
     if vars["package"] == "site":
         raise ValueError(
             'Sorry, you may not name your package "site". '
             'The package name "site" has a special meaning in '
             'Python.  Please name it anything except "site".'
         )
     vars["random_string"] = native_(binascii.hexlify(os.urandom(20)))
     package_logger = vars["package"]
     if package_logger == "root":
         # Rename the app logger in the rare case a project is named 'root'
         package_logger = "app"
     vars["package_logger"] = package_logger
     return Template.pre(self, command, output_dir, vars)
Пример #4
0
 def matcher(path):
     # This function really wants to consume Unicode patterns natively,
     # but if someone passes us a bytestring, we allow it by converting it
     # to Unicode using the ASCII decoding.  We decode it using ASCII
     # because we don't want to accept bytestrings with high-order
     # characters in them here as we have no idea what the encoding
     # represents.
     if path.__class__ is not text_type:
         path = text_(path, 'ascii')
     m = match(path)
     if m is None:
         return None
     d = {}
     for k, v in m.groupdict().items():
         # k and v will be Unicode 2.6.4 and lower doesnt accept unicode
         # kwargs as **kw, so we explicitly cast the keys to native
         # strings in case someone wants to pass the result as **kw
         nk = native_(k, 'ascii')
         if k == remainder:
             d[nk] = split_path_info(v)
         else:
             d[nk] = v
     return d
Пример #5
0
def copy_dir(
    source,
    dest,
    vars,
    verbosity,
    simulate,
    indent=0,
    sub_vars=True,
    interactive=False,
    overwrite=True,
    template_renderer=None,
    out_=sys.stdout,
):
    """
    Copies the ``source`` directory to the ``dest`` directory.

    ``vars``: A dictionary of variables to use in any substitutions.

    ``verbosity``: Higher numbers will show more about what is happening.

    ``simulate``: If true, then don't actually *do* anything.

    ``indent``: Indent any messages by this amount.

    ``sub_vars``: If true, variables in ``_tmpl`` files and ``+var+``
    in filenames will be substituted.

    ``overwrite``: If false, then don't every overwrite anything.

    ``interactive``: If you are overwriting a file and interactive is
    true, then ask before overwriting.

    ``template_renderer``: This is a function for rendering templates (if you
    don't want to use string.Template).  It should have the signature
    ``template_renderer(content_as_string, vars_as_dict,
    filename=filename)``.
    """

    def out(msg):
        out_.write(msg)
        out_.write("\n")
        out_.flush()

        # This allows you to use a leading +dot+ in filenames which would

    # otherwise be skipped because leading dots make the file hidden:
    vars.setdefault("dot", ".")
    vars.setdefault("plus", "+")
    use_pkg_resources = isinstance(source, tuple)
    if use_pkg_resources:
        names = sorted(pkg_resources.resource_listdir(source[0], source[1]))
    else:
        names = sorted(os.listdir(source))
    pad = " " * (indent * 2)
    if not os.path.exists(dest):
        if verbosity >= 1:
            out("%sCreating %s/" % (pad, dest))
        if not simulate:
            makedirs(dest, verbosity=verbosity, pad=pad)
    elif verbosity >= 2:
        out("%sDirectory %s exists" % (pad, dest))
    for name in names:
        if use_pkg_resources:
            full = "/".join([source[1], name])
        else:
            full = os.path.join(source, name)
        reason = should_skip_file(name)
        if reason:
            if verbosity >= 2:
                reason = pad + reason % {"filename": full}
                out(reason)
            continue  # pragma: no cover
        if sub_vars:
            dest_full = os.path.join(dest, substitute_filename(name, vars))
        sub_file = False
        if dest_full.endswith("_tmpl"):
            dest_full = dest_full[:-5]
            sub_file = sub_vars
        if use_pkg_resources and pkg_resources.resource_isdir(source[0], full):
            if verbosity:
                out("%sRecursing into %s" % (pad, os.path.basename(full)))
            copy_dir(
                (source[0], full),
                dest_full,
                vars,
                verbosity,
                simulate,
                indent=indent + 1,
                sub_vars=sub_vars,
                interactive=interactive,
                template_renderer=template_renderer,
                out_=out_,
            )
            continue
        elif not use_pkg_resources and os.path.isdir(full):
            if verbosity:
                out("%sRecursing into %s" % (pad, os.path.basename(full)))
            copy_dir(
                full,
                dest_full,
                vars,
                verbosity,
                simulate,
                indent=indent + 1,
                sub_vars=sub_vars,
                interactive=interactive,
                template_renderer=template_renderer,
                out_=out_,
            )
            continue
        elif use_pkg_resources:
            content = pkg_resources.resource_string(source[0], full)
        else:
            f = open(full, "rb")
            content = f.read()
            f.close()
        if sub_file:
            try:
                content = substitute_content(content, vars, filename=full, template_renderer=template_renderer)
            except SkipTemplate:
                continue  # pragma: no cover
            if content is None:
                continue  # pragma: no cover
        already_exists = os.path.exists(dest_full)
        if already_exists:
            f = open(dest_full, "rb")
            old_content = f.read()
            f.close()
            if old_content == content:
                if verbosity:
                    out("%s%s already exists (same content)" % (pad, dest_full))
                continue  # pragma: no cover
            if interactive:
                if not query_interactive(
                    native_(full, fsenc),
                    native_(dest_full, fsenc),
                    native_(content, fsenc),
                    native_(old_content, fsenc),
                    simulate=simulate,
                    out_=out_,
                ):
                    continue
            elif not overwrite:
                continue  # pragma: no cover
        if verbosity and use_pkg_resources:
            out("%sCopying %s to %s" % (pad, full, dest_full))
        elif verbosity:
            out("%sCopying %s to %s" % (pad, os.path.basename(full), dest_full))
        if not simulate:
            f = open(dest_full, "wb")
            f.write(content)
            f.close()
Пример #6
0
    def route_url(self, route_name, *elements, **kw):
        """Generates a fully qualified URL for a named :app:`Pyramid`
        :term:`route configuration`.

        Use the route's ``name`` as the first positional argument.
        Additional positional arguments (``*elements``) are appended to the
        URL as path segments after it is generated.

        Use keyword arguments to supply values which match any dynamic
        path elements in the route definition.  Raises a :exc:`KeyError`
        exception if the URL cannot be generated for any reason (not
        enough arguments, for example).

        For example, if you've defined a route named "foobar" with the path
        ``{foo}/{bar}/*traverse``::

            request.route_url('foobar',
                               foo='1')             => <KeyError exception>
            request.route_url('foobar',
                               foo='1',
                               bar='2')             => <KeyError exception>
            request.route_url('foobar',
                               foo='1',
                               bar='2',
                               traverse=('a','b'))  => http://e.com/1/2/a/b
            request.route_url('foobar',
                               foo='1',
                               bar='2',
                               traverse='/a/b')     => http://e.com/1/2/a/b

        Values replacing ``:segment`` arguments can be passed as strings
        or Unicode objects.  They will be encoded to UTF-8 and URL-quoted
        before being placed into the generated URL.

        Values replacing ``*remainder`` arguments can be passed as strings
        *or* tuples of Unicode/string values.  If a tuple is passed as a
        ``*remainder`` replacement value, its values are URL-quoted and
        encoded to UTF-8.  The resulting strings are joined with slashes
        and rendered into the URL.  If a string is passed as a
        ``*remainder`` replacement value, it is tacked on to the URL
        after being URL-quoted-except-for-embedded-slashes.

        If a keyword argument ``_query`` is present, it will be used to
        compose a query string that will be tacked on to the end of the
        URL.  The value of ``_query`` must be a sequence of two-tuples
        *or* a data structure with an ``.items()`` method that returns a
        sequence of two-tuples (presumably a dictionary).  This data
        structure will be turned into a query string per the documentation
        of :func:`pyramid.encode.urlencode` function.  After the query
        data is turned into a query string, a leading ``?`` is prepended,
        and the resulting string is appended to the generated URL.

        .. note::

           Python data structures that are passed as ``_query`` which are
           sequences or dictionaries are turned into a string under the same
           rules as when run through :func:`urllib.urlencode` with the ``doseq``
           argument equal to ``True``.  This means that sequences can be passed
           as values, and a k=v pair will be placed into the query string for
           each value.

        If a keyword argument ``_anchor`` is present, its string
        representation will be used as a named anchor in the generated URL
        (e.g. if ``_anchor`` is passed as ``foo`` and the route URL is
        ``http://example.com/route/url``, the resulting generated URL will
        be ``http://example.com/route/url#foo``).

        .. note::

           If ``_anchor`` is passed as a string, it should be UTF-8 encoded. If
           ``_anchor`` is passed as a Unicode object, it will be converted to
           UTF-8 before being appended to the URL.  The anchor value is not
           quoted in any way before being appended to the generated URL.

        If both ``_anchor`` and ``_query`` are specified, the anchor
        element will always follow the query element,
        e.g. ``http://example.com?foo=1#bar``.

        If any of the keyword arguments ``_scheme``, ``_host``, or ``_port``
        is passed and is non-``None``, the provided value will replace the
        named portion in the generated URL.  For example, if you pass
        ``_host='foo.com'``, and the URL that would have been generated
        without the host replacement is ``http://example.com/a``, the result
        will be ``https://foo.com/a``.
        
        Note that if ``_scheme`` is passed as ``https``, and ``_port`` is not
        passed, the ``_port`` value is assumed to have been passed as
        ``443``.  Likewise, if ``_scheme`` is passed as ``http`` and
        ``_port`` is not passed, the ``_port`` value is assumed to have been
        passed as ``80``. To avoid this behavior, always explicitly pass
        ``_port`` whenever you pass ``_scheme``.

        If a keyword ``_app_url`` is present, it will be used as the
        protocol/hostname/port/leading path prefix of the generated URL.
        For example, using an ``_app_url`` of
        ``http://example.com:8080/foo`` would cause the URL
        ``http://example.com:8080/foo/fleeb/flub`` to be returned from
        this function if the expansion of the route pattern associated
        with the ``route_name`` expanded to ``/fleeb/flub``.  If
        ``_app_url`` is not specified, the result of
        ``request.application_url`` will be used as the prefix (the
        default).

        If both ``_app_url`` and any of ``_scheme``, ``_host``, or ``_port``
        are passed, ``_app_url`` takes precedence and any values passed for
        ``_scheme``, ``_host``, and ``_port`` will be ignored.

        This function raises a :exc:`KeyError` if the URL cannot be
        generated due to missing replacement names.  Extra replacement
        names are ignored.

        If the route object which matches the ``route_name`` argument has
        a :term:`pregenerator`, the ``*elements`` and ``**kw``
        arguments passed to this function might be augmented or changed.
        """
        try:
            reg = self.registry
        except AttributeError:
            reg = get_current_registry() # b/c
        mapper = reg.getUtility(IRoutesMapper)
        route = mapper.get_route(route_name)

        if route is None:
            raise KeyError('No such route named %s' % route_name)

        if route.pregenerator is not None:
            elements, kw = route.pregenerator(self, elements, kw)

        anchor = ''
        qs = ''
        app_url = None
        host = None
        scheme = None
        port = None

        if '_query' in kw:
            query = kw.pop('_query')
            if query:
                qs = '?' + urlencode(query, doseq=True)

        if '_anchor' in kw:
            anchor = kw.pop('_anchor')
            anchor = native_(anchor, 'utf-8')
            anchor = '#' + anchor

        if '_app_url' in kw:
            app_url = kw.pop('_app_url')

        if '_host' in kw:
            host = kw.pop('_host')

        if '_scheme' in kw:
            scheme = kw.pop('_scheme')

        if '_port' in kw:
            port = kw.pop('_port')

        if app_url is None:
            if (scheme is not None or host is not None or port is not None):
                app_url = self._partial_application_url(scheme, host, port)
            else:
                app_url = self.application_url

        path = route.generate(kw) # raises KeyError if generate fails

        if elements:
            suffix = _join_elements(elements)
            if not path.endswith('/'):
                suffix = '/' + suffix
        else:
            suffix = ''

        return app_url + path + suffix + qs + anchor
Пример #7
0
    def resource_url(self, resource, *elements, **kw):
        """

        Generate a string representing the absolute URL of the
        :term:`resource` object based on the ``wsgi.url_scheme``,
        ``HTTP_HOST`` or ``SERVER_NAME`` in the request, plus any
        ``SCRIPT_NAME``.  The overall result of this method is always a
        UTF-8 encoded string.

        Examples::

            request.resource_url(resource) =>

                                       http://example.com/

            request.resource_url(resource, 'a.html') =>

                                       http://example.com/a.html

            request.resource_url(resource, 'a.html', query={'q':'1'}) =>

                                       http://example.com/a.html?q=1

            request.resource_url(resource, 'a.html', anchor='abc') =>

                                       http://example.com/a.html#abc

            request.resource_url(resource, app_url='') =>

                                       /

        Any positional arguments passed in as ``elements`` must be strings
        Unicode objects, or integer objects.  These will be joined by slashes
        and appended to the generated resource URL.  Each of the elements
        passed in is URL-quoted before being appended; if any element is
        Unicode, it will converted to a UTF-8 bytestring before being
        URL-quoted. If any element is an integer, it will be converted to its
        string representation before being URL-quoted.

        .. warning:: if no ``elements`` arguments are specified, the resource
                     URL will end with a trailing slash.  If any
                     ``elements`` are used, the generated URL will *not*
                     end in trailing a slash.

        If a keyword argument ``query`` is present, it will be used to
        compose a query string that will be tacked on to the end of the URL.
        The value of ``query`` must be a sequence of two-tuples *or* a data
        structure with an ``.items()`` method that returns a sequence of
        two-tuples (presumably a dictionary).  This data structure will be
        turned into a query string per the documentation of
        ``pyramid.url.urlencode`` function.  After the query data is turned
        into a query string, a leading ``?`` is prepended, and the resulting
        string is appended to the generated URL.

        .. note::

           Python data structures that are passed as ``query`` which are
           sequences or dictionaries are turned into a string under the same
           rules as when run through :func:`urllib.urlencode` with the ``doseq``
           argument equal to ``True``.  This means that sequences can be passed
           as values, and a k=v pair will be placed into the query string for
           each value.

        If a keyword argument ``anchor`` is present, its string
        representation will be used as a named anchor in the generated URL
        (e.g. if ``anchor`` is passed as ``foo`` and the resource URL is
        ``http://example.com/resource/url``, the resulting generated URL will
        be ``http://example.com/resource/url#foo``).

        .. note::

           If ``anchor`` is passed as a string, it should be UTF-8 encoded. If
           ``anchor`` is passed as a Unicode object, it will be converted to
           UTF-8 before being appended to the URL.  The anchor value is not
           quoted in any way before being appended to the generated URL.

        If both ``anchor`` and ``query`` are specified, the anchor element
        will always follow the query element,
        e.g. ``http://example.com?foo=1#bar``.

        If any of the keyword arguments ``scheme``, ``host``, or ``port`` is
        passed and is non-``None``, the provided value will replace the named
        portion in the generated URL.  For example, if you pass
        ``host='foo.com'``, and the URL that would have been generated
        without the host replacement is ``http://example.com/a``, the result
        will be ``https://foo.com/a``.
        
        If ``scheme`` is passed as ``https``, and an explicit ``port`` is not
        passed, the ``port`` value is assumed to have been passed as ``443``.
        Likewise, if ``scheme`` is passed as ``http`` and ``port`` is not
        passed, the ``port`` value is assumed to have been passed as
        ``80``. To avoid this behavior, always explicitly pass ``port``
        whenever you pass ``scheme``.

        If a keyword argument ``app_url`` is passed and is not ``None``, it
        should be a string that will be used as the port/hostname/initial
        path portion of the generated URL instead of the default request
        application URL.  For example, if ``app_url='http://foo'``, then the
        resulting url of a resource that has a path of ``/baz/bar`` will be
        ``http://foo/baz/bar``.  If you want to generate completely relative
        URLs with no leading scheme, host, port, or initial path, you can
        pass ``app_url=''`.  Passing ``app_url=''` when the resource path is
        ``/baz/bar`` will return ``/baz/bar``.

        .. note::

           ``app_url`` is new as of Pyramid 1.3.

        If ``app_url`` is passed and any of ``scheme``, ``port``, or ``host``
        are also passed, ``app_url`` will take precedence and the values
        passed for ``scheme``, ``host``, and/or ``port`` will be ignored.

        If the ``resource`` passed in has a ``__resource_url__`` method, it
        will be used to generate the URL (scheme, host, port, path) that for
        the base resource which is operated upon by this function.  See also
        :ref:`overriding_resource_url_generation`.

        .. note::

           If the :term:`resource` used is the result of a :term:`traversal`, it
           must be :term:`location`-aware.  The resource can also be the context
           of a :term:`URL dispatch`; contexts found this way do not need to be
           location-aware.

        .. note::

           If a 'virtual root path' is present in the request environment (the
           value of the WSGI environ key ``HTTP_X_VHM_ROOT``), and the resource
           was obtained via :term:`traversal`, the URL path will not include the
           virtual root prefix (it will be stripped off the left hand side of
           the generated URL).

        .. note::

           For backwards compatibility purposes, this method is also
           aliased as the ``model_url`` method of request.
        """
        try:
            reg = self.registry
        except AttributeError:
            reg = get_current_registry() # b/c

        url_adapter = reg.queryMultiAdapter((resource, self), IResourceURL)
        if url_adapter is None:
            url_adapter = ResourceURL(resource, self)

        virtual_path = getattr(url_adapter, 'virtual_path', None)

        if virtual_path is None:
            # old-style IContextURL adapter (Pyramid 1.2 and previous)
            warnings.warn(
                'Pyramid is using an IContextURL adapter to generate a '
                'resource URL; any "app_url", "host", "port", or "scheme" '
                'arguments passed to resource_url are being ignored.  To '
                'avoid this behavior, as of Pyramid 1.3, register an '
                'IResourceURL adapter instead of an IContextURL '
                'adapter for the resource type(s).  IContextURL adapters '
                'will be ignored in a later major release of Pyramid.',
                DeprecationWarning,
                2)

            resource_url = url_adapter()

        else:
            # newer-style IResourceURL adapter (Pyramid 1.3 and after)
            app_url = None
            scheme = None
            host = None
            port = None

            if 'app_url' in kw:
                app_url = kw['app_url']

            if 'scheme' in kw:
                scheme = kw['scheme']

            if 'host' in kw:
                host = kw['host']

            if 'port' in kw:
                port = kw['port']

            if app_url is None:
                if scheme or host or port:
                    app_url = self._partial_application_url(scheme, host, port)
                else:
                    app_url = self.application_url

            resource_url = None
            local_url = getattr(resource, '__resource_url__', None)

            if local_url is not None:
                # the resource handles its own url generation
                d = dict(
                    virtual_path=virtual_path,
                    physical_path=url_adapter.physical_path,
                    app_url=app_url,
                )
                # allow __resource_url__ to punt by returning None
                resource_url = local_url(self, d)

            if resource_url is None:
                # the resource did not handle its own url generation or the
                # __resource_url__ function returned None
                resource_url = app_url + virtual_path

        qs = ''
        anchor = ''

        if 'query' in kw:
            query = kw['query']
            if query:
                qs = '?' + urlencode(query, doseq=True)

        if 'anchor' in kw:
            anchor = kw['anchor']
            if isinstance(anchor, text_type):
                anchor = native_(anchor, 'utf-8')
            anchor = '#' + anchor

        if elements:
            suffix = _join_elements(elements)
        else:
            suffix = ''

        return resource_url + suffix + qs + anchor
Пример #8
0
def _compile_route(route):
    # This function really wants to consume Unicode patterns natively, but if
    # someone passes us a bytestring, we allow it by converting it to Unicode
    # using the ASCII decoding.  We decode it using ASCII because we don't
    # want to accept bytestrings with high-order characters in them here as
    # we have no idea what the encoding represents.
    if route.__class__ is not text_type:
        try:
            route = text_(route, 'ascii')
        except UnicodeDecodeError:
            raise ValueError(
                'The pattern value passed to add_route must be '
                'either a Unicode string or a plain string without '
                'any non-ASCII characters (you provided %r).' % route)

    if old_route_re.search(route) and not route_re.search(route):
        route = old_route_re.sub(update_pattern, route)

    if not route.startswith('/'):
        route = '/' + route

    remainder = None
    if star_at_end.search(route):
        route, remainder = route.rsplit('*', 1)

    pat = route_re.split(route)

    # every element in "pat" will be Unicode (regardless of whether the
    # route_re regex pattern is itself Unicode or str)
    pat.reverse()
    rpat = []
    gen = []
    prefix = pat.pop() # invar: always at least one element (route='/'+route)

    # We want to generate URL-encoded URLs, so we url-quote the prefix, being
    # careful not to quote any embedded slashes.  We have to replace '%' with
    # '%%' afterwards, as the strings that go into "gen" are used as string
    # replacement targets.
    gen.append(quote_path_segment(prefix, safe='/').replace('%', '%%')) # native
    rpat.append(re.escape(prefix)) # unicode

    while pat:
        name = pat.pop() # unicode
        name = name[1:-1]
        if ':' in name:
            # reg may contain colons as well,
            # so we must strictly split name into two parts
            name, reg = name.split(':', 1)
        else:
            reg = '[^/]+'
        gen.append('%%(%s)s' % native_(name)) # native
        name = '(?P<%s>%s)' % (name, reg) # unicode
        rpat.append(name)
        s = pat.pop() # unicode
        if s:
            rpat.append(re.escape(s)) # unicode
            # We want to generate URL-encoded URLs, so we url-quote this
            # literal in the pattern, being careful not to quote the embedded
            # slashes.  We have to replace '%' with '%%' afterwards, as the
            # strings that go into "gen" are used as string replacement
            # targets.  What is appended to gen is a native string.
            gen.append(quote_path_segment(s, safe='/').replace('%', '%%'))

    if remainder:
        rpat.append('(?P<%s>.*?)' % remainder) # unicode
        gen.append('%%(%s)s' % native_(remainder)) # native

    pattern = ''.join(rpat) + '$' # unicode

    match = re.compile(pattern).match

    def matcher(path):
        # This function really wants to consume Unicode patterns natively,
        # but if someone passes us a bytestring, we allow it by converting it
        # to Unicode using the ASCII decoding.  We decode it using ASCII
        # because we don't want to accept bytestrings with high-order
        # characters in them here as we have no idea what the encoding
        # represents.
        if path.__class__ is not text_type:
            path = text_(path, 'ascii')
        m = match(path)
        if m is None:
            return None
        d = {}
        for k, v in m.groupdict().items():
            # k and v will be Unicode 2.6.4 and lower doesnt accept unicode
            # kwargs as **kw, so we explicitly cast the keys to native
            # strings in case someone wants to pass the result as **kw
            nk = native_(k, 'ascii')
            if k == remainder:
                d[nk] = split_path_info(v)
            else:
                d[nk] = v
        return d

    gen = ''.join(gen)

    def generator(dict):
        newdict = {}
        for k, v in dict.items():
            if PY3: # pragma: no cover
                if v.__class__ is binary_type:
                    # url_quote below needs a native string, not bytes on Py3
                    v = v.decode('utf-8')
            else:
                if v.__class__ is text_type:
                    # url_quote below needs bytes, not unicode on Py2
                    v = v.encode('utf-8')

            if k == remainder:
                # a stararg argument
                if is_nonstr_iter(v):
                    v = '/'.join([quote_path_segment(x) for x in v]) # native
                else:
                    if v.__class__ not in string_types:
                        v = str(v)
                    v = quote_path_segment(v, safe='/')
            else:
                if v.__class__ not in string_types:
                    v = str(v)
                    # v may be bytes (py2) or native string (py3)
                v = quote_path_segment(v)

            # at this point, the value will be a native string
            newdict[k] = v

        result = gen % newdict # native string result
        return result

    return matcher, generator