summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt20
-rw-r--r--docs/narr/resources.rst217
-rw-r--r--docs/narr/vhosting.rst2
-rw-r--r--pyramid/tests/test_traversal.py24
-rw-r--r--pyramid/traversal.py94
-rw-r--r--pyramid/url.py4
6 files changed, 308 insertions, 53 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 362d66a90..b682f7119 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,6 +1,26 @@
Next release
============
+Features
+--------
+
+- If a resource implements a ``__resource_url__`` method, it will be called
+ as the result of invoking the ``pyramid.url.resource_url`` function to
+ generate a URL, overriding the default logic. See the new "Generating The
+ URL Of A Resource" section within the Resources narrative chapter.
+
+Documentation
+-------------
+
+- Added "Generating The URL Of A Resource" section to the Resources narrative
+ chapter (includes information about overriding URL generation using
+ ``__resource_url__``).
+
+- Added "Generating the Path To a Resource" section to the Resources
+ narrative chapter.
+
+- Added "Finding a Resource by Path" section to the Resources narrative
+ chapter.
1.0a7 (2010-12-20)
==================
diff --git a/docs/narr/resources.rst b/docs/narr/resources.rst
index 2c0d4deba..da2001960 100644
--- a/docs/narr/resources.rst
+++ b/docs/narr/resources.rst
@@ -1,11 +1,13 @@
Resources
=========
-A :term:`resource` is an object that represents a "place" in your
-application. Every :app:`Pyramid` application has at least one resource
-object: the :term:`root` resource. The root resource is the root of a
-:term:`resource tree`. A resource tree is a set of nested dictionary-like
-objects which you can use to represent your website's structure.
+A :term:`resource` is an object that represents a "place" in a tree related
+to your application. Every :app:`Pyramid` application has at least one
+resource object: the :term:`root` resource (even if you don't define one
+manually, a default root resource is created for you). The root resource is
+the root of a :term:`resource tree`. A resource tree is a set of nested
+dictionary-like objects which you can use to represent your website's
+structure.
In an application which uses :term:`traversal` to map URLs to code, the
resource tree structure is used heavily to map a URL to a :term:`view
@@ -150,11 +152,11 @@ retrieved from the container via ``__getitem__``. This pattern continues
recursively "up" the tree from the root.
The ``__parent__`` attributes of each resource form a linked list that points
-"upward" toward the root. This is analogous to the `..` entry in filesystem
-directories. If you follow the ``__parent__`` values from any resource in the
-resource tree, you will eventually come to the root resource, just like if
-you keep executing the ``cd ..`` filesystem command, eventually you will
-reach the filesystem root directory.
+"downwards" toward the root. This is analogous to the `..` entry in
+filesystem directories. If you follow the ``__parent__`` values from any
+resource in the resource tree, you will eventually come to the root resource,
+just like if you keep executing the ``cd ..`` filesystem command, eventually
+you will reach the filesystem root directory.
.. warning:: If your root resource has a ``__name__`` argument
that is not ``None`` or the empty string, URLs returned by the
@@ -199,8 +201,199 @@ and (usually) :func:`~pyramid.security.has_permission` and
In general, since so much :app:`Pyramid` infrastructure depends on
location-aware resources, it's a good idea to make each resource in your tree
-location-aware, even though location-awareness is not a prerequisite for
-plain traversal.
+location-aware.
+
+.. index::
+ single: resource_url
+ pair: generating; resource url
+
+Generating The URL Of A Resource
+--------------------------------
+
+If your resources are :term:`location` aware, you can use the
+:func:`pyramid.url.resource_url` API to generate a URL for the resource.
+This URL will use the resource's position in the parent tree to create a
+resource path, and it will prefix the path with the current application URL
+to form a fully-qualified URL with the scheme, host, port, and path. You can
+also pass extra arguments to :func:`~pyramid.url.resource_url` to influence
+the generated URL.
+
+The simplest call to :func:`~pyramid.url.resource_url` looks like this:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.url import resource_url
+ url = resource_url(resource, request)
+
+The ``request`` passed to ``resource_url`` in the above example is an
+instance of an :app:`Pyramid` :term:`request` object.
+
+If the resource referred to as ``resource`` in the above example was the root
+resource, and the host that was used to contact the server was
+``example.com``, the URL generated would be ``http://example.com/``.
+However, if the resource was a child of the root resource named ``a``, the
+generated URL would be ``http://example.com/a/``.
+
+A slash is appended to all resource URLs when
+:func:`~pyramid.url.resource_url` is used to generate them in this simple
+manner, because resources are "places" in the hierarchy, and URLs are meant
+to be clicked on to be visited. Relative URLs that you include on HTML pages
+rendered as the result of the default view of a resource are typically more
+apt to be relative to these resources than relative to their parent.
+
+You can also pass extra elements to :func:`~pyramid.url.resource_url`:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.url import resource_url
+ url = resource_url(resource, request, 'foo', 'bar')
+
+If the resource referred to as ``resource`` in the above example was the root
+resource, and the host that was used to contact the server was
+``example.com``, the URL generated would be ``http://example.com/foo/bar``.
+Any number of extra elements can be passed to
+:func:`~pyramid.url.resource_url` as extra positional arguments. When extra
+elements are passed, they are appended to the resource's URL. A slash is not
+appended to the final segment when elements are passed.
+
+You can also pass a query string:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.url import resource_url
+ url = resource_url(resource, request, query={'a':'1'})
+
+If the resource referred to as ``resource`` in the above example was the root
+resource, and the host that was used to contact the server was
+``example.com``, the URL generated would be ``http://example.com/?a=1``.
+
+When a :term:`virtual root` is active, the URL generated by
+:func:`~pyramid.url.resource_url` for a resource may be "shorter" than its
+physical tree path. See :ref:`virtual_root_support` for more information
+about virtually rooting a resource.
+
+The shortcut method of the :term:`request` named
+:meth:`pyramid.request.Request.resource_url` can be used instead of
+:func:`pyramid.url.resource_url` to generate a resource URL.
+
+For more information about generating resource URLs, see the documentation
+for :func:`pyramid.url.resource_url`.
+
+.. _overriding_resource_url_generation:
+
+Overriding Resource URL Generation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If a resource object implements a ``__resource_url__`` method, this method
+will be called when :func:`pyramid.url.resource_url` is called to generate a
+URL for the resource, overriding the default URL returned for the resource by
+:func:`~pyramid.url.resource_url`.
+
+The ``__resource_url__`` hook is passed two arguments: ``request`` and
+``info``. ``request`` is the :term:`request` object passed to
+:func:`pyramid.url.resource_url`. ``info`` is a dictionary with two
+keys:
+
+``physical_path``
+ The "physical path" computed for the resource, as defined by
+ ``pyramid.traversal.resource_path(resource)``.
+
+``virtual_path``
+ The "virtual path" computed for the resource, as defined by
+ :ref:`virtual_root_support`. This will be identical to the physical path
+ if virtual rooting is not enabled.
+
+The ``__resource_url__`` method of a resource should return a string
+representing a URL. If it cannot override the default, it should return
+``None``. If it returns ``None``, the default URL will be returned.
+
+Here's an example ``__resource_url__`` method.
+
+.. code-block:: python
+ :linenos:
+
+ class Resource(object):
+ def __resource_url__(self, request, info):
+ return request.application_url + info['virtual_path']
+
+The above example actually just generates and returns the default URL, which
+would have been what was returned anyway, but your code can perform arbitrary
+logic as necessary. For example, your code may wish to override the hostname
+or port number of the generated URL.
+
+Generating the Path To a Resource
+---------------------------------
+
+:func:`pyramid.traversal.resource_path` returns a string object representing
+the absolute physical path of the resource object based on its position in
+the resource tree. Each segment of the path is separated with a slash
+character.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.traversal import resource_path
+ url = resource_path(resource)
+
+If ``resource`` in the example above was accessible in the tree as
+``root['a']['b']``, the above example would generate the string ``/a/b``.
+
+Any positional arguments passed in to :func:`pyramid.traversal.resource_path`
+will be appended as path segments to the end of the resource path.
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.traversal import resource_path
+ url = resource_path(resource, 'foo', 'bar')
+
+If ``resource`` in the example above was accessible in the tree as
+``root['a']['b']``, the above example would generate the string
+``/a/b/foo/bar``.
+
+The resource passed in must be :term:`location`-aware.
+
+The presence or absence of a :term:`virtual root` has no impact on the
+behavior of :func:`~pyramid.traversal.resource_path`.
+
+Finding a Resource by Path
+--------------------------
+
+If you have a string path to a resource, you can grab the resource from
+that place in the application's resource tree using
+:func:`pyramid.traversal.find_resource`.
+
+You can resolve an absolute path by passing a string prefixed with a ``/`` as
+the ``path`` argument:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.traversal import find_resource
+ url = find_resource(anyresource, '/path')
+
+Or you can resolve a path relative to the resource you pass in by passing a
+string that isn't prefixed by ``/``:
+
+.. code-block:: python
+ :linenos:
+
+ from pyramid.traversal import find_resource
+ url = find_resource(anyresource, 'path')
+
+Often the paths you pass to :func:`~pyramid.traversal.find_resource` are
+generated by the :func:`~pyramid.traversal.resource_path` API. These APIs
+are "mirrors" of each other.
+
+If the path cannot be resolved when calling
+:func:`~pyramid.traversal.find_resource` (if the respective resource in the
+tree does not exist), a :exc:`KeyError` will be raised.
+
+See the :func:`pyramid.traversal.find_resource` documentation for more
+information about resolving a path to a resource.
.. index::
single: resource interfaces
diff --git a/docs/narr/vhosting.rst b/docs/narr/vhosting.rst
index 65168806e..d3ff260e3 100644
--- a/docs/narr/vhosting.rst
+++ b/docs/narr/vhosting.rst
@@ -78,6 +78,8 @@ In the above configuration, we root a :app:`Pyramid` application at
.. index::
single: virtual root
+.. _virtual_root_support:
+
Virtual Root Support
--------------------
diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py
index df9e1e554..147e53702 100644
--- a/pyramid/tests/test_traversal.py
+++ b/pyramid/tests/test_traversal.py
@@ -829,6 +829,30 @@ class TraversalContextURLTests(unittest.TestCase):
result = context_url()
self.assertEqual(result, 'http://example.com:5432//bar/')
+ def test_local_url_returns_None(self):
+ resource = DummyContext()
+ def resource_url(request, info):
+ self.assertEqual(info['virtual_path'], '/')
+ self.assertEqual(info['physical_path'], '/')
+ return None
+ resource.__resource_url__ = resource_url
+ request = DummyRequest()
+ context_url = self._makeOne(resource, request)
+ result = context_url()
+ self.assertEqual(result, 'http://example.com:5432/')
+
+ def test_local_url_returns_url(self):
+ resource = DummyContext()
+ def resource_url(request, info):
+ self.assertEqual(info['virtual_path'], '/')
+ self.assertEqual(info['physical_path'], '/')
+ return 'abc'
+ resource.__resource_url__ = resource_url
+ request = DummyRequest()
+ context_url = self._makeOne(resource, request)
+ result = context_url()
+ self.assertEqual(result, 'abc')
+
class TestVirtualRoot(unittest.TestCase):
def setUp(self):
cleanUp()
diff --git a/pyramid/traversal.py b/pyramid/traversal.py
index 086823695..73878022c 100644
--- a/pyramid/traversal.py
+++ b/pyramid/traversal.py
@@ -16,7 +16,7 @@ from pyramid.location import lineage
from pyramid.threadlocal import get_current_registry
def find_root(resource):
- """ Find the root node in the graph to which ``resource``
+ """ Find the root node in the resource tree to which ``resource``
belongs. Note that ``resource`` should be :term:`location`-aware.
Note that the root resource is available in the request object by
accessing the ``request.root`` attribute.
@@ -29,13 +29,12 @@ def find_root(resource):
def find_resource(resource, path):
""" Given a resource object and a string or tuple representing a path
- (such as the return value of
- :func:`pyramid.traversal.resource_path` or
- :func:`pyramid.traversal.resource_path_tuple`), return a context
- in this application's resource tree at the specified path. The
- resource passed in *must* be :term:`location`-aware. If the path
- cannot be resolved (if the respective node in the graph does not
- exist), a :exc:`KeyError` will be raised.
+ (such as the return value of :func:`pyramid.traversal.resource_path` or
+ :func:`pyramid.traversal.resource_path_tuple`), return a resource in this
+ application's resource tree at the specified path. The resource passed
+ in *must* be :term:`location`-aware. If the path cannot be resolved (if
+ the respective node in the resource tree does not exist), a
+ :exc:`KeyError` will be raised.
This function is the logical inverse of
:func:`pyramid.traversal.resource_path` and
@@ -44,10 +43,10 @@ def find_resource(resource, path):
Rules for passing a *string* as the ``path`` argument: if the
first character in the path string is the with the ``/``
- character, the path will considered absolute and the graph
+ character, the path will considered absolute and the resource tree
traversal will start at the root object. If the first character
of the path string is *not* the ``/`` character, the path is
- considered relative and graph traversal will begin at the resource
+ considered relative and resource tree traversal will begin at the resource
object supplied to the function as the ``resource`` argument. If an
empty string is passed as ``path``, the ``resource`` passed in will
be returned. Resource path strings must be escaped in the following
@@ -60,10 +59,10 @@ def find_resource(resource, path):
Rules for passing a *tuple* as the ``path`` argument: if the first
element in the path tuple is the empty string (for example ``('',
- 'a', 'b', 'c')``, the path is considered absolute and the graph
- traversal will start at the graph root object. If the first
+ 'a', 'b', 'c')``, the path is considered absolute and the resource tree
+ traversal will start at the resource tree root object. If the first
element in the path tuple is not the empty string (for example
- ``('a', 'b', 'c')``), the path is considered relative and graph
+ ``('a', 'b', 'c')``), the path is considered relative and resource tree
traversal will begin at the resource object supplied to the function
as the ``resource`` argument. If an empty sequence is passed as
``path``, the ``resource`` passed in itself will be returned. No
@@ -126,7 +125,7 @@ def resource_path(resource, *elements):
.. note:: Each segment in the path string returned will use the
``__name__`` attribute of the resource it represents within
- the graph. Each of these segments *should* be a unicode
+ the resource tree. Each of these segments *should* be a unicode
or string object (as per the contract of
:term:`location`-awareness). However, no conversion or
safety checking of resource names is performed. For
@@ -207,7 +206,7 @@ def traverse(resource, path):
via :term:`url dispatch`, traversed will be None.
- ``virtual_root``: A resource object representing the 'virtual' root
- of the object graph being traversed during :term:`traversal`.
+ of the resource tree being traversed during :term:`traversal`.
See :ref:`vhosting_chapter` for a definition of the virtual root
object. If no virtual hosting is in effect, and the ``path``
passed in was absolute, the ``virtual_root`` will be the
@@ -230,10 +229,10 @@ def traverse(resource, path):
Rules for passing a *string* as the ``path`` argument: if the
first character in the path string is the with the ``/``
- character, the path will considered absolute and the graph
+ character, the path will considered absolute and the resource tree
traversal will start at the root object. If the first character
of the path string is *not* the ``/`` character, the path is
- considered relative and graph traversal will begin at the resource
+ considered relative and resource tree traversal will begin at the resource
object supplied to the function as the ``resource`` argument. If an
empty string is passed as ``path``, the ``resource`` passed in will
be returned. Resource path strings must be escaped in the following
@@ -246,10 +245,10 @@ def traverse(resource, path):
Rules for passing a *tuple* as the ``path`` argument: if the first
element in the path tuple is the empty string (for example ``('',
- 'a', 'b', 'c')``, the path is considered absolute and the graph
- traversal will start at the graph root object. If the first
+ 'a', 'b', 'c')``, the path is considered absolute and the resource tree
+ traversal will start at the resource tree root object. If the first
element in the path tuple is not the empty string (for example
- ``('a', 'b', 'c')``), the path is considered relative and graph
+ ``('a', 'b', 'c')``), the path is considered relative and resource tree
traversal will begin at the resource object supplied to the function
as the ``resource`` argument. If an empty sequence is passed as
``path``, the ``resource`` passed in itself will be returned. No
@@ -313,7 +312,7 @@ def traverse(resource, path):
def resource_path_tuple(resource, *elements):
"""
Return a tuple representing the absolute physical path of the
- ``resource`` object based on its position in an object graph, e.g
+ ``resource`` object based on its position in a resource tree, e.g
``('', 'foo', 'bar')``. Any positional arguments passed in as
``elements`` will be appended as elements in the tuple
representing the resource path. For instance, if the resource's
@@ -331,7 +330,7 @@ def resource_path_tuple(resource, *elements):
.. note:: Each segment in the path tuple returned will equal the
``__name__`` attribute of the resource it represents within
- the graph. Each of these segments *should* be a unicode
+ the resource tree. Each of these segments *should* be a unicode
or string object (as per the contract of
:term:`location`-awareness). However, no conversion or
safety checking of resource names is performed. For
@@ -379,7 +378,7 @@ def virtual_root(resource, request):
API will be used to find the virtual root object using this path;
if the object is found, it will be returned. If the
``HTTP_X_VHM_ROOT`` key is is not present in the WSGI environment,
- the physical :term:`root` of the graph will be returned instead.
+ the physical :term:`root` of the resource tree will be returned instead.
Virtual roots are not useful at all in applications that use
:term:`URL dispatch`. Contexts obtained via URL dispatch don't
@@ -401,7 +400,7 @@ def virtual_root(resource, request):
def traversal_path(path):
""" Given a ``PATH_INFO`` string (slash-separated path segments),
return a tuple representing that path which can be used to
- traverse a graph.
+ traverse a resource tree.
The ``PATH_INFO`` is split on slashes, creating a list of
segments. Each segment is URL-unquoted, and subsequently decoded
@@ -657,30 +656,43 @@ class TraversalContextURL(object):
return find_root(self.context)
def __call__(self):
- """ Generate a URL based on the :term:`lineage` of a
- :term:`resource` object obtained via :term:`traversal`. If any
- resource in the context lineage has a Unicode name, it will be
- converted to a UTF-8 string before being attached to the URL.
- If a ``HTTP_X_VHM_ROOT`` key is present in the WSGI
- environment, its value will be treated as a 'virtual root
- path': the path of the URL generated by this will be
+ """ Generate a URL based on the :term:`lineage` of a :term:`resource`
+ object that is ``self.context``. If any resource in the context
+ lineage has a Unicode name, it will be converted to a UTF-8 string
+ before being attached to the URL. If a ``HTTP_X_VHM_ROOT`` key is
+ present in the WSGI environment, its value will be treated as a
+ 'virtual root path': the path of the URL generated by this will be
left-stripped of this virtual root path value.
"""
- path = resource_path(self.context)
- if path != '/':
- path = path + '/'
+ 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
-
- # if the path starts with the virtual root path, trim it out
- if vroot_varname in environ:
- vroot_path = environ[vroot_varname]
- if path.startswith(vroot_path):
- path = path[len(vroot_path):]
+ 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)
+ if local_url is not None:
+ result = local_url(request,
+ {'virtual_path':virtual_path,
+ 'physical_path':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 + path
+ return app_url + virtual_path
@lru_cache(1000)
def _join_path_tuple(tuple):
diff --git a/pyramid/url.py b/pyramid/url.py
index 6ae0ce8c4..4c2f5d393 100644
--- a/pyramid/url.py
+++ b/pyramid/url.py
@@ -255,6 +255,10 @@ def resource_url(resource, request, *elements, **kw):
will always follow the query element,
e.g. ``http://example.com?foo=1#bar``.
+ If the ``resource`` passed in has a ``__resource_url__`` method, it will
+ be used to generate the URL that is returned 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