diff options
author | Colin Watson <cjwatson@debian.org> | 2020-07-25 00:58:33 +0100 |
---|---|---|
committer | Colin Watson <cjwatson@debian.org> | 2020-07-25 00:58:33 +0100 |
commit | be15a5ec8a45c072c2262e3a7ce598004f8739cf (patch) | |
tree | 91242dabb2cc05de83b49a3cc41c629509ce4387 | |
parent | 1ff0be431951ff9b61e39d2bb2ca9a243cb90d78 (diff) |
New upstream version 1.7.0
-rw-r--r-- | .bumpversion.cfg | 2 | ||||
-rw-r--r-- | .cookiecutterrc | 75 | ||||
-rw-r--r-- | .readthedocs.yml | 10 | ||||
-rw-r--r-- | AUTHORS.rst | 1 | ||||
-rw-r--r-- | CHANGELOG.rst | 8 | ||||
-rw-r--r-- | LICENSE | 3 | ||||
-rw-r--r-- | MANIFEST.in | 4 | ||||
-rw-r--r-- | PKG-INFO | 28 | ||||
-rw-r--r-- | README.rst | 25 | ||||
-rw-r--r-- | ci/requirements.txt | 1 | ||||
-rw-r--r-- | docs/conf.py | 10 | ||||
-rw-r--r-- | docs/index.rst | 1 | ||||
-rw-r--r-- | docs/reference/index.rst | 7 | ||||
-rw-r--r-- | docs/reference/tblib.rst | 9 | ||||
-rw-r--r-- | docs/requirements.txt | 1 | ||||
-rw-r--r-- | setup.cfg | 3 | ||||
-rw-r--r-- | setup.py | 2 | ||||
-rw-r--r-- | src/tblib.egg-info/PKG-INFO | 28 | ||||
-rw-r--r-- | src/tblib.egg-info/SOURCES.txt | 6 | ||||
-rw-r--r-- | src/tblib/__init__.py | 99 | ||||
-rw-r--r-- | tests/test_tblib.py | 144 | ||||
-rw-r--r-- | tox.ini | 5 |
22 files changed, 356 insertions, 116 deletions
diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 828f46b..1d8622c 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.6.0 +current_version = 1.7.0 commit = True tag = True diff --git a/.cookiecutterrc b/.cookiecutterrc index 5e0c749..1836a3d 100644 --- a/.cookiecutterrc +++ b/.cookiecutterrc @@ -1,55 +1,52 @@ # Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) cookiecutter: - _extensions: - - jinja2_time.TimeExtension - _template: /home/ionel/open-source/cookiecutter-pylibrary - allow_tests_inside_package: no - appveyor: yes - c_extension_function: '-' - c_extension_module: '-' - c_extension_optional: no + full_name: Ionel Cristian Mărieș + email: contact@ionelmc.ro + website: https://blog.ionelmc.ro/ + project_name: tblib + repo_name: python-tblib + repo_hosting: github.com + repo_hosting_domain: github.com + repo_username: ionelmc + package_name: tblib + distribution_name: tblib + project_short_description: Traceback serialization library. + release_date: '2020-03-07' + year_from: '2013' + year_to: '2' + version: 1.6.0 + license: BSD 2-Clause License c_extension_support: no + c_extension_optional: no + c_extension_module: '-' + c_extension_function: '-' c_extension_test_pypi: no c_extension_test_pypi_username: '-' - codacy: no - codacy_projectid: '-' - codeclimate: no - codecov: yes + test_matrix_configurator: no + test_matrix_separate_coverage: no + test_runner: pytest + setup_py_uses_test_runner: no + setup_py_uses_setuptools_scm: no + pypi_badge: yes + pypi_disable_upload: no + allow_tests_inside_package: no + linter: flake8 command_line_interface: no command_line_interface_bin_name: '-' coveralls: no coveralls_token: '-' - distribution_name: tblib - email: contact@ionelmc.ro - full_name: Ionel Cristian Mărieș + codecov: yes landscape: no - license: BSD 2-Clause License - linter: flake8 - package_name: tblib - project_name: tblib - project_short_description: Traceback serialization library. - pypi_badge: yes - pypi_disable_upload: no - release_date: '2019-05-02' - repo_hosting: github.com - repo_hosting_domain: github.com - repo_name: python-tblib - repo_username: ionelmc - requiresio: yes scrutinizer: no - setup_py_uses_setuptools_scm: no - setup_py_uses_test_runner: no + codacy: no + codacy_projectid: '-' + codeclimate: no sphinx_docs: yes - sphinx_docs_hosting: https://python-tblib.readthedocs.io/ - sphinx_doctest: no sphinx_theme: sphinx-py3doc-enhanced-theme - test_matrix_configurator: no - test_matrix_separate_coverage: no - test_runner: pytest + sphinx_doctest: no + sphinx_docs_hosting: https://python-tblib.readthedocs.io/ travis: yes travis_osx: no - version: 1.4.0 - website: https://blog.ionelmc.ro/ - year_from: '2013' - year_to: '2019' + appveyor: yes + requiresio: yes diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 0000000..59ff5c0 --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,10 @@ +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details +version: 2 +sphinx: + configuration: docs/conf.py +formats: all +python: + install: + - requirements: docs/requirements.txt + - method: pip + path: . diff --git a/AUTHORS.rst b/AUTHORS.rst index 3800ae0..5f6547d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,3 +10,4 @@ Authors * Elliott Sales de Andrade - https://github.com/QuLogic * Victor Stinner - https://github.com/vstinner * Guido Imperiale - https://github.com/crusaderky +* Ivanq - https://github.com/imachug diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5b08ce8..c3c5aeb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,12 @@ Changelog ========= +1.7.0 (2020-07-24) +~~~~~~~~~~~~~~~~~~ + +* Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in + `#58 <https://github.com/ionelmc/python-tblib/pull/58>`_. + 1.6.0 (2019-12-07) ~~~~~~~~~~~~~~~~~~ @@ -13,7 +19,7 @@ Changelog ~~~~~~~~~~~~~~~~~~ * Added support for Python 3.8. Contributed by Victor Stinner in - `#42 <HTTPS://GITHUB.COM/IONELMC/PYTHON-TBLIB/ISSUES/42>`_. + `#42 <https://github.com/ionelmc/python-tblib/issues/42>`_. * Removed support for end of life Python 3.4. * Few CI improvements and fixes. @@ -1,7 +1,6 @@ BSD 2-Clause License -Copyright (c) 2013-2019, Ionel Cristian Mărieș -All rights reserved. +Copyright (c) 2013-2020, Ionel Cristian Mărieș. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/MANIFEST.in b/MANIFEST.in index 81df70c..8b9e93d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -14,6 +14,6 @@ include CONTRIBUTING.rst include LICENSE include README.rst -include tox.ini .travis.yml .appveyor.yml +include tox.ini .travis.yml .appveyor.yml .readthedocs.yml -global-exclude *.py[cod] __pycache__ *.so *.dylib +global-exclude *.py[cod] __pycache__/* *.so *.dylib @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: tblib -Version: 1.6.0 +Version: 1.7.0 Summary: Traceback serialization library. Home-page: https://github.com/ionelmc/python-tblib Author: Ionel Cristian Mărieș @@ -398,19 +398,23 @@ Description: ======== ... pprint(tb_dict) {'tb_frame': {'f_code': {'co_filename': '<doctest README.rst[...]>', 'co_name': '<module>'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 5}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_2'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_1'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_0'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': None}}}} @@ -669,6 +673,12 @@ Description: ======== Changelog ========= + 1.7.0 (2020-07-24) + ~~~~~~~~~~~~~~~~~~ + + * Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in + `#58 <https://github.com/ionelmc/python-tblib/pull/58>`_. + 1.6.0 (2019-12-07) ~~~~~~~~~~~~~~~~~~ @@ -680,7 +690,7 @@ Description: ======== ~~~~~~~~~~~~~~~~~~ * Added support for Python 3.8. Contributed by Victor Stinner in - `#42 <HTTPS://GITHUB.COM/IONELMC/PYTHON-TBLIB/ISSUES/42>`_. + `#42 <https://github.com/ionelmc/python-tblib/issues/42>`_. * Removed support for end of life Python 3.4. * Few CI improvements and fixes. @@ -15,7 +15,8 @@ Overview * - package - | |version| |wheel| |supported-versions| |supported-implementations| | |commits-since| -.. |docs| image:: https://readthedocs.org/projects/python-tblib/badge/?style=flat + +.. |docs| image:: https://codecov.io/gh/ionelmc/python-tblib/branch/master/graphs/badge.svg?branch=master :target: https://readthedocs.org/projects/python-tblib :alt: Documentation Status @@ -51,9 +52,9 @@ Overview :alt: Supported implementations :target: https://pypi.org/project/tblib -.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-tblib/v1.6.0.svg +.. |commits-since| image:: https://img.shields.io/github/commits-since/ionelmc/python-tblib/v1.7.0.svg :alt: Commits since latest release - :target: https://github.com/ionelmc/python-tblib/compare/v1.6.0...master + :target: https://github.com/ionelmc/python-tblib/compare/v1.7.0...master .. end-badges @@ -440,19 +441,23 @@ json.JSONDecoder:: ... pprint(tb_dict) {'tb_frame': {'f_code': {'co_filename': '<doctest README.rst[...]>', 'co_name': '<module>'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 5}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_2'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_1'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_0'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': None}}}} diff --git a/ci/requirements.txt b/ci/requirements.txt index 1c8d385..b2a21e5 100644 --- a/ci/requirements.txt +++ b/ci/requirements.txt @@ -1,3 +1,4 @@ virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 +six>=1.12.0 diff --git a/docs/conf.py b/docs/conf.py index f6a4e14..af0256c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -4,8 +4,7 @@ from __future__ import unicode_literals import os extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.autosummary', + 'autoapi.extension', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', @@ -14,13 +13,16 @@ extensions = [ 'sphinx.ext.todo', 'sphinx.ext.viewcode', ] +autoapi_type = 'python' +autoapi_dirs = ['../src'] + source_suffix = '.rst' master_doc = 'index' project = 'tblib' -year = '2013-2019' +year = '2013-2020' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) -version = release = '1.6.0' +version = release = '1.7.0' pygments_style = 'trac' templates_path = ['.'] diff --git a/docs/index.rst b/docs/index.rst index 40f35b5..e55d633 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,7 +8,6 @@ Contents readme installation usage - reference/index contributing authors changelog diff --git a/docs/reference/index.rst b/docs/reference/index.rst deleted file mode 100644 index 42fbe62..0000000 --- a/docs/reference/index.rst +++ /dev/null @@ -1,7 +0,0 @@ -Reference -========= - -.. toctree:: - :glob: - - tblib* diff --git a/docs/reference/tblib.rst b/docs/reference/tblib.rst deleted file mode 100644 index 26354ee..0000000 --- a/docs/reference/tblib.rst +++ /dev/null @@ -1,9 +0,0 @@ -tblib -===== - -.. testsetup:: - - from tblib import * - -.. automodule:: tblib - :members: diff --git a/docs/requirements.txt b/docs/requirements.txt index 62bc14e..deb6219 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,3 @@ sphinx>=1.3 sphinx-py3doc-enhanced-theme +sphinx-autoapi @@ -6,7 +6,6 @@ max-line-length = 140 exclude = */migrations/* [tool:pytest] -testpaths = tests norecursedirs = migrations python_files = @@ -22,6 +21,8 @@ addopts = --doctest-continue-on-failure --doctest-glob=\*.rst --tb=short +testpaths = + tests [tool:isort] force_single_line = True @@ -25,7 +25,7 @@ def read(*names, **kwargs): setup( name='tblib', - version='1.6.0', + version='1.7.0', license='BSD-2-Clause', description='Traceback serialization library.', long_description='%s\n%s' % ( diff --git a/src/tblib.egg-info/PKG-INFO b/src/tblib.egg-info/PKG-INFO index 7f8efc3..122805d 100644 --- a/src/tblib.egg-info/PKG-INFO +++ b/src/tblib.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.2 Name: tblib -Version: 1.6.0 +Version: 1.7.0 Summary: Traceback serialization library. Home-page: https://github.com/ionelmc/python-tblib Author: Ionel Cristian Mărieș @@ -398,19 +398,23 @@ Description: ======== ... pprint(tb_dict) {'tb_frame': {'f_code': {'co_filename': '<doctest README.rst[...]>', 'co_name': '<module>'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 5}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_2'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_1'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, - 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... + 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_0'}, - 'f_globals': {'__name__': '__main__'}}, + 'f_globals': {'__name__': '__main__'}, + 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': None}}}} @@ -669,6 +673,12 @@ Description: ======== Changelog ========= + 1.7.0 (2020-07-24) + ~~~~~~~~~~~~~~~~~~ + + * Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in + `#58 <https://github.com/ionelmc/python-tblib/pull/58>`_. + 1.6.0 (2019-12-07) ~~~~~~~~~~~~~~~~~~ @@ -680,7 +690,7 @@ Description: ======== ~~~~~~~~~~~~~~~~~~ * Added support for Python 3.8. Contributed by Victor Stinner in - `#42 <HTTPS://GITHUB.COM/IONELMC/PYTHON-TBLIB/ISSUES/42>`_. + `#42 <https://github.com/ionelmc/python-tblib/issues/42>`_. * Removed support for end of life Python 3.4. * Few CI improvements and fixes. diff --git a/src/tblib.egg-info/SOURCES.txt b/src/tblib.egg-info/SOURCES.txt index a913451..f5cb383 100644 --- a/src/tblib.egg-info/SOURCES.txt +++ b/src/tblib.egg-info/SOURCES.txt @@ -4,6 +4,7 @@ .coveragerc .editorconfig .gitignore +.readthedocs.yml .travis.yml AUTHORS.rst CHANGELOG.rst @@ -30,8 +31,6 @@ docs/readme.rst docs/requirements.txt docs/spelling_wordlist.txt docs/usage.rst -docs/reference/index.rst -docs/reference/tblib.rst src/tblib/__init__.py src/tblib/cpython.py src/tblib/decorators.py @@ -45,4 +44,5 @@ tests/badmodule.py tests/badsyntax.py tests/examples.py tests/test_issue30.py -tests/test_pickle_exception.py
\ No newline at end of file +tests/test_pickle_exception.py +tests/test_tblib.py
\ No newline at end of file diff --git a/src/tblib/__init__.py b/src/tblib/__init__.py index 163fc62..7e717b4 100644 --- a/src/tblib/__init__.py +++ b/src/tblib/__init__.py @@ -1,6 +1,7 @@ import re import sys from types import CodeType +from types import FrameType from types import TracebackType try: @@ -15,8 +16,8 @@ except ImportError: if not tb_set_next and not tproxy: raise ImportError("Cannot use tblib. Runtime not supported.") -__version__ = '1.6.0' -__all__ = 'Traceback', +__version__ = '1.7.0' +__all__ = 'Traceback', 'TracebackParseError', 'Frame', 'Code' PY3 = sys.version_info[0] == 3 FRAME_RE = re.compile(r'^\s*File "(?P<co_filename>.+)", line (?P<tb_lineno>\d+)(, in (?P<co_name>.+))?$') @@ -24,7 +25,12 @@ FRAME_RE = re.compile(r'^\s*File "(?P<co_filename>.+)", line (?P<tb_lineno>\d+)( class _AttrDict(dict): __slots__ = () - __getattr__ = dict.__getitem__ + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + raise AttributeError(name) # noinspection PyPep8Naming @@ -37,33 +43,73 @@ class TracebackParseError(Exception): class Code(object): - + """ + Class that replicates just enough of the builtin Code object to enable serialization and traceback rendering. + """ co_code = None def __init__(self, code): self.co_filename = code.co_filename self.co_name = code.co_name + self.co_argcount = 0 + self.co_kwonlyargcount = 0 + self.co_varnames = () + self.co_nlocals = 0 + self.co_stacksize = 0 + self.co_flags = 64 + self.co_firstlineno = 0 + + # noinspection SpellCheckingInspection + def __tproxy__(self, operation, *args, **kwargs): + """ + Necessary for PyPy's tproxy. + """ + if operation in ('__getattribute__', '__getattr__'): + return getattr(self, args[0]) + else: + return getattr(self, operation)(*args, **kwargs) class Frame(object): + """ + Class that replicates just enough of the builtin Frame object to enable serialization and traceback rendering. + """ def __init__(self, frame): + self.f_locals = {} self.f_globals = { k: v for k, v in frame.f_globals.items() if k in ("__file__", "__name__") } self.f_code = Code(frame.f_code) + self.f_lineno = frame.f_lineno def clear(self): - # For compatibility with PyPy 3.5; - # clear() was added to frame in Python 3.4 - # and is called by traceback.clear_frames(), which - # in turn is called by unittest.TestCase.assertRaises - pass + """ + For compatibility with PyPy 3.5; + clear() was added to frame in Python 3.4 + and is called by traceback.clear_frames(), which + in turn is called by unittest.TestCase.assertRaises + """ + # noinspection SpellCheckingInspection + def __tproxy__(self, operation, *args, **kwargs): + """ + Necessary for PyPy's tproxy. + """ + if operation in ('__getattribute__', '__getattr__'): + if args[0] == 'f_code': + return tproxy(CodeType, self.f_code.__tproxy__) + else: + return getattr(self, args[0]) + else: + return getattr(self, operation)(*args, **kwargs) -class Traceback(object): +class Traceback(object): + """ + Class that wraps builtin Traceback objects. + """ tb_next = None def __init__(self, tb): @@ -84,8 +130,11 @@ class Traceback(object): tb = tb.tb_next def as_traceback(self): + """ + Convert to a builtin Traceback object that is usable for raising or rendering a stacktrace. + """ if tproxy: - return tproxy(TracebackType, self.__tproxy_handler) + return tproxy(TracebackType, self.__tproxy__) if not tb_set_next: raise RuntimeError("Unsupported Python interpreter!") @@ -119,7 +168,7 @@ class Traceback(object): # noinspection PyBroadException try: - exec(code, current.tb_frame.f_globals, {}) + exec(code, dict(current.tb_frame.f_globals), {}) except Exception: next_tb = sys.exc_info()[2].tb_next if top_tb is None: @@ -135,19 +184,28 @@ class Traceback(object): finally: del top_tb del tb + to_traceback = as_traceback # noinspection SpellCheckingInspection - def __tproxy_handler(self, operation, *args, **kwargs): + def __tproxy__(self, operation, *args, **kwargs): + """ + Necessary for PyPy's tproxy. + """ if operation in ('__getattribute__', '__getattr__'): if args[0] == 'tb_next': return self.tb_next and self.tb_next.as_traceback() + elif args[0] == 'tb_frame': + return tproxy(FrameType, self.tb_frame.__tproxy__) else: return getattr(self, args[0]) else: return getattr(self, operation)(*args, **kwargs) - def to_dict(self): - """Convert a Traceback into a dictionary representation""" + def as_dict(self): + """ + Converts to a dictionary representation. You can serialize the result to JSON as it only has + builtin objects like dicts, lists, ints or strings. + """ if self.tb_next is None: tb_next = None else: @@ -160,15 +218,20 @@ class Traceback(object): frame = { 'f_globals': self.tb_frame.f_globals, 'f_code': code, + 'f_lineno': self.tb_frame.f_lineno, } return { 'tb_frame': frame, 'tb_lineno': self.tb_lineno, 'tb_next': tb_next, } + to_dict = as_dict @classmethod def from_dict(cls, dct): + """ + Creates an instance from a dictionary with the same structure as ``.as_dict()`` returns. + """ if dct['tb_next']: tb_next = cls.from_dict(dct['tb_next']) else: @@ -181,6 +244,7 @@ class Traceback(object): frame = _AttrDict( f_globals=dct['tb_frame']['f_globals'], f_code=code, + f_lineno=dct['tb_frame']['f_lineno'], ) tb = _AttrDict( tb_frame=frame, @@ -191,6 +255,10 @@ class Traceback(object): @classmethod def from_string(cls, string, strict=True): + """ + Creates an instance by parsing a stacktrace. Strict means that parsing stops when lines are not indented by at least two spaces + anymore. + """ frames = [] header = strict @@ -220,6 +288,7 @@ class Traceback(object): __name__='?', ), f_code=_AttrDict(frame), + f_lineno=int(frame['tb_lineno']), ), tb_next=previous, ) diff --git a/tests/test_tblib.py b/tests/test_tblib.py new file mode 100644 index 0000000..bade6d4 --- /dev/null +++ b/tests/test_tblib.py @@ -0,0 +1,144 @@ +import pickle +import traceback + +from tblib import Traceback +from tblib import pickling_support + +pickling_support.install() + +pytest_plugins = 'pytester', + + +def test_parse_traceback(): + tb1 = Traceback.from_string( + """ +Traceback (most recent call last): + File "file1", line 123, in <module> + code1 + File "file2", line 234, in ??? + code2 + File "file3", line 345, in function3 + File "file4", line 456, in + code4 +KeyboardInterrupt""" + ) + pytb = tb1.as_traceback() + assert traceback.format_tb(pytb) == [ + ' File "file1", line 123, in <module>\n', + ' File "file2", line 234, in ???\n', + ' File "file3", line 345, in function3\n', + ] + tb2 = Traceback(pytb) + + expected_dict = { + "tb_frame": { + "f_code": {"co_filename": "file1", "co_name": "<module>"}, + "f_globals": {"__file__": "file1", "__name__": "?"}, + "f_lineno": 123, + }, + "tb_lineno": 123, + "tb_next": { + "tb_frame": { + "f_code": {"co_filename": "file2", "co_name": "???"}, + "f_globals": {"__file__": "file2", "__name__": "?"}, + "f_lineno": 234, + }, + "tb_lineno": 234, + "tb_next": { + "tb_frame": { + "f_code": {"co_filename": "file3", "co_name": "function3"}, + "f_globals": {"__file__": "file3", "__name__": "?"}, + "f_lineno": 345, + }, + "tb_lineno": 345, + "tb_next": None, + }, + }, + } + tb3 = Traceback.from_dict(expected_dict) + tb4 = pickle.loads(pickle.dumps(tb3)) + assert tb4.as_dict() == tb3.as_dict() == tb2.as_dict() == tb1.as_dict() == expected_dict + + +def test_pytest_integration(testdir): + test = testdir.makepyfile(""" +import six + +from tblib import Traceback + +def test_raise(): + tb1 = Traceback.from_string(''' +Traceback (most recent call last): + File "file1", line 123, in <module> + code1 + File "file2", line 234, in ??? + code2 + File "file3", line 345, in function3 + File "file4", line 456, in "" +''') + pytb = tb1.as_traceback() + six.reraise(RuntimeError, RuntimeError(), pytb) +""") + + # mode(auto / long / short / line / native / no). + + result = testdir.runpytest_subprocess('--tb=long', '-vv', test) + result.stdout.fnmatch_lines([ + "_ _ _ _ _ _ _ _ *", + "", + "> [?][?][?]", + "", + "file1:123:*", + "_ _ _ _ _ _ _ _ *", + "", + "> [?][?][?]", + "", + "file2:234:*", + "_ _ _ _ _ _ _ _ *", + "", + "> [?][?][?]", + "", + "file3:345:*", + "_ _ _ _ _ _ _ _ *", + "", + "> [?][?][?]", + "E RuntimeError", + "", + "file4:456: RuntimeError", + "===*=== 1 failed in * ===*===", + ]) + + result = testdir.runpytest_subprocess('--tb=short', '-vv', test) + result.stdout.fnmatch_lines([ + 'test_pytest_integration.py:*: in test_raise', + ' six.reraise(RuntimeError, RuntimeError(), pytb)', + 'file1:123: in <module>', + ' ???', + 'file2:234: in ???', + ' ???', + 'file3:345: in function3', + ' ???', + 'file4:456: in ""', + ' ???', + 'E RuntimeError', + ]) + + result = testdir.runpytest_subprocess('--tb=line', '-vv', test) + result.stdout.fnmatch_lines([ + "===*=== FAILURES ===*===", + "file4:456: RuntimeError", + "===*=== 1 failed in * ===*===", + ]) + + result = testdir.runpytest_subprocess('--tb=native', '-vv', test) + result.stdout.fnmatch_lines([ + 'Traceback (most recent call last):', + ' File "*test_pytest_integration.py", line *, in test_raise', + ' six.reraise(RuntimeError, RuntimeError(), pytb)', + ' File "file1", line 123, in <module>', + ' File "file2", line 234, in ???', + ' File "file3", line 345, in function3', + ' File "file4", line 456, in ""', + 'RuntimeError', + + ]) @@ -25,10 +25,10 @@ basepython = pypy3: {env:TOXPYTHON:pypy3} py27: {env:TOXPYTHON:python2.7} py35: {env:TOXPYTHON:python3.5} - {py36,docs}: {env:TOXPYTHON:python3.6} + py36: {env:TOXPYTHON:python3.6} py37: {env:TOXPYTHON:python3.7} py38: {env:TOXPYTHON:python3.8} - {bootstrap,clean,check,report,codecov}: {env:TOXPYTHON:python3} + {bootstrap,clean,check,report,codecov,docs}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes @@ -39,6 +39,7 @@ deps = pytest pytest-travis-fold pytest-cov + pytest-clarity six py{27,35,36,37,38,py,py3}: twisted commands = |