Skip to content

Commit

Permalink
Added Python 3 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
ajalt committed May 12, 2014
1 parent e363c19 commit 4dc8167
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 28 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
language: python
python:
- "2.7"
- "3.4"
install:
- "pip install coverage"
- "pip install coveralls"
Expand Down
2 changes: 1 addition & 1 deletion broke.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
def f():
broken_code
print 'fuckit chaining works'
print('fuckit chaining works')

for

Expand Down
12 changes: 4 additions & 8 deletions example.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ def f(self):
return 'Class decorator works'

with fuckit:
print 'Context manager works'
print('Context manager works')
raise RuntimeError()

print broken_function()
print BrokenClass().f()
print(broken_function())
print(BrokenClass().f())
broke.f()
print broke.var




print(broke.var)
59 changes: 41 additions & 18 deletions fuckit.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
block? Use fuckit as a context manager.
>>> with fuckit:
... print 'This works'
... print('This works')
... raise RuntimeError()
This works
"""
Expand All @@ -58,22 +58,33 @@
import types

class _fuckit(types.ModuleType):
# We overwrite the sys.moduoles entry for this function later, which will
# We overwrite the sys.modules entry for this function later, which will
# cause all the values in globals() to be changed to None to allow garbage
# collection. That forces us to do all of our imports into locals().
class _Fucker(ast.NodeTransformer):
"""Surround each statement with a try/except block to silence errors."""
def generic_visit(self, node):
import ast
import sys
ast.NodeTransformer.generic_visit(self, node)

if isinstance(node, ast.stmt) and not isinstance(node, ast.FunctionDef):
return ast.copy_location(ast.TryExcept(
body=[node],
handlers=[ast.ExceptHandler(type=None,
name=None,
body=[ast.Pass()])],
orelse=[]), node)
if sys.version_info[0] == 3:
new_node = ast.Try(
body=[node],
handlers=[ast.ExceptHandler(type=None,
name=None,
body=[ast.Pass()])],
orelse=[],
finalbody=[ast.Pass()])
else:
new_node = ast.TryExcept(
body=[node],
handlers=[ast.ExceptHandler(type=None,
name=None,
body=[ast.Pass()])],
orelse=[])
return ast.copy_location(new_node, node)
return node

def __call__(self, victim):
Expand All @@ -90,8 +101,20 @@ def __call__(self, victim):
import traceback
import functools
import re

if isinstance(victim, (str, unicode)):

PY3 = sys.version_info[0] == 3
if PY3:
basestring = str
get_func_code = lambda f: f.__code__
exec_ = __builtins__['exec']
else:
basestring = __builtins__['basestring']
get_func_code = lambda f: f.func_code
def exec_(_code_, _globs_):
_locs_ = _globs_
exec('exec _code_ in _globs_, _locs_')

if isinstance(victim, basestring):
sourcefile, pathname, _description = imp.find_module(victim)
source = sourcefile.read()
# Compile the module with more and more lines removed until it
Expand All @@ -102,21 +125,22 @@ def __call__(self, victim):
module = types.ModuleType(victim)
module.__file__ = pathname
sys.modules[victim] = module
exec code in module.__dict__
exec_(code, module.__dict__)
except Exception as exc:
extracted_ln = traceback.extract_tb(sys.exc_info()[2])[-1][1]
lineno = getattr(exc, 'lineno', extracted_ln)
lines = source.splitlines()
del lines[lineno - 1]
source = '\n'.join(lines)
source <- True # Dereference assignment to fix truthiness
if not PY3:
source <- True # Dereference assignment to fix truthiness in Py2
else:
break
inspect.stack()[1][0].f_locals[victim] = module
return module
elif inspect.isfunction(victim) or inspect.ismethod(victim):
try:
sourcelines = inspect.getsource(victim.func_code).splitlines()
sourcelines = inspect.getsource(get_func_code(victim)).splitlines()
indent = re.match(r'\s*', sourcelines[0]).group()
source = '\n'.join(l.replace(indent, '', 1) for l in sourcelines)
except IOError:
Expand All @@ -135,18 +159,18 @@ def wrapper(*args, **kw):
tree = self._Fucker().visit(ast.parse(source))
del tree.body[0].decorator_list[:]
ast.fix_missing_locations(tree)
code = compile(tree, victim.func_name, 'exec')
code = compile(tree, victim.__name__, 'exec')
namespace = {}
exec code in namespace
exec_(code, namespace)
return namespace[victim.__name__]
elif isinstance(victim, types.ModuleType):
# Allow chaining of fuckit import calls
for name, obj in victim.__dict__.iteritems():
for name, obj in victim.__dict__.items():
if inspect.isfunction(obj) or inspect.ismethod(obj):
victim.__dict__[name] = self(obj)
return victim
elif isinstance(victim, (types.ClassType, type)):
for name, member in victim.__dict__.iteritems():
for name, member in victim.__dict__.items():
if isinstance(member, (type, types.ClassType, types.FunctionType,
types.LambdaType, types.MethodType)):
setattr(victim, name, self(member))
Expand All @@ -163,6 +187,5 @@ def __exit__(self, exc_type, exc_value, traceback):
return exc_type is None or issubclass(exc_type, Exception)



sys.modules[__name__] = _fuckit('fuckit', __doc__)

3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ def read(*parts):
platforms='any',
test_suite='nose.collector',
classifiers = [
'Programming Language :: Python :: 2 :: Only',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 3',
'Development Status :: 4 - Beta',
'Natural Language :: English',
'Environment :: Console',
Expand Down

0 comments on commit 4dc8167

Please sign in to comment.