[py-svn] py-trunk commit 60aa42a32814: remove code.new() function and store lines directly into linecache.cache instead.

commits-noreply at bitbucket.org commits-noreply at bitbucket.org
Tue May 11 22:55:24 CEST 2010


# HG changeset patch -- Bitbucket.org
# Project py-trunk
# URL http://bitbucket.org/hpk42/py-trunk/overview
# User holger krekel <holger at merlinux.eu>
# Date 1273611244 -7200
# Node ID 60aa42a328142d6c80df87d29377833daf3b6bea
# Parent  9992aac916258d2450a62315e081e5e47b54f23d
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.

--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
 ==================================================
 

--- a/py/_code/code.py
+++ b/py/_code/code.py
@@ -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")
 

--- a/py/_builtin.py
+++ b/py/_builtin.py
@@ -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. """ 

--- a/py/_code/source.py
+++ b/py/_code/source.py
@@ -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:

--- a/testing/code/test_source.py
+++ b/testing/code/test_source.py
@@ -80,11 +80,10 @@ def test_source_strip_multiline():
     source2 = source.strip() 
     assert source2.lines == [" hello"]
 
- at 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
 
- at 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)'
 
- at failsonjython
 def test_getfuncsource_dynamic():
     source = """
         def f():
@@ -341,7 +336,6 @@ def test_getsource_fallback():
     src = getsource(x)
     assert src == expected
 
- at 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():'
 
- at 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():"
     
 
- at failsonjython
 def test_getfslineno():
     from py.code import getfslineno
 

--- a/testing/code/test_code.py
+++ b/testing/code/test_code.py
@@ -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
 
- at failsonjython
-def test_newcode_unknown_args(): 
-    code = py.code.Code(compile("", '', 'exec'))
-    py.test.raises(TypeError, 'code.new(filename="hello")')
-
- at 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
-
- at 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 
-
-
- at 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')

--- a/testing/plugin/test_pytest_runner.py
+++ b/testing/plugin/test_pytest_runner.py
@@ -316,7 +316,11 @@ def test_runtest_in_module_ordering(test
 
 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 = []") 

--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -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()



More information about the pytest-commit mailing list