Chris McDonough
2012-02-17 c51896756eeffc7e8c50ad71300ec355ae47465a
Features
--------

- Add ``pyramid.config.Configurator.add_resource_url_adapter`` API method.
See the Hooks narrative documentation section entitled "Changing How
pyramid.request.Request.resource_url Generates a URL" for more information.
This is not a new feature, it just provides an API for adding a resource
url adapter without needing to use the ZCA API.

- A new interface was added: ``pyramid.interfaces.IResourceURL``. An adapter
implementing its interface can be used to override resource URL generation
when ``request.resource_url`` is called. This interface replaces the
now-deprecated ``pyramid.interfaces.IContextURL`` interface.

- The dictionary passed to a resource's ``__resource_url__`` method (see
"Overriding Resource URL Generation" in the "Resources" chapter) now
contains an ``app_url`` key, representing the application URL generated
during ``request.resource_url``. It represents a potentially customized
URL prefix, containing potentially custom scheme, host and port information
passed by the user to ``request.resource_url``. It should be used instead
of ``request.application_url`` where necessary.

- The ``request.resource_url`` API now accepts these arguments: ``app_url``,
``scheme``, ``host``, and ``port``. The app_url argument can be used to
replace the URL prefix wholesale during url generation. The ``scheme``,
``host``, and ``port`` arguments can be used to replace the respective
default values of ``request.application_url`` partially.

- A new API named ``request.resource_path`` now exists. It works like
``request.resource_url`` but produces a relative URL rather than an
absolute one.

- The ``request.route_url`` API now accepts these arguments: ``_app_url``,
``_scheme``, ``_host``, and ``_port``. The ``_app_url`` argument can be
used to replace the URL prefix wholesale during url generation. The
``_scheme``, ``_host``, and ``_port`` arguments can be used to replace the
respective default values of ``request.application_url`` partially.

Backwards Incompatibilities
---------------------------

- The ``pyramid.interfaces.IContextURL`` interface has been deprecated.
People have been instructed to use this to register a resource url adapter
in the "Hooks" chapter to use to influence ``request.resource_url`` URL
generation for resources found via custom traversers since Pyramid 1.0.

The interface still exists and registering such an adapter still works, but
this interface will be removed from the software after a few major Pyramid
releases. You should replace it with an equivalent
``pyramid.interfaces.IResourceURL`` adapter, registered using the new
``pyramid.config.Configurator.add_resource_url_adapter`` API. A
deprecation warning is now emitted when a
``pyramid.interfaces.IContextURL`` adapter is found when
``request.resource_url`` is called.

Misc
----

- Change ``set_traverser`` API name to ``add_traverser``.

Ref #438.
13 files modified
1065 ■■■■ changed files
CHANGES.txt 54 ●●●●● patch | view | raw | blame | history
docs/api/config.rst 2 ●●● patch | view | raw | blame | history
docs/api/interfaces.rst 4 ●●●● patch | view | raw | blame | history
docs/api/request.rst 2 ●●●●● patch | view | raw | blame | history
docs/narr/hooks.rst 59 ●●●● patch | view | raw | blame | history
docs/whatsnew-1.3.rst 56 ●●●●● patch | view | raw | blame | history
pyramid/config/factories.py 77 ●●●●● patch | view | raw | blame | history
pyramid/interfaces.py 43 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_config/test_factories.py 86 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_request.py 25 ●●●●● patch | view | raw | blame | history
pyramid/tests/test_url.py 365 ●●●●● patch | view | raw | blame | history
pyramid/traversal.py 66 ●●●● patch | view | raw | blame | history
pyramid/url.py 226 ●●●●● patch | view | raw | blame | history
CHANGES.txt
@@ -23,10 +23,16 @@
  something like "AttributeError: 'NoneType' object has no attribute
  'rfind'".
- Add ``pyramid.config.Configurator.set_traverser`` API method.  See the
- Add ``pyramid.config.Configurator.add_traverser`` API method.  See the
  Hooks narrative documentation section entitled "Changing the Traverser" for
  more information.  This is not a new feature, it just provides an API for
  adding a traverser without needing to use the ZCA API.
- Add ``pyramid.config.Configurator.add_resource_url_adapter`` API method.
  See the Hooks narrative documentation section entitled "Changing How
  pyramid.request.Request.resource_url Generates a URL" for more information.
  This is not a new feature, it just provides an API for adding a resource
  url adapter without needing to use the ZCA API.
- The system value ``req`` is now supplied to renderers as an alias for
  ``request``.  This means that you can now, for example, in a template, do
@@ -35,6 +41,52 @@
  methods and attributes from within templates.  The value ``request`` is
  still available too, this is just an alternative.
- A new interface was added: ``pyramid.interfaces.IResourceURL``.  An adapter
  implementing its interface can be used to override resource URL generation
  when ``request.resource_url`` is called.  This interface replaces the
  now-deprecated ``pyramid.interfaces.IContextURL`` interface.
- The dictionary passed to a resource's ``__resource_url__`` method (see
  "Overriding Resource URL Generation" in the "Resources" chapter) now
  contains an ``app_url`` key, representing the application URL generated
  during ``request.resource_url``.  It represents a potentially customized
  URL prefix, containing potentially custom scheme, host and port information
  passed by the user to ``request.resource_url``.  It should be used instead
  of ``request.application_url`` where necessary.
- The ``request.resource_url`` API now accepts these arguments: ``app_url``,
  ``scheme``, ``host``, and ``port``.  The app_url argument can be used to
  replace the URL prefix wholesale during url generation.  The ``scheme``,
  ``host``, and ``port`` arguments can be used to replace the respective
  default values of ``request.application_url`` partially.
- A new API named ``request.resource_path`` now exists.  It works like
  ``request.resource_url`` but produces a relative URL rather than an
  absolute one.
- The ``request.route_url`` API now accepts these arguments: ``_app_url``,
  ``_scheme``, ``_host``, and ``_port``.  The ``_app_url`` argument can be
  used to replace the URL prefix wholesale during url generation.  The
  ``_scheme``, ``_host``, and ``_port`` arguments can be used to replace the
  respective default values of ``request.application_url`` partially.
Backwards Incompatibilities
---------------------------
- The ``pyramid.interfaces.IContextURL`` interface has been deprecated.
  People have been instructed to use this to register a resource url adapter
  in the "Hooks" chapter to use to influence ``request.resource_url`` URL
  generation for resources found via custom traversers since Pyramid 1.0.
  The interface still exists and registering such an adapter still works, but
  this interface will be removed from the software after a few major Pyramid
  releases.  You should replace it with an equivalent
  ``pyramid.interfaces.IResourceURL`` adapter, registered using the new
  ``pyramid.config.Configurator.add_resource_url_adapter`` API.  A
  deprecation warning is now emitted when a
  ``pyramid.interfaces.IContextURL`` adapter is found when
  ``request.resource_url`` is called.
Documentation
-------------
docs/api/config.rst
@@ -94,7 +94,7 @@
     .. automethod:: set_notfound_view
     .. automethod:: set_traverser
     .. automethod:: add_traverser
     .. automethod:: set_renderer_globals_factory(factory)
docs/api/interfaces.rst
@@ -79,3 +79,7 @@
  .. autointerface:: IAssetDescriptor
     :members:
  .. autointerface:: IResourceURL
     :members:
docs/api/request.rst
@@ -183,6 +183,8 @@
   .. automethod:: resource_url
   .. automethod:: resource_path
   .. attribute::  response_*
      In Pyramid 1.0, you could set attributes on a
docs/narr/hooks.rst
@@ -479,58 +479,55 @@
often convenient to continue to use the
:meth:`pyramid.request.Request.resource_url` API.  However, since the way
traversal is done will have been modified, the URLs it generates by default
may be incorrect.
may be incorrect when used against resources derived from your custom
traverser.
If you've added a traverser, you can change how
:meth:`~pyramid.request.Request.resource_url` generates a URL for a specific
type of resource by adding a registerAdapter call for
:class:`pyramid.interfaces.IContextURL` to your application:
type of resource by adding a call to
:meth:`pyramid.config.add_resource_url_adapter`.
For example:
.. code-block:: python
   :linenos:
   from pyramid.interfaces import ITraverser
   from zope.interface import Interface
   from myapp.traversal import URLGenerator
   from myapp.traversal import ResourceURLAdapter
   from myapp.resources import MyRoot
   config.registry.registerAdapter(URLGenerator, (MyRoot, Interface),
                                   IContextURL)
   config.add_resource_url_adapter(ResourceURLAdapter, resource_iface=MyRoot)
In the above example, the ``myapp.traversal.URLGenerator`` class will be used
to provide services to :meth:`~pyramid.request.Request.resource_url` any time
the :term:`context` passed to ``resource_url`` is of class
``myapp.resources.MyRoot``.  The second argument in the ``(MyRoot,
Interface)`` tuple represents the type of interface that must be possessed by
the :term:`request` (in this case, any interface, represented by
``zope.interface.Interface``).
In the above example, the ``myapp.traversal.ResourceURLAdapter`` class will
be used to provide services to :meth:`~pyramid.request.Request.resource_url`
any time the :term:`resource` passed to ``resource_url`` is of the class
``myapp.resources.MyRoot``.  The ``resource_iface`` argument ``MyRoot``
represents the type of interface that must be possessed by the resource for
this resource url factory to be found.  If the ``resource_iface`` argument is
omitted, this resource url adapter will be used for *all* resources.
The API that must be implemented by a class that provides
:class:`~pyramid.interfaces.IContextURL` is as follows:
The API that must be implemented by your a class that provides
:class:`~pyramid.interfaces.IResourceURL` is as follows:
.. code-block:: python
  :linenos:
  from zope.interface import Interface
  class IContextURL(Interface):
      """ An adapter which deals with URLs related to a context.
  class MyResourceURL(object):
      """ An adapter which provides the virtual and physical paths of a
          resource
      """
      def __init__(self, context, request):
          """ Accept the context and request """
      def virtual_root(self):
          """ Return the virtual root object related to a request and the
          current context"""
      def __call__(self):
          """ Return a URL that points to the context """
      def __init__(self, resource, request):
          """ Accept the resource and request and set self.physical_path and
          self.virtual_path"""
          self.virtual_path =  some_function_of(resource, request)
          self.physical_path =  some_other_function_of(resource, request)
The default context URL generator is available for perusal as the class
:class:`pyramid.traversal.TraversalContextURL` in the `traversal module
:class:`pyramid.traversal.ResourceURL` in the `traversal module
<http://github.com/Pylons/pyramid/blob/master/pyramid/traversal.py>`_ of the
:term:`Pylons` GitHub Pyramid repository.
See :meth:`pyramid.config.add_resource_url_adapter` for more information.
.. index::
   single: IResponse
   single: special view responses
docs/whatsnew-1.3.rst
@@ -260,16 +260,21 @@
  http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for
  more information about how to use the ``ignore`` argument to ``scan``.
- Add :meth:`pyramid.config.Configurator.set_traverser` API method.  See
- Add :meth:`pyramid.config.Configurator.add_traverser` API method.  See
  :ref:`changing_the_traverser` for more information.  This is not a new
  feature, it just provides an API for adding a traverser without needing to
  use the ZCA API.
- Add :meth:`pyramid.config.Configurator.add_resource_url_adapter` API
  method.  See :ref:`changing_resource_url` for more information.  This is
  not a new feature, it just provides an API for adding a resource url
  adapter without needing to use the ZCA API.
- The :meth:`pyramid.config.Configurator.scan` method can now be passed an
  ``ignore`` argument, which can be a string, a callable, or a list
  consisting of strings and/or callables.  This feature allows submodules,
  subpackages, and global objects from being scanned.  See
  http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for
   http://readthedocs.org/docs/venusian/en/latest/#ignore-scan-argument for
  more information about how to use the ``ignore`` argument to ``scan``.
- Better error messages when a view callable returns a value that cannot be
@@ -290,6 +295,38 @@
  purely a change to reduce the amount of typing required to use request
  methods and attributes from within templates.  The value ``request`` is
  still available too, this is just an alternative.
- A new interface was added: :class:`pyramid.interfaces.IResourceURL`.  An
  adapter implementing its interface can be used to override resource URL
  generation when :meth:`pyramid.request.Request.resource_url` is called.
  This interface replaces the now-deprecated
  ``pyramid.interfaces.IContextURL`` interface.
- The dictionary passed to a resource's ``__resource_url__`` method (see
  :ref:`overriding_resource_url_generation`) now contains an ``app_url`` key,
  representing the application URL generated during
  :meth:`pyramid.request.Request.resource_url`.  It represents a potentially
  customized URL prefix, containing potentially custom scheme, host and port
  information passed by the user to ``request.resource_url``.  It should be
  used instead of ``request.application_url`` where necessary.
- The :meth:`pyramid.request.Request.resource_url` API now accepts these
  arguments: ``app_url``, ``scheme``, ``host``, and ``port``.  The app_url
  argument can be used to replace the URL prefix wholesale during url
  generation.  The ``scheme``, ``host``, and ``port`` arguments can be used
  to replace the respective default values of ``request.application_url``
  partially.
- A new API named :meth:`pyramid.request.Request.resource_path` now exists.
  It works like :meth:`pyramid.request.Request.resource_url`` but produces a
  relative URL rather than an absolute one.
- The :meth:`pyramid.request.Request.route_url` API now accepts these
  arguments: ``_app_url``, ``_scheme``, ``_host``, and ``_port``.  The
  ``_app_url`` argument can be used to replace the URL prefix wholesale
  during url generation.  The ``_scheme``, ``_host``, and ``_port`` arguments
  can be used to replace the respective default values of
  ``request.application_url`` partially.
Backwards Incompatibilities
---------------------------
@@ -360,6 +397,21 @@
  no negative affect because the implementation was broken for dict-based
  arguments.
- The ``pyramid.interfaces.IContextURL`` interface has been deprecated.
  People have been instructed to use this to register a resource url adapter
  in the "Hooks" chapter to use to influence
  :meth:`pyramid.request.Request.resource_url` URL generation for resources
  found via custom traversers since Pyramid 1.0.
  The interface still exists and registering such an adapter still works, but
  this interface will be removed from the software after a few major Pyramid
  releases.  You should replace it with an equivalent
  :class:`pyramid.interfaces.IResourceURL` adapter, registered using the new
  :meth:`pyramid.config.Configurator.add_resource_url_adapter` API.  A
  deprecation warning is now emitted when a
  ``pyramid.interfaces.IContextURL`` adapter is found when
  :meth:`pyramid.request.Request.resource_url` is called.
Documentation Enhancements
--------------------------
pyramid/config/factories.py
@@ -10,6 +10,7 @@
    IRootFactory,
    ISessionFactory,
    ITraverser,
    IResourceURL,
    )
from pyramid.traversal import DefaultRootFactory
@@ -143,7 +144,8 @@
        self.action(('request properties', name), register,
                    introspectables=(intr,))
    def set_traverser(self, factory, iface=None):
    @action_method
    def add_traverser(self, factory, iface=None):
        """
        The superdefault :term:`traversal` algorithm that :app:`Pyramid` uses
        is explained in :ref:`traversal_algorithm`.  Though it is rarely
@@ -158,7 +160,7 @@
        .. code-block:: python
           from myapp.traversal import MyCustomTraverser
           config.set_traverser(MyCustomTraverser)
           config.add_traverser(MyCustomTraverser)
        This would cause the Pyramid superdefault traverser to never be used;
        intead all traversal would be done using your ``MyCustomTraverser``
@@ -186,7 +188,7 @@
        .. code-block:: python
           config.set_traverser(MyCustomTraverser, MyRootClass)
           config.add_traverser(MyCustomTraverser, MyRootClass)
        When more than one traverser is active, the "most specific" traverser
        will be used (the one that matches the class or interface of the
@@ -212,7 +214,74 @@
            )
        intr['factory'] = factory
        intr['iface'] = iface
        self.action(('traverser', iface), register, introspectables=(intr,))
        self.action(discriminator, register, introspectables=(intr,))
    @action_method
    def add_resource_url_adapter(self, factory, resource_iface=None,
                                 request_iface=None):
        """
        When you add a traverser as described in
        :ref:`changing_the_traverser`, it's convenient to continue to use the
        :meth:`pyramid.request.Request.resource_url` API.  However, since the
        way traversal is done may have been modified, the URLs that
        ``resource_url`` generates by default may be incorrect when resources
        are returned by a custom traverser.
        If you've added a traverser, you can change how
        :meth:`~pyramid.request.Request.resource_url` generates a URL for a
        specific type of resource by calling this method.
        The ``factory`` argument represents a class that implements the
        :class:`~pyramid.interfaces.IResourceURL` interface.  The class
        constructor should accept two arguments in its constructor (the
        resource and the request) and the resulting instance should provide
        the attributes detailed in that interface (``virtual_path`` and
        ``physical_path``, in particular).
        The ``resource_iface`` argument represents a class or interface that
        the resource should possess for this url adapter to be used when
        :meth:`pyramid.request.Request.resource_url` looks up a resource url
        adapter.  If ``resource_iface`` is not passed, or it is passed as
        ``None``, the adapter will be used for every type of resource.
        The ``request_iface`` argument represents a class or interface that
        the request should possess for this url adapter to be used when
        :meth:`pyramid.request.Request.resource_url` looks up a resource url
        adapter.  If ``request_iface`` is not epassed, or it is passed as
        ``None``, the adapter will be used for every type of request.
        See :ref:`changing_resource_url` for more information.
        .. note::
           This API is new in Pyramid 1.3.
        """
        factory = self.maybe_dotted(factory)
        resource_iface = self.maybe_dotted(resource_iface)
        request_iface = self.maybe_dotted(request_iface)
        def register(resource_iface=resource_iface,
                     request_iface=request_iface):
            if resource_iface is None:
                resource_iface = Interface
            if request_iface is None:
                request_iface = Interface
            self.registry.registerAdapter(
                factory,
                (resource_iface, request_iface),
                IResourceURL,
                )
        discriminator = ('resource url adapter', resource_iface, request_iface)
        intr = self.introspectable(
            'resource url adapters',
            discriminator,
            'resource url adapter for resource iface %r, request_iface %r' % (
                resource_iface, request_iface),
            'resource url adapter',
            )
        intr['factory'] = factory
        intr['resource_iface'] = resource_iface
        intr['request_iface'] = request_iface
        self.action(discriminator, register, introspectables=(intr,))
def _set_request_properties(event):
    request = event.request
pyramid/interfaces.py
@@ -731,15 +731,54 @@
        ``match`` key will be the matchdict or ``None`` if no route
        matched.  Static routes will not be considered for matching.  """
class IContextURL(Interface):
class IResourceURL(Interface):
    virtual_path = Attribute('The virtual url path of the resource.')
    physical_path = Attribute('The physical url path of the resource.')
class IContextURL(IResourceURL):
    """ An adapter which deals with URLs related to a context.
    ..warning::
      This interface is deprecated as of Pyramid 1.3 with the introduction of
      IResourceURL.
    """
    # this class subclasses IResourceURL because request.resource_url looks
    # for IResourceURL via queryAdapter.  queryAdapter will find a deprecated
    # IContextURL registration if no registration for IResourceURL exists.
    # In reality, however, IContextURL objects were never required to have
    # the virtual_path or physical_path attributes spelled in IResourceURL.
    # The inheritance relationship is purely to benefit adapter lookup,
    # not to imply an inheritance relationship of interface attributes
    # and methods.
    #
    # Mechanics:
    #
    # class Fudge(object):
    #     def __init__(self, one, two):
    #         print one, two
    # class Another(object):
    #     def __init__(self, one, two):
    #         print one, two
    # ob = object()
    # r.registerAdapter(Fudge, (Interface, Interface), IContextURL)
    # print r.queryMultiAdapter((ob, ob), IResourceURL)
    # r.registerAdapter(Another, (Interface, Interface), IResourceURL)
    # print r.queryMultiAdapter((ob, ob), IResourceURL)
    #
    # prints
    #
    # <object object at 0x7fa678f3e2a0> <object object at 0x7fa678f3e2a0>
    # <__main__.Fudge object at 0x1cda890>
    # <object object at 0x7fa678f3e2a0> <object object at 0x7fa678f3e2a0>
    # <__main__.Another object at 0x1cda850>
    def virtual_root():
        """ Return the virtual root related to a request and the
        current context"""
    def __call__():
        """ Return a URL that points to the context """
        """ Return a URL that points to the context. """
class IPackageOverrides(Interface):
    """ Utility for pkg_resources overrides """
pyramid/tests/test_config/test_factories.py
@@ -129,10 +129,10 @@
        self.assertEqual(callables, [('foo', foo, False),
                                     ('bar', foo, True)])
    def test_set_traverser_dotted_names(self):
    def test_add_traverser_dotted_names(self):
        from pyramid.interfaces import ITraverser
        config = self._makeOne(autocommit=True)
        config.set_traverser(
        config.add_traverser(
            'pyramid.tests.test_config.test_factories.DummyTraverser',
            'pyramid.tests.test_config.test_factories.DummyIface')
        iface = DummyIface()
@@ -140,25 +140,25 @@
        self.assertEqual(traverser.__class__, DummyTraverser)
        self.assertEqual(traverser.root, iface)
    def test_set_traverser_default_iface_means_Interface(self):
    def test_add_traverser_default_iface_means_Interface(self):
        from pyramid.interfaces import ITraverser
        config = self._makeOne(autocommit=True)
        config.set_traverser(DummyTraverser)
        config.add_traverser(DummyTraverser)
        traverser = config.registry.getAdapter(None, ITraverser)
        self.assertEqual(traverser.__class__, DummyTraverser)
    def test_set_traverser_nondefault_iface(self):
    def test_add_traverser_nondefault_iface(self):
        from pyramid.interfaces import ITraverser
        config = self._makeOne(autocommit=True)
        config.set_traverser(DummyTraverser, DummyIface)
        config.add_traverser(DummyTraverser, DummyIface)
        iface = DummyIface()
        traverser = config.registry.getAdapter(iface, ITraverser)
        self.assertEqual(traverser.__class__, DummyTraverser)
        self.assertEqual(traverser.root, iface)
        
    def test_set_traverser_introspectables(self):
    def test_add_traverser_introspectables(self):
        config = self._makeOne()
        config.set_traverser(DummyTraverser, DummyIface)
        config.add_traverser(DummyTraverser, DummyIface)
        actions = config.action_state.actions
        self.assertEqual(len(actions), 1)
        intrs  = actions[0]['introspectables']
@@ -169,6 +169,70 @@
        self.assertEqual(intr.category_name, 'traversers')
        self.assertEqual(intr.title, 'traverser for %r' % DummyIface)
    def test_add_resource_url_adapter_dotted_names(self):
        from pyramid.interfaces import IResourceURL
        config = self._makeOne(autocommit=True)
        config.add_resource_url_adapter(
            'pyramid.tests.test_config.test_factories.DummyResourceURL',
            'pyramid.tests.test_config.test_factories.DummyIface',
            'pyramid.tests.test_config.test_factories.DummyIface',
            )
        iface = DummyIface()
        adapter = config.registry.getMultiAdapter((iface, iface),
                                                    IResourceURL)
        self.assertEqual(adapter.__class__, DummyResourceURL)
        self.assertEqual(adapter.resource, iface)
        self.assertEqual(adapter.request, iface)
    def test_add_resource_url_default_interfaces_mean_Interface(self):
        from pyramid.interfaces import IResourceURL
        config = self._makeOne(autocommit=True)
        config.add_resource_url_adapter(DummyResourceURL)
        iface = DummyIface()
        adapter = config.registry.getMultiAdapter((iface, iface),
                                                    IResourceURL)
        self.assertEqual(adapter.__class__, DummyResourceURL)
        self.assertEqual(adapter.resource, iface)
        self.assertEqual(adapter.request, iface)
    def test_add_resource_url_nodefault_interfaces(self):
        from zope.interface import Interface
        from pyramid.interfaces import IResourceURL
        config = self._makeOne(autocommit=True)
        config.add_resource_url_adapter(DummyResourceURL, DummyIface,
                                        DummyIface)
        iface = DummyIface()
        adapter = config.registry.getMultiAdapter((iface, iface),
                                                    IResourceURL)
        self.assertEqual(adapter.__class__, DummyResourceURL)
        self.assertEqual(adapter.resource, iface)
        self.assertEqual(adapter.request, iface)
        bad_result = config.registry.queryMultiAdapter(
            (Interface, Interface),
            IResourceURL,
            )
        self.assertEqual(bad_result, None)
    def test_add_resource_url_adapter_introspectables(self):
        config = self._makeOne()
        config.add_resource_url_adapter(DummyResourceURL, DummyIface)
        actions = config.action_state.actions
        self.assertEqual(len(actions), 1)
        intrs  = actions[0]['introspectables']
        self.assertEqual(len(intrs), 1)
        intr = intrs[0]
        self.assertEqual(intr.type_name, 'resource url adapter')
        self.assertEqual(intr.discriminator,
                         ('resource url adapter', DummyIface, None))
        self.assertEqual(intr.category_name, 'resource url adapters')
        self.assertEqual(
            intr.title,
            "resource url adapter for resource iface "
            "<class 'pyramid.tests.test_config.test_factories.DummyIface'>, "
            "request_iface None"
            )
class DummyRequest(object):
    callables = None
@@ -186,3 +250,9 @@
class DummyIface(object):
    pass
class DummyResourceURL(object):
    def __init__(self, resource, request):
        self.resource = resource
        self.request = request
pyramid/tests/test_request.py
@@ -21,17 +21,16 @@
        from pyramid.request import Request
        return Request
    def _registerContextURL(self):
        from pyramid.interfaces import IContextURL
    def _registerResourceURL(self):
        from pyramid.interfaces import IResourceURL
        from zope.interface import Interface
        class DummyContextURL(object):
        class DummyResourceURL(object):
            def __init__(self, context, request):
                pass
            def __call__(self):
                return 'http://example.com/context/'
                self.physical_path = '/context/'
                self.virtual_path = '/context/'
        self.config.registry.registerAdapter(
            DummyContextURL, (Interface, Interface),
            IContextURL)
            DummyResourceURL, (Interface, Interface),
            IResourceURL)
    def test_charset_defaults_to_utf8(self):
        r = self._makeOne({'PATH_INFO':'/'})
@@ -151,8 +150,14 @@
        self.assertEqual(inst.finished_callbacks, [])
    def test_resource_url(self):
        self._registerContextURL()
        inst = self._makeOne({})
        self._registerResourceURL()
        environ = {
            'PATH_INFO':'/',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            'wsgi.url_scheme':'http',
            }
        inst = self._makeOne(environ)
        root = DummyContext()
        result = inst.resource_url(root)
        self.assertEqual(result, 'http://example.com/context/')
pyramid/tests/test_url.py
@@ -1,4 +1,5 @@
import unittest
import warnings
from pyramid.testing import setUp
from pyramid.testing import tearDown
@@ -12,11 +13,16 @@
    def tearDown(self):
        tearDown()
        
    def _makeOne(self):
    def _makeOne(self, environ=None):
        from pyramid.url import URLMethodsMixin
        if environ is None:
            environ = {}
        class Request(URLMethodsMixin):
            application_url = 'http://example.com:5432'
        request = Request()
            script_name = ''
            def __init__(self, environ):
                self.environ = environ
        request = Request(environ)
        request.registry = self.config.registry
        return request
@@ -31,114 +37,124 @@
        reg.registerAdapter(DummyContextURL, (Interface, Interface),
                            IContextURL)
    def _registerResourceURL(self, reg):
        from pyramid.interfaces import IResourceURL
        from zope.interface import Interface
        class DummyResourceURL(object):
            def __init__(self, context, request):
                self.physical_path = '/context/'
                self.virtual_path = '/context/'
        reg.registerAdapter(DummyResourceURL, (Interface, Interface),
                            IResourceURL)
    def test_resource_url_root_default(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_url(root)
        self.assertEqual(result, 'http://example.com/context/')
        self.assertEqual(result, 'http://example.com:5432/context/')
    def test_resource_url_extra_args(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, 'this/theotherthing', 'that')
        self.assertEqual(
            result,
            'http://example.com/context/this%2Ftheotherthing/that')
            'http://example.com:5432/context/this%2Ftheotherthing/that')
    def test_resource_url_unicode_in_element_names(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
        context = DummyContext()
        result = request.resource_url(context, uc)
        self.assertEqual(result,
                     'http://example.com/context/La%20Pe%C3%B1a')
                     'http://example.com:5432/context/La%20Pe%C3%B1a')
    def test_resource_url_at_sign_in_element_names(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, '@@myview')
        self.assertEqual(result,
                     'http://example.com/context/@@myview')
                     'http://example.com:5432/context/@@myview')
    def test_resource_url_element_names_url_quoted(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, 'a b c')
        self.assertEqual(result, 'http://example.com/context/a%20b%20c')
        self.assertEqual(result, 'http://example.com:5432/context/a%20b%20c')
    def test_resource_url_with_query_dict(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
        result = request.resource_url(context, 'a', query={'a':uc})
        self.assertEqual(result,
                         'http://example.com/context/a?a=La+Pe%C3%B1a')
                         'http://example.com:5432/context/a?a=La+Pe%C3%B1a')
    def test_resource_url_with_query_seq(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        uc = text_(b'La Pe\xc3\xb1a', 'utf-8')
        result = request.resource_url(context, 'a', query=[('a', 'hi there'),
                                                           ('b', uc)])
        self.assertEqual(result,
                     'http://example.com/context/a?a=hi+there&b=La+Pe%C3%B1a')
            'http://example.com:5432/context/a?a=hi+there&b=La+Pe%C3%B1a')
    def test_resource_url_anchor_is_after_root_when_no_elements(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, anchor='a')
        self.assertEqual(result,
                         'http://example.com/context/#a')
                         'http://example.com:5432/context/#a')
    def test_resource_url_anchor_is_after_elements_when_no_qs(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, 'a', anchor='b')
        self.assertEqual(result,
                         'http://example.com/context/a#b')
                         'http://example.com:5432/context/a#b')
    def test_resource_url_anchor_is_after_qs_when_qs_is_present(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, 'a', 
                                      query={'b':'c'}, anchor='d')
        self.assertEqual(result,
                         'http://example.com/context/a?b=c#d')
                         'http://example.com:5432/context/a?b=c#d')
    def test_resource_url_anchor_is_encoded_utf8_if_unicode(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        uc = text_(b'La Pe\xc3\xb1a', 'utf-8') 
        result = request.resource_url(context, anchor=uc)
        self.assertEqual(
            result,
            native_(
                text_(b'http://example.com/context/#La Pe\xc3\xb1a',
                text_(b'http://example.com:5432/context/#La Pe\xc3\xb1a',
                      'utf-8'),
                'utf-8')
            )
    def test_resource_url_anchor_is_not_urlencoded(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        context = DummyContext()
        result = request.resource_url(context, anchor=' /#')
        self.assertEqual(result,
                         'http://example.com/context/# /#')
                         'http://example.com:5432/context/# /#')
    def test_resource_url_no_IContextURL_registered(self):
        # falls back to TraversalContextURL
    def test_resource_url_no_IResourceURL_registered(self):
        # falls back to ResourceURL
        root = DummyContext()
        root.__name__ = ''
        root.__parent__ = None
@@ -149,12 +165,98 @@
    def test_resource_url_no_registry_on_request(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        self._registerResourceURL(request.registry)
        del request.registry
        root = DummyContext()
        result = request.resource_url(root)
        self.assertEqual(result, 'http://example.com/context/')
        self.assertEqual(result, 'http://example.com:5432/context/')
    def test_resource_url_finds_IContextURL(self):
        request = self._makeOne()
        self._registerContextURL(request.registry)
        root = DummyContext()
        with warnings.catch_warnings(record=True) as w:
            warnings.simplefilter('always')
            result = request.resource_url(root)
            self.assertEqual(len(w), 1)
        self.assertEqual(result, 'http://example.com/context/')
    def test_resource_url_with_app_url(self):
        request = self._makeOne()
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_url(root, app_url='http://somewhere.com')
        self.assertEqual(result, 'http://somewhere.com/context/')
    def test_resource_url_with_scheme(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'8080',
            'SERVER_NAME':'example.com',
            }
        request = self._makeOne(environ)
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_url(root, scheme='https')
        self.assertEqual(result, 'https://example.com/context/')
    def test_resource_url_with_host(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'8080',
            'SERVER_NAME':'example.com',
            }
        request = self._makeOne(environ)
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_url(root, host='someotherhost.com')
        self.assertEqual(result, 'http://someotherhost.com:8080/context/')
    def test_resource_url_with_port(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'8080',
            'SERVER_NAME':'example.com',
            }
        request = self._makeOne(environ)
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_url(root, port='8181')
        self.assertEqual(result, 'http://example.com:8181/context/')
    def test_resource_url_with_local_url(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'8080',
            'SERVER_NAME':'example.com',
            }
        request = self._makeOne(environ)
        self._registerResourceURL(request.registry)
        root = DummyContext()
        def resource_url(req, info):
            self.assertEqual(req, request)
            self.assertEqual(info['virtual_path'], '/context/')
            self.assertEqual(info['physical_path'], '/context/')
            self.assertEqual(info['app_url'], 'http://example.com:5432')
            return 'http://example.com/contextabc/'
        root.__resource_url__ = resource_url
        result = request.resource_url(root)
        self.assertEqual(result, 'http://example.com/contextabc/')
    def test_resource_path(self):
        request = self._makeOne()
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_path(root)
        self.assertEqual(result, '/context/')
    def test_resource_path_kwarg(self):
        request = self._makeOne()
        self._registerResourceURL(request.registry)
        root = DummyContext()
        result = request.resource_path(root, anchor='abc')
        self.assertEqual(result, '/context/#abc')
    def test_route_url_with_elements(self):
        from pyramid.interfaces import IRoutesMapper
        request = self._makeOne()
@@ -234,6 +336,47 @@
        self.assertEqual(result,
                         'http://example2.com/1/2/3')
    def test_route_url_with_host(self):
        from pyramid.interfaces import IRoutesMapper
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'5432',
            }
        request = self._makeOne(environ)
        mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
        request.registry.registerUtility(mapper, IRoutesMapper)
        result = request.route_url('flub', _host='someotherhost.com')
        self.assertEqual(result,
                         'http://someotherhost.com:5432/1/2/3')
    def test_route_url_with_port(self):
        from pyramid.interfaces import IRoutesMapper
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'5432',
            'SERVER_NAME':'example.com',
            }
        request = self._makeOne(environ)
        mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
        request.registry.registerUtility(mapper, IRoutesMapper)
        result = request.route_url('flub', _port='8080')
        self.assertEqual(result,
                         'http://example.com:8080/1/2/3')
    def test_route_url_with_scheme(self):
        from pyramid.interfaces import IRoutesMapper
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_PORT':'5432',
            'SERVER_NAME':'example.com',
            }
        request = self._makeOne(environ)
        mapper = DummyRoutesMapper(route=DummyRoute('/1/2/3'))
        request.registry.registerUtility(mapper, IRoutesMapper)
        result = request.route_url('flub', _scheme='https')
        self.assertEqual(result,
                         'https://example.com/1/2/3')
    def test_route_url_generation_error(self):
        from pyramid.interfaces import IRoutesMapper
        request = self._makeOne()
@@ -471,6 +614,168 @@
                          {'_app_url':'/foo'})
                         )
    def test_partial_application_url_with_http_host_default_port_http(self):
        environ = {
            'wsgi.url_scheme':'http',
            'HTTP_HOST':'example.com:80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url()
        self.assertEqual(result, 'http://example.com')
    def test_partial_application_url_with_http_host_default_port_https(self):
        environ = {
            'wsgi.url_scheme':'https',
            'HTTP_HOST':'example.com:443',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url()
        self.assertEqual(result, 'https://example.com')
    def test_partial_application_url_with_http_host_nondefault_port_http(self):
        environ = {
            'wsgi.url_scheme':'http',
            'HTTP_HOST':'example.com:8080',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url()
        self.assertEqual(result, 'http://example.com:8080')
    def test_partial_application_url_with_http_host_nondefault_port_https(self):
        environ = {
            'wsgi.url_scheme':'https',
            'HTTP_HOST':'example.com:4443',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url()
        self.assertEqual(result, 'https://example.com:4443')
    def test_partial_application_url_with_http_host_no_colon(self):
        environ = {
            'wsgi.url_scheme':'http',
            'HTTP_HOST':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url()
        self.assertEqual(result, 'http://example.com')
    def test_partial_application_url_no_http_host(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url()
        self.assertEqual(result, 'http://example.com')
    def test_partial_application_replace_port(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(port=8080)
        self.assertEqual(result, 'http://example.com:8080')
    def test_partial_application_replace_scheme_https_special_case(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(scheme='https')
        self.assertEqual(result, 'https://example.com')
    def test_partial_application_replace_scheme_https_special_case_avoid(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(scheme='https', port='8080')
        self.assertEqual(result, 'https://example.com:8080')
    def test_partial_application_replace_scheme_http_special_case(self):
        environ = {
            'wsgi.url_scheme':'https',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'8080',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(scheme='http')
        self.assertEqual(result, 'http://example.com')
    def test_partial_application_replace_scheme_http_special_case_avoid(self):
        environ = {
            'wsgi.url_scheme':'https',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'8000',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(scheme='http', port='8080')
        self.assertEqual(result, 'http://example.com:8080')
    def test_partial_application_replace_host_no_port(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(host='someotherhost.com')
        self.assertEqual(result, 'http://someotherhost.com')
    def test_partial_application_replace_host_with_port(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'8000',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(host='someotherhost.com:8080')
        self.assertEqual(result, 'http://someotherhost.com:8080')
    def test_partial_application_replace_host_and_port(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(host='someotherhost.com:8080',
                                                 port='8000')
        self.assertEqual(result, 'http://someotherhost.com:8000')
    def test_partial_application_replace_host_port_and_scheme(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'80',
            }
        request = self._makeOne(environ)
        result = request.partial_application_url(
            host='someotherhost.com:8080',
            port='8000',
            scheme='https',
            )
        self.assertEqual(result, 'https://someotherhost.com:8000')
    def test_partial_application_url_with_custom_script_name(self):
        environ = {
            'wsgi.url_scheme':'http',
            'SERVER_NAME':'example.com',
            'SERVER_PORT':'8000',
            }
        request = self._makeOne(environ)
        request.script_name = '/abc'
        result = request.partial_application_url()
        self.assertEqual(result, 'http://example.com:8000/abc')
class Test_route_url(unittest.TestCase):
    def _callFUT(self, route_name, request, *elements, **kw):
        from pyramid.url import route_url
pyramid/traversal.py
@@ -6,6 +6,7 @@
from repoze.lru import lru_cache
from pyramid.interfaces import (
    IResourceURL,
    IContextURL,
    IRequestFactory,
    ITraverser,
@@ -730,17 +731,33 @@
ModelGraphTraverser = ResourceTreeTraverser # b/w compat, not API, used in wild
@implementer(IContextURL)
class TraversalContextURL(object):
    """ The IContextURL adapter used to generate URLs for a resource in a
    resource tree"""
@implementer(IResourceURL, IContextURL)
class ResourceURL(object):
    vroot_varname = VH_ROOT_KEY
    def __init__(self, context, request):
        self.context = context
    def __init__(self, resource, request):
        physical_path = resource_path(resource)
        if physical_path != '/':
            physical_path = physical_path + '/'
        virtual_path = physical_path
        environ = request.environ
        vroot_path = environ.get(self.vroot_varname)
        # if the physical path starts with the virtual root path, trim it out
        # of the virtual path
        if vroot_path is not None:
            if physical_path.startswith(vroot_path):
                virtual_path = physical_path[len(vroot_path):]
        self.virtual_path = virtual_path
        self.physical_path = physical_path
        self.resource = resource
        self.context = resource # bw compat alias for IContextURL compat
        self.request = request
    # IContextURL method (deprecated in 1.3)
    def virtual_root(self):
        environ = self.request.environ
        vroot_varname = self.vroot_varname
@@ -753,6 +770,7 @@
        except AttributeError:
            return find_root(self.context)
    # IContextURL method (deprecated in 1.3)
    def __call__(self):
        """ Generate a URL based on the :term:`lineage` of a :term:`resource`
        object that is ``self.context``.  If any resource in the context
@@ -762,35 +780,21 @@
        'virtual root path': the path of the URL generated by this will be
        left-stripped of this virtual root path value.
        """
        resource = self.context
        physical_path = resource_path(resource)
        if physical_path != '/':
            physical_path = physical_path + '/'
        virtual_path = physical_path
        request = self.request
        environ = request.environ
        vroot_varname = self.vroot_varname
        vroot_path = environ.get(vroot_varname)
        # if the physical path starts with the virtual root path, trim it out
        # of the virtual path
        if vroot_path is not None:
            if physical_path.startswith(vroot_path):
                virtual_path = physical_path[len(vroot_path):]
        local_url = getattr(resource, '__resource_url__', None)
        local_url = getattr(self.context, '__resource_url__', None)
        if local_url is not None:
            result = local_url(request,
                               {'virtual_path':virtual_path,
                                'physical_path':physical_path},
                               )
            result = local_url(
                self.request,
                {'virtual_path':self.virtual_path,
                 'physical_path':self.physical_path},
                )
            if result is not None:
                # allow it to punt by returning ``None``
                return result
        app_url = request.application_url # never ends in a slash
        return app_url + virtual_path
        app_url = self.request.application_url # never ends in a slash
        return app_url + self.virtual_path
TraversalContextURL = ResourceURL # bw compat as of 1.3
@lru_cache(1000)
def _join_path_tuple(tuple):
pyramid/url.py
@@ -1,31 +1,86 @@
""" Utility functions for dealing with URLs in pyramid """
import os
import warnings
from repoze.lru import lru_cache
from pyramid.interfaces import (
    IContextURL,
    IResourceURL,
    IRoutesMapper,
    IStaticURLInfo,
    )
from pyramid.compat import (
    native_,
    bytes_,
    text_type,
    url_quote,
    )
from pyramid.encode import urlencode
from pyramid.path import caller_package
from pyramid.threadlocal import get_current_registry
from pyramid.traversal import (
    TraversalContextURL,
    ResourceURL,
    quote_path_segment,
    )
PATH_SAFE = '/:@&+$,' # from webob
class URLMethodsMixin(object):
    """ Request methods mixin for BaseRequest having to do with URL
    generation """
    def partial_application_url(self, scheme=None, host=None, port=None):
        """
        Construct the URL defined by request.application_url, replacing any
        of the default scheme, host, or port portions with user-supplied
        variants.
        If ``scheme`` is passed as ``https``, and the ``port`` is *not*
        passed, the ``port`` value is assumed to ``443``.  Likewise, if
        ``scheme`` is passed as ``http`` and ``port`` is not passed, the
        ``port`` value is assumed to be ``80``.
        """
        e = self.environ
        if scheme is None:
            scheme = e['wsgi.url_scheme']
        else:
            if scheme == 'https':
                if port is None:
                    port = '443'
            if scheme == 'http':
                if port is None:
                    port = '80'
        url = scheme + '://'
        if port is not None:
            port = str(port)
        if host is None:
            host = e.get('HTTP_HOST')
        if host is None:
            host = e['SERVER_NAME']
        if port is None:
            if ':' in host:
                host, port = host.split(':', 1)
            else:
                port = e['SERVER_PORT']
        else:
            if ':' in host:
                host, _ = host.split(':', 1)
        if scheme == 'https':
            if port == '443':
                port = None
        elif scheme == 'http':
            if port == '80':
                port = None
        url += host
        if port:
            url += ':%s' % port
        url_encoding = getattr(self, 'url_encoding', 'utf-8') # webob 1.2b3+
        bscript_name = bytes_(self.script_name, url_encoding)
        return url + url_quote(bscript_name, PATH_SAFE)
    def route_url(self, route_name, *elements, **kw):
        """Generates a fully qualified URL for a named :app:`Pyramid`
@@ -105,6 +160,15 @@
        element will always follow the query element,
        e.g. ``http://example.com?foo=1#bar``.
        If any of ``_scheme``, ``_host``, or ``_port`` is passed and is
        non-``None``, the provided value will replace the named portion in
        the generated URL.  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
@@ -115,6 +179,10 @@
        ``_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
@@ -140,6 +208,9 @@
        anchor = ''
        qs = ''
        app_url = None
        host = None
        scheme = None
        port = None
        if '_query' in kw:
            qs = '?' + urlencode(kw.pop('_query'), doseq=True)
@@ -152,6 +223,21 @@
        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:
@@ -160,12 +246,6 @@
                suffix = '/' + suffix
        else:
            suffix = ''
        if app_url is None:
            # we only defer lookup of application_url until here because
            # it's somewhat expensive; we won't need to do it if we've
            # been passed _app_url
            app_url = self.application_url
        return app_url + path + suffix + qs + anchor
@@ -206,7 +286,7 @@
        :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 (never Unicode).
        UTF-8 encoded string.
        Examples::
@@ -225,6 +305,10 @@
            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
@@ -275,6 +359,38 @@
        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
        ``scheme='https'``, and the URL that would be generated without the
        scheme replacement is ``http://foo.com``, the result will be
        ``https://foo.com``.
        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
@@ -305,10 +421,69 @@
        except AttributeError:
            reg = get_current_registry() # b/c
        context_url = reg.queryMultiAdapter((resource, self), IContextURL)
        if context_url is None:
            context_url = TraversalContextURL(resource, self)
        resource_url = context_url()
        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 = ''
@@ -331,6 +506,31 @@
    model_url = resource_url # b/w compat forever
    def resource_path(self, resource, *elements, **kw):
        """
        Generates a path (aka a 'relative URL', a URL minus the host, scheme,
        and port) for a :term:`resource`.
        This function accepts the same argument as
        :meth:`pyramid.request.Request.resource_url` and performs the same
        duty.  It just omits the host, port, and scheme information in the
        return value; only the script_name, path, query parameters, and
        anchor data are present in the returned string.
        .. note::
           Calling ``request.resource_path(resource)`` is the same as calling
           ``request.resource_path(resource, app_url=request.script_name)``.
           :meth:`pyramid.request.Request.resource_path` is, in fact,
           implemented in terms of
           :meth:`pyramid.request.Request.resource_url` in just this way. As
           a result, any ``app_url`` passed within the ``**kw`` values to
           ``route_path`` will be ignored.  ``scheme``, ``host``, and
           ``port`` are also ignored.
        """
        kw['app_url'] = self.script_name
        return self.resource_url(resource, *elements, **kw)
    def static_url(self, path, **kw):
        """
        Generates a fully qualified URL for a static :term:`asset`.