diff --git a/.travis.yml b/.travis.yml index cefe09b..65e4f3d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python python: - "2.7" + - "3.6" install: "pip install -r requirements.txt" script: nosetests diff --git a/bin/view_trace b/bin/view_trace index 789bb2d..8e53a61 100644 --- a/bin/view_trace +++ b/bin/view_trace @@ -1,5 +1,7 @@ #!/usr/bin/env python - +from __future__ import print_function from execution_trace.viewer.viewer import main -print "Starting UI web server, please open the URL listed below." -main() + +if __name__ == '__main__': + print("Starting UI web server, please open the URL listed below.") + main() diff --git a/example.py b/example.py index 1c93770..8b87b09 100644 --- a/example.py +++ b/example.py @@ -1,3 +1,4 @@ +from __future__ import print_function from collections import defaultdict import re @@ -13,6 +14,5 @@ def wordcount(text): if __name__ == '__main__': - print wordcount('Hello, world!') - print wordcount('echo echo echo echo') - + print(wordcount('Hello, world!')) + print(wordcount('echo echo echo echo')) diff --git a/execution_trace/constants.py b/execution_trace/constants.py index 8b030a5..f04ccd2 100644 --- a/execution_trace/constants.py +++ b/execution_trace/constants.py @@ -1,4 +1,5 @@ from voluptuous import Schema, Required +from builtins import str RECORD_FN_NAME = '_record_state_fn_hidden_123' RETVAL_NAME = '_retval_hidden_123' @@ -14,5 +15,5 @@ }) SOURCE_DUMP_SCHEMA = Schema({ - Required('source'): basestring + Required('source'): str # Python 2.7 & 3.x }) diff --git a/execution_trace/record.py b/execution_trace/record.py index f7062a5..0521f3e 100644 --- a/execution_trace/record.py +++ b/execution_trace/record.py @@ -1,4 +1,10 @@ +from future.utils import viewitems import ast +# Python 2 and 3 +try: + from ast import TryExcept as Try +except ImportError: + from ast import Try import inspect import json import logging @@ -30,7 +36,8 @@ def _record_state_fn_hidden_123(lineno, f_locals): """Stores local line data.""" # Make sure we have just primitive types. - f_locals = {k: repr(v) for k, v in f_locals.iteritems()} + f_locals = {k: repr(v) for k, v in viewitems(f_locals)} # Python 2 and 3 + data = { 'lineno': lineno, 'state': f_locals, @@ -44,6 +51,8 @@ def _record_state_fn_hidden_123(lineno, f_locals): # TL;DR need this because the decorator would # recursively apply on the new generated function. _blocked = False + + def record(num_executions=1): def _record(f): """Transforms `f` such that after every line record_state is called. @@ -76,7 +85,9 @@ def _record(f): env[RECORD_FN_NAME] = globals()[RECORD_FN_NAME] _blocked = True - exec new_f_compiled in env + # https://stackoverflow.com/questions/15086040/behavior-of-exec-function-in-python-2-and-python-3 + exec(new_f_compiled, env) # Python 2 and 3 + _blocked = False # Keep a reference to the (original) mangled function, because our decorator @@ -135,6 +146,7 @@ def wrapped(*args, **kwargs): return ret return wrapped + return _record @@ -203,7 +215,7 @@ def _fill_body_with_record(original_body, prepend=False, lineno=None): has_nested = True # Don't want to prepend call for try/except, but we want for the others. - if isinstance(item, ast.TryExcept): + if isinstance(item, Try): prepend = False else: prepend = True diff --git a/execution_trace/tests/test_record.py b/execution_trace/tests/test_record.py index 4b0adc6..ad0af9b 100644 --- a/execution_trace/tests/test_record.py +++ b/execution_trace/tests/test_record.py @@ -1,14 +1,19 @@ import importlib import json import mock -import StringIO +from sys import version_info import unittest - +# from parameterized import parameterized - +# from execution_trace import record from execution_trace.constants import SOURCE_DUMP_SCHEMA, EXECUTION_DUMP_SCHEMA, RECORD_FN_NAME +if version_info[0] < 3: + from StringIO import StringIO # Python 2.7 +else: + # https://stackoverflow.com/questions/11914472/stringio-in-python3 + from io import StringIO # Python 3.x # Covers all supported syntax. See `execution_trace.tests.functions` # package for context. @@ -44,7 +49,7 @@ def setUp(self): self.get_dump_file_patcher = mock.patch('execution_trace.record._get_dump_file') self.get_dump_file = self.get_dump_file_patcher.start() - self.dump_file = StringIO.StringIO() + self.dump_file = StringIO() self.get_dump_file.return_value = self.dump_file, '/tmp/mock_path' def tearDown(self): @@ -157,8 +162,7 @@ def f(): # 2 expected_trace = [{u'data': [{u'lineno': 3, u'state': {}}, {u'lineno': 4, u'state': {u'x': u'5'}}]}] - self._check_dump_file(self.dump_file, expected_trace==expected_trace) - + self._check_dump_file(self.dump_file, expected_trace == expected_trace) def _check_dump_file(self, dump_file, num_executions=1, expected_trace=None): # Rewind the file. diff --git a/requirements.txt b/requirements.txt index 3a79a93..b7ac616 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ mock==1.3.0 nose==1.3.7 voluptuous==0.8.10 Flask==0.10.1 +future==0.16.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 61f3ea0..dc93812 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,5 @@ from setuptools import setup - - setup(name='execution-trace', version='1.1.0', description="Trace the local context of a Python function's execution with just a decorator", @@ -13,17 +11,17 @@ 'execution_trace.viewer'], include_package_data=True, install_requires=[ - 'voluptuous==0.8.10', - 'Flask==0.10.1', + 'voluptuous==0.8.10', + 'Flask==0.10.1', ], test_suite='nose.collector', tests_require=[ - 'nose==1.3.7', - 'mock==1.3.0', - 'parameterized==0.6.1', + 'nose==1.3.7', + 'mock==1.3.0', + 'parameterized==0.6.1', ], scripts=[ - 'bin/view_trace', + 'bin/view_trace', ], zip_safe=False -) + )