remove code.new() function and store lines directly into linecache.cache instead.
This avoids the need for custom code objects, improving compatibility for jython and pypy-c. --HG-- branch : trunk
This commit is contained in:
parent
8ba2a98e11
commit
379390a8aa
|
@ -11,6 +11,11 @@ Changes between 1.3.0 and 1.3.1
|
|||
- improve tracebacks presentation:
|
||||
- raises shows shorter more relevant tracebacks
|
||||
|
||||
- improve support for raises and other dynamically compiled code by
|
||||
manipulating python's linecache.cache instead of the previous
|
||||
rather hacky way of creating custom code objects. This makes
|
||||
it seemlessly work on Jython and PyPy at least.
|
||||
|
||||
Changes between 1.2.1 and 1.3.0
|
||||
==================================================
|
||||
|
||||
|
|
|
@ -151,7 +151,10 @@ else:
|
|||
return getattr(function, "__dict__", None)
|
||||
|
||||
def _getcode(function):
|
||||
return getattr(function, "func_code", None)
|
||||
try:
|
||||
return getattr(function, "__code__")
|
||||
except AttributeError:
|
||||
return getattr(function, "func_code", None)
|
||||
|
||||
def print_(*args, **kwargs):
|
||||
""" minimal backport of py3k print statement. """
|
||||
|
|
|
@ -23,64 +23,14 @@ class Code(object):
|
|||
def __ne__(self, other):
|
||||
return not self == other
|
||||
|
||||
def new(self, rec=False, **kwargs):
|
||||
""" return new code object with modified attributes.
|
||||
if rec-cursive is true then dive into code
|
||||
objects contained in co_consts.
|
||||
"""
|
||||
if sys.platform.startswith("java"):
|
||||
# XXX jython does not support the below co_filename hack
|
||||
return self.raw
|
||||
names = [x for x in dir(self.raw) if x[:3] == 'co_']
|
||||
for name in kwargs:
|
||||
if name not in names:
|
||||
raise TypeError("unknown code attribute: %r" %(name, ))
|
||||
if rec and hasattr(self.raw, 'co_consts'): # jython
|
||||
newconstlist = []
|
||||
co = self.raw
|
||||
cotype = type(co)
|
||||
for c in co.co_consts:
|
||||
if isinstance(c, cotype):
|
||||
c = self.__class__(c).new(rec=True, **kwargs)
|
||||
newconstlist.append(c)
|
||||
return self.new(rec=False, co_consts=tuple(newconstlist), **kwargs)
|
||||
for name in names:
|
||||
if name not in kwargs:
|
||||
kwargs[name] = getattr(self.raw, name)
|
||||
arglist = [
|
||||
kwargs['co_argcount'],
|
||||
kwargs['co_nlocals'],
|
||||
kwargs.get('co_stacksize', 0), # jython
|
||||
kwargs.get('co_flags', 0), # jython
|
||||
kwargs.get('co_code', ''), # jython
|
||||
kwargs.get('co_consts', ()), # jython
|
||||
kwargs.get('co_names', []), #
|
||||
kwargs['co_varnames'],
|
||||
kwargs['co_filename'],
|
||||
kwargs['co_name'],
|
||||
kwargs['co_firstlineno'],
|
||||
kwargs.get('co_lnotab', ''), #jython
|
||||
kwargs.get('co_freevars', None), #jython
|
||||
kwargs.get('co_cellvars', None), # jython
|
||||
]
|
||||
if sys.version_info >= (3,0):
|
||||
arglist.insert(1, kwargs['co_kwonlyargcount'])
|
||||
return self.raw.__class__(*arglist)
|
||||
else:
|
||||
return py.std.new.code(*arglist)
|
||||
|
||||
def path(self):
|
||||
""" return a path object pointing to source code"""
|
||||
fn = self.raw.co_filename
|
||||
try:
|
||||
return fn.__path__
|
||||
except AttributeError:
|
||||
p = py.path.local(self.raw.co_filename)
|
||||
if not p.check():
|
||||
# XXX maybe try harder like the weird logic
|
||||
# in the standard lib [linecache.updatecache] does?
|
||||
p = self.raw.co_filename
|
||||
return p
|
||||
p = py.path.local(self.raw.co_filename)
|
||||
if not p.check():
|
||||
# XXX maybe try harder like the weird logic
|
||||
# in the standard lib [linecache.updatecache] does?
|
||||
p = self.raw.co_filename
|
||||
return p
|
||||
|
||||
path = property(path, None, None, "path of this code object")
|
||||
|
||||
|
|
|
@ -212,10 +212,10 @@ class Source(object):
|
|||
else:
|
||||
if flag & _AST_FLAG:
|
||||
return co
|
||||
co_filename = MyStr(filename)
|
||||
co_filename.__source__ = self
|
||||
return py.code.Code(co).new(rec=1, co_filename=co_filename)
|
||||
#return newcode_withfilename(co, co_filename)
|
||||
from types import ModuleType
|
||||
lines = [(x + "\n") for x in self.lines]
|
||||
py.std.linecache.cache[filename] = (1, None, lines, filename)
|
||||
return co
|
||||
|
||||
#
|
||||
# public API shortcut functions
|
||||
|
@ -224,11 +224,9 @@ class Source(object):
|
|||
def compile_(source, filename=None, mode='exec', flags=
|
||||
generators.compiler_flag, dont_inherit=0):
|
||||
""" compile the given source to a raw code object,
|
||||
which points back to the source code through
|
||||
"co_filename.__source__". All code objects
|
||||
contained in the code object will recursively
|
||||
also have this special subclass-of-string
|
||||
filename.
|
||||
and maintain an internal cache which allows later
|
||||
retrieval of the source code for the code object
|
||||
and any recursively created code objects.
|
||||
"""
|
||||
if _ast is not None and isinstance(source, _ast.AST):
|
||||
# XXX should Source support having AST?
|
||||
|
@ -262,44 +260,26 @@ def getfslineno(obj):
|
|||
#
|
||||
# helper functions
|
||||
#
|
||||
class MyStr(str):
|
||||
""" custom string which allows to add attributes. """
|
||||
|
||||
def findsource(obj):
|
||||
obj = py.code.getrawcode(obj)
|
||||
try:
|
||||
fullsource = obj.co_filename.__source__
|
||||
except AttributeError:
|
||||
try:
|
||||
sourcelines, lineno = py.std.inspect.findsource(obj)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
return None, None
|
||||
source = Source()
|
||||
source.lines = [line.rstrip() for line in sourcelines]
|
||||
return source, lineno
|
||||
else:
|
||||
lineno = obj.co_firstlineno - 1
|
||||
return fullsource, lineno
|
||||
|
||||
sourcelines, lineno = py.std.inspect.findsource(obj)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
return None, None
|
||||
source = Source()
|
||||
source.lines = [line.rstrip() for line in sourcelines]
|
||||
return source, lineno
|
||||
|
||||
def getsource(obj, **kwargs):
|
||||
obj = py.code.getrawcode(obj)
|
||||
try:
|
||||
fullsource = obj.co_filename.__source__
|
||||
except AttributeError:
|
||||
try:
|
||||
strsrc = inspect.getsource(obj)
|
||||
except IndentationError:
|
||||
strsrc = "\"Buggy python version consider upgrading, cannot get source\""
|
||||
assert isinstance(strsrc, str)
|
||||
return Source(strsrc, **kwargs)
|
||||
else:
|
||||
lineno = obj.co_firstlineno - 1
|
||||
end = fullsource.getblockend(lineno)
|
||||
return Source(fullsource[lineno:end+1], deident=True)
|
||||
|
||||
strsrc = inspect.getsource(obj)
|
||||
except IndentationError:
|
||||
strsrc = "\"Buggy python version consider upgrading, cannot get source\""
|
||||
assert isinstance(strsrc, str)
|
||||
return Source(strsrc, **kwargs)
|
||||
|
||||
def deindent(lines, offset=None):
|
||||
if offset is None:
|
||||
|
|
|
@ -1,87 +1,12 @@
|
|||
from __future__ import generators
|
||||
import py
|
||||
import sys
|
||||
|
||||
failsonjython = py.test.mark.xfail("sys.platform.startswith('java')")
|
||||
|
||||
def test_newcode():
|
||||
source = "i = 3"
|
||||
co = compile(source, '', 'exec')
|
||||
code = py.code.Code(co)
|
||||
newco = code.new()
|
||||
assert co == newco
|
||||
|
||||
def test_ne():
|
||||
code1 = py.code.Code(compile('foo = "bar"', '', 'exec'))
|
||||
assert code1 == code1
|
||||
code2 = py.code.Code(compile('foo = "baz"', '', 'exec'))
|
||||
assert code2 != code1
|
||||
|
||||
@failsonjython
|
||||
def test_newcode_unknown_args():
|
||||
code = py.code.Code(compile("", '', 'exec'))
|
||||
py.test.raises(TypeError, 'code.new(filename="hello")')
|
||||
|
||||
@failsonjython
|
||||
def test_newcode_withfilename():
|
||||
source = py.code.Source("""
|
||||
def f():
|
||||
def g():
|
||||
pass
|
||||
""")
|
||||
co = compile(str(source)+'\n', 'nada', 'exec')
|
||||
obj = 'hello'
|
||||
newco = py.code.Code(co).new(rec=True, co_filename=obj)
|
||||
def walkcode(co):
|
||||
for x in co.co_consts:
|
||||
if isinstance(x, type(co)):
|
||||
for y in walkcode(x):
|
||||
yield y
|
||||
yield co
|
||||
|
||||
names = []
|
||||
for code in walkcode(newco):
|
||||
assert newco.co_filename == obj
|
||||
assert newco.co_filename is obj
|
||||
names.append(code.co_name)
|
||||
assert 'f' in names
|
||||
assert 'g' in names
|
||||
|
||||
@failsonjython
|
||||
def test_newcode_with_filename():
|
||||
source = "i = 3"
|
||||
co = compile(source, '', 'exec')
|
||||
code = py.code.Code(co)
|
||||
class MyStr(str):
|
||||
pass
|
||||
filename = MyStr("hello")
|
||||
filename.__source__ = py.code.Source(source)
|
||||
newco = code.new(rec=True, co_filename=filename)
|
||||
assert newco.co_filename.__source__ == filename.__source__
|
||||
s = py.code.Source(newco)
|
||||
assert str(s) == source
|
||||
|
||||
|
||||
@failsonjython
|
||||
def test_new_code_object_carries_filename_through():
|
||||
class mystr(str):
|
||||
pass
|
||||
filename = mystr("dummy")
|
||||
co = compile("hello\n", filename, 'exec')
|
||||
assert not isinstance(co.co_filename, mystr)
|
||||
args = [
|
||||
co.co_argcount, co.co_nlocals, co.co_stacksize,
|
||||
co.co_flags, co.co_code, co.co_consts,
|
||||
co.co_names, co.co_varnames,
|
||||
filename,
|
||||
co.co_name, co.co_firstlineno, co.co_lnotab,
|
||||
co.co_freevars, co.co_cellvars
|
||||
]
|
||||
if sys.version_info > (3,0):
|
||||
args.insert(1, co.co_kwonlyargcount)
|
||||
c2 = py.std.types.CodeType(*args)
|
||||
assert c2.co_filename is filename
|
||||
|
||||
def test_code_gives_back_name_for_not_existing_file():
|
||||
name = 'abc-123'
|
||||
co_code = compile("pass\n", name, 'exec')
|
||||
|
|
|
@ -293,7 +293,6 @@ class TestFormattedExcinfo:
|
|||
assert lines[0] == "| def f(x):"
|
||||
assert lines[1] == " pass"
|
||||
|
||||
@failsonjython
|
||||
def test_repr_source_excinfo(self):
|
||||
""" check if indentation is right """
|
||||
pr = FormattedExcinfo()
|
||||
|
|
|
@ -80,11 +80,10 @@ def test_source_strip_multiline():
|
|||
source2 = source.strip()
|
||||
assert source2.lines == [" hello"]
|
||||
|
||||
@failsonjython
|
||||
def test_syntaxerror_rerepresentation():
|
||||
ex = py.test.raises(SyntaxError, py.code.compile, 'x x')
|
||||
ex = py.test.raises(SyntaxError, py.code.compile, 'xyz xyz')
|
||||
assert ex.value.lineno == 1
|
||||
assert ex.value.offset == 3
|
||||
assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython?
|
||||
assert ex.value.text.strip(), 'x x'
|
||||
|
||||
def test_isparseable():
|
||||
|
@ -132,7 +131,6 @@ class TestSourceParsingAndCompiling:
|
|||
exec (co, d)
|
||||
assert d['x'] == 3
|
||||
|
||||
@failsonjython
|
||||
def test_compile_and_getsource_simple(self):
|
||||
co = py.code.compile("x=3")
|
||||
exec (co)
|
||||
|
@ -203,7 +201,6 @@ class TestSourceParsingAndCompiling:
|
|||
assert isinstance(mod, ast.Module)
|
||||
compile(mod, "<filename>", "exec")
|
||||
|
||||
@failsonjython
|
||||
def test_compile_and_getsource(self):
|
||||
co = self.source.compile()
|
||||
py.builtin.exec_(co, globals())
|
||||
|
@ -260,7 +257,6 @@ def test_getstartingblock_multiline():
|
|||
l = [i for i in x.source.lines if i.strip()]
|
||||
assert len(l) == 4
|
||||
|
||||
@failsonjython
|
||||
def test_getline_finally():
|
||||
def c(): pass
|
||||
excinfo = py.test.raises(TypeError, """
|
||||
|
@ -274,7 +270,6 @@ def test_getline_finally():
|
|||
source = excinfo.traceback[-1].statement
|
||||
assert str(source).strip() == 'c(1)'
|
||||
|
||||
@failsonjython
|
||||
def test_getfuncsource_dynamic():
|
||||
source = """
|
||||
def f():
|
||||
|
@ -341,7 +336,6 @@ def test_getsource_fallback():
|
|||
src = getsource(x)
|
||||
assert src == expected
|
||||
|
||||
@failsonjython
|
||||
def test_idem_compile_and_getsource():
|
||||
from py._code.source import getsource
|
||||
expected = "def x(): pass"
|
||||
|
@ -355,8 +349,7 @@ def test_findsource_fallback():
|
|||
assert 'test_findsource_simple' in str(src)
|
||||
assert src[lineno] == ' def x():'
|
||||
|
||||
@failsonjython
|
||||
def test_findsource___source__():
|
||||
def test_findsource():
|
||||
from py._code.source import findsource
|
||||
co = py.code.compile("""if 1:
|
||||
def x():
|
||||
|
@ -373,7 +366,6 @@ def test_findsource___source__():
|
|||
assert src[lineno] == " def x():"
|
||||
|
||||
|
||||
@failsonjython
|
||||
def test_getfslineno():
|
||||
from py.code import getfslineno
|
||||
|
||||
|
|
|
@ -316,7 +316,11 @@ def test_runtest_in_module_ordering(testdir):
|
|||
|
||||
class TestRaises:
|
||||
def test_raises(self):
|
||||
py.test.raises(ValueError, "int('qwe')")
|
||||
source = "int('qwe')"
|
||||
excinfo = py.test.raises(ValueError, source)
|
||||
code = excinfo.traceback[-1].frame.code
|
||||
s = str(code.fullsource)
|
||||
assert s == source
|
||||
|
||||
def test_raises_exec(self):
|
||||
py.test.raises(ValueError, "a,x = []")
|
||||
|
|
Loading…
Reference in New Issue