diff options
| author | Ben Bangert <ben@groovie.org> | 2010-11-18 14:13:14 -0800 |
|---|---|---|
| committer | Ben Bangert <ben@groovie.org> | 2010-11-18 14:13:14 -0800 |
| commit | c1a3d2687cd27e85af95110f72c068a877cb662c (patch) | |
| tree | 7359e63f728e342d93e227950764a6288aa7f626 | |
| parent | e84116c068f45c68752f89062fa545dd40acd63f (diff) | |
| parent | a66593d25e77f1a0e749f5590b45498bbaa66755 (diff) | |
| download | pyramid-c1a3d2687cd27e85af95110f72c068a877cb662c.tar.gz pyramid-c1a3d2687cd27e85af95110f72c068a877cb662c.tar.bz2 pyramid-c1a3d2687cd27e85af95110f72c068a877cb662c.zip | |
Merge branch 'master' of github.com:Pylons/pyramid
| -rw-r--r-- | .gitignore | 9 | ||||
| -rw-r--r-- | CHANGES.txt | 15 | ||||
| -rw-r--r-- | TODO.txt | 141 | ||||
| -rw-r--r-- | pyramid/authentication.py | 6 | ||||
| -rw-r--r-- | pyramid/configuration.py | 18 | ||||
| -rw-r--r-- | pyramid/request.py | 3 | ||||
| -rw-r--r-- | pyramid/tests/test_authentication.py | 6 | ||||
| -rw-r--r-- | pyramid/tests/test_configuration.py | 9 | ||||
| -rw-r--r-- | pyramid/tests/test_traversal.py | 26 | ||||
| -rw-r--r-- | pyramid/traversal.py | 13 | ||||
| -rw-r--r-- | setup.py | 2 |
11 files changed, 163 insertions, 85 deletions
diff --git a/.gitignore b/.gitignore index 5d46de01f..706f6493d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,10 @@ *.pt.py *.txt.py .coverage -env26 -env24 -env27 -jyenv +env26/ +env24/ +env27/ +jyenv/ +pypyenv/ build/ dist/ diff --git a/CHANGES.txt b/CHANGES.txt index b418565fa..4d51b89f5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -17,6 +17,9 @@ Features - Add a ``pyramid.request.Request.route_path`` API. This is a convenience method of the request which calls ``pyramid.url.route_url``. +- Make test suite pass on Jython (requires PasteScript trunk, presumably to + be 1.7.4). + Bug Fixes --------- @@ -30,6 +33,18 @@ Bug Fixes - The ``pyramid_alchemy`` paster template had a typo, preventing an import from working. +- Fix apparent failures when calling ``pyramid.traversal.find_model(root, + path)`` or ``pyramid.traversal.traverse(path)`` when ``path`` is + (erroneously) a Unicode object. The user is meant to pass these APIs a + string object, never a Unicode object. In practice, however, users indeed + pass Unicode. Because the string that is passed must be ASCII encodeable, + now, if they pass a Unicode object, its data is eagerly converted to an + ASCII string rather than being passed along to downstream code as a + convenience to the user and to prevent puzzling second-order failures from + cropping up (all failures will occur within ``pyramid.traversal.traverse`` + rather than later down the line as the result of calling + ``traversal_path``). + Backwards Incompatibilities --------------------------- @@ -1,5 +1,80 @@ -:mod:`repoze.bfg` TODOs -======================= +Pyramid TODOs +============= + +Must-Have (before 1.0) +---------------------- + +- Add a ``handler`` ZCML directive. This implies some slightly dicey + refactoring of the configurator to allow it to generate ZCML + "discriminators" for views and routes. + +- Provide a .flash API on session object. + +- Make default renderer work (renderer registered with no name, which is + active for every view unless the view names a specific renderer). + +- Use ``@register_view`` instead of ``@view_config`` and change view docs to + use "view registration" instead of "view configuration". + +- Remove calls to config.begin()/config.end() from startup config code in + tutorials and paster templates (no longer required). + +- SQLAlchemy idiomatics: + + <RaFromBRC> mcdonc: those paster templates all look pretty good... the + only thing i'd consider is adjusting your config variable names to match + exactly what sqlalchemy uses as parameter names, see here: + http://www.sqlalchemy.org/docs/core/engines.html + + <RaFromBRC> mcdonc: especially in the pylons_sqla ini file, where the db + initialization is mixed in w/ the app config... + + <RaFromBRC> ... i'd use "sqlalchemy.PARAMETER" for all of the sqla + settings, so it could easily be handed to engine_from_config w/o any need + to parse by hand + + <RaFromBRC> mcdonc: in the other ini files, where sqlalchemy is given its + own part, the "sqlalchemy." prefix probably isn't necessary, but matching + the parameter names (e.g. 'url' instead of 'db_string') is still probably + a good idea + +- Non-bwcompat use of threadlocals that need to be documented or ameliorated: + + security.principals_allowed_by_permission + + resource.OverrideProvider._get_overrides: can't credibly be removed, + because it stores an overrideprovider as a module-scope global. + + traversal.traverse: this API is a stepchild, and needs to be changed. + + Configurator.add_translation_dirs: not passed any context but a message, + can't credibly be removed. + +- Better ``config.add_handler`` documentation. + +Should-Have +----------- + +- Try to make test suite pass on PyPy, IronPython. + +- Add docs for httpexceptions module for each webob.exc class that inherits + from WSGIHTTPException. + +- Create a ``docs`` directory for each paster template. + +- Remove "BFG" from Pyramid-specific environ variables. + +- translationdir ZCML directive use of ``path_spec`` should maybe die. + +- Add CRSF token creation/checking machinery (only "should have" vs. "must + have" because I'm not sure it belongs in Pyramid.. it definitely must exist + in formgen libraries, and *might* belong in Pyramid). + +- Change "Cleaning up After a Request" in the urldispatch chapter to + use ``request.add_response_callback``. + +Nice-to-Have +------------ - Supply ``X-Vhm-Host`` support. @@ -50,65 +125,7 @@ - Raise an exception when a value in response_headerlist is not a string or decide to encode. -- Change "Cleaning up After a Request" in the urldispatch chapter to - use ``request.add_response_callback``. - -- Update App engine chapter. - -- Browser id? - -- .flash API on session. - -- CRSF token machinery - -- ``add_handler`` documentation. - -- ``handler`` ZCML directive. - -- ``docs`` directory for each paster template. - -- "BFG" in environ variables. - -- Test on GAE, Jython, PyPy, IronPython. +- Update App engine chapter with less creaky directions. -- Add docs for httpexceptions. +- Add functionality that mocks the behavior of ``repoze.browserid``. -- RendererHelper -> RendererInfo? - -- translationdir ZCML directive use of ``path_spec`` should maybe die. - -- SQLAlchemy idiomatics: - - <RaFromBRC> mcdonc: those paster templates all look pretty good... the - only thing i'd consider is adjusting your config variable names to match - exactly what sqlalchemy uses as parameter names, see here: - http://www.sqlalchemy.org/docs/core/engines.html - - <RaFromBRC> mcdonc: especially in the pylons_sqla ini file, where the db - initialization is mixed in w/ the app config... - - <RaFromBRC> ... i'd use "sqlalchemy.PARAMETER" for all of the sqla - settings, so it could easily be handed to engine_from_config w/o any need - to parse by hand - - <RaFromBRC> mcdonc: in the other ini files, where sqlalchemy is given its - own part, the "sqlalchemy." prefix probably isn't necessary, but matching - the parameter names (e.g. 'url' instead of 'db_string') is still probably - a good idea - -- Default renderer. - -- Non-bwcompat use of threadlocals: - - security.principals_allowed_by_permission - - resource.OverrideProvider._get_overrides: can't credibly be removed, - because it stores an overrideprovider as a module-scope global. - - traversal.traverse: this API is a stepchild, and needs to be changed. - - Configurator.add_translation_dirs: not passed any context but a message, - can't credibly be removed. - -- Use ``@register_view`` instead of ``@view_config`` and change view docs to - use "view registration" instead of "view configuration". diff --git a/pyramid/authentication.py b/pyramid/authentication.py index 4849d2c41..86d725bcf 100644 --- a/pyramid/authentication.py +++ b/pyramid/authentication.py @@ -286,6 +286,7 @@ EXPIRE = object() class AuthTktCookieHelper(object): auth_tkt = auth_tkt # for tests + now = None # for tests userid_type_decoders = { 'int':int, @@ -373,7 +374,10 @@ class AuthTktCookieHelper(object): except self.auth_tkt.BadTicket: return None - now = time.time() + now = self.now # service tests + + if now is None: + now = time.time() if self.timeout and ( (timestamp + self.timeout) < now ): return None diff --git a/pyramid/configuration.py b/pyramid/configuration.py index 41b774e65..543f8f82d 100644 --- a/pyramid/configuration.py +++ b/pyramid/configuration.py @@ -24,6 +24,8 @@ from pyramid.interfaces import IChameleonTranslate from pyramid.interfaces import IDebugLogger from pyramid.interfaces import IDefaultPermission from pyramid.interfaces import IDefaultRootFactory +from pyramid.interfaces import IException +from pyramid.interfaces import IExceptionResponse from pyramid.interfaces import IExceptionViewClassifier from pyramid.interfaces import ILocaleNegotiator from pyramid.interfaces import IMultiView @@ -36,34 +38,32 @@ from pyramid.interfaces import IRootFactory from pyramid.interfaces import IRouteRequest from pyramid.interfaces import IRoutesMapper from pyramid.interfaces import ISecuredView +from pyramid.interfaces import ISessionFactory from pyramid.interfaces import IStaticURLInfo from pyramid.interfaces import ITranslationDirectories from pyramid.interfaces import ITraverser from pyramid.interfaces import IView from pyramid.interfaces import IViewClassifier -from pyramid.interfaces import IExceptionResponse -from pyramid.interfaces import IException -from pyramid.interfaces import ISessionFactory from pyramid import chameleon_text from pyramid import chameleon_zpt -from pyramid.mako_templating import renderer_factory as mako_renderer_factory from pyramid import renderers -from pyramid.renderers import RendererHelper from pyramid.authorization import ACLAuthorizationPolicy from pyramid.compat import all from pyramid.compat import md5 from pyramid.events import ApplicationCreated +from pyramid.exceptions import ConfigurationError from pyramid.exceptions import Forbidden from pyramid.exceptions import NotFound from pyramid.exceptions import PredicateMismatch -from pyramid.exceptions import ConfigurationError from pyramid.i18n import get_localizer from pyramid.log import make_stream_logger +from pyramid.mako_templating import renderer_factory as mako_renderer_factory from pyramid.path import caller_package -from pyramid.path import package_path from pyramid.path import package_of +from pyramid.path import package_path from pyramid.registry import Registry +from pyramid.renderers import RendererHelper from pyramid.request import route_request_iface from pyramid.resource import PackageOverrides from pyramid.resource import resolve_resource_spec @@ -72,12 +72,12 @@ from pyramid.static import StaticURLInfo from pyramid.threadlocal import get_current_registry from pyramid.threadlocal import get_current_request from pyramid.threadlocal import manager -from pyramid.traversal import traversal_path from pyramid.traversal import DefaultRootFactory from pyramid.traversal import find_interface +from pyramid.traversal import traversal_path from pyramid.urldispatch import RoutesMapper -from pyramid.view import render_view_to_response from pyramid.view import default_exceptionresponse_view +from pyramid.view import render_view_to_response MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() diff --git a/pyramid/request.py b/pyramid/request.py index 43a4a3aa2..2f9ca5819 100644 --- a/pyramid/request.py +++ b/pyramid/request.py @@ -259,9 +259,6 @@ class Request(WebobRequest): scheme, and port) for a named :app:`Pyramid` :term:`route configuration`. - .. note:: Calling :meth:`pyramid.Request.route_path` can be used to - achieve the same result as :func:`pyramid.url.route_path`. - This is a convenience method. The result of calling :meth:`pyramid.request.Request.route_path` is the same as calling :func:`pyramid.url.route_path` with an explicit ``request`` diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py index 69762fdb0..d9d0c2c97 100644 --- a/pyramid/tests/test_authentication.py +++ b/pyramid/tests/test_authentication.py @@ -411,8 +411,10 @@ class TestAuthTktCookieHelper(unittest.TestCase): def test_identify_cookie_reissue(self): import time - plugin = self._makeOne('secret', timeout=5000, reissue_time=0) - plugin.auth_tkt.timestamp = time.time() + plugin = self._makeOne('secret', timeout=10, reissue_time=0) + now = time.time() + plugin.auth_tkt.timestamp = now + plugin.now = now + 1 request = self._makeRequest({'HTTP_COOKIE':'auth_tkt=bogus'}) result = plugin.identify(request) self.failUnless(result) diff --git a/pyramid/tests/test_configuration.py b/pyramid/tests/test_configuration.py index ded17cb33..0d8905f7c 100644 --- a/pyramid/tests/test_configuration.py +++ b/pyramid/tests/test_configuration.py @@ -2940,6 +2940,7 @@ class ConfiguratorTests(unittest.TestCase): pyramid.tests) def test_scan_integration(self): + import os from zope.interface import alsoProvides from pyramid.interfaces import IRequest from pyramid.view import render_view_to_response @@ -3011,8 +3012,12 @@ class ConfiguratorTests(unittest.TestCase): result = render_view_to_response(ctx, req, 'another_stacked_class2') self.assertEqual(result, 'another_stacked_class') - self.assertRaises(TypeError, - render_view_to_response, ctx, req, 'basemethod') + if not os.name.startswith('java'): + # on Jython, a class without an __init__ apparently accepts + # any number of arguments without raising a TypeError. + + self.assertRaises(TypeError, + render_view_to_response, ctx, req, 'basemethod') result = render_view_to_response(ctx, req, 'method1') self.assertEqual(result, 'method1') diff --git a/pyramid/tests/test_traversal.py b/pyramid/tests/test_traversal.py index 2deb5982c..3245d6302 100644 --- a/pyramid/tests/test_traversal.py +++ b/pyramid/tests/test_traversal.py @@ -522,6 +522,32 @@ class FindModelTests(unittest.TestCase): self.assertEqual(root.wascontext, True) self.assertEqual(root.request.environ['PATH_INFO'], '/') + def test_absolute_unicode_found(self): + # test for bug wiggy found in wild, traceback stack: + # root = u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF' + # wiggy's code: section=find_model(page, root) + # find_model L76: D = traverse(model, path) + # traverse L291: return traverser(request) + # __call__ line 568: vpath_tuple = traversal_path(vpath) + # lru_cached line 91: f(*arg) + # traversal_path line 443: path.encode('ascii') + # UnicodeEncodeError: 'ascii' codec can't encode characters in + # position 1-12: ordinal not in range(128) + # + # solution: encode string to ascii in pyramid.traversal.traverse + # before passing it along to webob as path_info + from pyramid.traversal import ModelGraphTraverser + unprintable = DummyContext() + root = DummyContext(unprintable) + unprintable.__parent__ = root + unprintable.__name__ = unicode( + '/\xe6\xb5\x81\xe8\xa1\x8c\xe8\xb6\x8b\xe5\x8a\xbf', 'utf-8') + root.__parent__ = None + root.__name__ = None + traverser = ModelGraphTraverser + self._registerTraverser(traverser) + result = self._callFUT(root, u'/%E6%B5%81%E8%A1%8C%E8%B6%8B%E5%8A%BF') + self.assertEqual(result, unprintable) class ModelPathTests(unittest.TestCase): def _callFUT(self, model, *elements): diff --git a/pyramid/traversal.py b/pyramid/traversal.py index e928c33f7..fb73ad906 100644 --- a/pyramid/traversal.py +++ b/pyramid/traversal.py @@ -228,7 +228,7 @@ def traverse(model, path): object supplied to the function as the ``model`` argument. If an empty string is passed as ``path``, the ``model`` passed in will be returned. Model path strings must be escaped in the following - manner: each Unicode path segment must be encoded as UTF-8 and as + manner: each Unicode path segment must be encoded as UTF-8 and each path segment must escaped via Python's :mod:`urllib.quote`. For example, ``/path/to%20the/La%20Pe%C3%B1a`` (absolute) or ``to%20the/La%20Pe%C3%B1a`` (relative). The @@ -272,6 +272,17 @@ def traverse(model, path): else: path = '' + # The user is supposed to pass us a string object, never Unicode. In + # practice, however, users indeed pass Unicode to this API. If they do + # pass a Unicode object, its data *must* be entirely encodeable to ASCII, + # so we encode it here as a convenience to the user and to prevent + # second-order failures from cropping up (all failures will occur at this + # step rather than later down the line as the result of calling + # ``traversal_path``). + + if isinstance(path, unicode): + path = path.encode('ascii') + if path and path[0] == '/': model = find_root(model) @@ -47,7 +47,7 @@ install_requires=[ if platform.system() == 'Java': tests_require = install_requires + ['WebTest'] else: - tests_require= install_requires + ['Sphinx', 'docutils', 'coverage', + tests_require= install_requires + ['Sphinx', 'docutils', 'WebTest', 'repoze.sphinx.autointerface'] if sys.version_info[:2] < (2, 6): |
