summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris McDonough <chrism@plope.com>2011-12-04 16:16:44 -0500
committerChris McDonough <chrism@plope.com>2011-12-04 16:16:44 -0500
commit549cf70449226539bd5b5db48fce3a6095c26cd9 (patch)
tree83f4da2e3c0265e9b7a81ab6505d8c37874384b5
parent38e6b4012164eec480ca9604e68d737bff83b68e (diff)
downloadpyramid-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__.py4
-rw-r--r--pyramid/config/util.py37
-rw-r--r--pyramid/interfaces.py23
-rw-r--r--pyramid/tests/test_config/test_init.py2
-rw-r--r--pyramid/tests/test_config/test_util.py28
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):