Permalink
Browse files

- Add ``pyramid.handlers.method_name_xformer`` setting, which allows …

…the view

  associated with a handler action to match when a converted action name is
  found in the URL (as opposed to the original method name).  See this
  version's docs for more info.

- Use submodule for docs theme.

- Modify tox testing scheme.
  • Loading branch information...
1 parent 1eb9fa1 commit 4c5dbf0890479807dc65c78680d80684afee3ca3 @mcdonc mcdonc committed Oct 25, 2011
Showing with 196 additions and 35 deletions.
  1. +3 −0 .gitmodules
  2. +12 −0 CHANGES.txt
  3. +0 −1 docs/.gitignore
  4. +1 −1 docs/Makefile
  5. +1 −0 docs/_themes
  6. +4 −0 docs/conf.py
  7. +82 −0 docs/index.rst
  8. +8 −2 pyramid_handlers/__init__.py
  9. +64 −20 pyramid_handlers/tests.py
  10. +21 −11 tox.ini
View
@@ -0,0 +1,3 @@
+[submodule "docs/_themes"]
+ path = docs/_themes
+ url = git://github.com/Pylons/pylons_sphinx_theme.git
View
@@ -1,3 +1,15 @@
+Next release
+------------
+
+- Add ``pyramid.handlers.method_name_xformer`` setting, which allows the view
+ associated with a handler action to match when a converted action name is
+ found in the URL (as opposed to the original method name). See this
+ version's docs for more info.
+
+- Use submodule for docs theme.
+
+- Modify tox testing scheme.
+
0.3 (2011-08-24)
----------------
View
@@ -1,2 +1 @@
-_themes
_build/
View
@@ -85,4 +85,4 @@ epub:
@echo "Build finished. The epub file is in _build/epub."
_themes:
- git clone git://github.com/Pylons/pylons_sphinx_theme.git _themes
+ cd ..; git submodule update --init; cd docs
Submodule _themes added at 03e5e5
View
@@ -75,6 +75,10 @@
# List of documents that shouldn't be included in the build.
#unused_docs = []
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = ['_themes/README.rst',]
+
# List of directories, relative to source directories, that shouldn't be
# searched for source files.
#exclude_dirs = []
View
@@ -347,6 +347,88 @@ When the ``index`` method of the above example handler is invoked, it will
raise ``MySpecialException``. As a result, the action decorator will cath
this exception and turn it into a response.
+Configuration Knobs
+-------------------
+
+If your handler action methods that have characters in them (such as
+underscores) that you don't find appropriate in a URL, such as
+``a_method_with_underscores``:
+
+.. code-block:: python
+ :linenos:
+
+ # in a module named mypackage.handlers
+
+ from pyramid_handlers import action
+
+ class AHandler(object):
+ def __init__(self, request):
+ self.request = request
+
+ @action(renderer='some/renderer.pt')
+ def a_method_with_underscores(self):
+ return {}
+
+And there is some regular transform you can perform against all action method
+registrations (such as converting the underscores to dashes), you can define
+a "method name transformer":
+
+.. code-block:: python
+ :linenos:
+
+ # in the same module named mypackage.handlers
+
+ def transformer(method_name):
+ return method_name.replace('_', '-')
+
+You can then use the method name transformer in your Pyramid ``settings`` via
+the `.ini`` file:
+
+.. code-block:: ini
+ :linenos:
+
+ [app:myapp]
+ ...
+ pyramid_handlers.method_name_xformer = mypackage.handlers.transformer
+
+Or directly in your ``main()`` function:
+
+.. code-block:: python
+ :linenos:
+
+ # in a module named mypackage.handlers
+
+ from mypackage.handlers import transformer
+
+ def main(global_conf, *settings):
+ settings['pyramid_handlers.method_name_xformer'] = transformer
+ config = Configurator(settings=settings)
+ # .. rest of configuration ...
+
+Once you've set up a method name transformer, any ``{action}`` substitution
+in the pattern associated with a handler will be matched against the
+transformed method name value instead of the untransformed method name value:
+
+.. code-block:: python
+ :linenos:
+
+ # in a module named mypackage.handlers
+
+ from mypackage.handlers import transformer
+ from mypackage.handlers import AHandler
+
+ def main(global_conf, *settings):
+ settings['pyramid_handlers.method_name_xformer'] = transformer
+ config = Configurator(settings=settings)
+ config.add_handler('ahandler', '/ahandler/{action}', handler=AHandler)
+ # .. rest of configuration ...
+
+Now, when ``/ahandler/a-method-with-underscores`` is visited, it will invoke
+the ``AHandler.a_method_with_underscores`` method. Note that
+``/ahandler/a_method_with_underscores`` will however no longer work to invoke
+the method.
+
+
More Information
----------------
@@ -84,6 +84,9 @@ def add_handler(self, route_name, pattern, handler, action=None, **kw):
def scan_handler(config, handler, route_name, action_decorator,
**default_view_args):
"""Scan a handler for automatically exposed views to register"""
+ xformer = config.registry.settings.get(
+ 'pyramid_handlers.method_name_xformer')
+ xformer = config.maybe_dotted(xformer)
autoexpose = getattr(handler, '__autoexpose__', r'[A-Za-z]+')
if autoexpose:
try:
@@ -100,7 +103,11 @@ def scan_handler(config, handler, route_name, action_decorator,
# so we copy each
view_args = default_view_args.copy()
view_args.update(expose_config.copy())
- action = view_args.pop('name', method_name)
+ action = view_args.pop('name', None)
+ if action is None:
+ action = method_name
+ if xformer is not None:
+ action = xformer(action)
preds = list(view_args.pop('custom_predicates', []))
preds.append(ActionPredicate(action))
view_args['custom_predicates'] = preds
@@ -202,7 +209,6 @@ def __call__(self, wrapped):
wrapped.__exposed__ = [self.kw]
return wrapped
-
def includeme(config):
config.add_directive('add_handler', add_handler)
View
@@ -43,6 +43,43 @@ def dummy_add_view(**kw):
self.assertEqual(view['attr'], 'action2')
self.assertEqual(view['view'], DummyHandler)
+ def test_add_handler_action_in_route_pattern_with_xformer(self):
+ config = self._makeOne()
+ def x(name):
+ return name.upper()
+ config.registry.settings['pyramid_handlers.method_name_xformer'] = x
+ views = []
+ def dummy_add_view(**kw):
+ views.append(kw)
+ config.add_view = dummy_add_view
+ config.add_handler('name', '/:action', DummyHandler)
+ self._assertRoute(config, 'name', '/:action', 0)
+ self.assertEqual(len(views), 2)
+
+ view = views[0]
+ preds = view['custom_predicates']
+ self.assertEqual(len(preds), 1)
+ pred = preds[0]
+ request = testing.DummyRequest()
+ self.assertEqual(pred(None, request), False)
+ request.matchdict = {'action':'ACTION1'}
+ self.assertEqual(pred(None, request), True)
+ self.assertEqual(view['route_name'], 'name')
+ self.assertEqual(view['attr'], 'action1')
+ self.assertEqual(view['view'], DummyHandler)
+
+ view = views[1]
+ preds = view['custom_predicates']
+ self.assertEqual(len(preds), 1)
+ pred = preds[0]
+ request = testing.DummyRequest()
+ self.assertEqual(pred(None, request), False)
+ request.matchdict = {'action':'ACTION2'}
+ self.assertEqual(pred(None, request), True)
+ self.assertEqual(view['route_name'], 'name')
+ self.assertEqual(view['attr'], 'action2')
+ self.assertEqual(view['view'], DummyHandler)
+
def test_add_handler_with_view_overridden_autoexpose_None(self):
config = self._makeOne()
views = []
@@ -357,13 +394,12 @@ def _assertRoute(self, config, name, path, num_predicates=0):
def test_conflict_add_handler(self):
class AHandler(object):
def aview(self): pass
- from zope.configuration.config import ConfigurationConflictError
config = self._makeOne(autocommit=False)
config.add_handler('h1', '/h1', handler=AHandler)
config.add_handler('h1', '/h1', handler=AHandler)
try:
config.commit()
- except ConfigurationConflictError, why:
+ except Exception, why:
c = list(self._conflictFunctions(why))
self.assertEqual(c[0], 'test_conflict_add_handler')
self.assertEqual(c[1], 'test_conflict_add_handler')
@@ -443,7 +479,7 @@ def test_call_no_previous__exposed__(self):
def wrapped():
""" """
result = inst(wrapped)
- self.failUnless(result is wrapped)
+ self.assertTrue(result is wrapped)
self.assertEqual(result.__exposed__, [{'a':1, 'b':2}])
def test_call_with_previous__exposed__(self):
@@ -452,13 +488,15 @@ def wrapped():
""" """
wrapped.__exposed__ = [None]
result = inst(wrapped)
- self.failUnless(result is wrapped)
+ self.assertTrue(result is wrapped)
self.assertEqual(result.__exposed__, [None, {'a':1, 'b':2}])
class TestHandlerDirective(unittest.TestCase):
def setUp(self):
self.config = testing.setUp(autocommit=False)
- self.config._ctx = self.config._make_context()
+ _ctx = self.config._ctx
+ if _ctx is None: # pragma: no cover ; will never be true under 1.2a5+
+ self.config._ctx = self.config._make_context()
def tearDown(self):
testing.tearDown()
@@ -467,26 +505,14 @@ def _callFUT(self, *arg, **kw):
from pyramid_handlers.zcml import handler
return handler(*arg, **kw)
- def _assertRoute(self, name, pattern, num_predicates=0):
- from pyramid.interfaces import IRoutesMapper
- reg = self.config.registry
- mapper = reg.getUtility(IRoutesMapper)
- routes = mapper.get_routes()
- route = routes[0]
- self.assertEqual(len(routes), 1)
- self.assertEqual(route.name, name)
- self.assertEqual(route.pattern, pattern)
- self.assertEqual(len(routes[0].predicates), num_predicates)
- return route
-
def test_it(self):
from pyramid_handlers import action
from zope.interface import Interface
from pyramid.interfaces import IView
from pyramid.interfaces import IViewClassifier
from pyramid.interfaces import IRouteRequest
reg = self.config.registry
- context = self.config._ctx
+ context = DummyZCMLContext(self.config)
class Handler(object): # pragma: no cover
def __init__(self, request):
self.request = request
@@ -502,7 +528,7 @@ def two(self):
request_type = reg.getUtility(IRouteRequest, 'name')
wrapped = reg.adapters.lookup(
(IViewClassifier, request_type, Interface), IView, name='')
- self.failUnless(wrapped)
+ self.assertTrue(wrapped)
def test_pattern_is_None(self):
from pyramid.exceptions import ConfigurationError
@@ -521,7 +547,7 @@ def test_it(self):
from pyramid_handlers import includeme
c = Configurator(autocommit=True)
c.include(includeme)
- self.failUnless(c.add_handler.im_func.__docobj__ is add_handler)
+ self.assertTrue(c.add_handler.im_func.__docobj__ is add_handler)
class DummyHandler(object): # pragma: no cover
def __init__(self, request):
@@ -553,3 +579,21 @@ def _execute_actions(actions):
if 'callable' in action:
if action['callable']:
action['callable']()
+
+class DummyZCMLContext(object):
+ def __init__(self, config):
+ if hasattr(config, '_make_context'): # pragma: no cover
+ # 1.0, 1.1 b/c
+ config._ctx = config._make_context()
+ self.registry = config.registry
+ self.package = config.package
+ self.autocommit = config.autocommit
+ self.route_prefix = getattr(config, 'route_prefix', None)
+ self.basepath = getattr(config, 'basepath', None)
+ self.includepath = getattr(config, 'includepath', ())
+ self.info = getattr(config, 'info', '')
+ self.actions = config._ctx.actions
+ self._ctx = config._ctx
+
+ def action(self, *arg, **kw): # pragma: no cover
+ self._ctx.action(*arg, **kw)
View
32 tox.ini
@@ -1,25 +1,23 @@
[tox]
envlist =
- py25,py26,py27,jython,pypy,cover
+ py25,py26,py27,pypy,jython,cover
+
+# 2.6+-based systems can nominally use the latest pyramid release
[testenv]
commands =
- python setup.py test -q
+ python -Wd setup.py test -q
deps =
pyramid
pyramid_zcml
Sphinx
repoze.sphinx.autointerface
-[testenv:jython]
-commands =
- jython setup.py test -q
-
[testenv:cover]
basepython =
python2.6
commands =
- python setup.py nosetests --with-xunit --with-xcoverage
+ python -Wd setup.py nosetests --with-xunit --with-xcoverage
deps =
pyramid
pyramid_zcml
@@ -29,8 +27,20 @@ deps =
coverage==3.4
nosexcover
-# we separate coverage into its own testenv because a) "last run wins" wrt
-# cobertura jenkins reporting and b) pypy and jython can't handle any
-# combination of versions of coverage and nosexcover that i can find.
-# coverage==3.4 is required by nosexcover.
+# 2.5-based systems require fixed (potentially older) pyramid and webob setups
+[testenv:py25]
+deps =
+ https://github.com/Pylons/webob/zipball/1.1-branch
+ https://github.com/Pylons/pyramid/zipball/1.2-branch
+ pyramid_zcml
+ Sphinx
+ repoze.sphinx.autointerface
+
+[testenv:jython]
+commands =
+ jython setup.py test -q
+deps =
+ https://github.com/Pylons/webob/zipball/1.1-branch
+ https://github.com/Pylons/pyramid/zipball/1.2-branch
+ pyramid_zcml

0 comments on commit 4c5dbf0

Please sign in to comment.