diff options
| author | Chris McDonough <chrism@plope.com> | 2013-10-02 19:02:46 -0400 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2013-10-02 19:02:46 -0400 |
| commit | 95d032468d5d445c64f6065e4b4b2889fa853e31 (patch) | |
| tree | 66bb56b03f2df802dd0836d017a59f993bcf86c8 | |
| parent | 66be39bf656a2840931603bc959e38ff95e53164 (diff) | |
| parent | 61d1f729f4d9cc17a5fbcff0612512bc9f5fe7a2 (diff) | |
| download | pyramid-95d032468d5d445c64f6065e4b4b2889fa853e31.tar.gz pyramid-95d032468d5d445c64f6065e4b4b2889fa853e31.tar.bz2 pyramid-95d032468d5d445c64f6065e4b4b2889fa853e31.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
| -rw-r--r-- | CHANGES.txt | 16 | ||||
| -rw-r--r-- | docs/glossary.rst | 32 | ||||
| -rw-r--r-- | docs/narr/project.rst | 15 | ||||
| -rw-r--r-- | docs/narr/renderers.rst | 10 | ||||
| -rw-r--r-- | docs/narr/sessions.rst | 13 | ||||
| -rw-r--r-- | docs/narr/traversal.rst | 30 | ||||
| -rw-r--r-- | docs/quick_tour.rst | 7 | ||||
| -rw-r--r-- | pyramid/encode.py | 6 | ||||
| -rw-r--r-- | pyramid/scripts/pviews.py | 27 | ||||
| -rw-r--r-- | pyramid/tests/test_encode.py | 12 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/dummy.py | 7 | ||||
| -rw-r--r-- | pyramid/tests/test_scripts/test_pviews.py | 51 |
12 files changed, 131 insertions, 95 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index cb28d880b..051328a02 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -15,6 +15,12 @@ Bug Fixes on resources. Previously it did not try to call the ``__acl__`` if it was callable. +- The ``pviews`` script did not work when a url required custom request + methods in order to perform traversal. Custom methods and descriptors added + via ``pyramid.config.Configurator.add_request_method`` will now be present, + allowing traversal to continue. + See https://github.com/Pylons/pyramid/issues/1104 + Documentation ------------- @@ -23,6 +29,16 @@ Documentation - Removed mention of ``pyramid_beaker`` from docs. Beaker is no longer maintained. Point people at ``pyramid_redis_sessions`` instead. +Backwards Incompatibilities +--------------------------- + +- The key/values in the ``_query`` parameter of ``request.route_url`` and the + ``query`` parameter of ``request.resource_url`` (and their variants), used + to encode a value of ``None`` as the string ``'None'``, leaving the resulting + query string to be ``a=b&key=None``. The value is now dropped in this + situation, leaving a query string of ``a=b&key=``. + See https://github.com/Pylons/pyramid/issues/1119 + 1.5a2 (2013-09-22) ================== diff --git a/docs/glossary.rst b/docs/glossary.rst index 7dc69c7c4..406b81778 100644 --- a/docs/glossary.rst +++ b/docs/glossary.rst @@ -505,15 +505,20 @@ Glossary available as its ``__parent__`` attribute. root factory - The "root factory" of a :app:`Pyramid` application is called - on every request sent to the application. The root factory - returns the traversal root of an application. It is - conventionally named ``get_root``. An application may supply a - root factory to :app:`Pyramid` during the construction of a - :term:`Configurator`. If a root factory is not supplied, the - application uses a default root object. Use of the default root - object is useful in application which use :term:`URL dispatch` for - all URL-to-view code mappings. + The "root factory" of a :app:`Pyramid` application is called on every + request sent to the application. The root factory returns the traversal + root of an application. It is conventionally named ``get_root``. An + application may supply a root factory to :app:`Pyramid` during the + construction of a :term:`Configurator`. If a root factory is not + supplied, the application creates a default root object using the + :term:`default root factory`. + + default root factory + If an application does not register a :term:`root factory` at Pyramid + configuration time, a *default* root factory is used to created the + default root object. Use of the default root object is useful in + application which use :term:`URL dispatch` for all URL-to-view code + mappings, and does not (knowingly) use traversal otherwise. SQLAlchemy `SQLAlchemy <http://www.sqlalchemy.org/>`_ is an object @@ -1009,7 +1014,8 @@ Glossary Green Unicorn Aka ``gunicorn``, a fast :term:`WSGI` server that runs on UNIX under - Python 2.6+ or Python 3.1+. See http://gunicorn.org/ for detailed information. + Python 2.6+ or Python 3.1+. See http://gunicorn.org/ for detailed + information. predicate factory A callable which is used by a third party during the registration of a @@ -1021,3 +1027,9 @@ Glossary A Python :term:`distribution` that uses Pyramid's extensibility to plug into a Pyramid application and provide extra, configurable services. + + pyramid_redis_sessions + A package by Eric Rasmussen which allows you to store Pyramid session + data in a Redis database. See + https://pypi.python.org/pypi/pyramid_redis_sessions for more information. + diff --git a/docs/narr/project.rst b/docs/narr/project.rst index 4c19982d6..9451f41b1 100644 --- a/docs/narr/project.rst +++ b/docs/narr/project.rst @@ -871,18 +871,17 @@ specification` that specifies the ``mytemplate.pt`` file within the ``templates`` directory of the ``myproject`` package. The asset specification could have also been specified as ``myproject:templates/mytemplate.pt``; the leading package name and colon is -optional. The template file it actually points to is a :term:`Chameleon` ZPT -template file. +optional. The template file pointed to is a :term:`Chameleon` ZPT +template file (``templates/my_template.pt``). This view callable function is handed a single piece of information: the :term:`request`. The *request* is an instance of the :term:`WebOb` ``Request`` class representing the browser's request to our server. -This view returns a dictionary. When this view is invoked, a -:term:`renderer` converts the dictionary returned by the view into HTML, and -returns the result as the :term:`response`. This view is configured to -invoke a renderer which uses a :term:`Chameleon` ZPT template -(``templates/my_template.pt``). +This view is configured to invoke a :term:`renderer` on a template. The +dictionary the view returns (on line 6) provides the value the renderer +substitutes into the template when generating HTML. The renderer then +returns the HTML in a :term:`response`. See :ref:`views_which_use_a_renderer` for more information about how views, renderers, and templates relate and cooperate. @@ -978,7 +977,7 @@ named ``views`` instead of within a single ``views.py`` file, you might: - *Move* the existing ``views.py`` file to a file inside the new ``views`` directory named, say, ``blog.py``. Because the ``templates`` directory remains in the ``myproject`` package, the template :term:`asset - specification`s in ``blog.py`` must now be fully qualified with the + specification` values in ``blog.py`` must now be fully qualified with the project's package name (``myproject:templates/blog.pt``). You can then continue to add view callable functions to the ``blog.py`` diff --git a/docs/narr/renderers.rst b/docs/narr/renderers.rst index 3059aef35..4046c67fa 100644 --- a/docs/narr/renderers.rst +++ b/docs/narr/renderers.rst @@ -49,10 +49,12 @@ Writing View Callables Which Use a Renderer ------------------------------------------- As we've seen, a view callable needn't always return a Response object. -Instead, it may return an arbitrary Python object, with the expectation -that a :term:`renderer` will convert that object into a response instance on -your behalf. Some renderers use a templating system; other renderers use -object serialization techniques. +Instead, it may return an arbitrary Python object, with the expectation that +a :term:`renderer` will convert that object into a response instance on your +behalf. Some renderers use a templating system; other renderers use object +serialization techniques. In practice, renderers obtain application data +values from Python dictionaries so, in practice, view callables which use +renderers return Python dictionaries. View configuration can vary the renderer associated with a view callable via the ``renderer`` attribute. For example, this call to diff --git a/docs/narr/sessions.rst b/docs/narr/sessions.rst index f8279b0a5..649d22bd2 100644 --- a/docs/narr/sessions.rst +++ b/docs/narr/sessions.rst @@ -99,6 +99,11 @@ example: else: return Response('Fred was not in the session') +The first time this view is invoked produces ``Fred was not in the +session``. Subsequent invocations produce ``Fred was in the +session``, assuming of course that the client side maintains the +session's identity across multiple requests. + You can use a session much like a Python dictionary. It supports all dictionary methods, along with some extra attributes, and methods. @@ -154,10 +159,10 @@ Some gotchas: Using Alternate Session Factories --------------------------------- -At the time of this writing, exactly one project-endorsed alternate session -factory exists named``pyramid_redis_sessions``. It can be downloaded from PyPI. -It uses Redis as a backend. It is the recommended persistent session solution -at the time of this writing. +At the time of this writing, exactly one project-endorsed alternate session +factory exists named :term:`pyramid_redis_sessions`. It can be downloaded from +PyPI. It uses the Redis database as a backend. It is the recommended +persistent session solution at the time of this writing. .. index:: single: session factory (custom) diff --git a/docs/narr/traversal.rst b/docs/narr/traversal.rst index a60c5ba56..454bb5620 100644 --- a/docs/narr/traversal.rst +++ b/docs/narr/traversal.rst @@ -146,35 +146,15 @@ refer to a root factory defined in a different module. If no :term:`root factory` is passed to the :app:`Pyramid` :term:`Configurator` constructor, or if the ``root_factory`` value -specified is ``None``, a *default* root factory is used. The default +specified is ``None``, a :term:`default root factory` is used. The default root factory always returns a resource that has no child resources; it is effectively empty. Usually a root factory for a traversal-based application will be more -complicated than the above ``Root`` class; in particular it may be -associated with a database connection or another persistence mechanism. - -.. sidebar:: Emulating the Default Root Factory - - For purposes of understanding the default root factory better, we'll note - that you can emulate the default root factory by using this code as an - explicit root factory in your application setup: - - .. code-block:: python - :linenos: - - class Root(object): - def __init__(self, request): - pass - - config = Configurator(root_factory=Root) - - The default root factory is just a really stupid object that has no - behavior or state. Using :term:`traversal` against an application that - uses the resource tree supplied by the default root resource is not very - interesting, because the default root resource has no children. Its - availability is more useful when you're developing an application using - :term:`URL dispatch`. +complicated than the above ``Root`` class; in particular it may be associated +with a database connection or another persistence mechanism. The above +``Root`` class is analogous to the default root factory present in Pyramid. The +default root factory is very simple and not very useful. .. note:: diff --git a/docs/quick_tour.rst b/docs/quick_tour.rst index 98584e608..2db18c8a7 100644 --- a/docs/quick_tour.rst +++ b/docs/quick_tour.rst @@ -729,8 +729,8 @@ that requires semi-permanent data to be saved. For example, a shopping cart. This is called a :term:`session`. Pyramid has basic built-in support for sessions, with add-ons such as -*Beaker* (or your own custom sessioning engine) that provide richer -session support. Let's take a look at the +``pyramid_redis_sessions`` (or your own custom sessioning engine) that provide +richer session support. Let's take a look at the :doc:`built-in sessioning support <../narr/sessions>`. In our ``__init__.py`` we first import the kind of sessioning we want: @@ -768,8 +768,7 @@ Jinja2 template: .. seealso:: See Also: :ref:`Quick Tutorial Sessions <qtut_sessions>`, :ref:`sessions_chapter`, :ref:`flash_messages`, - :ref:`session_module`, and - :ref:`Beaker sessioning middleware <beaker:overview>` + :ref:`session_module`, and :term:`pyramid_redis_sessions`. Databases ========= diff --git a/pyramid/encode.py b/pyramid/encode.py index 65bc95032..9e190bc21 100644 --- a/pyramid/encode.py +++ b/pyramid/encode.py @@ -32,6 +32,10 @@ def urlencode(query, doseq=True): See the Python stdlib documentation for ``urllib.urlencode`` for more information. + + .. versionchanged:: 1.5 + In a key/value pair, if the value is ``None`` then it will be + dropped from the resulting output. """ try: # presumed to be a dictionary @@ -50,6 +54,8 @@ def urlencode(query, doseq=True): x = _enc(x) result += '%s%s=%s' % (prefix, k, x) prefix = '&' + elif v is None: + result += '%s%s=' % (prefix, k) else: v = _enc(v) result += '%s%s=%s' % (prefix, k, v) diff --git a/pyramid/scripts/pviews.py b/pyramid/scripts/pviews.py index 081c13e9d..504f583b7 100644 --- a/pyramid/scripts/pviews.py +++ b/pyramid/scripts/pviews.py @@ -4,6 +4,7 @@ import textwrap from pyramid.interfaces import IMultiView from pyramid.paster import bootstrap +from pyramid.request import Request from pyramid.scripts.common import parse_vars def main(argv=sys.argv, quiet=False): @@ -52,7 +53,7 @@ class PViewsCommand(object): infos.append(info) return infos - def _find_view(self, url, registry): + def _find_view(self, request): """ Accept ``url`` and ``registry``; create a :term:`request` and find a :app:`Pyramid` view based on introspection of :term:`view @@ -63,22 +64,19 @@ class PViewsCommand(object): from pyramid.interfaces import IRequest from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest - from pyramid.interfaces import IRequestFactory from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier from pyramid.interfaces import ITraverser - from pyramid.request import Request from pyramid.traversal import DefaultRootFactory from pyramid.traversal import ResourceTreeTraverser + registry = request.registry q = registry.queryUtility root_factory = q(IRootFactory, default=DefaultRootFactory) routes_mapper = q(IRoutesMapper) - request_factory = q(IRequestFactory, default=Request) adapters = registry.adapters - request = None @implementer(IMultiView) class RoutesMultiView(object): @@ -111,20 +109,9 @@ class PViewsCommand(object): view.__view_attr__ = '' self.views.append((None, view, None)) - - # create the request - environ = { - 'wsgi.url_scheme':'http', - 'SERVER_NAME':'localhost', - 'SERVER_PORT':'8080', - 'REQUEST_METHOD':'GET', - 'PATH_INFO':url, - } - request = request_factory(environ) context = None routes_multiview = None attrs = request.__dict__ - attrs['registry'] = registry request_iface = IRequest # find the root object @@ -236,9 +223,10 @@ class PViewsCommand(object): if not url.startswith('/'): url = '/%s' % url - env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:])) - registry = env['registry'] - view = self._find_view(url, registry) + request = Request.blank(url) + env = self.bootstrap[0](config_uri, options=parse_vars(self.args[2:]), + request=request) + view = self._find_view(request) self.out('') self.out("URL = %s" % url) self.out('') @@ -257,5 +245,6 @@ class PViewsCommand(object): else: self.out(" Not found.") self.out('') + env['closer']() return 0 diff --git a/pyramid/tests/test_encode.py b/pyramid/tests/test_encode.py index 736ecb5b3..908249877 100644 --- a/pyramid/tests/test_encode.py +++ b/pyramid/tests/test_encode.py @@ -41,6 +41,18 @@ class UrlEncodeTests(unittest.TestCase): result = self._callFUT({'a':1}) self.assertEqual(result, 'a=1') + def test_None_value(self): + result = self._callFUT([('a', None)]) + self.assertEqual(result, 'a=') + + def test_None_value_with_prefix(self): + result = self._callFUT([('a', '1'), ('b', None)]) + self.assertEqual(result, 'a=1&b=') + + def test_None_value_with_prefix_values(self): + result = self._callFUT([('a', '1'), ('b', None), ('c', None)]) + self.assertEqual(result, 'a=1&b=&c=') + class URLQuoteTests(unittest.TestCase): def _callFUT(self, val, safe=''): from pyramid.encode import url_quote diff --git a/pyramid/tests/test_scripts/dummy.py b/pyramid/tests/test_scripts/dummy.py index d580203af..366aa00b5 100644 --- a/pyramid/tests/test_scripts/dummy.py +++ b/pyramid/tests/test_scripts/dummy.py @@ -146,10 +146,13 @@ class DummyBootstrap(object): def __call__(self, *a, **kw): self.a = a self.kw = kw + registry = kw.get('registry', self.registry) + request = kw.get('request', self.request) + request.registry = registry return { 'app': self.app, - 'registry': self.registry, - 'request': self.request, + 'registry': registry, + 'request': request, 'root': self.root, 'root_factory': self.root_factory, 'closer': self.closer, diff --git a/pyramid/tests/test_scripts/test_pviews.py b/pyramid/tests/test_scripts/test_pviews.py index 266d1ec90..b162144a7 100644 --- a/pyramid/tests/test_scripts/test_pviews.py +++ b/pyramid/tests/test_scripts/test_pviews.py @@ -12,6 +12,12 @@ class TestPViewsCommand(unittest.TestCase): cmd.args = ('/foo/bar/myapp.ini#myapp',) return cmd + def _makeRequest(self, url, registry): + from pyramid.request import Request + request = Request.blank('/a') + request.registry = registry + return request + def _register_mapper(self, registry, routes): from pyramid.interfaces import IRoutesMapper mapper = dummy.DummyMapper(*routes) @@ -22,7 +28,8 @@ class TestPViewsCommand(unittest.TestCase): registry = Registry() self._register_mapper(registry, []) command = self._makeOne(registry) - result = command._find_view('/a', registry) + request = self._makeRequest('/a', registry) + result = command._find_view(request) self.assertEqual(result, None) def test__find_view_no_match_multiview_registered(self): @@ -45,7 +52,8 @@ class TestPViewsCommand(unittest.TestCase): IMultiView) self._register_mapper(registry, []) command = self._makeOne(registry=registry) - result = command._find_view('/x', registry) + request = self._makeRequest('/x', registry) + result = command._find_view(request) self.assertEqual(result, None) def test__find_view_traversal(self): @@ -65,7 +73,8 @@ class TestPViewsCommand(unittest.TestCase): IView, name='a') self._register_mapper(registry, []) command = self._makeOne(registry=registry) - result = command._find_view('/a', registry) + request = self._makeRequest('/a', registry) + result = command._find_view(request) self.assertEqual(result, view1) def test__find_view_traversal_multiview(self): @@ -89,7 +98,8 @@ class TestPViewsCommand(unittest.TestCase): IMultiView, name='a') self._register_mapper(registry, []) command = self._makeOne(registry=registry) - result = command._find_view('/a', registry) + request = self._makeRequest('/a', registry) + result = command._find_view(request) self.assertEqual(result, view) def test__find_view_route_no_multiview(self): @@ -117,7 +127,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/b', factory=Factory)] self._register_mapper(registry, routes) command = self._makeOne(registry=registry) - result = command._find_view('/a', registry) + request = self._makeRequest('/a', registry) + result = command._find_view(request) self.assertEqual(result, view) def test__find_view_route_multiview_no_view_registered(self): @@ -147,7 +158,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', matchdict={})] self._register_mapper(registry, routes) command = self._makeOne(registry=registry) - result = command._find_view('/a', registry) + request = self._makeRequest('/a', registry) + result = command._find_view(request) self.assertTrue(IMultiView.providedBy(result)) def test__find_view_route_multiview(self): @@ -185,7 +197,8 @@ class TestPViewsCommand(unittest.TestCase): dummy.DummyRoute('b', '/a', matchdict={})] self._register_mapper(registry, routes) command = self._makeOne(registry=registry) - result = command._find_view('/a', registry) + request = self._makeRequest('/a', registry) + result = command._find_view(request) self.assertTrue(IMultiView.providedBy(result)) self.assertEqual(len(result.views), 2) self.assertTrue((None, view1, None) in result.views) @@ -228,7 +241,7 @@ class TestPViewsCommand(unittest.TestCase): command = self._makeOne(registry=registry) L = [] command.out = L.append - command._find_view = lambda arg1, arg2: None + command._find_view = lambda arg1: None command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -241,7 +254,7 @@ class TestPViewsCommand(unittest.TestCase): command = self._makeOne(registry=registry) L = [] command.out = L.append - command._find_view = lambda arg1, arg2: None + command._find_view = lambda arg1: None command.args = ('/foo/bar/myapp.ini#myapp', 'a') result = command.run() self.assertEqual(result, 0) @@ -255,7 +268,7 @@ class TestPViewsCommand(unittest.TestCase): L = [] command.out = L.append view = dummy.DummyView(context='context', view_name='a') - command._find_view = lambda arg1, arg2: view + command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -273,7 +286,7 @@ class TestPViewsCommand(unittest.TestCase): command.out = L.append def view(): pass view.__request_attrs__ = {'context': 'context', 'view_name': 'a'} - command._find_view = lambda arg1, arg2: view + command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -291,7 +304,7 @@ class TestPViewsCommand(unittest.TestCase): command.out = L.append view = dummy.DummyView(context='context', view_name='a') view.__permission__ = 'test' - command._find_view = lambda arg1, arg2: view + command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -312,7 +325,7 @@ class TestPViewsCommand(unittest.TestCase): predicate.text = lambda *arg: "predicate = x" view = dummy.DummyView(context='context', view_name='a') view.__predicates__ = [predicate] - command._find_view = lambda arg1, arg2: view + command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -332,7 +345,7 @@ class TestPViewsCommand(unittest.TestCase): route = dummy.DummyRoute('a', '/a', matchdict={}) view = dummy.DummyView(context='context', view_name='a', matched_route=route, subpath='') - command._find_view = lambda arg1, arg2: view + command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -360,7 +373,7 @@ class TestPViewsCommand(unittest.TestCase): view_name='a1') multiview2 = dummy.DummyMultiView(multiview1, context='context', view_name='a') - command._find_view = lambda arg1, arg2: multiview2 + command._find_view = lambda arg1: multiview2 command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -383,7 +396,7 @@ class TestPViewsCommand(unittest.TestCase): route = dummy.DummyRoute('a', '/a', matchdict={}, predicate=predicate) view = dummy.DummyView(context='context', view_name='a', matched_route=route, subpath='') - command._find_view = lambda arg1, arg2: view + command._find_view = lambda arg1: view command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -409,7 +422,7 @@ class TestPViewsCommand(unittest.TestCase): view.__name__ = 'view' view.__view_attr__ = 'call' multiview = dummy.DummyMultiView(view, context='context', view_name='a') - command._find_view = lambda arg1, arg2: multiview + command._find_view = lambda arg1: multiview command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -430,7 +443,7 @@ class TestPViewsCommand(unittest.TestCase): view.__view_attr__ = 'call' view.__permission__ = 'test' multiview = dummy.DummyMultiView(view, context='context', view_name='a') - command._find_view = lambda arg1, arg2: multiview + command._find_view = lambda arg1: multiview command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) @@ -454,7 +467,7 @@ class TestPViewsCommand(unittest.TestCase): view.__view_attr__ = 'call' view.__predicates__ = [predicate] multiview = dummy.DummyMultiView(view, context='context', view_name='a') - command._find_view = lambda arg1, arg2: multiview + command._find_view = lambda arg1: multiview command.args = ('/foo/bar/myapp.ini#myapp', '/a') result = command.run() self.assertEqual(result, 0) |
