././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7943244 tblib-1.7.0/0000775000175000017500000000000000000000000013054 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756046.0 tblib-1.7.0/.appveyor.yml0000664000175000017500000000477100000000000015533 0ustar00ionelionel00000000000000version: '{branch}-{build}' build: off environment: matrix: - TOXENV: check TOXPYTHON: C:\Python36\python.exe PYTHON_HOME: C:\Python36 PYTHON_VERSION: '3.6' PYTHON_ARCH: '32' - TOXENV: py27,codecov TOXPYTHON: C:\Python27\python.exe PYTHON_HOME: C:\Python27 PYTHON_VERSION: '2.7' PYTHON_ARCH: '32' - TOXENV: py27,codecov TOXPYTHON: C:\Python27-x64\python.exe PYTHON_HOME: C:\Python27-x64 PYTHON_VERSION: '2.7' PYTHON_ARCH: '64' WINDOWS_SDK_VERSION: v7.0 - TOXENV: py35,codecov TOXPYTHON: C:\Python35\python.exe PYTHON_HOME: C:\Python35 PYTHON_VERSION: '3.5' PYTHON_ARCH: '32' - TOXENV: py35,codecov TOXPYTHON: C:\Python35-x64\python.exe PYTHON_HOME: C:\Python35-x64 PYTHON_VERSION: '3.5' PYTHON_ARCH: '64' - TOXENV: py36,codecov TOXPYTHON: C:\Python36\python.exe PYTHON_HOME: C:\Python36 PYTHON_VERSION: '3.6' PYTHON_ARCH: '32' - TOXENV: py36,codecov TOXPYTHON: C:\Python36-x64\python.exe PYTHON_HOME: C:\Python36-x64 PYTHON_VERSION: '3.6' PYTHON_ARCH: '64' - TOXENV: py37,codecov TOXPYTHON: C:\Python37\python.exe PYTHON_HOME: C:\Python37 PYTHON_VERSION: '3.7' PYTHON_ARCH: '32' - TOXENV: py37,codecov TOXPYTHON: C:\Python37-x64\python.exe PYTHON_HOME: C:\Python37-x64 PYTHON_VERSION: '3.7' PYTHON_ARCH: '64' - TOXENV: py38,codecov TOXPYTHON: C:\Python38\python.exe PYTHON_HOME: C:\Python38 PYTHON_VERSION: '3.8' PYTHON_ARCH: '32' - TOXENV: py38,codecov TOXPYTHON: C:\Python38-x64\python.exe PYTHON_HOME: C:\Python38-x64 PYTHON_VERSION: '3.8' PYTHON_ARCH: '64' init: - ps: echo $env:TOXENV - ps: ls C:\Python* install: - '%PYTHON_HOME%\python -mpip install --progress-bar=off tox -rci/requirements.txt' - '%PYTHON_HOME%\Scripts\virtualenv --version' - '%PYTHON_HOME%\Scripts\easy_install --version' - '%PYTHON_HOME%\Scripts\pip --version' - '%PYTHON_HOME%\Scripts\tox --version' test_script: - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd %PYTHON_HOME%\Scripts\tox on_failure: - ps: dir "env:" - ps: get-content .tox\*\log\* ### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker): # on_finish: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546253.0 tblib-1.7.0/.bumpversion.cfg0000664000175000017500000000076700000000000016176 0ustar00ionelionel00000000000000[bumpversion] current_version = 1.7.0 commit = True tag = True [bumpversion:file:setup.py] search = version='{current_version}' replace = version='{new_version}' [bumpversion:file:README.rst] search = v{current_version}. replace = v{new_version}. [bumpversion:file:docs/conf.py] search = version = release = '{current_version}' replace = version = release = '{new_version}' [bumpversion:file:src/tblib/__init__.py] search = __version__ = '{current_version}' replace = __version__ = '{new_version}' ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756421.0 tblib-1.7.0/.cookiecutterrc0000664000175000017500000000274300000000000016110 0ustar00ionelionel00000000000000# Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) cookiecutter: 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: '-' 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: '-' codecov: yes landscape: no scrutinizer: no codacy: no codacy_projectid: '-' codeclimate: no sphinx_docs: yes sphinx_theme: sphinx-py3doc-enhanced-theme sphinx_doctest: no sphinx_docs_hosting: https://python-tblib.readthedocs.io/ travis: yes travis_osx: no appveyor: yes requiresio: yes ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/.coveragerc0000664000175000017500000000025500000000000015177 0ustar00ionelionel00000000000000[paths] source = src */site-packages [run] branch = true source = tblib tests parallel = true [report] show_missing = true precision = 2 omit = *migrations* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/.editorconfig0000664000175000017500000000033100000000000015526 0ustar00ionelionel00000000000000# see https://editorconfig.org/ root = true [*] end_of_line = lf trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 4 charset = utf-8 [*.{bat,cmd,ps1}] end_of_line = crlf ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/.gitignore0000664000175000017500000000120100000000000015036 0ustar00ionelionel00000000000000*.py[cod] __pycache__ # C extensions *.so # Packages *.egg *.egg-info dist build eggs .eggs parts bin var sdist wheelhouse develop-eggs .installed.cfg lib lib64 venv*/ pyvenv*/ pip-wheel-metadata/ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox .coverage.* .pytest_cache/ nosetests.xml coverage.xml htmlcov # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject .idea *.iml *.komodoproject # Complexity output/*.html output/*/index.html # Sphinx docs/_build .DS_Store *~ .*.sw[po] .build .ve .env .cache .pytest .benchmarks .bootstrap .appveyor.token *.bak # Mypy Cache .mypy_cache/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583758738.0 tblib-1.7.0/.readthedocs.yml0000664000175000017500000000034700000000000016146 0ustar00ionelionel00000000000000# 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: . ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756046.0 tblib-1.7.0/.travis.yml0000664000175000017500000000214300000000000015165 0ustar00ionelionel00000000000000language: python dist: xenial cache: false env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all matrix: include: - python: '3.6' env: - TOXENV=check - python: '3.6' env: - TOXENV=docs - env: - TOXENV=py27,codecov python: '2.7' - env: - TOXENV=py35,codecov python: '3.5' - env: - TOXENV=py36,codecov python: '3.6' - env: - TOXENV=py37,codecov python: '3.7' - env: - TOXENV=py38,codecov python: '3.8' - env: - TOXENV=pypy,codecov python: 'pypy' - env: - TOXENV=pypy3,codecov - TOXPYTHON=pypy3 python: 'pypy3' before_install: - python --version - uname -a - lsb_release -a || true install: - python -mpip install --progress-bar=off tox -rci/requirements.txt - virtualenv --version - easy_install --version - pip --version - tox --version script: - tox -v after_failure: - more .tox/log/* | cat - more .tox/*/log/* | cat notifications: email: on_success: never on_failure: always ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756406.0 tblib-1.7.0/AUTHORS.rst0000664000175000017500000000073200000000000014735 0ustar00ionelionel00000000000000Authors ======= * Ionel Cristian Mărieș - https://blog.ionelmc.ro * Arcadiy Ivanov - https://github.com/arcivanov * Beckjake - https://github.com/beckjake * DRayX - https://github.com/DRayX * Jason Madden - https://github.com/jamadden * Jon Dufresne - https://github.com/jdufresne * 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 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546144.0 tblib-1.7.0/CHANGELOG.rst0000664000175000017500000000427100000000000015101 0ustar00ionelionel00000000000000 Changelog ========= 1.7.0 (2020-07-24) ~~~~~~~~~~~~~~~~~~ * Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in `#58 `_. 1.6.0 (2019-12-07) ~~~~~~~~~~~~~~~~~~ * When pickling an Exception, also pickle its traceback and the Exception chain (``raise ... from ...``). Contributed by Guido Imperiale in `#53 `_. 1.5.0 (2019-10-23) ~~~~~~~~~~~~~~~~~~ * Added support for Python 3.8. Contributed by Victor Stinner in `#42 `_. * Removed support for end of life Python 3.4. * Few CI improvements and fixes. 1.4.0 (2019-05-02) ~~~~~~~~~~~~~~~~~~ * Removed support for end of life Python 3.3. * Fixed tests for Python 3.7. Contributed by Elliott Sales de Andrade in `#36 `_. * Fixed compatibility issue with Twised (``twisted.python.failure.Failure`` expected a ``co_code`` attribute). 1.3.2 (2017-04-09) ~~~~~~~~~~~~~~~~~~ * Add support for PyPy3.5-5.7.1-beta. Previously ``AttributeError: 'Frame' object has no attribute 'clear'`` could be raised. See PyPy issue `#2532 `_. 1.3.1 (2017-03-27) ~~~~~~~~~~~~~~~~~~ * Fixed handling for tracebacks due to exceeding the recursion limit. Fixes `#15 `_. 1.3.0 (2016-03-08) ~~~~~~~~~~~~~~~~~~ * Added ``Traceback.from_string``. 1.2.0 (2015-12-18) ~~~~~~~~~~~~~~~~~~ * Fixed handling for tracebacks from generators and other internal improvements and optimizations. Contributed by DRayX in `#10 `_ and `#11 `_. 1.1.0 (2015-07-27) ~~~~~~~~~~~~~~~~~~ * Added support for Python 2.6. Contributed by Arcadiy Ivanov in `#8 `_. 1.0.0 (2015-03-30) ~~~~~~~~~~~~~~~~~~ * Added ``to_dict`` method and ``from_dict`` classmethod on Tracebacks. Contributed by beckjake in `#5 `_. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/CONTRIBUTING.rst0000664000175000017500000000521000000000000015513 0ustar00ionelionel00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. Bug reports =========== When `reporting a bug `_ please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Documentation improvements ========================== tblib could always use more documentation, whether as part of the official tblib docs, in docstrings, or even on the web in blog posts, articles, and such. Feature requests and feedback ============================= The best way to send feedback is to file an issue at https://github.com/ionelmc/python-tblib/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that code contributions are welcome :) Development =========== To set up `python-tblib` for local development: 1. Fork `python-tblib `_ (look for the "Fork" button). 2. Clone your fork locally:: git clone git@github.com:ionelmc/python-tblib.git 3. Create a branch for local development:: git checkout -b name-of-your-bugfix-or-feature Now you can make your changes locally. 4. When you're done making changes run all the checks and docs builder with `tox `_ one command:: tox 5. Commit your changes and push your branch to GitHub:: git add . git commit -m "Your detailed description of your changes." git push origin name-of-your-bugfix-or-feature 6. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- If you need some code review or feedback while you're developing the code just make the pull request. For merging, you should: 1. Include passing tests (run ``tox``) [1]_. 2. Update documentation when there's new API, functionality etc. 3. Add a note to ``CHANGELOG.rst`` about the changes. 4. Add yourself to ``AUTHORS.rst``. .. [1] If you don't have all the necessary python versions available locally you can rely on Travis - it will `run the tests `_ for each change you add in the pull request. It will be slower though ... Tips ---- To run a subset of tests:: tox -e envname -- pytest -k test_myfeature To run all the test environments in *parallel* (you need to ``pip install detox``):: detox ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756406.0 tblib-1.7.0/LICENSE0000664000175000017500000000246200000000000014065 0ustar00ionelionel00000000000000BSD 2-Clause License 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: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/MANIFEST.in0000664000175000017500000000053600000000000014616 0ustar00ionelionel00000000000000graft docs graft src graft ci graft tests include .bumpversion.cfg include .coveragerc include .cookiecutterrc include .editorconfig include AUTHORS.rst include CHANGELOG.rst include CONTRIBUTING.rst include LICENSE include README.rst include tox.ini .travis.yml .appveyor.yml .readthedocs.yml global-exclude *.py[cod] __pycache__/* *.so *.dylib ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7943244 tblib-1.7.0/PKG-INFO0000664000175000017500000007377100000000000014170 0ustar00ionelionel00000000000000Metadata-Version: 1.2 Name: tblib Version: 1.7.0 Summary: Traceback serialization library. Home-page: https://github.com/ionelmc/python-tblib Author: Ionel Cristian Mărieș Author-email: contact@ionelmc.ro License: BSD-2-Clause Project-URL: Documentation, https://python-tblib.readthedocs.io/ Project-URL: Changelog, https://python-tblib.readthedocs.io/en/latest/changelog.html Project-URL: Issue Tracker, https://github.com/ionelmc/python-tblib/issues Description: ======== Overview ======== Serialization library for Exceptions and Tracebacks. * Free software: BSD license It allows you to: * `Pickle `_ tracebacks and raise exceptions with pickled tracebacks in different processes. This allows better error handling when running code over multiple processes (imagine multiprocessing, billiard, futures, celery etc). * Create traceback objects from strings (the ``from_string`` method). *No pickling is used*. * Serialize tracebacks to/from plain dicts (the ``from_dict`` and ``to_dict`` methods). *No pickling is used*. * Raise the tracebacks created from the aforementioned sources. * Pickle an Exception together with its traceback and exception chain (``raise ... from ...``) *(Python 3 only)* **Again, note that using the pickle support is completely optional. You are solely responsible for security problems should you decide to use the pickle support.** Installation ============ :: pip install tblib Documentation ============= .. contents:: :local: Pickling tracebacks ~~~~~~~~~~~~~~~~~~~ **Note**: The traceback objects that come out are stripped of some attributes (like variables). But you'll be able to raise exceptions with those tracebacks or print them - that should cover 99% of the usecases. :: >>> from tblib import pickling_support >>> pickling_support.install() >>> import pickle, sys >>> def inner_0(): ... raise Exception('fail') ... >>> def inner_1(): ... inner_0() ... >>> def inner_2(): ... inner_1() ... >>> try: ... inner_2() ... except: ... s1 = pickle.dumps(sys.exc_info()) ... >>> len(s1) > 1 True >>> try: ... inner_2() ... except: ... s2 = pickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL) ... >>> len(s2) > 1 True >>> try: ... import cPickle ... except ImportError: ... import pickle as cPickle >>> try: ... inner_2() ... except: ... s3 = cPickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL) ... >>> len(s3) > 1 True Unpickling tracebacks ~~~~~~~~~~~~~~~~~~~~~ :: >>> pickle.loads(s1) (<...Exception'>, Exception('fail'...), ) >>> pickle.loads(s2) (<...Exception'>, Exception('fail'...), ) >>> pickle.loads(s3) (<...Exception'>, Exception('fail'...), ) Raising ~~~~~~~ :: >>> from six import reraise >>> reraise(*pickle.loads(s1)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail >>> reraise(*pickle.loads(s2)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail >>> reraise(*pickle.loads(s3)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail Pickling Exceptions together with their traceback and chain (Python 3 only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: >>> try: # doctest: +SKIP ... try: ... 1 / 0 ... except Exception as e: ... raise Exception("foo") from e ... except Exception as e: ... s = pickle.dumps(e) >>> raise pickle.loads(s) # doctest: +SKIP Traceback (most recent call last): File "", line 3, in 1 / 0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "", line 1, in raise pickle.loads(s) File "", line 5, in raise Exception("foo") from e Exception: foo BaseException subclasses defined after calling ``pickling_support.install()`` will **not** retain their traceback and exception chain pickling. To cover custom Exceptions, there are three options: 1. Use ``@pickling_support.install`` as a decorator for each custom Exception .. code-block:: python >>> from tblib import pickling_support >>> # Declare all imports of your package's dependencies >>> import numpy # doctest: +SKIP >>> pickling_support.install() # install for all modules imported so far >>> @pickling_support.install ... class CustomError(Exception): ... pass Eventual subclasses of ``CustomError`` will need to be decorated again. 2. Invoke ``pickling_support.install()`` after all modules have been imported and all Exception subclasses have been declared .. code-block:: python >>> # Declare all imports of your package's dependencies >>> import numpy # doctest: +SKIP >>> from tblib import pickling_support >>> # Declare your own custom Exceptions >>> class CustomError(Exception): ... pass >>> # Finally, install tblib >>> pickling_support.install() 3. Selectively install tblib for Exception instances just before they are pickled .. code-block:: python pickling_support.install(, [Exception instance], ...) The above will install tblib pickling for all listed exceptions as well as any other exceptions in their exception chains. For example, one could write a wrapper to be used with `ProcessPoolExecutor `_, `Dask.distributed `_, or similar libraries: :: >>> from tblib import pickling_support >>> def wrapper(func, *args, **kwargs): ... try: ... return func(*args, **kwargs) ... except Exception as e: ... pickling_support.install(e) ... raise What if we have a local stack, does it show correctly ? ------------------------------------------------------- Yes it does:: >>> exc_info = pickle.loads(s3) >>> def local_0(): ... reraise(*exc_info) ... >>> def local_1(): ... local_0() ... >>> def local_2(): ... local_1() ... >>> local_2() Traceback (most recent call last): File "...doctest.py", line ..., in __run compileflags, 1) in test.globs File "", line 1, in local_2() File "", line 2, in local_2 local_1() File "", line 2, in local_1 local_0() File "", line 2, in local_0 reraise(*exc_info) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail It also supports more contrived scenarios ----------------------------------------- Like tracebacks with syntax errors:: >>> from tblib import Traceback >>> from examples import bad_syntax >>> try: ... bad_syntax() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in reraise(et, ev, tb.as_traceback()) File "", line 2, in bad_syntax() File "...tests...examples.py", line 18, in bad_syntax import badsyntax File "...tests...badsyntax.py", line 5 is very bad ^ SyntaxError: invalid syntax Or other import failures:: >>> from examples import bad_module >>> try: ... bad_module() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in reraise(et, ev, tb.as_traceback()) File "", line 2, in bad_module() File "...tests...examples.py", line 23, in bad_module import badmodule File "...tests...badmodule.py", line 3, in raise Exception("boom!") Exception: boom! Or a traceback that's caused by exceeding the recursion limit (here we're forcing the type and value to have consistency across platforms):: >>> def f(): f() >>> try: ... f() ... except RuntimeError: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(RuntimeError, RuntimeError("maximum recursion depth exceeded"), tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in f def f(): f() File "", line 1, in f def f(): f() File "", line 1, in f def f(): f() ... RuntimeError: maximum recursion depth exceeded Reference ~~~~~~~~~ tblib.Traceback --------------- It is used by the ``pickling_support``. You can use it too if you want more flexibility:: >>> from tblib import Traceback >>> try: ... inner_2() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail tblib.Traceback.to_dict ``````````````````````` You can use the ``to_dict`` method and the ``from_dict`` classmethod to convert a Traceback into and from a dictionary serializable by the stdlib json.JSONDecoder:: >>> import json >>> from pprint import pprint >>> try: ... inner_2() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... tb_dict = tb.to_dict() ... pprint(tb_dict) {'tb_frame': {'f_code': {'co_filename': '', 'co_name': ''}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 5}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_2'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_1'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_0'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': None}}}} tblib.Traceback.from_dict ````````````````````````` Building on the previous example:: >>> tb_json = json.dumps(tb_dict) >>> tb = Traceback.from_dict(json.loads(tb_json)) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail tblib.Traceback.from_string ``````````````````````````` :: >>> tb = Traceback.from_string(""" ... File "skipped.py", line 123, in func_123 ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: fail If you use the ``strict=False`` option then parsing is a bit more lax:: >>> tb = Traceback.from_string(""" ... File "bogus.py", line 123, in bogus ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """, strict=False) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "bogus.py", line 123, in bogus File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: fail tblib.decorators.return_error ----------------------------- :: >>> from tblib.decorators import return_error >>> inner_2r = return_error(inner_2) >>> e = inner_2r() >>> e >>> e.reraise() Traceback (most recent call last): ... File "", line 1, in e.reraise() File "...tblib...decorators.py", line 19, in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line 25, in return_exceptions_wrapper return func(*args, **kwargs) File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail How's this useful? Imagine you're using multiprocessing like this:: # Note that Python 3.4 and later will show the remote traceback (but as a string sadly) so we skip testing this. >>> import traceback >>> from multiprocessing import Pool >>> from examples import func_a >>> pool = Pool() # doctest: +SKIP >>> try: # doctest: +SKIP ... for i in pool.map(func_a, range(5)): ... print(i) ... except: ... print(traceback.format_exc()) ... Traceback (most recent call last): File "", line 2, in for i in pool.map(func_a, range(5)): File "...multiprocessing...pool.py", line ..., in map ... File "...multiprocessing...pool.py", line ..., in get ... Exception: Guessing time ! >>> pool.terminate() # doctest: +SKIP Not very useful is it? Let's sort this out:: >>> from tblib.decorators import apply_with_return_error, Error >>> from itertools import repeat >>> pool = Pool() >>> try: ... for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))): ... if isinstance(i, Error): ... i.reraise() ... else: ... print(i) ... except: ... print(traceback.format_exc()) ... Traceback (most recent call last): File "", line 4, in i.reraise() File "...tblib...decorators.py", line ..., in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line ..., in return_exceptions_wrapper return func(*args, **kwargs) File "...tblib...decorators.py", line ..., in apply_with_return_error return args[0](*args[1:]) File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: Guessing time ! >>> pool.terminate() Much better ! What if we have a local call stack ? ```````````````````````````````````` :: >>> def local_0(): ... pool = Pool() ... try: ... for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))): ... if isinstance(i, Error): ... i.reraise() ... else: ... print(i) ... finally: ... pool.close() ... >>> def local_1(): ... local_0() ... >>> def local_2(): ... local_1() ... >>> try: ... local_2() ... except: ... print(traceback.format_exc()) Traceback (most recent call last): File "", line 2, in local_2() File "", line 2, in local_2 local_1() File "", line 2, in local_1 local_0() File "", line 6, in local_0 i.reraise() File "...tblib...decorators.py", line 20, in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line 27, in return_exceptions_wrapper return func(*args, **kwargs) File "...tblib...decorators.py", line 47, in apply_with_return_error return args[0](*args[1:]) File "...tests...examples.py", line 2, in func_a func_b() File "...tests...examples.py", line 6, in func_b func_c() File "...tests...examples.py", line 10, in func_c func_d() File "...tests...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: Guessing time ! Other weird stuff ````````````````` Clearing traceback works (Python 3.4 and up):: >>> tb = Traceback.from_string(""" ... File "skipped.py", line 123, in func_123 ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """) >>> import traceback, sys >>> if sys.version_info > (3, 4): ... traceback.clear_frames(tb) Credits ======= * `mitsuhiko/jinja2 `_ for figuring a way to create traceback objects. Changelog ========= 1.7.0 (2020-07-24) ~~~~~~~~~~~~~~~~~~ * Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in `#58 `_. 1.6.0 (2019-12-07) ~~~~~~~~~~~~~~~~~~ * When pickling an Exception, also pickle its traceback and the Exception chain (``raise ... from ...``). Contributed by Guido Imperiale in `#53 `_. 1.5.0 (2019-10-23) ~~~~~~~~~~~~~~~~~~ * Added support for Python 3.8. Contributed by Victor Stinner in `#42 `_. * Removed support for end of life Python 3.4. * Few CI improvements and fixes. 1.4.0 (2019-05-02) ~~~~~~~~~~~~~~~~~~ * Removed support for end of life Python 3.3. * Fixed tests for Python 3.7. Contributed by Elliott Sales de Andrade in `#36 `_. * Fixed compatibility issue with Twised (``twisted.python.failure.Failure`` expected a ``co_code`` attribute). 1.3.2 (2017-04-09) ~~~~~~~~~~~~~~~~~~ * Add support for PyPy3.5-5.7.1-beta. Previously ``AttributeError: 'Frame' object has no attribute 'clear'`` could be raised. See PyPy issue `#2532 `_. 1.3.1 (2017-03-27) ~~~~~~~~~~~~~~~~~~ * Fixed handling for tracebacks due to exceeding the recursion limit. Fixes `#15 `_. 1.3.0 (2016-03-08) ~~~~~~~~~~~~~~~~~~ * Added ``Traceback.from_string``. 1.2.0 (2015-12-18) ~~~~~~~~~~~~~~~~~~ * Fixed handling for tracebacks from generators and other internal improvements and optimizations. Contributed by DRayX in `#10 `_ and `#11 `_. 1.1.0 (2015-07-27) ~~~~~~~~~~~~~~~~~~ * Added support for Python 2.6. Contributed by Arcadiy Ivanov in `#8 `_. 1.0.0 (2015-03-30) ~~~~~~~~~~~~~~~~~~ * Added ``to_dict`` method and ``from_dict`` classmethod on Tracebacks. Contributed by beckjake in `#5 `_. Keywords: traceback,debugging,exceptions Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: Unix Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Utilities Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546253.0 tblib-1.7.0/README.rst0000664000175000017500000005550300000000000014553 0ustar00ionelionel00000000000000======== Overview ======== .. start-badges .. list-table:: :stub-columns: 1 * - docs - |docs| * - tests - | |travis| |appveyor| |requires| | |codecov| * - package - | |version| |wheel| |supported-versions| |supported-implementations| | |commits-since| .. |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 .. |travis| image:: https://api.travis-ci.org/ionelmc/python-tblib.svg?branch=master :alt: Travis-CI Build Status :target: https://travis-ci.org/ionelmc/python-tblib .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/ionelmc/python-tblib?branch=master&svg=true :alt: AppVeyor Build Status :target: https://ci.appveyor.com/project/ionelmc/python-tblib .. |requires| image:: https://requires.io/github/ionelmc/python-tblib/requirements.svg?branch=master :alt: Requirements Status :target: https://requires.io/github/ionelmc/python-tblib/requirements/?branch=master .. |codecov| image:: https://codecov.io/github/ionelmc/python-tblib/coverage.svg?branch=master :alt: Coverage Status :target: https://codecov.io/github/ionelmc/python-tblib .. |version| image:: https://img.shields.io/pypi/v/tblib.svg :alt: PyPI Package latest release :target: https://pypi.org/project/tblib .. |wheel| image:: https://img.shields.io/pypi/wheel/tblib.svg :alt: PyPI Wheel :target: https://pypi.org/project/tblib .. |supported-versions| image:: https://img.shields.io/pypi/pyversions/tblib.svg :alt: Supported versions :target: https://pypi.org/project/tblib .. |supported-implementations| image:: https://img.shields.io/pypi/implementation/tblib.svg :alt: Supported implementations :target: https://pypi.org/project/tblib .. |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.7.0...master .. end-badges Serialization library for Exceptions and Tracebacks. * Free software: BSD license It allows you to: * `Pickle `_ tracebacks and raise exceptions with pickled tracebacks in different processes. This allows better error handling when running code over multiple processes (imagine multiprocessing, billiard, futures, celery etc). * Create traceback objects from strings (the ``from_string`` method). *No pickling is used*. * Serialize tracebacks to/from plain dicts (the ``from_dict`` and ``to_dict`` methods). *No pickling is used*. * Raise the tracebacks created from the aforementioned sources. * Pickle an Exception together with its traceback and exception chain (``raise ... from ...``) *(Python 3 only)* **Again, note that using the pickle support is completely optional. You are solely responsible for security problems should you decide to use the pickle support.** Installation ============ :: pip install tblib Documentation ============= .. contents:: :local: Pickling tracebacks ~~~~~~~~~~~~~~~~~~~ **Note**: The traceback objects that come out are stripped of some attributes (like variables). But you'll be able to raise exceptions with those tracebacks or print them - that should cover 99% of the usecases. :: >>> from tblib import pickling_support >>> pickling_support.install() >>> import pickle, sys >>> def inner_0(): ... raise Exception('fail') ... >>> def inner_1(): ... inner_0() ... >>> def inner_2(): ... inner_1() ... >>> try: ... inner_2() ... except: ... s1 = pickle.dumps(sys.exc_info()) ... >>> len(s1) > 1 True >>> try: ... inner_2() ... except: ... s2 = pickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL) ... >>> len(s2) > 1 True >>> try: ... import cPickle ... except ImportError: ... import pickle as cPickle >>> try: ... inner_2() ... except: ... s3 = cPickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL) ... >>> len(s3) > 1 True Unpickling tracebacks ~~~~~~~~~~~~~~~~~~~~~ :: >>> pickle.loads(s1) (<...Exception'>, Exception('fail'...), ) >>> pickle.loads(s2) (<...Exception'>, Exception('fail'...), ) >>> pickle.loads(s3) (<...Exception'>, Exception('fail'...), ) Raising ~~~~~~~ :: >>> from six import reraise >>> reraise(*pickle.loads(s1)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail >>> reraise(*pickle.loads(s2)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail >>> reraise(*pickle.loads(s3)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail Pickling Exceptions together with their traceback and chain (Python 3 only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: >>> try: # doctest: +SKIP ... try: ... 1 / 0 ... except Exception as e: ... raise Exception("foo") from e ... except Exception as e: ... s = pickle.dumps(e) >>> raise pickle.loads(s) # doctest: +SKIP Traceback (most recent call last): File "", line 3, in 1 / 0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "", line 1, in raise pickle.loads(s) File "", line 5, in raise Exception("foo") from e Exception: foo BaseException subclasses defined after calling ``pickling_support.install()`` will **not** retain their traceback and exception chain pickling. To cover custom Exceptions, there are three options: 1. Use ``@pickling_support.install`` as a decorator for each custom Exception .. code-block:: python >>> from tblib import pickling_support >>> # Declare all imports of your package's dependencies >>> import numpy # doctest: +SKIP >>> pickling_support.install() # install for all modules imported so far >>> @pickling_support.install ... class CustomError(Exception): ... pass Eventual subclasses of ``CustomError`` will need to be decorated again. 2. Invoke ``pickling_support.install()`` after all modules have been imported and all Exception subclasses have been declared .. code-block:: python >>> # Declare all imports of your package's dependencies >>> import numpy # doctest: +SKIP >>> from tblib import pickling_support >>> # Declare your own custom Exceptions >>> class CustomError(Exception): ... pass >>> # Finally, install tblib >>> pickling_support.install() 3. Selectively install tblib for Exception instances just before they are pickled .. code-block:: python pickling_support.install(, [Exception instance], ...) The above will install tblib pickling for all listed exceptions as well as any other exceptions in their exception chains. For example, one could write a wrapper to be used with `ProcessPoolExecutor `_, `Dask.distributed `_, or similar libraries: :: >>> from tblib import pickling_support >>> def wrapper(func, *args, **kwargs): ... try: ... return func(*args, **kwargs) ... except Exception as e: ... pickling_support.install(e) ... raise What if we have a local stack, does it show correctly ? ------------------------------------------------------- Yes it does:: >>> exc_info = pickle.loads(s3) >>> def local_0(): ... reraise(*exc_info) ... >>> def local_1(): ... local_0() ... >>> def local_2(): ... local_1() ... >>> local_2() Traceback (most recent call last): File "...doctest.py", line ..., in __run compileflags, 1) in test.globs File "", line 1, in local_2() File "", line 2, in local_2 local_1() File "", line 2, in local_1 local_0() File "", line 2, in local_0 reraise(*exc_info) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail It also supports more contrived scenarios ----------------------------------------- Like tracebacks with syntax errors:: >>> from tblib import Traceback >>> from examples import bad_syntax >>> try: ... bad_syntax() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in reraise(et, ev, tb.as_traceback()) File "", line 2, in bad_syntax() File "...tests...examples.py", line 18, in bad_syntax import badsyntax File "...tests...badsyntax.py", line 5 is very bad ^ SyntaxError: invalid syntax Or other import failures:: >>> from examples import bad_module >>> try: ... bad_module() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in reraise(et, ev, tb.as_traceback()) File "", line 2, in bad_module() File "...tests...examples.py", line 23, in bad_module import badmodule File "...tests...badmodule.py", line 3, in raise Exception("boom!") Exception: boom! Or a traceback that's caused by exceeding the recursion limit (here we're forcing the type and value to have consistency across platforms):: >>> def f(): f() >>> try: ... f() ... except RuntimeError: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(RuntimeError, RuntimeError("maximum recursion depth exceeded"), tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in f def f(): f() File "", line 1, in f def f(): f() File "", line 1, in f def f(): f() ... RuntimeError: maximum recursion depth exceeded Reference ~~~~~~~~~ tblib.Traceback --------------- It is used by the ``pickling_support``. You can use it too if you want more flexibility:: >>> from tblib import Traceback >>> try: ... inner_2() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail tblib.Traceback.to_dict ``````````````````````` You can use the ``to_dict`` method and the ``from_dict`` classmethod to convert a Traceback into and from a dictionary serializable by the stdlib json.JSONDecoder:: >>> import json >>> from pprint import pprint >>> try: ... inner_2() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... tb_dict = tb.to_dict() ... pprint(tb_dict) {'tb_frame': {'f_code': {'co_filename': '', 'co_name': ''}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 5}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_2'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_1'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_0'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': None}}}} tblib.Traceback.from_dict ````````````````````````` Building on the previous example:: >>> tb_json = json.dumps(tb_dict) >>> tb = Traceback.from_dict(json.loads(tb_json)) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail tblib.Traceback.from_string ``````````````````````````` :: >>> tb = Traceback.from_string(""" ... File "skipped.py", line 123, in func_123 ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: fail If you use the ``strict=False`` option then parsing is a bit more lax:: >>> tb = Traceback.from_string(""" ... File "bogus.py", line 123, in bogus ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """, strict=False) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "bogus.py", line 123, in bogus File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: fail tblib.decorators.return_error ----------------------------- :: >>> from tblib.decorators import return_error >>> inner_2r = return_error(inner_2) >>> e = inner_2r() >>> e >>> e.reraise() Traceback (most recent call last): ... File "", line 1, in e.reraise() File "...tblib...decorators.py", line 19, in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line 25, in return_exceptions_wrapper return func(*args, **kwargs) File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail How's this useful? Imagine you're using multiprocessing like this:: # Note that Python 3.4 and later will show the remote traceback (but as a string sadly) so we skip testing this. >>> import traceback >>> from multiprocessing import Pool >>> from examples import func_a >>> pool = Pool() # doctest: +SKIP >>> try: # doctest: +SKIP ... for i in pool.map(func_a, range(5)): ... print(i) ... except: ... print(traceback.format_exc()) ... Traceback (most recent call last): File "", line 2, in for i in pool.map(func_a, range(5)): File "...multiprocessing...pool.py", line ..., in map ... File "...multiprocessing...pool.py", line ..., in get ... Exception: Guessing time ! >>> pool.terminate() # doctest: +SKIP Not very useful is it? Let's sort this out:: >>> from tblib.decorators import apply_with_return_error, Error >>> from itertools import repeat >>> pool = Pool() >>> try: ... for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))): ... if isinstance(i, Error): ... i.reraise() ... else: ... print(i) ... except: ... print(traceback.format_exc()) ... Traceback (most recent call last): File "", line 4, in i.reraise() File "...tblib...decorators.py", line ..., in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line ..., in return_exceptions_wrapper return func(*args, **kwargs) File "...tblib...decorators.py", line ..., in apply_with_return_error return args[0](*args[1:]) File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: Guessing time ! >>> pool.terminate() Much better ! What if we have a local call stack ? ```````````````````````````````````` :: >>> def local_0(): ... pool = Pool() ... try: ... for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))): ... if isinstance(i, Error): ... i.reraise() ... else: ... print(i) ... finally: ... pool.close() ... >>> def local_1(): ... local_0() ... >>> def local_2(): ... local_1() ... >>> try: ... local_2() ... except: ... print(traceback.format_exc()) Traceback (most recent call last): File "", line 2, in local_2() File "", line 2, in local_2 local_1() File "", line 2, in local_1 local_0() File "", line 6, in local_0 i.reraise() File "...tblib...decorators.py", line 20, in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line 27, in return_exceptions_wrapper return func(*args, **kwargs) File "...tblib...decorators.py", line 47, in apply_with_return_error return args[0](*args[1:]) File "...tests...examples.py", line 2, in func_a func_b() File "...tests...examples.py", line 6, in func_b func_c() File "...tests...examples.py", line 10, in func_c func_d() File "...tests...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: Guessing time ! Other weird stuff ````````````````` Clearing traceback works (Python 3.4 and up):: >>> tb = Traceback.from_string(""" ... File "skipped.py", line 123, in func_123 ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """) >>> import traceback, sys >>> if sys.version_info > (3, 4): ... traceback.clear_frames(tb) Credits ======= * `mitsuhiko/jinja2 `_ for figuring a way to create traceback objects. ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7913244 tblib-1.7.0/ci/0000775000175000017500000000000000000000000013447 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1571799532.0 tblib-1.7.0/ci/appveyor-download.py0000775000175000017500000000735200000000000017505 0ustar00ionelionel00000000000000#!/usr/bin/env python """ Use the AppVeyor API to download Windows artifacts. Taken from: https://bitbucket.org/ned/coveragepy/src/tip/ci/download_appveyor.py # Licensed under the Apache License: https://www.apache.org/licenses/LICENSE-2.0 # For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt """ from __future__ import unicode_literals import argparse import os import zipfile import requests def make_auth_headers(): """Make the authentication headers needed to use the Appveyor API.""" path = os.path.expanduser("~/.appveyor.token") if not os.path.exists(path): raise RuntimeError( "Please create a file named `.appveyor.token` in your home directory. " "You can get the token from https://ci.appveyor.com/api-token" ) with open(path) as f: token = f.read().strip() headers = { 'Authorization': 'Bearer {}'.format(token), } return headers def download_latest_artifacts(account_project, build_id): """Download all the artifacts from the latest build.""" if build_id is None: url = "https://ci.appveyor.com/api/projects/{}".format(account_project) else: url = "https://ci.appveyor.com/api/projects/{}/build/{}".format(account_project, build_id) build = requests.get(url, headers=make_auth_headers()).json() jobs = build['build']['jobs'] print(u"Build {0[build][version]}, {1} jobs: {0[build][message]}".format(build, len(jobs))) for job in jobs: name = job['name'] print(u" {0}: {1[status]}, {1[artifactsCount]} artifacts".format(name, job)) url = "https://ci.appveyor.com/api/buildjobs/{}/artifacts".format(job['jobId']) response = requests.get(url, headers=make_auth_headers()) artifacts = response.json() for artifact in artifacts: is_zip = artifact['type'] == "Zip" filename = artifact['fileName'] print(u" {0}, {1} bytes".format(filename, artifact['size'])) url = "https://ci.appveyor.com/api/buildjobs/{}/artifacts/{}".format(job['jobId'], filename) download_url(url, filename, make_auth_headers()) if is_zip: unpack_zipfile(filename) os.remove(filename) def ensure_dirs(filename): """Make sure the directories exist for `filename`.""" dirname = os.path.dirname(filename) if dirname and not os.path.exists(dirname): os.makedirs(dirname) def download_url(url, filename, headers): """Download a file from `url` to `filename`.""" ensure_dirs(filename) response = requests.get(url, headers=headers, stream=True) if response.status_code == 200: with open(filename, 'wb') as f: for chunk in response.iter_content(16 * 1024): f.write(chunk) else: print(u" Error downloading {}: {}".format(url, response)) def unpack_zipfile(filename): """Unpack a zipfile, using the names in the zip.""" with open(filename, 'rb') as fzip: z = zipfile.ZipFile(fzip) for name in z.namelist(): print(u" extracting {}".format(name)) ensure_dirs(name) z.extract(name) parser = argparse.ArgumentParser(description='Download artifacts from AppVeyor.') parser.add_argument('--id', metavar='PROJECT_ID', default='ionelmc/python-tblib', help='Project ID in AppVeyor.') parser.add_argument('build', nargs='?', metavar='BUILD_ID', help='Build ID in AppVeyor. Eg: master-123') if __name__ == "__main__": # import logging # logging.basicConfig(level="DEBUG") args = parser.parse_args() download_latest_artifacts(args.id, args.build) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/ci/appveyor-with-compiler.cmd0000664000175000017500000000137300000000000020566 0ustar00ionelionel00000000000000:: Very simple setup: :: - if WINDOWS_SDK_VERSION is set then activate the SDK. :: - disable the WDK if it's around. SET COMMAND_TO_RUN=%* SET WIN_SDK_ROOT=C:\Program Files\Microsoft SDKs\Windows SET WIN_WDK="c:\Program Files (x86)\Windows Kits\10\Include\wdf" ECHO SDK: %WINDOWS_SDK_VERSION% ARCH: %PYTHON_ARCH% IF EXIST %WIN_WDK% ( REM See: https://connect.microsoft.com/VisualStudio/feedback/details/1610302/ REN %WIN_WDK% 0wdf ) IF "%WINDOWS_SDK_VERSION%"=="" GOTO main SET DISTUTILS_USE_SDK=1 SET MSSdk=1 "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Setup\WindowsSdkVer.exe" -q -version:%WINDOWS_SDK_VERSION% CALL "%WIN_SDK_ROOT%\%WINDOWS_SDK_VERSION%\Bin\SetEnv.cmd" /x64 /release :main ECHO Executing: %COMMAND_TO_RUN% CALL %COMMAND_TO_RUN% || EXIT 1 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/ci/bootstrap.py0000775000175000017500000000550600000000000016047 0ustar00ionelionel00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import from __future__ import print_function from __future__ import unicode_literals import os import subprocess import sys from os.path import abspath from os.path import dirname from os.path import exists from os.path import join base_path = dirname(dirname(abspath(__file__))) def check_call(args): print("+", *args) subprocess.check_call(args) def exec_in_env(): env_path = join(base_path, ".tox", "bootstrap") if sys.platform == "win32": bin_path = join(env_path, "Scripts") else: bin_path = join(env_path, "bin") if not exists(env_path): import subprocess print("Making bootstrap env in: {0} ...".format(env_path)) try: check_call([sys.executable, "-m", "venv", env_path]) except subprocess.CalledProcessError: try: check_call([sys.executable, "-m", "virtualenv", env_path]) except subprocess.CalledProcessError: check_call(["virtualenv", env_path]) print("Installing `jinja2` into bootstrap environment...") check_call([join(bin_path, "pip"), "install", "jinja2", "tox"]) python_executable = join(bin_path, "python") if not os.path.exists(python_executable): python_executable += '.exe' print("Re-executing with: {0}".format(python_executable)) print("+ exec", python_executable, __file__, "--no-env") os.execv(python_executable, [python_executable, __file__, "--no-env"]) def main(): import jinja2 print("Project path: {0}".format(base_path)) jinja = jinja2.Environment( loader=jinja2.FileSystemLoader(join(base_path, "ci", "templates")), trim_blocks=True, lstrip_blocks=True, keep_trailing_newline=True ) tox_environments = [ line.strip() # 'tox' need not be installed globally, but must be importable # by the Python that is running this script. # This uses sys.executable the same way that the call in # cookiecutter-pylibrary/hooks/post_gen_project.py # invokes this bootstrap.py itself. for line in subprocess.check_output([sys.executable, '-m', 'tox', '--listenvs'], universal_newlines=True).splitlines() ] tox_environments = [line for line in tox_environments if line.startswith('py')] for name in os.listdir(join("ci", "templates")): with open(join(base_path, name), "w") as fh: fh.write(jinja.get_template(name).render(tox_environments=tox_environments)) print("Wrote {}".format(name)) print("DONE.") if __name__ == "__main__": args = sys.argv[1:] if args == ["--no-env"]: main() elif not args: exec_in_env() else: print("Unexpected arguments {0}".format(args), file=sys.stderr) sys.exit(1) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/ci/requirements.txt0000664000175000017500000000007600000000000016736 0ustar00ionelionel00000000000000virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 six>=1.12.0 ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7913244 tblib-1.7.0/ci/templates/0000775000175000017500000000000000000000000015445 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/ci/templates/.appveyor.yml0000664000175000017500000000325400000000000020117 0ustar00ionelionel00000000000000version: '{branch}-{build}' build: off environment: matrix: - TOXENV: check TOXPYTHON: C:\Python36\python.exe PYTHON_HOME: C:\Python36 PYTHON_VERSION: '3.6' PYTHON_ARCH: '32' {% for env in tox_environments %} {% if env.startswith(('py2', 'py3')) %} - TOXENV: {{ env }},codecov{{ "" }} TOXPYTHON: C:\Python{{ env[2:4] }}\python.exe PYTHON_HOME: C:\Python{{ env[2:4] }} PYTHON_VERSION: '{{ env[2] }}.{{ env[3] }}' PYTHON_ARCH: '32' {% if 'nocov' in env %} WHEEL_PATH: .tox/dist {% endif %} - TOXENV: {{ env }},codecov{{ "" }} TOXPYTHON: C:\Python{{ env[2:4] }}-x64\python.exe PYTHON_HOME: C:\Python{{ env[2:4] }}-x64 PYTHON_VERSION: '{{ env[2] }}.{{ env[3] }}' PYTHON_ARCH: '64' {% if 'nocov' in env %} WHEEL_PATH: .tox/dist {% endif %} {% if env.startswith('py2') %} WINDOWS_SDK_VERSION: v7.0 {% endif %} {% endif %}{% endfor %} init: - ps: echo $env:TOXENV - ps: ls C:\Python* install: - '%PYTHON_HOME%\python -mpip install --progress-bar=off tox -rci/requirements.txt' - '%PYTHON_HOME%\Scripts\virtualenv --version' - '%PYTHON_HOME%\Scripts\easy_install --version' - '%PYTHON_HOME%\Scripts\pip --version' - '%PYTHON_HOME%\Scripts\tox --version' test_script: - cmd /E:ON /V:ON /C .\ci\appveyor-with-compiler.cmd %PYTHON_HOME%\Scripts\tox on_failure: - ps: dir "env:" - ps: get-content .tox\*\log\* ### To enable remote debugging uncomment this (also, see: http://www.appveyor.com/docs/how-to/rdp-to-build-worker): # on_finish: # - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/ci/templates/.travis.yml0000664000175000017500000000176600000000000017570 0ustar00ionelionel00000000000000language: python dist: xenial cache: false env: global: - LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so - SEGFAULT_SIGNALS=all matrix: include: - python: '3.6' env: - TOXENV=check - python: '3.6' env: - TOXENV=docs {%- for env in tox_environments %}{{ '' }} - env: - TOXENV={{ env }},codecov {%- if env.startswith('pypy3') %}{{ '' }} - TOXPYTHON=pypy3 python: 'pypy3' {%- elif env.startswith('pypy') %}{{ '' }} python: 'pypy' {%- else %}{{ '' }} python: '{{ '{0[2]}.{0[3]}'.format(env) }}' {%- endif %} {%- endfor %}{{ '' }} before_install: - python --version - uname -a - lsb_release -a || true install: - python -mpip install --progress-bar=off tox -rci/requirements.txt - virtualenv --version - easy_install --version - pip --version - tox --version script: - tox -v after_failure: - more .tox/log/* | cat - more .tox/*/log/* | cat notifications: email: on_success: never on_failure: always ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7923243 tblib-1.7.0/docs/0000775000175000017500000000000000000000000014004 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/authors.rst0000664000175000017500000000003400000000000016220 0ustar00ionelionel00000000000000.. include:: ../AUTHORS.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/changelog.rst0000664000175000017500000000003600000000000016464 0ustar00ionelionel00000000000000.. include:: ../CHANGELOG.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546253.0 tblib-1.7.0/docs/conf.py0000664000175000017500000000241000000000000015300 0ustar00ionelionel00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import os extensions = [ 'autoapi.extension', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', ] autoapi_type = 'python' autoapi_dirs = ['../src'] source_suffix = '.rst' master_doc = 'index' project = 'tblib' year = '2013-2020' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) version = release = '1.7.0' pygments_style = 'trac' templates_path = ['.'] extlinks = { 'issue': ('https://github.com/ionelmc/python-tblib/issues/%s', '#'), 'pr': ('https://github.com/ionelmc/python-tblib/pull/%s', 'PR #'), } import sphinx_py3doc_enhanced_theme html_theme = "sphinx_py3doc_enhanced_theme" html_theme_path = [sphinx_py3doc_enhanced_theme.get_html_theme_path()] html_theme_options = { 'githuburl': 'https://github.com/ionelmc/python-tblib/' } html_use_smartypants = True html_last_updated_fmt = '%b %d, %Y' html_split_index = False html_sidebars = { '**': ['searchbox.html', 'globaltoc.html', 'sourcelink.html'], } html_short_title = '%s-%s' % (project, version) napoleon_use_ivar = True napoleon_use_rtype = False napoleon_use_param = False ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/contributing.rst0000664000175000017500000000004100000000000017240 0ustar00ionelionel00000000000000.. include:: ../CONTRIBUTING.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583757376.0 tblib-1.7.0/docs/index.rst0000664000175000017500000000034200000000000015644 0ustar00ionelionel00000000000000======== Contents ======== .. toctree:: :maxdepth: 2 readme installation usage contributing authors changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/installation.rst0000664000175000017500000000012500000000000017235 0ustar00ionelionel00000000000000============ Installation ============ At the command line:: pip install tblib ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/readme.rst0000664000175000017500000000003300000000000015767 0ustar00ionelionel00000000000000.. include:: ../README.rst ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756647.0 tblib-1.7.0/docs/requirements.txt0000664000175000017500000000007000000000000017265 0ustar00ionelionel00000000000000sphinx>=1.3 sphinx-py3doc-enhanced-theme sphinx-autoapi ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/spelling_wordlist.txt0000664000175000017500000000015500000000000020312 0ustar00ionelionel00000000000000builtin builtins classmethod staticmethod classmethods staticmethods args kwargs callstack Changelog Indices ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756045.0 tblib-1.7.0/docs/usage.rst0000664000175000017500000000007600000000000015645 0ustar00ionelionel00000000000000===== Usage ===== To use tblib in a project:: import tblib ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7953243 tblib-1.7.0/setup.cfg0000664000175000017500000000111300000000000014671 0ustar00ionelionel00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 140 exclude = */migrations/* [tool:pytest] norecursedirs = migrations python_files = test_*.py *_test.py tests.py addopts = -ra --strict --ignore=tests/badmodule.py --ignore=tests/badsyntax.py --doctest-modules --doctest-continue-on-failure --doctest-glob=\*.rst --tb=short testpaths = tests [tool:isort] force_single_line = True line_length = 120 known_first_party = tblib default_section = THIRDPARTY forced_separate = test_tblib not_skip = __init__.py skip = migrations [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546253.0 tblib-1.7.0/setup.py0000664000175000017500000000560700000000000014576 0ustar00ionelionel00000000000000#!/usr/bin/env python # -*- encoding: utf-8 -*- from __future__ import absolute_import from __future__ import print_function import io import re from glob import glob from os.path import basename from os.path import dirname from os.path import join from os.path import splitext from setuptools import find_packages from setuptools import setup def read(*names, **kwargs): with io.open( join(dirname(__file__), *names), encoding=kwargs.get('encoding', 'utf8') ) as fh: return fh.read() setup( name='tblib', version='1.7.0', license='BSD-2-Clause', description='Traceback serialization library.', long_description='%s\n%s' % ( re.compile('^.. start-badges.*^.. end-badges', re.M | re.S).sub('', read('README.rst')), re.sub(':[a-z]+:`~?(.*?)`', r'``\1``', read('CHANGELOG.rst')) ), author='Ionel Cristian Mărieș', author_email='contact@ionelmc.ro', url='https://github.com/ionelmc/python-tblib', packages=find_packages('src'), package_dir={'': 'src'}, py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')], include_package_data=True, zip_safe=False, classifiers=[ # complete classifier list: http://pypi.python.org/pypi?%3Aaction=list_classifiers 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: Unix', 'Operating System :: POSIX', 'Operating System :: Microsoft :: Windows', 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', # uncomment if you test on these interpreters: # 'Programming Language :: Python :: Implementation :: IronPython', # 'Programming Language :: Python :: Implementation :: Jython', # 'Programming Language :: Python :: Implementation :: Stackless', 'Topic :: Utilities', ], project_urls={ 'Documentation': 'https://python-tblib.readthedocs.io/', 'Changelog': 'https://python-tblib.readthedocs.io/en/latest/changelog.html', 'Issue Tracker': 'https://github.com/ionelmc/python-tblib/issues', }, keywords=[ 'traceback', 'debugging', 'exceptions', ], python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*', install_requires=[ # eg: 'aspectlib==1.1.1', 'six>=1.7', ], extras_require={ # eg: # 'rst': ['docutils>=0.11'], # ':python_version=="2.6"': ['argparse'], }, ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7883244 tblib-1.7.0/src/0000775000175000017500000000000000000000000013643 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7933245 tblib-1.7.0/src/tblib/0000775000175000017500000000000000000000000014737 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546253.0 tblib-1.7.0/src/tblib/__init__.py0000664000175000017500000002233100000000000017051 0ustar00ionelionel00000000000000import re import sys from types import CodeType from types import FrameType from types import TracebackType try: from __pypy__ import tproxy except ImportError: tproxy = None try: from .cpython import tb_set_next except ImportError: tb_set_next = None if not tb_set_next and not tproxy: raise ImportError("Cannot use tblib. Runtime not supported.") __version__ = '1.7.0' __all__ = 'Traceback', 'TracebackParseError', 'Frame', 'Code' PY3 = sys.version_info[0] == 3 FRAME_RE = re.compile(r'^\s*File "(?P.+)", line (?P\d+)(, in (?P.+))?$') class _AttrDict(dict): __slots__ = () def __getattr__(self, name): try: return self[name] except KeyError: raise AttributeError(name) # noinspection PyPep8Naming class __traceback_maker(Exception): pass class TracebackParseError(Exception): pass 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 """ # 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 that wraps builtin Traceback objects. """ tb_next = None def __init__(self, tb): self.tb_frame = Frame(tb.tb_frame) # noinspection SpellCheckingInspection self.tb_lineno = int(tb.tb_lineno) # Build in place to avoid exceeding the recursion limit tb = tb.tb_next prev_traceback = self cls = type(self) while tb is not None: traceback = object.__new__(cls) traceback.tb_frame = Frame(tb.tb_frame) traceback.tb_lineno = int(tb.tb_lineno) prev_traceback.tb_next = traceback prev_traceback = traceback 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__) if not tb_set_next: raise RuntimeError("Unsupported Python interpreter!") current = self top_tb = None tb = None while current: f_code = current.tb_frame.f_code code = compile('\n' * (current.tb_lineno - 1) + 'raise __traceback_maker', current.tb_frame.f_code.co_filename, 'exec') if hasattr(code, "replace"): # Python 3.8 and newer code = code.replace(co_argcount=0, co_filename=f_code.co_filename, co_name=f_code.co_name, co_freevars=(), co_cellvars=()) elif PY3: code = CodeType( 0, code.co_kwonlyargcount, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, f_code.co_filename, f_code.co_name, code.co_firstlineno, code.co_lnotab, (), () ) else: code = CodeType( 0, code.co_nlocals, code.co_stacksize, code.co_flags, code.co_code, code.co_consts, code.co_names, code.co_varnames, f_code.co_filename.encode(), f_code.co_name.encode(), code.co_firstlineno, code.co_lnotab, (), () ) # noinspection PyBroadException try: exec(code, dict(current.tb_frame.f_globals), {}) except Exception: next_tb = sys.exc_info()[2].tb_next if top_tb is None: top_tb = next_tb if tb is not None: tb_set_next(tb, next_tb) tb = next_tb del next_tb current = current.tb_next try: return top_tb finally: del top_tb del tb to_traceback = as_traceback # noinspection SpellCheckingInspection 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 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: tb_next = self.tb_next.to_dict() code = { 'co_filename': self.tb_frame.f_code.co_filename, 'co_name': self.tb_frame.f_code.co_name, } 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: tb_next = None code = _AttrDict( co_filename=dct['tb_frame']['f_code']['co_filename'], co_name=dct['tb_frame']['f_code']['co_name'], ) frame = _AttrDict( f_globals=dct['tb_frame']['f_globals'], f_code=code, f_lineno=dct['tb_frame']['f_lineno'], ) tb = _AttrDict( tb_frame=frame, tb_lineno=dct['tb_lineno'], tb_next=tb_next, ) return cls(tb) @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 for line in string.splitlines(): line = line.rstrip() if header: if line == 'Traceback (most recent call last):': header = False continue frame_match = FRAME_RE.match(line) if frame_match: frames.append(frame_match.groupdict()) elif line.startswith(' '): pass elif strict: break # traceback ended if frames: previous = None for frame in reversed(frames): previous = _AttrDict( frame, tb_frame=_AttrDict( frame, f_globals=_AttrDict( __file__=frame['co_filename'], __name__='?', ), f_code=_AttrDict(frame), f_lineno=int(frame['tb_lineno']), ), tb_next=previous, ) return cls(previous) else: raise TracebackParseError("Could not find any frames in %r." % string) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1556791884.0 tblib-1.7.0/src/tblib/cpython.py0000664000175000017500000000451500000000000017002 0ustar00ionelionel00000000000000""" Taken verbatim from Jinja2. https://github.com/mitsuhiko/jinja2/blob/master/jinja2/debug.py#L267 """ import platform import sys def _init_ugly_crap(): """This function implements a few ugly things so that we can patch the traceback objects. The function returned allows resetting `tb_next` on any python traceback object. Do not attempt to use this on non cpython interpreters """ import ctypes from types import TracebackType # figure out side of _Py_ssize_t if hasattr(ctypes.pythonapi, 'Py_InitModule4_64'): _Py_ssize_t = ctypes.c_int64 else: _Py_ssize_t = ctypes.c_int # regular python class _PyObject(ctypes.Structure): pass _PyObject._fields_ = [ ('ob_refcnt', _Py_ssize_t), ('ob_type', ctypes.POINTER(_PyObject)) ] # python with trace if hasattr(sys, 'getobjects'): class _PyObject(ctypes.Structure): pass _PyObject._fields_ = [ ('_ob_next', ctypes.POINTER(_PyObject)), ('_ob_prev', ctypes.POINTER(_PyObject)), ('ob_refcnt', _Py_ssize_t), ('ob_type', ctypes.POINTER(_PyObject)) ] class _Traceback(_PyObject): pass _Traceback._fields_ = [ ('tb_next', ctypes.POINTER(_Traceback)), ('tb_frame', ctypes.POINTER(_PyObject)), ('tb_lasti', ctypes.c_int), ('tb_lineno', ctypes.c_int) ] def tb_set_next(tb, next): """Set the tb_next attribute of a traceback object.""" if not (isinstance(tb, TracebackType) and (next is None or isinstance(next, TracebackType))): raise TypeError('tb_set_next arguments must be traceback objects') obj = _Traceback.from_address(id(tb)) if tb.tb_next is not None: old = _Traceback.from_address(id(tb.tb_next)) old.ob_refcnt -= 1 if next is None: obj.tb_next = ctypes.POINTER(_Traceback)() else: next = _Traceback.from_address(id(next)) next.ob_refcnt += 1 obj.tb_next = ctypes.pointer(next) return tb_set_next tb_set_next = None try: if platform.python_implementation() == 'CPython': tb_set_next = _init_ugly_crap() except Exception as exc: sys.stderr.write("Failed to initialize cpython support: {!r}".format(exc)) del _init_ugly_crap ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1556791884.0 tblib-1.7.0/src/tblib/decorators.py0000664000175000017500000000206400000000000017460 0ustar00ionelionel00000000000000import sys from functools import wraps from six import reraise from . import Traceback class Error(object): def __init__(self, exc_type, exc_value, traceback): self.exc_type = exc_type self.exc_value = exc_value self.__traceback = Traceback(traceback) @property def traceback(self): return self.__traceback.as_traceback() def reraise(self): reraise(self.exc_type, self.exc_value, self.traceback) def return_error(func, exc_type=Exception): @wraps(func) def return_exceptions_wrapper(*args, **kwargs): try: return func(*args, **kwargs) except exc_type: return Error(*sys.exc_info()) return return_exceptions_wrapper returns_error = return_errors = returns_errors = return_error # cause I make too many typos @return_error def apply_with_return_error(args): """ args is a tuple where the first argument is a callable. eg:: apply_with_return_error((func, 1, 2, 3)) - this will call func(1, 2, 3) """ return args[0](*args[1:]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1575664815.0 tblib-1.7.0/src/tblib/pickling_support.py0000664000175000017500000000554500000000000020716 0ustar00ionelionel00000000000000import sys from types import TracebackType from . import Frame from . import Traceback if sys.version_info.major >= 3: import copyreg else: import copy_reg as copyreg def unpickle_traceback(tb_frame, tb_lineno, tb_next): ret = object.__new__(Traceback) ret.tb_frame = tb_frame ret.tb_lineno = tb_lineno ret.tb_next = tb_next return ret.as_traceback() def pickle_traceback(tb): return unpickle_traceback, (Frame(tb.tb_frame), tb.tb_lineno, tb.tb_next and Traceback(tb.tb_next)) def unpickle_exception(func, args, cause, tb): inst = func(*args) inst.__cause__ = cause inst.__traceback__ = tb return inst def pickle_exception(obj): # All exceptions, unlike generic Python objects, define __reduce_ex__ # __reduce_ex__(4) should be no different from __reduce_ex__(3). # __reduce_ex__(5) could bring benefits in the unlikely case the exception # directly contains buffers, but PickleBuffer objects will cause a crash when # running on protocol=4, and there's no clean way to figure out the current # protocol from here. Note that any object returned by __reduce_ex__(3) will # still be pickled with protocol 5 if pickle.dump() is running with it. rv = obj.__reduce_ex__(3) if isinstance(rv, str): raise TypeError("str __reduce__ output is not supported") assert isinstance(rv, tuple) and len(rv) >= 2 return (unpickle_exception, rv[:2] + (obj.__cause__, obj.__traceback__)) + rv[2:] def _get_subclasses(cls): # Depth-first traversal of all direct and indirect subclasses of cls to_visit = [cls] while to_visit: this = to_visit.pop() yield this to_visit += list(this.__subclasses__()) def install(*exc_classes_or_instances): copyreg.pickle(TracebackType, pickle_traceback) if sys.version_info.major < 3: # Dummy decorator? if len(exc_classes_or_instances) == 1: exc = exc_classes_or_instances[0] if isinstance(exc, type) and issubclass(exc, BaseException): return exc return if not exc_classes_or_instances: for exception_cls in _get_subclasses(BaseException): copyreg.pickle(exception_cls, pickle_exception) return for exc in exc_classes_or_instances: if isinstance(exc, BaseException): while exc is not None: copyreg.pickle(type(exc), pickle_exception) exc = exc.__cause__ elif isinstance(exc, type) and issubclass(exc, BaseException): copyreg.pickle(exc, pickle_exception) # Allow using @install as a decorator for Exception classes if len(exc_classes_or_instances) == 1: return exc else: raise TypeError( "Expected subclasses or instances of BaseException, got %s" % (type(exc)) ) ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7933245 tblib-1.7.0/src/tblib.egg-info/0000775000175000017500000000000000000000000016431 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546258.0 tblib-1.7.0/src/tblib.egg-info/PKG-INFO0000664000175000017500000007377100000000000017545 0ustar00ionelionel00000000000000Metadata-Version: 1.2 Name: tblib Version: 1.7.0 Summary: Traceback serialization library. Home-page: https://github.com/ionelmc/python-tblib Author: Ionel Cristian Mărieș Author-email: contact@ionelmc.ro License: BSD-2-Clause Project-URL: Documentation, https://python-tblib.readthedocs.io/ Project-URL: Changelog, https://python-tblib.readthedocs.io/en/latest/changelog.html Project-URL: Issue Tracker, https://github.com/ionelmc/python-tblib/issues Description: ======== Overview ======== Serialization library for Exceptions and Tracebacks. * Free software: BSD license It allows you to: * `Pickle `_ tracebacks and raise exceptions with pickled tracebacks in different processes. This allows better error handling when running code over multiple processes (imagine multiprocessing, billiard, futures, celery etc). * Create traceback objects from strings (the ``from_string`` method). *No pickling is used*. * Serialize tracebacks to/from plain dicts (the ``from_dict`` and ``to_dict`` methods). *No pickling is used*. * Raise the tracebacks created from the aforementioned sources. * Pickle an Exception together with its traceback and exception chain (``raise ... from ...``) *(Python 3 only)* **Again, note that using the pickle support is completely optional. You are solely responsible for security problems should you decide to use the pickle support.** Installation ============ :: pip install tblib Documentation ============= .. contents:: :local: Pickling tracebacks ~~~~~~~~~~~~~~~~~~~ **Note**: The traceback objects that come out are stripped of some attributes (like variables). But you'll be able to raise exceptions with those tracebacks or print them - that should cover 99% of the usecases. :: >>> from tblib import pickling_support >>> pickling_support.install() >>> import pickle, sys >>> def inner_0(): ... raise Exception('fail') ... >>> def inner_1(): ... inner_0() ... >>> def inner_2(): ... inner_1() ... >>> try: ... inner_2() ... except: ... s1 = pickle.dumps(sys.exc_info()) ... >>> len(s1) > 1 True >>> try: ... inner_2() ... except: ... s2 = pickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL) ... >>> len(s2) > 1 True >>> try: ... import cPickle ... except ImportError: ... import pickle as cPickle >>> try: ... inner_2() ... except: ... s3 = cPickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL) ... >>> len(s3) > 1 True Unpickling tracebacks ~~~~~~~~~~~~~~~~~~~~~ :: >>> pickle.loads(s1) (<...Exception'>, Exception('fail'...), ) >>> pickle.loads(s2) (<...Exception'>, Exception('fail'...), ) >>> pickle.loads(s3) (<...Exception'>, Exception('fail'...), ) Raising ~~~~~~~ :: >>> from six import reraise >>> reraise(*pickle.loads(s1)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail >>> reraise(*pickle.loads(s2)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail >>> reraise(*pickle.loads(s3)) Traceback (most recent call last): ... File "", line 1, in reraise(*pickle.loads(s2)) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail Pickling Exceptions together with their traceback and chain (Python 3 only) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: >>> try: # doctest: +SKIP ... try: ... 1 / 0 ... except Exception as e: ... raise Exception("foo") from e ... except Exception as e: ... s = pickle.dumps(e) >>> raise pickle.loads(s) # doctest: +SKIP Traceback (most recent call last): File "", line 3, in 1 / 0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "", line 1, in raise pickle.loads(s) File "", line 5, in raise Exception("foo") from e Exception: foo BaseException subclasses defined after calling ``pickling_support.install()`` will **not** retain their traceback and exception chain pickling. To cover custom Exceptions, there are three options: 1. Use ``@pickling_support.install`` as a decorator for each custom Exception .. code-block:: python >>> from tblib import pickling_support >>> # Declare all imports of your package's dependencies >>> import numpy # doctest: +SKIP >>> pickling_support.install() # install for all modules imported so far >>> @pickling_support.install ... class CustomError(Exception): ... pass Eventual subclasses of ``CustomError`` will need to be decorated again. 2. Invoke ``pickling_support.install()`` after all modules have been imported and all Exception subclasses have been declared .. code-block:: python >>> # Declare all imports of your package's dependencies >>> import numpy # doctest: +SKIP >>> from tblib import pickling_support >>> # Declare your own custom Exceptions >>> class CustomError(Exception): ... pass >>> # Finally, install tblib >>> pickling_support.install() 3. Selectively install tblib for Exception instances just before they are pickled .. code-block:: python pickling_support.install(, [Exception instance], ...) The above will install tblib pickling for all listed exceptions as well as any other exceptions in their exception chains. For example, one could write a wrapper to be used with `ProcessPoolExecutor `_, `Dask.distributed `_, or similar libraries: :: >>> from tblib import pickling_support >>> def wrapper(func, *args, **kwargs): ... try: ... return func(*args, **kwargs) ... except Exception as e: ... pickling_support.install(e) ... raise What if we have a local stack, does it show correctly ? ------------------------------------------------------- Yes it does:: >>> exc_info = pickle.loads(s3) >>> def local_0(): ... reraise(*exc_info) ... >>> def local_1(): ... local_0() ... >>> def local_2(): ... local_1() ... >>> local_2() Traceback (most recent call last): File "...doctest.py", line ..., in __run compileflags, 1) in test.globs File "", line 1, in local_2() File "", line 2, in local_2 local_1() File "", line 2, in local_1 local_0() File "", line 2, in local_0 reraise(*exc_info) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail It also supports more contrived scenarios ----------------------------------------- Like tracebacks with syntax errors:: >>> from tblib import Traceback >>> from examples import bad_syntax >>> try: ... bad_syntax() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in reraise(et, ev, tb.as_traceback()) File "", line 2, in bad_syntax() File "...tests...examples.py", line 18, in bad_syntax import badsyntax File "...tests...badsyntax.py", line 5 is very bad ^ SyntaxError: invalid syntax Or other import failures:: >>> from examples import bad_module >>> try: ... bad_module() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in reraise(et, ev, tb.as_traceback()) File "", line 2, in bad_module() File "...tests...examples.py", line 23, in bad_module import badmodule File "...tests...badmodule.py", line 3, in raise Exception("boom!") Exception: boom! Or a traceback that's caused by exceeding the recursion limit (here we're forcing the type and value to have consistency across platforms):: >>> def f(): f() >>> try: ... f() ... except RuntimeError: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(RuntimeError, RuntimeError("maximum recursion depth exceeded"), tb.as_traceback()) Traceback (most recent call last): ... File "", line 1, in f def f(): f() File "", line 1, in f def f(): f() File "", line 1, in f def f(): f() ... RuntimeError: maximum recursion depth exceeded Reference ~~~~~~~~~ tblib.Traceback --------------- It is used by the ``pickling_support``. You can use it too if you want more flexibility:: >>> from tblib import Traceback >>> try: ... inner_2() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail tblib.Traceback.to_dict ``````````````````````` You can use the ``to_dict`` method and the ``from_dict`` classmethod to convert a Traceback into and from a dictionary serializable by the stdlib json.JSONDecoder:: >>> import json >>> from pprint import pprint >>> try: ... inner_2() ... except: ... et, ev, tb = sys.exc_info() ... tb = Traceback(tb) ... tb_dict = tb.to_dict() ... pprint(tb_dict) {'tb_frame': {'f_code': {'co_filename': '', 'co_name': ''}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 5}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_2'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_1'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ..., 'co_name': 'inner_0'}, 'f_globals': {'__name__': '__main__'}, 'f_lineno': 2}, 'tb_lineno': 2, 'tb_next': None}}}} tblib.Traceback.from_dict ````````````````````````` Building on the previous example:: >>> tb_json = json.dumps(tb_dict) >>> tb = Traceback.from_dict(json.loads(tb_json)) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "", line 2, in inner_2() File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail tblib.Traceback.from_string ``````````````````````````` :: >>> tb = Traceback.from_string(""" ... File "skipped.py", line 123, in func_123 ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: fail If you use the ``strict=False`` option then parsing is a bit more lax:: >>> tb = Traceback.from_string(""" ... File "bogus.py", line 123, in bogus ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """, strict=False) >>> reraise(et, ev, tb.as_traceback()) Traceback (most recent call last): ... File "", line 6, in reraise(et, ev, tb.as_traceback()) File "bogus.py", line 123, in bogus File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: fail tblib.decorators.return_error ----------------------------- :: >>> from tblib.decorators import return_error >>> inner_2r = return_error(inner_2) >>> e = inner_2r() >>> e >>> e.reraise() Traceback (most recent call last): ... File "", line 1, in e.reraise() File "...tblib...decorators.py", line 19, in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line 25, in return_exceptions_wrapper return func(*args, **kwargs) File "", line 2, in inner_2 inner_1() File "", line 2, in inner_1 inner_0() File "", line 2, in inner_0 raise Exception('fail') Exception: fail How's this useful? Imagine you're using multiprocessing like this:: # Note that Python 3.4 and later will show the remote traceback (but as a string sadly) so we skip testing this. >>> import traceback >>> from multiprocessing import Pool >>> from examples import func_a >>> pool = Pool() # doctest: +SKIP >>> try: # doctest: +SKIP ... for i in pool.map(func_a, range(5)): ... print(i) ... except: ... print(traceback.format_exc()) ... Traceback (most recent call last): File "", line 2, in for i in pool.map(func_a, range(5)): File "...multiprocessing...pool.py", line ..., in map ... File "...multiprocessing...pool.py", line ..., in get ... Exception: Guessing time ! >>> pool.terminate() # doctest: +SKIP Not very useful is it? Let's sort this out:: >>> from tblib.decorators import apply_with_return_error, Error >>> from itertools import repeat >>> pool = Pool() >>> try: ... for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))): ... if isinstance(i, Error): ... i.reraise() ... else: ... print(i) ... except: ... print(traceback.format_exc()) ... Traceback (most recent call last): File "", line 4, in i.reraise() File "...tblib...decorators.py", line ..., in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line ..., in return_exceptions_wrapper return func(*args, **kwargs) File "...tblib...decorators.py", line ..., in apply_with_return_error return args[0](*args[1:]) File "...examples.py", line 2, in func_a func_b() File "...examples.py", line 6, in func_b func_c() File "...examples.py", line 10, in func_c func_d() File "...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: Guessing time ! >>> pool.terminate() Much better ! What if we have a local call stack ? ```````````````````````````````````` :: >>> def local_0(): ... pool = Pool() ... try: ... for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))): ... if isinstance(i, Error): ... i.reraise() ... else: ... print(i) ... finally: ... pool.close() ... >>> def local_1(): ... local_0() ... >>> def local_2(): ... local_1() ... >>> try: ... local_2() ... except: ... print(traceback.format_exc()) Traceback (most recent call last): File "", line 2, in local_2() File "", line 2, in local_2 local_1() File "", line 2, in local_1 local_0() File "", line 6, in local_0 i.reraise() File "...tblib...decorators.py", line 20, in reraise reraise(self.exc_type, self.exc_value, self.traceback) File "...tblib...decorators.py", line 27, in return_exceptions_wrapper return func(*args, **kwargs) File "...tblib...decorators.py", line 47, in apply_with_return_error return args[0](*args[1:]) File "...tests...examples.py", line 2, in func_a func_b() File "...tests...examples.py", line 6, in func_b func_c() File "...tests...examples.py", line 10, in func_c func_d() File "...tests...examples.py", line 14, in func_d raise Exception("Guessing time !") Exception: Guessing time ! Other weird stuff ````````````````` Clearing traceback works (Python 3.4 and up):: >>> tb = Traceback.from_string(""" ... File "skipped.py", line 123, in func_123 ... Traceback (most recent call last): ... File "tests/examples.py", line 2, in func_a ... func_b() ... File "tests/examples.py", line 6, in func_b ... func_c() ... File "tests/examples.py", line 10, in func_c ... func_d() ... File "tests/examples.py", line 14, in func_d ... Doesn't: matter ... """) >>> import traceback, sys >>> if sys.version_info > (3, 4): ... traceback.clear_frames(tb) Credits ======= * `mitsuhiko/jinja2 `_ for figuring a way to create traceback objects. Changelog ========= 1.7.0 (2020-07-24) ~~~~~~~~~~~~~~~~~~ * Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in `#58 `_. 1.6.0 (2019-12-07) ~~~~~~~~~~~~~~~~~~ * When pickling an Exception, also pickle its traceback and the Exception chain (``raise ... from ...``). Contributed by Guido Imperiale in `#53 `_. 1.5.0 (2019-10-23) ~~~~~~~~~~~~~~~~~~ * Added support for Python 3.8. Contributed by Victor Stinner in `#42 `_. * Removed support for end of life Python 3.4. * Few CI improvements and fixes. 1.4.0 (2019-05-02) ~~~~~~~~~~~~~~~~~~ * Removed support for end of life Python 3.3. * Fixed tests for Python 3.7. Contributed by Elliott Sales de Andrade in `#36 `_. * Fixed compatibility issue with Twised (``twisted.python.failure.Failure`` expected a ``co_code`` attribute). 1.3.2 (2017-04-09) ~~~~~~~~~~~~~~~~~~ * Add support for PyPy3.5-5.7.1-beta. Previously ``AttributeError: 'Frame' object has no attribute 'clear'`` could be raised. See PyPy issue `#2532 `_. 1.3.1 (2017-03-27) ~~~~~~~~~~~~~~~~~~ * Fixed handling for tracebacks due to exceeding the recursion limit. Fixes `#15 `_. 1.3.0 (2016-03-08) ~~~~~~~~~~~~~~~~~~ * Added ``Traceback.from_string``. 1.2.0 (2015-12-18) ~~~~~~~~~~~~~~~~~~ * Fixed handling for tracebacks from generators and other internal improvements and optimizations. Contributed by DRayX in `#10 `_ and `#11 `_. 1.1.0 (2015-07-27) ~~~~~~~~~~~~~~~~~~ * Added support for Python 2.6. Contributed by Arcadiy Ivanov in `#8 `_. 1.0.0 (2015-03-30) ~~~~~~~~~~~~~~~~~~ * Added ``to_dict`` method and ``from_dict`` classmethod on Tracebacks. Contributed by beckjake in `#5 `_. Keywords: traceback,debugging,exceptions Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: Unix Classifier: Operating System :: POSIX Classifier: Operating System :: Microsoft :: Windows Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Utilities Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.* ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546258.0 tblib-1.7.0/src/tblib.egg-info/SOURCES.txt0000664000175000017500000000164500000000000020323 0ustar00ionelionel00000000000000.appveyor.yml .bumpversion.cfg .cookiecutterrc .coveragerc .editorconfig .gitignore .readthedocs.yml .travis.yml AUTHORS.rst CHANGELOG.rst CONTRIBUTING.rst LICENSE MANIFEST.in README.rst setup.cfg setup.py tox.ini ci/appveyor-download.py ci/appveyor-with-compiler.cmd ci/bootstrap.py ci/requirements.txt ci/templates/.appveyor.yml ci/templates/.travis.yml docs/authors.rst docs/changelog.rst docs/conf.py docs/contributing.rst docs/index.rst docs/installation.rst docs/readme.rst docs/requirements.txt docs/spelling_wordlist.txt docs/usage.rst src/tblib/__init__.py src/tblib/cpython.py src/tblib/decorators.py src/tblib/pickling_support.py src/tblib.egg-info/PKG-INFO src/tblib.egg-info/SOURCES.txt src/tblib.egg-info/dependency_links.txt src/tblib.egg-info/not-zip-safe src/tblib.egg-info/top_level.txt tests/badmodule.py tests/badsyntax.py tests/examples.py tests/test_issue30.py tests/test_pickle_exception.py tests/test_tblib.py././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546258.0 tblib-1.7.0/src/tblib.egg-info/dependency_links.txt0000664000175000017500000000000100000000000022477 0ustar00ionelionel00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1556792240.0 tblib-1.7.0/src/tblib.egg-info/not-zip-safe0000664000175000017500000000000100000000000020657 0ustar00ionelionel00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1595546258.0 tblib-1.7.0/src/tblib.egg-info/top_level.txt0000664000175000017500000000000600000000000021157 0ustar00ionelionel00000000000000tblib ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1595546258.7943244 tblib-1.7.0/tests/0000775000175000017500000000000000000000000014216 5ustar00ionelionel00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1556791884.0 tblib-1.7.0/tests/badmodule.py0000664000175000017500000000004500000000000016523 0ustar00ionelionel00000000000000a = 1 b = 2 raise Exception("boom!") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1556796189.0 tblib-1.7.0/tests/badsyntax.py0000664000175000017500000000005700000000000016567 0ustar00ionelionel00000000000000# flake8: noqa """ bad bad bad """ is very bad ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1556791884.0 tblib-1.7.0/tests/examples.py0000664000175000017500000000037300000000000016411 0ustar00ionelionel00000000000000def func_a(_): func_b() def func_b(): func_c() def func_c(): func_d() def func_d(): raise Exception("Guessing time !") def bad_syntax(): import badsyntax badsyntax def bad_module(): import badmodule badmodule ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1571799532.0 tblib-1.7.0/tests/test_issue30.py0000664000175000017500000000071400000000000017124 0ustar00ionelionel00000000000000import pickle import sys import pytest import six from tblib import pickling_support # noqa: E402 pytest.importorskip('twisted') def test_30(): from twisted.python.failure import Failure pickling_support.install() try: raise ValueError except ValueError: s = pickle.dumps(sys.exc_info()) f = None try: six.reraise(*pickle.loads(s)) except ValueError: f = Failure() assert f is not None ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1575664815.0 tblib-1.7.0/tests/test_pickle_exception.py0000664000175000017500000000462700000000000021165 0ustar00ionelionel00000000000000try: import copyreg except ImportError: # Python 2 import copy_reg as copyreg import pickle import sys import pytest import tblib.pickling_support has_python3 = sys.version_info.major >= 3 @pytest.fixture def clear_dispatch_table(): bak = copyreg.dispatch_table.copy() copyreg.dispatch_table.clear() yield copyreg.dispatch_table.clear() copyreg.dispatch_table.update(bak) class CustomError(Exception): pass @pytest.mark.parametrize( "protocol", [None] + list(range(1, pickle.HIGHEST_PROTOCOL + 1)) ) @pytest.mark.parametrize("how", ["global", "instance", "class"]) def test_install(clear_dispatch_table, how, protocol): if how == "global": tblib.pickling_support.install() elif how == "class": tblib.pickling_support.install(CustomError, ZeroDivisionError) try: try: 1 / 0 except Exception as e: # Python 3 only syntax # raise CustomError("foo") from e new_e = CustomError("foo") if has_python3: new_e.__cause__ = e raise new_e except Exception as e: exc = e else: assert False # Populate Exception.__dict__, which is used in some cases exc.x = 1 if has_python3: exc.__cause__.x = 2 if how == "instance": tblib.pickling_support.install(exc) if protocol: exc = pickle.loads(pickle.dumps(exc, protocol=protocol)) assert isinstance(exc, CustomError) assert exc.args == ("foo",) assert exc.x == 1 if has_python3: assert exc.__traceback__ is not None assert isinstance(exc.__cause__, ZeroDivisionError) assert exc.__cause__.__traceback__ is not None assert exc.__cause__.x == 2 assert exc.__cause__.__cause__ is None @tblib.pickling_support.install class RegisteredError(Exception): pass def test_install_decorator(): with pytest.raises(RegisteredError) as ewrap: raise RegisteredError("foo") exc = ewrap.value exc.x = 1 exc = pickle.loads(pickle.dumps(exc)) assert isinstance(exc, RegisteredError) assert exc.args == ("foo",) assert exc.x == 1 if has_python3: assert exc.__traceback__ is not None @pytest.mark.skipif(sys.version_info[0] < 3, reason="No checks done in Python 2") def test_install_typeerror(): with pytest.raises(TypeError): tblib.pickling_support.install("foo") ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756406.0 tblib-1.7.0/tests/test_tblib.py0000664000175000017500000000773200000000000016734 0ustar00ionelionel00000000000000import 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 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 \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": ""}, "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 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 ', ' ???', '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 ', ' File "file2", line 234, in ???', ' File "file3", line 345, in function3', ' File "file4", line 456, in ""', 'RuntimeError', ]) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1583756462.0 tblib-1.7.0/tox.ini0000664000175000017500000000345300000000000014374 0ustar00ionelionel00000000000000[testenv:bootstrap] deps = jinja2 matrix tox skip_install = true commands = python ci/bootstrap.py --no-env passenv = * ; a generative tox configuration, see: https://tox.readthedocs.io/en/latest/config.html#generative-envlist [tox] envlist = clean, check, docs, {py27,py35,py36,py37,py38,pypy,pypy3}, report ignore_basepython_conflict = true [testenv] basepython = pypy: {env:TOXPYTHON:pypy} pypy3: {env:TOXPYTHON:pypy3} py27: {env:TOXPYTHON:python2.7} py35: {env:TOXPYTHON:python3.5} py36: {env:TOXPYTHON:python3.6} py37: {env:TOXPYTHON:python3.7} py38: {env:TOXPYTHON:python3.8} {bootstrap,clean,check,report,codecov,docs}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes passenv = * usedevelop = false deps = pytest pytest-travis-fold pytest-cov pytest-clarity six py{27,35,36,37,38,py,py3}: twisted commands = {posargs:py.test --cov=tblib --cov-report=term-missing -vv tests README.rst} [testenv:check] deps = docutils check-manifest flake8 readme-renderer pygments isort skip_install = true commands = python setup.py check --strict --metadata --restructuredtext check-manifest {toxinidir} flake8 src tests setup.py isort --verbose --check-only --diff --recursive src tests setup.py [testenv:docs] usedevelop = true deps = -r{toxinidir}/docs/requirements.txt commands = sphinx-build {posargs:-E} -b html docs dist/docs sphinx-build -b linkcheck docs dist/docs [testenv:codecov] deps = codecov skip_install = true commands = codecov [] [testenv:report] deps = coverage skip_install = true commands = coverage report coverage html [testenv:clean] commands = coverage erase skip_install = true deps = coverage