Permalink
Browse files

Merge branch 'master' of github.com:tzicatl/pyramid_handlers

  • Loading branch information...
mcdonc committed Mar 20, 2012
2 parents 9d42ac2 + a90bf80 commit f9224771317a90987ff7064a42c4744e0e9ad0a0
Showing with 237 additions and 189 deletions.
  1. +1 −1 .gitignore
  2. +13 −0 CHANGES.txt
  3. +3 −0 docs/index.rst
  4. +8 −1 docs/zcml.rst
  5. +19 −18 pyramid_handlers/__init__.py
  6. +91 −66 pyramid_handlers/tests.py
  7. +84 −72 pyramid_handlers/zcml.py
  8. +10 −6 setup.py
  9. +8 −25 tox.ini
View
@@ -1,4 +1,4 @@
-env26/
+env*/
*.egg
*.egg-info
*.pyc
View
@@ -1,3 +1,16 @@
+Next release
+------------
+
+- Python 3.2 compatibility (sans ZCML support; ``pyramid_zcml`` does not work
+ under Python 3).
+
+- Removed Python 2.5 compatibility. This also means Jython compatibility is
+ nonexistent. If you need Python 2.5 compatibility, use 0.4.
+
+- No longer raises a ConfigurationError when an ``__action_decorator__`` is
+ an instance method (there's no distinction between methods and functions
+ under Python 3).
+
0.4 (2011-11-25)
----------------
View
@@ -10,6 +10,9 @@ Pyramid *url dispatch* and method introspection of a view class that makes it
easier to create bundles of view logic which reacts to particular route
patterns.
+``pyramid_handlers`` works under Python 2.6 and 2.7. It also works under
+Pyton 3.2, but :term:`ZCML` support is not available under Python 3.2.
+
Installation
------------
View
@@ -5,7 +5,14 @@ Configuring a Handler via ZCML
Instead of using the imperative
:meth:`pyramid.config.Configurator.add_handler` method to add a new
-route, you can alternately use :term:`ZCML`. :ref:`handler_directive`
+route, you can alternately use :term:`ZCML`.
+
+.. warning::
+
+ ZCML works under Python 2.6 and 2.7; it, however, does not work under
+ Python 3.2 or any other version of Python 3.
+
+:ref:`handler_directive`
statements in a :term:`ZCML` file used by your application is a sign that
you're using :term:`URL dispatch`. For example, the following :term:`ZCML
declaration` causes a route to be added to the application.
@@ -1,8 +1,11 @@
import inspect
import re
+import sys
from pyramid.exceptions import ConfigurationError
+PY3 = sys.version_info[0] == 3
+
action_re = re.compile(r'''({action}|:action)''')
def add_handler(self, route_name, pattern, handler, action=None, **kw):
@@ -48,18 +51,6 @@ def add_handler(self, route_name, pattern, handler, action=None, **kw):
handler = self.maybe_dotted(handler)
action_decorator = getattr(handler, '__action_decorator__', None)
- if action_decorator is not None:
- if hasattr(action_decorator, 'im_self'):
- # instance methods have an im_self == None
- # classmethods have an im_self == cls
- # staticmethods have no im_self
- # instances have no im_self
- if action_decorator.im_self is not handler:
- raise ConfigurationError(
- 'The "__action_decorator__" attribute of a handler '
- 'must not be an instance method (must be a '
- 'staticmethod, classmethod, function, or an instance '
- 'which is a callable')
action_pattern = action_re.search(pattern)
if action and action_pattern:
@@ -91,9 +82,10 @@ def scan_handler(config, handler, route_name, action_decorator,
if autoexpose:
try:
autoexpose = re.compile(autoexpose).match
- except (re.error, TypeError), why:
- raise ConfigurationError(why[0])
- for method_name, method in inspect.getmembers(handler, inspect.ismethod):
+ except (re.error, TypeError) as why:
+ raise ConfigurationError(why.args[0])
+ method_info = get_method_info(handler)
+ for method_name, method in method_info:
configs = getattr(method, '__exposed__', [])
if autoexpose and not configs:
if autoexpose(method_name):
@@ -125,7 +117,8 @@ def locate_view_by_name(config, handler, route_name, action_decorator, name,
method_name = '__call__'
# Scan the controller for any other methods with this action name
- for attr, method in inspect.getmembers(handler, inspect.ismethod):
+ method_info = get_method_info(handler)
+ for attr, method in method_info:
configs = getattr(method, '__exposed__', [{}])
for expose_config in configs:
# Don't re-register the same view if this method name is
@@ -168,8 +161,8 @@ def __init__(self, action):
self.action = action
try:
self.action_re = re.compile(action + '$')
- except (re.error, TypeError), why:
- raise ConfigurationError(why[0])
+ except (re.error, TypeError) as why:
+ raise ConfigurationError(why.args[0])
def __call__(self, context, request):
matchdict = request.matchdict
@@ -209,6 +202,14 @@ def __call__(self, wrapped):
wrapped.__exposed__ = [self.kw]
return wrapped
+def get_method_info(cls):
+ if PY3: # pragma: no cover
+ # no unbound methods in Py3
+ method_info = inspect.getmembers(cls, inspect.isfunction)
+ else:
+ method_info = inspect.getmembers(cls, inspect.ismethod)
+ return method_info
+
def includeme(config):
config.add_directive('add_handler', add_handler)
View
@@ -1,5 +1,9 @@
+import sys
import unittest
from pyramid import testing
+from pyramid.config import Configurator
+
+PY3 = sys.version_info[0] == 3
class Test_add_handler(unittest.TestCase):
def _makeOne(self, autocommit=True):
@@ -208,8 +212,7 @@ def __action_decorator__(self, fn): # pragma: no cover
return fn
def action(self): # pragma: no cover
return 'response'
- from pyramid.exceptions import ConfigurationError
- self.assertRaises(ConfigurationError, config.add_handler,
+ self.assertRaises(TypeError, config.add_handler,
'name', '/{action}', MyHandler)
def test_add_handler_doesnt_mutate_expose_dict(self):
@@ -399,7 +402,7 @@ def aview(self): pass
config.add_handler('h1', '/h1', handler=AHandler)
try:
config.commit()
- except Exception, why:
+ except Exception as why:
c = list(self._conflictFunctions(why))
self.assertEqual(c[0], 'test_conflict_add_handler')
self.assertEqual(c[1], 'test_conflict_add_handler')
@@ -412,7 +415,11 @@ def _conflictFunctions(self, e):
conflicts = e._conflicts.values()
for conflict in conflicts:
for confinst in conflict:
- yield confinst[2]
+ try:
+ # pyramid 1.2
+ yield confinst[2]
+ except TypeError:
+ yield confinst.function
class TestActionPredicate(unittest.TestCase):
def _getTargetClass(self):
@@ -491,53 +498,54 @@ def 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)
- _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()
-
- def _callFUT(self, *arg, **kw):
- from pyramid_handlers.zcml import handler
- return handler(*arg, **kw)
-
- 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 = DummyZCMLContext(self.config)
- class Handler(object): # pragma: no cover
- def __init__(self, request):
- self.request = request
- action(renderer='json')
- def one(self):
- return 'OK'
- action(renderer='json')
- def two(self):
- return 'OK'
- self._callFUT(context, 'name', '/:action', Handler)
- actions = extract_actions(context.actions)
- _execute_actions(actions)
- request_type = reg.getUtility(IRouteRequest, 'name')
- wrapped = reg.adapters.lookup(
- (IViewClassifier, request_type, Interface), IView, name='')
- self.assertTrue(wrapped)
-
- def test_pattern_is_None(self):
- from pyramid.exceptions import ConfigurationError
-
- context = self.config._ctx
- class Handler(object):
- pass
- self.assertRaises(ConfigurationError, self._callFUT,
- context, 'name', None, Handler)
+if not PY3:
+ class TestHandlerDirective(unittest.TestCase):
+ def setUp(self):
+ self.config = testing.setUp(autocommit=False)
+ _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()
+
+ def _callFUT(self, *arg, **kw):
+ from pyramid_handlers.zcml import handler
+ return handler(*arg, **kw)
+
+ 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 = DummyZCMLContext(self.config)
+ class Handler(object): # pragma: no cover
+ def __init__(self, request):
+ self.request = request
+ action(renderer='json')
+ def one(self):
+ return 'OK'
+ action(renderer='json')
+ def two(self):
+ return 'OK'
+ self._callFUT(context, 'name', '/:action', Handler)
+ actions = extract_actions(context.actions)
+ _execute_actions(actions)
+ request_type = reg.getUtility(IRouteRequest, 'name')
+ wrapped = reg.adapters.lookup(
+ (IViewClassifier, request_type, Interface), IView, name='')
+ self.assertTrue(wrapped)
+
+ def test_pattern_is_None(self):
+ from pyramid.exceptions import ConfigurationError
+
+ context = self.config._ctx
+ class Handler(object):
+ pass
+ self.assertRaises(ConfigurationError, self._callFUT,
+ context, 'name', None, Handler)
class Test_includeme(unittest.TestCase):
@@ -547,7 +555,7 @@ def test_it(self):
from pyramid_handlers import includeme
c = Configurator(autocommit=True)
c.include(includeme)
- self.assertTrue(c.add_handler.im_func.__docobj__ is add_handler)
+ self.assertTrue(c.add_handler.__func__.__docobj__ is add_handler)
class DummyHandler(object): # pragma: no cover
def __init__(self, request):
@@ -559,28 +567,45 @@ def action1(self):
def action2(self):
return 'response 2'
-def extract_actions(native):
+try:
+ from pyramid.config import expand_action
+ dict_actions = True
+except ImportError: # pragma: no cover
from zope.configuration.config import expand_action
- L = []
- for action in native:
- (discriminator, callable, args, kw, includepath, info, order
- ) = expand_action(*action)
- d = {}
- d['discriminator'] = discriminator
- d['callable'] = callable
- d['args'] = args
- d['kw'] = kw
- d['order'] = order
- L.append(d)
- return L
-
+ dict_actions = False
+
+if dict_actions:
+ # pyramid 1.Xsomeversion uses dictionary-based actions; the imprecision
+ # of which is because i'm at a sprint and figuring out exactly which
+ # version is less important than keeping things moving, sorry.
+ def extract_actions(native):
+ return native
+
+else:
+ # some other version of pyramid uses tuple-based actions
+ def extract_actions(native): # pragma: no cover
+ L = []
+ for action in native:
+ (discriminator, callable, args, kw, includepath, info, order
+ ) = expand_action(*action)
+ d = {}
+ d['discriminator'] = discriminator
+ d['callable'] = callable
+ d['args'] = args
+ d['kw'] = kw
+ d['order'] = order
+ L.append(d)
+ return L
+
def _execute_actions(actions):
for action in sorted(actions, key=lambda x: x['order']):
if 'callable' in action:
if action['callable']:
action['callable']()
class DummyZCMLContext(object):
+ config_class = Configurator
+ introspection = False
def __init__(self, config):
if hasattr(config, '_make_context'): # pragma: no cover
# 1.0, 1.1 b/c
Oops, something went wrong.

0 comments on commit f922477

Please sign in to comment.