diff options
| author | Chris McDonough <chrism@plope.com> | 2011-12-04 16:16:44 -0500 |
|---|---|---|
| committer | Chris McDonough <chrism@plope.com> | 2011-12-04 16:16:44 -0500 |
| commit | 549cf70449226539bd5b5db48fce3a6095c26cd9 (patch) | |
| tree | 83f4da2e3c0265e9b7a81ab6505d8c37874384b5 | |
| parent | 38e6b4012164eec480ca9604e68d737bff83b68e (diff) | |
| download | pyramid-549cf70449226539bd5b5db48fce3a6095c26cd9.tar.gz pyramid-549cf70449226539bd5b5db48fce3a6095c26cd9.tar.bz2 pyramid-549cf70449226539bd5b5db48fce3a6095c26cd9.zip | |
change the ActionInfo interface to match ZCML's ParserInfo interface
| -rw-r--r-- | pyramid/config/__init__.py | 4 | ||||
| -rw-r--r-- | pyramid/config/util.py | 37 | ||||
| -rw-r--r-- | pyramid/interfaces.py | 23 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_init.py | 2 | ||||
| -rw-r--r-- | pyramid/tests/test_config/test_util.py | 28 |
5 files changed, 67 insertions, 27 deletions
diff --git a/pyramid/config/__init__.py b/pyramid/config/__init__.py index 3ffbdbb47..3b15e8ef2 100644 --- a/pyramid/config/__init__.py +++ b/pyramid/config/__init__.py @@ -488,13 +488,13 @@ class Configurator( @property def action_info(self): - info = self.info # usually a ZCML action if self.info has data + info = self.info # usually a ZCML action (ParserInfo) if self.info if not info: # Try to provide more accurate info for conflict reports if self._ainfo: info = self._ainfo[0] else: - info = ActionInfo('<unknown>', 0, '<unknown>', '<unknown>') + info = ActionInfo(None, 0, '', '') return info def action(self, discriminator, callable=None, args=(), kw=None, order=0, diff --git a/pyramid/config/util.py b/pyramid/config/util.py index 3fcb5d154..b65e44725 100644 --- a/pyramid/config/util.py +++ b/pyramid/config/util.py @@ -1,7 +1,10 @@ -import collections import re import traceback +from zope.interface import implementer + +from pyramid.interfaces import IActionInfo + from pyramid.compat import ( string_types, bytes_, @@ -20,19 +23,27 @@ from hashlib import md5 MAX_ORDER = 1 << 30 DEFAULT_PHASH = md5().hexdigest() -_ActionInfo = collections.namedtuple( - 'ActionInfo', - ('filename', 'lineno', 'function', 'linerepr') - ) +@implementer(IActionInfo) +class ActionInfo(object): + def __init__(self, file, line, function, src): + line = line or 0 + src = src or '' + ssrc = src.strip() + column = src.rfind(ssrc) + eline = line + len(src.split('\n')) + ecolumn = len(src.split('\n')[-1]) + srclines = src.split('\n') + src = '\n'.join(' %s' % x for x in srclines) + self._src = src + self.file = file + self.line = line + self.column = column + self.eline = eline + self.ecolumn = ecolumn + self.function = function -class ActionInfo(_ActionInfo): - # this is a namedtuple subclass for (minor) backwards compat - slots = () def __str__(self): - return ( - 'Line %s of file %s in %s: %r' % ( - self.lineno, self.filename, self.function, self.linerepr) - ) + return 'Line %s of file %s:\n%s' % (self.line, self.file, self._src) def action_method(wrapped): """ Wrapper to provide the right conflict info report data when a method @@ -46,7 +57,7 @@ def action_method(wrapped): f = traceback.extract_stack(limit=3) info = ActionInfo(*f[-2]) except: # pragma: no cover - info = ActionInfo('<unknown>', 0, '<unknown>', '<unknown>') + info = ActionInfo(None, 0, '', '') self._ainfo.append(info) try: result = wrapped(self, *arg, **kw) diff --git a/pyramid/interfaces.py b/pyramid/interfaces.py index 559d3c110..2c096cf40 100644 --- a/pyramid/interfaces.py +++ b/pyramid/interfaces.py @@ -1001,13 +1001,22 @@ class IIntrospectable(Interface): """ class IActionInfo(Interface): - filename = Attribute('filename of action-invoking code as a string') - lineno = Attribute('line number in file (as an integer) of action-invoking ' - 'code') - function = Attribute('a string representing the module, function or method ' - 'that enclosed the line which invoked the action') - linerepr = Attribute('a string representing the source code line ' - 'which invoked the action') + """ Class which provides code introspection capability associated with an + action. The ParserInfo class used by ZCML implements the same interface.""" + file = Attribute( + 'filename of action-invoking code as a string') + line = Attribute( + 'starting line number in file (as an integer) of action-invoking code') + column = Attribute( + 'start column number in file (as an integer) of action-invoking code') + eline = Attribute( + 'ending line number in file (as an integer) of action-invoking code') + ecolumn = Attribute( + 'ending column number in file (as an integer) of action-invoking code') + + def __str__(): + """ Return a representation of the action information (including + source code from file, if possible) """ # configuration phases: a lower phase number means the actions associated # with this phase will be executed earlier than those with later phase diff --git a/pyramid/tests/test_config/test_init.py b/pyramid/tests/test_config/test_init.py index fc44908d7..c2b63dfc0 100644 --- a/pyramid/tests/test_config/test_init.py +++ b/pyramid/tests/test_config/test_init.py @@ -1925,7 +1925,7 @@ def _conflictFunctions(e): conflicts = e._conflicts.values() for conflict in conflicts: for confinst in conflict: - yield confinst[2] + yield confinst.function class DummyActionState(object): autocommit = False diff --git a/pyramid/tests/test_config/test_util.py b/pyramid/tests/test_config/test_util.py index 1225b3e21..31aa7f77a 100644 --- a/pyramid/tests/test_config/test_util.py +++ b/pyramid/tests/test_config/test_util.py @@ -313,14 +313,34 @@ class Test__make_predicates(unittest.TestCase): self.assertEqual(hash1, hash2) class TestActionInfo(unittest.TestCase): - def _makeOne(self, filename, lineno, function, linerepr): + def _getTargetClass(self): from pyramid.config.util import ActionInfo - return ActionInfo(filename, lineno, function, linerepr) + return ActionInfo + + def _makeOne(self, filename, lineno, function, linerepr): + return self._getTargetClass()(filename, lineno, function, linerepr) + + def test_class_conforms(self): + from zope.interface.verify import verifyClass + from pyramid.interfaces import IActionInfo + verifyClass(IActionInfo, self._getTargetClass()) + + def test_instance_conforms(self): + from zope.interface.verify import verifyObject + from pyramid.interfaces import IActionInfo + verifyObject(IActionInfo, self._makeOne('f', 0, 'f', 'f')) + + def test_ctor(self): + inst = self._makeOne('filename', 10, 'function', ' linerepr\n\nfoo') + self.assertEqual(inst.line, 10) + self.assertEqual(inst.column, 2) + self.assertEqual(inst.eline, 13) + self.assertEqual(inst.ecolumn, 3) def test___str__(self): - inst = self._makeOne('filename', 'lineno', 'function', 'linerepr') + inst = self._makeOne('filename', 0, 'function', ' linerepr ') self.assertEqual(str(inst), - "Line lineno of file filename in function: 'linerepr'") + "Line 0 of file filename:\n linerepr ") class DummyCustomPredicate(object): def __init__(self): |
