tblib-1.6.0/0000775000175000017500000000000013572540702013057 5ustar ionelionel00000000000000tblib-1.6.0/ci/0000775000175000017500000000000013572540702013452 5ustar ionelionel00000000000000tblib-1.6.0/ci/requirements.txt0000664000175000017500000000006213553747511016742 0ustar ionelionel00000000000000virtualenv>=16.6.0 pip>=19.1.1 setuptools>=18.0.1 tblib-1.6.0/ci/bootstrap.py0000775000175000017500000000550613553747511016060 0ustar ionelionel00000000000000#!/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) tblib-1.6.0/ci/templates/0000775000175000017500000000000013572540702015450 5ustar ionelionel00000000000000tblib-1.6.0/ci/templates/.appveyor.yml0000664000175000017500000000325413553747511020130 0ustar ionelionel00000000000000version: '{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')) tblib-1.6.0/ci/templates/.travis.yml0000664000175000017500000000176613553747511017601 0ustar ionelionel00000000000000language: 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 tblib-1.6.0/ci/appveyor-with-compiler.cmd0000664000175000017500000000137313553746532020601 0ustar ionelionel00000000000000:: 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 tblib-1.6.0/ci/appveyor-download.py0000775000175000017500000000735213553740754017520 0ustar ionelionel00000000000000#!/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) tblib-1.6.0/README.rst0000664000175000017500000005513313572540661014561 0ustar ionelionel00000000000000======== 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://readthedocs.org/projects/python-tblib/badge/?style=flat :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.6.0.svg :alt: Commits since latest release :target: https://github.com/ionelmc/python-tblib/compare/v1.6.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__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_2'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_1'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_0'}, 'f_globals': {'__name__': '__main__'}}, '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. tblib-1.6.0/CONTRIBUTING.rst0000664000175000017500000000521013553747511015524 0ustar ionelionel00000000000000============ 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 tblib-1.6.0/src/0000775000175000017500000000000013572540702013646 5ustar ionelionel00000000000000tblib-1.6.0/src/tblib/0000775000175000017500000000000013572540702014742 5ustar ionelionel00000000000000tblib-1.6.0/src/tblib/cpython.py0000664000175000017500000000451513462541114017001 0ustar ionelionel00000000000000""" 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 tblib-1.6.0/src/tblib/decorators.py0000664000175000017500000000206413462541114017457 0ustar ionelionel00000000000000import 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:]) tblib-1.6.0/src/tblib/pickling_support.py0000664000175000017500000000554513572536257020733 0ustar ionelionel00000000000000import 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)) ) tblib-1.6.0/src/tblib/__init__.py0000664000175000017500000001544513572540661017070 0ustar ionelionel00000000000000import re import sys from types import CodeType 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.6.0' __all__ = 'Traceback', PY3 = sys.version_info[0] == 3 FRAME_RE = re.compile(r'^\s*File "(?P.+)", line (?P\d+)(, in (?P.+))?$') class _AttrDict(dict): __slots__ = () __getattr__ = dict.__getitem__ # noinspection PyPep8Naming class __traceback_maker(Exception): pass class TracebackParseError(Exception): pass class Code(object): co_code = None def __init__(self, code): self.co_filename = code.co_filename self.co_name = code.co_name class Frame(object): def __init__(self, frame): 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) def clear(self): # For compatibility with PyPy 3.5; # clear() was added to frame in Python 3.4 # and is called by traceback.clear_frames(), which # in turn is called by unittest.TestCase.assertRaises pass class Traceback(object): 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): if tproxy: return tproxy(TracebackType, self.__tproxy_handler) 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, 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 # noinspection SpellCheckingInspection def __tproxy_handler(self, operation, *args, **kwargs): if operation in ('__getattribute__', '__getattr__'): if args[0] == 'tb_next': return self.tb_next and self.tb_next.as_traceback() else: return getattr(self, args[0]) else: return getattr(self, operation)(*args, **kwargs) def to_dict(self): """Convert a Traceback into a dictionary representation""" 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, } return { 'tb_frame': frame, 'tb_lineno': self.tb_lineno, 'tb_next': tb_next, } @classmethod def from_dict(cls, dct): 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, ) 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): 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), ), tb_next=previous, ) return cls(previous) else: raise TracebackParseError("Could not find any frames in %r." % string) tblib-1.6.0/src/tblib.egg-info/0000775000175000017500000000000013572540702016434 5ustar ionelionel00000000000000tblib-1.6.0/src/tblib.egg-info/SOURCES.txt0000664000175000017500000000166213572540702020325 0ustar ionelionel00000000000000.appveyor.yml .bumpversion.cfg .cookiecutterrc .coveragerc .editorconfig .gitignore .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 docs/reference/index.rst docs/reference/tblib.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.pytblib-1.6.0/src/tblib.egg-info/dependency_links.txt0000664000175000017500000000000113572540701022501 0ustar ionelionel00000000000000 tblib-1.6.0/src/tblib.egg-info/top_level.txt0000664000175000017500000000000613572540701021161 0ustar ionelionel00000000000000tblib tblib-1.6.0/src/tblib.egg-info/PKG-INFO0000664000175000017500000007301413572540701017535 0ustar ionelionel00000000000000Metadata-Version: 1.2 Name: tblib Version: 1.6.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__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_2'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_1'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_0'}, 'f_globals': {'__name__': '__main__'}}, '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.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.* tblib-1.6.0/src/tblib.egg-info/not-zip-safe0000664000175000017500000000000113462541660020664 0ustar ionelionel00000000000000 tblib-1.6.0/.appveyor.yml0000664000175000017500000000477113553747511015544 0ustar ionelionel00000000000000version: '{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')) tblib-1.6.0/.cookiecutterrc0000664000175000017500000000312713553747511016116 0ustar ionelionel00000000000000# Generated by cookiepatcher, a small shim around cookiecutter (pip install cookiepatcher) cookiecutter: _extensions: - jinja2_time.TimeExtension _template: /home/ionel/open-source/cookiecutter-pylibrary allow_tests_inside_package: no appveyor: yes c_extension_function: '-' c_extension_module: '-' c_extension_optional: no c_extension_support: no c_extension_test_pypi: no c_extension_test_pypi_username: '-' codacy: no codacy_projectid: '-' codeclimate: no codecov: yes command_line_interface: no command_line_interface_bin_name: '-' coveralls: no coveralls_token: '-' distribution_name: tblib email: contact@ionelmc.ro full_name: Ionel Cristian Mărieș landscape: no license: BSD 2-Clause License linter: flake8 package_name: tblib project_name: tblib project_short_description: Traceback serialization library. pypi_badge: yes pypi_disable_upload: no release_date: '2019-05-02' repo_hosting: github.com repo_hosting_domain: github.com repo_name: python-tblib repo_username: ionelmc requiresio: yes scrutinizer: no setup_py_uses_setuptools_scm: no setup_py_uses_test_runner: no sphinx_docs: yes sphinx_docs_hosting: https://python-tblib.readthedocs.io/ sphinx_doctest: no sphinx_theme: sphinx-py3doc-enhanced-theme test_matrix_configurator: no test_matrix_separate_coverage: no test_runner: pytest travis: yes travis_osx: no version: 1.4.0 website: https://blog.ionelmc.ro/ year_from: '2013' year_to: '2019' tblib-1.6.0/setup.cfg0000664000175000017500000000111113572540702014672 0ustar ionelionel00000000000000[bdist_wheel] universal = 1 [flake8] max-line-length = 140 exclude = */migrations/* [tool:pytest] testpaths = tests 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 [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 tblib-1.6.0/AUTHORS.rst0000664000175000017500000000066513572536257014757 0ustar ionelionel00000000000000Authors ======= * 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 tblib-1.6.0/.bumpversion.cfg0000664000175000017500000000076713572540661016205 0ustar ionelionel00000000000000[bumpversion] current_version = 1.6.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}' tblib-1.6.0/.gitignore0000664000175000017500000000120113553747511015047 0ustar ionelionel00000000000000*.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/ tblib-1.6.0/PKG-INFO0000664000175000017500000007301413572540702014161 0ustar ionelionel00000000000000Metadata-Version: 1.2 Name: tblib Version: 1.6.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__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_2'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_1'}, 'f_globals': {'__name__': '__main__'}}, 'tb_lineno': 2, 'tb_next': {'tb_frame': {'f_code': {'co_filename': ... 'co_name': 'inner_0'}, 'f_globals': {'__name__': '__main__'}}, '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.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.* tblib-1.6.0/.travis.yml0000664000175000017500000000214313553747511015176 0ustar ionelionel00000000000000language: 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 tblib-1.6.0/LICENSE0000664000175000017500000000246113553746607014102 0ustar ionelionel00000000000000BSD 2-Clause License Copyright (c) 2013-2019, 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. tblib-1.6.0/.coveragerc0000664000175000017500000000025513553746532015212 0ustar ionelionel00000000000000[paths] source = src */site-packages [run] branch = true source = tblib tests parallel = true [report] show_missing = true precision = 2 omit = *migrations* tblib-1.6.0/CHANGELOG.rst0000664000175000017500000000375313572536277015124 0ustar ionelionel00000000000000 Changelog ========= 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 `_. tblib-1.6.0/setup.py0000664000175000017500000000560713572540661014605 0ustar ionelionel00000000000000#!/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.6.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'], }, ) tblib-1.6.0/MANIFEST.in0000664000175000017500000000051313553747511014622 0ustar ionelionel00000000000000graft 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 global-exclude *.py[cod] __pycache__ *.so *.dylib tblib-1.6.0/docs/0000775000175000017500000000000013572540702014007 5ustar ionelionel00000000000000tblib-1.6.0/docs/conf.py0000664000175000017500000000236413572540661015317 0ustar ionelionel00000000000000# -*- coding: utf-8 -*- from __future__ import unicode_literals import os extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx.ext.coverage', 'sphinx.ext.doctest', 'sphinx.ext.extlinks', 'sphinx.ext.ifconfig', 'sphinx.ext.napoleon', 'sphinx.ext.todo', 'sphinx.ext.viewcode', ] source_suffix = '.rst' master_doc = 'index' project = 'tblib' year = '2013-2019' author = 'Ionel Cristian Mărieș' copyright = '{0}, {1}'.format(year, author) version = release = '1.6.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 tblib-1.6.0/docs/changelog.rst0000664000175000017500000000003613553746532016477 0ustar ionelionel00000000000000.. include:: ../CHANGELOG.rst tblib-1.6.0/docs/reference/0000775000175000017500000000000013572540702015745 5ustar ionelionel00000000000000tblib-1.6.0/docs/reference/tblib.rst0000664000175000017500000000013213553746532017577 0ustar ionelionel00000000000000tblib ===== .. testsetup:: from tblib import * .. automodule:: tblib :members: tblib-1.6.0/docs/reference/index.rst0000664000175000017500000000007113553746532017614 0ustar ionelionel00000000000000Reference ========= .. toctree:: :glob: tblib* tblib-1.6.0/docs/usage.rst0000664000175000017500000000007613553746532015660 0ustar ionelionel00000000000000===== Usage ===== To use tblib in a project:: import tblib tblib-1.6.0/docs/requirements.txt0000664000175000017500000000005113553747511017275 0ustar ionelionel00000000000000sphinx>=1.3 sphinx-py3doc-enhanced-theme tblib-1.6.0/docs/spelling_wordlist.txt0000664000175000017500000000015513553746532020325 0ustar ionelionel00000000000000builtin builtins classmethod staticmethod classmethods staticmethods args kwargs callstack Changelog Indices tblib-1.6.0/docs/readme.rst0000664000175000017500000000003313553746532016002 0ustar ionelionel00000000000000.. include:: ../README.rst tblib-1.6.0/docs/authors.rst0000664000175000017500000000003413553746532016233 0ustar ionelionel00000000000000.. include:: ../AUTHORS.rst tblib-1.6.0/docs/index.rst0000664000175000017500000000036513553746532015664 0ustar ionelionel00000000000000======== Contents ======== .. toctree:: :maxdepth: 2 readme installation usage reference/index contributing authors changelog Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` tblib-1.6.0/docs/installation.rst0000664000175000017500000000012513553746532017250 0ustar ionelionel00000000000000============ Installation ============ At the command line:: pip install tblib tblib-1.6.0/docs/contributing.rst0000664000175000017500000000004113553746532017253 0ustar ionelionel00000000000000.. include:: ../CONTRIBUTING.rst tblib-1.6.0/tox.ini0000664000175000017500000000343213553747511014402 0ustar ionelionel00000000000000[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,docs}: {env:TOXPYTHON:python3.6} py37: {env:TOXPYTHON:python3.7} py38: {env:TOXPYTHON:python3.8} {bootstrap,clean,check,report,codecov}: {env:TOXPYTHON:python3} setenv = PYTHONPATH={toxinidir}/tests PYTHONUNBUFFERED=yes passenv = * usedevelop = false deps = pytest pytest-travis-fold pytest-cov 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 tblib-1.6.0/tests/0000775000175000017500000000000013572540702014221 5ustar ionelionel00000000000000tblib-1.6.0/tests/badmodule.py0000664000175000017500000000004513462541114016522 0ustar ionelionel00000000000000a = 1 b = 2 raise Exception("boom!") tblib-1.6.0/tests/examples.py0000664000175000017500000000037313462541114016410 0ustar ionelionel00000000000000def 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 tblib-1.6.0/tests/badsyntax.py0000664000175000017500000000005713462551435016575 0ustar ionelionel00000000000000# flake8: noqa """ bad bad bad """ is very bad tblib-1.6.0/tests/test_pickle_exception.py0000664000175000017500000000462713572536257021202 0ustar ionelionel00000000000000try: 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") tblib-1.6.0/tests/test_issue30.py0000664000175000017500000000071413553740754017137 0ustar ionelionel00000000000000import 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 tblib-1.6.0/.editorconfig0000664000175000017500000000033113553746532015541 0ustar ionelionel00000000000000# 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