tblib


Nametblib JSON
Version 3.0.0 PyPI version JSON
download
home_pagehttps://github.com/ionelmc/python-tblib
SummaryTraceback serialization library.
upload_time2023-10-22 00:35:48
maintainer
docs_urlNone
authorIonel Cristian Mărieș
requires_python>=3.8
licenseBSD-2-Clause
keywords traceback debugging exceptions
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            ========
Overview
========



Serialization library for Exceptions and Tracebacks.

* Free software: BSD license

It allows you to:

* `Pickle <https://docs.python.org/3/library/pickle.html>`_ 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'...), <traceback object at ...>)

    >>> pickle.loads(s2)
    (<...Exception'>, Exception('fail'...), <traceback object at ...>)

    >>> pickle.loads(s3)
    (<...Exception'>, Exception('fail'...), <traceback object at ...>)

Raising
~~~~~~~

::

    >>> from six import reraise
    >>> reraise(*pickle.loads(s1))
    Traceback (most recent call last):
      ...
      File "<doctest README.rst[14]>", line 1, in <module>
        reraise(*pickle.loads(s2))
      File "<doctest README.rst[8]>", line 2, in <module>
        inner_2()
      File "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", line 2, in inner_0
        raise Exception('fail')
    Exception: fail
    >>> reraise(*pickle.loads(s2))
    Traceback (most recent call last):
      ...
      File "<doctest README.rst[14]>", line 1, in <module>
        reraise(*pickle.loads(s2))
      File "<doctest README.rst[8]>", line 2, in <module>
        inner_2()
      File "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", line 2, in inner_0
        raise Exception('fail')
    Exception: fail
    >>> reraise(*pickle.loads(s3))
    Traceback (most recent call last):
      ...
      File "<doctest README.rst[14]>", line 1, in <module>
        reraise(*pickle.loads(s2))
      File "<doctest README.rst[8]>", line 2, in <module>
        inner_2()
      File "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", 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 "<doctest README.rst[16]>", line 3, in <module>
        1 / 0
    ZeroDivisionError: division by zero

    The above exception was the direct cause of the following exception:

    Traceback (most recent call last):
      File "<doctest README.rst[17]>", line 1, in <module>
        raise pickle.loads(s)
      File "<doctest README.rst[16]>", line 5, in <module>
        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>, [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 <https://docs.python.org/3/library/concurrent.futures.html>`_,
   `Dask.distributed <https://distributed.dask.org/>`_, 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 "<doctest README.rst[24]>", line 1, in <module>
        local_2()
      File "<doctest README.rst[23]>", line 2, in local_2
        local_1()
      File "<doctest README.rst[22]>", line 2, in local_1
        local_0()
      File "<doctest README.rst[21]>", line 2, in local_0
        reraise(*exc_info)
      File "<doctest README.rst[11]>", line 2, in <module>
        inner_2()
      File "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", 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 "<doctest README.rst[58]>", line 1, in <module>
        reraise(et, ev, tb.as_traceback())
      File "<doctest README.rst[57]>", line 2, in <module>
        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 "<doctest README.rst[61]>", line 1, in <module>
        reraise(et, ev, tb.as_traceback())
      File "<doctest README.rst[60]>", line 2, in <module>
        bad_module()
      File "...tests...examples.py", line 23, in bad_module
        import badmodule
      File "...tests...badmodule.py", line 3, in <module>
        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 "<doctest README.rst[32]>", line 1, in f
        def f(): f()
      File "<doctest README.rst[32]>", line 1, in f
        def f(): f()
      File "<doctest README.rst[32]>", 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 "<doctest README.rst[21]>", line 6, in <module>
        reraise(et, ev, tb.as_traceback())
      File "<doctest README.rst[21]>", line 2, in <module>
        inner_2()
      File "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", 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': '<doctest README.rst[...]>',
                             'co_name': '<module>'},
                  'f_globals': {'__name__': '__main__'},
                  'f_lineno': 5},
     'tb_lineno': 2,
     'tb_next': {'tb_frame': {'f_code': {'co_filename': ...,
                                         'co_name': 'inner_2'},
                              'f_globals': {'__name__': '__main__'},
                              'f_lineno': 2},
                 'tb_lineno': 2,
                 'tb_next': {'tb_frame': {'f_code': {'co_filename': ...,
                                                     'co_name': 'inner_1'},
                                          'f_globals': {'__name__': '__main__'},
                                          'f_lineno': 2},
                             'tb_lineno': 2,
                             'tb_next': {'tb_frame': {'f_code': {'co_filename': ...,
                                                                 'co_name': 'inner_0'},
                                                      'f_globals': {'__name__': '__main__'},
                                                      'f_lineno': 2},
                                         'tb_lineno': 2,
                                         'tb_next': None}}}}

tblib.Traceback.from_dict
`````````````````````````

Building on the previous example::

    >>> tb_json = json.dumps(tb_dict)
    >>> tb = Traceback.from_dict(json.loads(tb_json))
    >>> reraise(et, ev, tb.as_traceback())
    Traceback (most recent call last):
      ...
      File "<doctest README.rst[21]>", line 6, in <module>
        reraise(et, ev, tb.as_traceback())
      File "<doctest README.rst[21]>", line 2, in <module>
        inner_2()
      File "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", 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 "<doctest README.rst[42]>", line 6, in <module>
        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 "<doctest README.rst[42]>", line 6, in <module>
        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
    <tblib.decorators.Error object at ...>
    >>> e.reraise()
    Traceback (most recent call last):
      ...
      File "<doctest README.rst[26]>", line 1, in <module>
        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 "<doctest README.rst[5]>", line 2, in inner_2
        inner_1()
      File "<doctest README.rst[4]>", line 2, in inner_1
        inner_0()
      File "<doctest README.rst[3]>", 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 "<doctest README.rst[...]>", line 2, in <module>
        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 !
    <BLANKLINE>
    >>> 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 "<doctest README.rst[...]>", line 4, in <module>
        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 !
    <BLANKLINE>
    >>> 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 "<doctest README.rst[...]>", line 2, in <module>
        local_2()
      File "<doctest README.rst[...]>", line 2, in local_2
        local_1()
      File "<doctest README.rst[...]>", line 2, in local_1
        local_0()
      File "<doctest README.rst[...]>", 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 !
    <BLANKLINE>

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 <https://github.com/mitsuhiko/jinja2>`_ for figuring a way to create traceback objects.


Changelog
=========

3.0.0 (2023-10-22)
~~~~~~~~~~~~~~~~~~

* Added support for  ``__context__``, ``__suppress_context__`` and ``__notes__``.
  Contributed by Tim Maxwell in `#72 <https://github.com/ionelmc/python-tblib/pull/72>`_.
* Added the ``get_locals`` argument to ``tblib.pickling_support.install()``, ``tblib.Traceback`` and ``tblib.Frame``.
  Fixes `#41 <https://github.com/ionelmc/python-tblib/issues/41>`_.
* Dropped support for now-EOL Python 3.7 and added 3.12 in the test grid.

2.0.0 (2023-06-22)
~~~~~~~~~~~~~~~~~~

* Removed support for legacy Pythons (2.7 and 3.6) and added Python 3.11 in the test grid.
* Some cleanups and refactors (mostly from ruff).

1.7.0 (2020-07-24)
~~~~~~~~~~~~~~~~~~

* Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in
  `#58 <https://github.com/ionelmc/python-tblib/pull/58>`_.

1.6.0 (2019-12-07)
~~~~~~~~~~~~~~~~~~

* When pickling an Exception, also pickle its traceback and the Exception chain
  (``raise ... from ...``). Contributed by Guido Imperiale in
  `#53 <https://github.com/ionelmc/python-tblib/issues/53>`_.

1.5.0 (2019-10-23)
~~~~~~~~~~~~~~~~~~

* Added support for Python 3.8. Contributed by Victor Stinner in
  `#42 <https://github.com/ionelmc/python-tblib/issues/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 <https://github.com/ionelmc/python-tblib/issues/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 <https://foss.heptapod.net/pypy/pypy/-/issues/2532>`_.

1.3.1 (2017-03-27)
~~~~~~~~~~~~~~~~~~

* Fixed handling for tracebacks due to exceeding the recursion limit.
  Fixes `#15 <https://github.com/ionelmc/python-tblib/issues/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 <https://github.com/ionelmc/python-tblib/issues/10>`_
  and `#11 <https://github.com/ionelmc/python-tblib/pull/11>`_.

1.1.0 (2015-07-27)
~~~~~~~~~~~~~~~~~~

* Added support for Python 2.6. Contributed by Arcadiy Ivanov in
  `#8 <https://github.com/ionelmc/python-tblib/pull/8>`_.

1.0.0 (2015-03-30)
~~~~~~~~~~~~~~~~~~

* Added ``to_dict`` method and ``from_dict`` classmethod on Tracebacks.
  Contributed by beckjake in `#5 <https://github.com/ionelmc/python-tblib/pull/5>`_.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/ionelmc/python-tblib",
    "name": "tblib",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "traceback,debugging,exceptions",
    "author": "Ionel Cristian M\u0103rie\u0219",
    "author_email": "contact@ionelmc.ro",
    "download_url": "https://files.pythonhosted.org/packages/1a/df/4f2cd7eaa6d41a7994d46527349569d46e34d9cdd07590b5c5b0dcf53de3/tblib-3.0.0.tar.gz",
    "platform": null,
    "description": "========\nOverview\n========\n\n\n\nSerialization library for Exceptions and Tracebacks.\n\n* Free software: BSD license\n\nIt allows you to:\n\n* `Pickle <https://docs.python.org/3/library/pickle.html>`_ tracebacks and raise exceptions\n  with pickled tracebacks in different processes. This allows better error handling when running\n  code over multiple processes (imagine multiprocessing, billiard, futures, celery etc).\n* Create traceback objects from strings (the ``from_string`` method). *No pickling is used*.\n* Serialize tracebacks to/from plain dicts (the ``from_dict`` and ``to_dict`` methods). *No pickling is used*.\n* Raise the tracebacks created from the aforementioned sources.\n* Pickle an Exception together with its traceback and exception chain\n  (``raise ... from ...``) *(Python 3 only)*\n\n**Again, note that using the pickle support is completely optional. You are solely responsible for\nsecurity problems should you decide to use the pickle support.**\n\nInstallation\n============\n\n::\n\n    pip install tblib\n\nDocumentation\n=============\n\n.. contents::\n   :local:\n\nPickling tracebacks\n~~~~~~~~~~~~~~~~~~~\n\n**Note**: The traceback objects that come out are stripped of some attributes (like variables). But you'll be able to raise exceptions with\nthose tracebacks or print them - that should cover 99% of the usecases.\n\n::\n\n    >>> from tblib import pickling_support\n    >>> pickling_support.install()\n    >>> import pickle, sys\n    >>> def inner_0():\n    ...     raise Exception('fail')\n    ...\n    >>> def inner_1():\n    ...     inner_0()\n    ...\n    >>> def inner_2():\n    ...     inner_1()\n    ...\n    >>> try:\n    ...     inner_2()\n    ... except:\n    ...     s1 = pickle.dumps(sys.exc_info())\n    ...\n    >>> len(s1) > 1\n    True\n    >>> try:\n    ...     inner_2()\n    ... except:\n    ...     s2 = pickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL)\n    ...\n    >>> len(s2) > 1\n    True\n\n    >>> try:\n    ...     import cPickle\n    ... except ImportError:\n    ...     import pickle as cPickle\n    >>> try:\n    ...     inner_2()\n    ... except:\n    ...     s3 = cPickle.dumps(sys.exc_info(), protocol=pickle.HIGHEST_PROTOCOL)\n    ...\n    >>> len(s3) > 1\n    True\n\nUnpickling tracebacks\n~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n    >>> pickle.loads(s1)\n    (<...Exception'>, Exception('fail'...), <traceback object at ...>)\n\n    >>> pickle.loads(s2)\n    (<...Exception'>, Exception('fail'...), <traceback object at ...>)\n\n    >>> pickle.loads(s3)\n    (<...Exception'>, Exception('fail'...), <traceback object at ...>)\n\nRaising\n~~~~~~~\n\n::\n\n    >>> from six import reraise\n    >>> reraise(*pickle.loads(s1))\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[14]>\", line 1, in <module>\n        reraise(*pickle.loads(s2))\n      File \"<doctest README.rst[8]>\", line 2, in <module>\n        inner_2()\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n    >>> reraise(*pickle.loads(s2))\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[14]>\", line 1, in <module>\n        reraise(*pickle.loads(s2))\n      File \"<doctest README.rst[8]>\", line 2, in <module>\n        inner_2()\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n    >>> reraise(*pickle.loads(s3))\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[14]>\", line 1, in <module>\n        reraise(*pickle.loads(s2))\n      File \"<doctest README.rst[8]>\", line 2, in <module>\n        inner_2()\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n\nPickling Exceptions together with their traceback and chain (Python 3 only)\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n::\n\n    >>> try:  # doctest: +SKIP\n    ...     try:\n    ...         1 / 0\n    ...     except Exception as e:\n    ...         raise Exception(\"foo\") from e\n    ... except Exception as e:\n    ...     s = pickle.dumps(e)\n    >>> raise pickle.loads(s)  # doctest: +SKIP\n    Traceback (most recent call last):\n      File \"<doctest README.rst[16]>\", line 3, in <module>\n        1 / 0\n    ZeroDivisionError: division by zero\n\n    The above exception was the direct cause of the following exception:\n\n    Traceback (most recent call last):\n      File \"<doctest README.rst[17]>\", line 1, in <module>\n        raise pickle.loads(s)\n      File \"<doctest README.rst[16]>\", line 5, in <module>\n        raise Exception(\"foo\") from e\n    Exception: foo\n\nBaseException subclasses defined after calling ``pickling_support.install()`` will\n**not** retain their traceback and exception chain pickling.\nTo cover custom Exceptions, there are three options:\n\n1. Use ``@pickling_support.install`` as a decorator for each custom Exception\n\n    .. code-block:: python\n\n        >>> from tblib import pickling_support\n        >>> # Declare all imports of your package's dependencies\n        >>> import numpy  # doctest: +SKIP\n\n        >>> pickling_support.install()  # install for all modules imported so far\n\n        >>> @pickling_support.install\n        ... class CustomError(Exception):\n        ...     pass\n\n   Eventual subclasses of ``CustomError`` will need to be decorated again.\n\n2. Invoke ``pickling_support.install()`` after all modules have been imported and all\n   Exception subclasses have been declared\n\n    .. code-block:: python\n\n        >>> # Declare all imports of your package's dependencies\n        >>> import numpy  # doctest: +SKIP\n        >>> from tblib import pickling_support\n\n        >>> # Declare your own custom Exceptions\n        >>> class CustomError(Exception):\n        ...     pass\n\n        >>> # Finally, install tblib\n        >>> pickling_support.install()\n\n3. Selectively install tblib for Exception instances just before they are pickled\n\n    .. code-block:: python\n\n       pickling_support.install(<Exception instance>, [Exception instance], ...)\n\n   The above will install tblib pickling for all listed exceptions as well as any other\n   exceptions in their exception chains.\n\n   For example, one could write a wrapper to be used with\n   `ProcessPoolExecutor <https://docs.python.org/3/library/concurrent.futures.html>`_,\n   `Dask.distributed <https://distributed.dask.org/>`_, or similar libraries:\n\n::\n\n    >>> from tblib import pickling_support\n    >>> def wrapper(func, *args, **kwargs):\n    ...     try:\n    ...         return func(*args, **kwargs)\n    ...     except Exception as e:\n    ...         pickling_support.install(e)\n    ...         raise\n\nWhat if we have a local stack, does it show correctly ?\n-------------------------------------------------------\n\nYes it does::\n\n    >>> exc_info = pickle.loads(s3)\n    >>> def local_0():\n    ...     reraise(*exc_info)\n    ...\n    >>> def local_1():\n    ...     local_0()\n    ...\n    >>> def local_2():\n    ...     local_1()\n    ...\n    >>> local_2()\n    Traceback (most recent call last):\n      File \"...doctest.py\", line ..., in __run\n        compileflags, 1) in test.globs\n      File \"<doctest README.rst[24]>\", line 1, in <module>\n        local_2()\n      File \"<doctest README.rst[23]>\", line 2, in local_2\n        local_1()\n      File \"<doctest README.rst[22]>\", line 2, in local_1\n        local_0()\n      File \"<doctest README.rst[21]>\", line 2, in local_0\n        reraise(*exc_info)\n      File \"<doctest README.rst[11]>\", line 2, in <module>\n        inner_2()\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n\nIt also supports more contrived scenarios\n-----------------------------------------\n\nLike tracebacks with syntax errors::\n\n    >>> from tblib import Traceback\n    >>> from examples import bad_syntax\n    >>> try:\n    ...     bad_syntax()\n    ... except:\n    ...     et, ev, tb = sys.exc_info()\n    ...     tb = Traceback(tb)\n    ...\n    >>> reraise(et, ev, tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[58]>\", line 1, in <module>\n        reraise(et, ev, tb.as_traceback())\n      File \"<doctest README.rst[57]>\", line 2, in <module>\n        bad_syntax()\n      File \"...tests...examples.py\", line 18, in bad_syntax\n        import badsyntax\n      File \"...tests...badsyntax.py\", line 5\n        is very bad\n         ^\n    SyntaxError: invalid syntax\n\nOr other import failures::\n\n    >>> from examples import bad_module\n    >>> try:\n    ...     bad_module()\n    ... except:\n    ...     et, ev, tb = sys.exc_info()\n    ...     tb = Traceback(tb)\n    ...\n    >>> reraise(et, ev, tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[61]>\", line 1, in <module>\n        reraise(et, ev, tb.as_traceback())\n      File \"<doctest README.rst[60]>\", line 2, in <module>\n        bad_module()\n      File \"...tests...examples.py\", line 23, in bad_module\n        import badmodule\n      File \"...tests...badmodule.py\", line 3, in <module>\n        raise Exception(\"boom!\")\n    Exception: boom!\n\nOr a traceback that's caused by exceeding the recursion limit (here we're\nforcing the type and value to have consistency across platforms)::\n\n    >>> def f(): f()\n    >>> try:\n    ...    f()\n    ... except RuntimeError:\n    ...    et, ev, tb = sys.exc_info()\n    ...    tb = Traceback(tb)\n    ...\n    >>> reraise(RuntimeError, RuntimeError(\"maximum recursion depth exceeded\"), tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[32]>\", line 1, in f\n        def f(): f()\n      File \"<doctest README.rst[32]>\", line 1, in f\n        def f(): f()\n      File \"<doctest README.rst[32]>\", line 1, in f\n        def f(): f()\n      ...\n    RuntimeError: maximum recursion depth exceeded\n\nReference\n~~~~~~~~~\n\ntblib.Traceback\n---------------\n\nIt is used by the ``pickling_support``. You can use it too if you want more flexibility::\n\n    >>> from tblib import Traceback\n    >>> try:\n    ...     inner_2()\n    ... except:\n    ...     et, ev, tb = sys.exc_info()\n    ...     tb = Traceback(tb)\n    ...\n    >>> reraise(et, ev, tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[21]>\", line 6, in <module>\n        reraise(et, ev, tb.as_traceback())\n      File \"<doctest README.rst[21]>\", line 2, in <module>\n        inner_2()\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n\ntblib.Traceback.to_dict\n```````````````````````\n\nYou can use the ``to_dict`` method and the ``from_dict`` classmethod to\nconvert a Traceback into and from a dictionary serializable by the stdlib\njson.JSONDecoder::\n\n    >>> import json\n    >>> from pprint import pprint\n    >>> try:\n    ...     inner_2()\n    ... except:\n    ...     et, ev, tb = sys.exc_info()\n    ...     tb = Traceback(tb)\n    ...     tb_dict = tb.to_dict()\n    ...     pprint(tb_dict)\n    {'tb_frame': {'f_code': {'co_filename': '<doctest README.rst[...]>',\n                             'co_name': '<module>'},\n                  'f_globals': {'__name__': '__main__'},\n                  'f_lineno': 5},\n     'tb_lineno': 2,\n     'tb_next': {'tb_frame': {'f_code': {'co_filename': ...,\n                                         'co_name': 'inner_2'},\n                              'f_globals': {'__name__': '__main__'},\n                              'f_lineno': 2},\n                 'tb_lineno': 2,\n                 'tb_next': {'tb_frame': {'f_code': {'co_filename': ...,\n                                                     'co_name': 'inner_1'},\n                                          'f_globals': {'__name__': '__main__'},\n                                          'f_lineno': 2},\n                             'tb_lineno': 2,\n                             'tb_next': {'tb_frame': {'f_code': {'co_filename': ...,\n                                                                 'co_name': 'inner_0'},\n                                                      'f_globals': {'__name__': '__main__'},\n                                                      'f_lineno': 2},\n                                         'tb_lineno': 2,\n                                         'tb_next': None}}}}\n\ntblib.Traceback.from_dict\n`````````````````````````\n\nBuilding on the previous example::\n\n    >>> tb_json = json.dumps(tb_dict)\n    >>> tb = Traceback.from_dict(json.loads(tb_json))\n    >>> reraise(et, ev, tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[21]>\", line 6, in <module>\n        reraise(et, ev, tb.as_traceback())\n      File \"<doctest README.rst[21]>\", line 2, in <module>\n        inner_2()\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n\ntblib.Traceback.from_string\n```````````````````````````\n\n::\n\n    >>> tb = Traceback.from_string(\"\"\"\n    ... File \"skipped.py\", line 123, in func_123\n    ... Traceback (most recent call last):\n    ...   File \"tests/examples.py\", line 2, in func_a\n    ...     func_b()\n    ...   File \"tests/examples.py\", line 6, in func_b\n    ...     func_c()\n    ...   File \"tests/examples.py\", line 10, in func_c\n    ...     func_d()\n    ...   File \"tests/examples.py\", line 14, in func_d\n    ... Doesn't: matter\n    ... \"\"\")\n    >>> reraise(et, ev, tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[42]>\", line 6, in <module>\n        reraise(et, ev, tb.as_traceback())\n      File \"...examples.py\", line 2, in func_a\n        func_b()\n      File \"...examples.py\", line 6, in func_b\n        func_c()\n      File \"...examples.py\", line 10, in func_c\n        func_d()\n      File \"...examples.py\", line 14, in func_d\n        raise Exception(\"Guessing time !\")\n    Exception: fail\n\n\nIf you use the ``strict=False`` option then parsing is a bit more lax::\n\n    >>> tb = Traceback.from_string(\"\"\"\n    ... File \"bogus.py\", line 123, in bogus\n    ... Traceback (most recent call last):\n    ...  File \"tests/examples.py\", line 2, in func_a\n    ...   func_b()\n    ...    File \"tests/examples.py\", line 6, in func_b\n    ...     func_c()\n    ...    File \"tests/examples.py\", line 10, in func_c\n    ...   func_d()\n    ...  File \"tests/examples.py\", line 14, in func_d\n    ... Doesn't: matter\n    ... \"\"\", strict=False)\n    >>> reraise(et, ev, tb.as_traceback())\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[42]>\", line 6, in <module>\n        reraise(et, ev, tb.as_traceback())\n      File \"bogus.py\", line 123, in bogus\n      File \"...examples.py\", line 2, in func_a\n        func_b()\n      File \"...examples.py\", line 6, in func_b\n        func_c()\n      File \"...examples.py\", line 10, in func_c\n        func_d()\n      File \"...examples.py\", line 14, in func_d\n        raise Exception(\"Guessing time !\")\n    Exception: fail\n\ntblib.decorators.return_error\n-----------------------------\n\n::\n\n    >>> from tblib.decorators import return_error\n    >>> inner_2r = return_error(inner_2)\n    >>> e = inner_2r()\n    >>> e\n    <tblib.decorators.Error object at ...>\n    >>> e.reraise()\n    Traceback (most recent call last):\n      ...\n      File \"<doctest README.rst[26]>\", line 1, in <module>\n        e.reraise()\n      File \"...tblib...decorators.py\", line 19, in reraise\n        reraise(self.exc_type, self.exc_value, self.traceback)\n      File \"...tblib...decorators.py\", line 25, in return_exceptions_wrapper\n        return func(*args, **kwargs)\n      File \"<doctest README.rst[5]>\", line 2, in inner_2\n        inner_1()\n      File \"<doctest README.rst[4]>\", line 2, in inner_1\n        inner_0()\n      File \"<doctest README.rst[3]>\", line 2, in inner_0\n        raise Exception('fail')\n    Exception: fail\n\nHow's this useful? Imagine you're using multiprocessing like this::\n\n    # Note that Python 3.4 and later will show the remote traceback (but as a string sadly) so we skip testing this.\n    >>> import traceback\n    >>> from multiprocessing import Pool\n    >>> from examples import func_a\n    >>> pool = Pool()  # doctest: +SKIP\n    >>> try:  # doctest: +SKIP\n    ...     for i in pool.map(func_a, range(5)):\n    ...         print(i)\n    ... except:\n    ...     print(traceback.format_exc())\n    ...\n    Traceback (most recent call last):\n      File \"<doctest README.rst[...]>\", line 2, in <module>\n        for i in pool.map(func_a, range(5)):\n      File \"...multiprocessing...pool.py\", line ..., in map\n        ...\n      File \"...multiprocessing...pool.py\", line ..., in get\n        ...\n    Exception: Guessing time !\n    <BLANKLINE>\n    >>> pool.terminate()  # doctest: +SKIP\n\nNot very useful is it? Let's sort this out::\n\n    >>> from tblib.decorators import apply_with_return_error, Error\n    >>> from itertools import repeat\n    >>> pool = Pool()\n    >>> try:\n    ...     for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))):\n    ...         if isinstance(i, Error):\n    ...             i.reraise()\n    ...         else:\n    ...             print(i)\n    ... except:\n    ...     print(traceback.format_exc())\n    ...\n    Traceback (most recent call last):\n      File \"<doctest README.rst[...]>\", line 4, in <module>\n        i.reraise()\n      File \"...tblib...decorators.py\", line ..., in reraise\n        reraise(self.exc_type, self.exc_value, self.traceback)\n      File \"...tblib...decorators.py\", line ..., in return_exceptions_wrapper\n        return func(*args, **kwargs)\n      File \"...tblib...decorators.py\", line ..., in apply_with_return_error\n        return args[0](*args[1:])\n      File \"...examples.py\", line 2, in func_a\n        func_b()\n      File \"...examples.py\", line 6, in func_b\n        func_c()\n      File \"...examples.py\", line 10, in func_c\n        func_d()\n      File \"...examples.py\", line 14, in func_d\n        raise Exception(\"Guessing time !\")\n    Exception: Guessing time !\n    <BLANKLINE>\n    >>> pool.terminate()\n\nMuch better !\n\nWhat if we have a local call stack ?\n````````````````````````````````````\n\n::\n\n    >>> def local_0():\n    ...     pool = Pool()\n    ...     try:\n    ...         for i in pool.map(apply_with_return_error, zip(repeat(func_a), range(5))):\n    ...             if isinstance(i, Error):\n    ...                 i.reraise()\n    ...             else:\n    ...                 print(i)\n    ...     finally:\n    ...         pool.close()\n    ...\n    >>> def local_1():\n    ...     local_0()\n    ...\n    >>> def local_2():\n    ...     local_1()\n    ...\n    >>> try:\n    ...     local_2()\n    ... except:\n    ...     print(traceback.format_exc())\n    Traceback (most recent call last):\n      File \"<doctest README.rst[...]>\", line 2, in <module>\n        local_2()\n      File \"<doctest README.rst[...]>\", line 2, in local_2\n        local_1()\n      File \"<doctest README.rst[...]>\", line 2, in local_1\n        local_0()\n      File \"<doctest README.rst[...]>\", line 6, in local_0\n        i.reraise()\n      File \"...tblib...decorators.py\", line 20, in reraise\n        reraise(self.exc_type, self.exc_value, self.traceback)\n      File \"...tblib...decorators.py\", line 27, in return_exceptions_wrapper\n        return func(*args, **kwargs)\n      File \"...tblib...decorators.py\", line 47, in apply_with_return_error\n        return args[0](*args[1:])\n      File \"...tests...examples.py\", line 2, in func_a\n        func_b()\n      File \"...tests...examples.py\", line 6, in func_b\n        func_c()\n      File \"...tests...examples.py\", line 10, in func_c\n        func_d()\n      File \"...tests...examples.py\", line 14, in func_d\n        raise Exception(\"Guessing time !\")\n    Exception: Guessing time !\n    <BLANKLINE>\n\nOther weird stuff\n`````````````````\n\nClearing traceback works (Python 3.4 and up)::\n\n    >>> tb = Traceback.from_string(\"\"\"\n    ... File \"skipped.py\", line 123, in func_123\n    ... Traceback (most recent call last):\n    ...   File \"tests/examples.py\", line 2, in func_a\n    ...     func_b()\n    ...   File \"tests/examples.py\", line 6, in func_b\n    ...     func_c()\n    ...   File \"tests/examples.py\", line 10, in func_c\n    ...     func_d()\n    ...   File \"tests/examples.py\", line 14, in func_d\n    ... Doesn't: matter\n    ... \"\"\")\n    >>> import traceback, sys\n    >>> if sys.version_info > (3, 4):\n    ...     traceback.clear_frames(tb)\n\nCredits\n=======\n\n* `mitsuhiko/jinja2 <https://github.com/mitsuhiko/jinja2>`_ for figuring a way to create traceback objects.\n\n\nChangelog\n=========\n\n3.0.0 (2023-10-22)\n~~~~~~~~~~~~~~~~~~\n\n* Added support for  ``__context__``, ``__suppress_context__`` and ``__notes__``.\n  Contributed by Tim Maxwell in `#72 <https://github.com/ionelmc/python-tblib/pull/72>`_.\n* Added the ``get_locals`` argument to ``tblib.pickling_support.install()``, ``tblib.Traceback`` and ``tblib.Frame``.\n  Fixes `#41 <https://github.com/ionelmc/python-tblib/issues/41>`_.\n* Dropped support for now-EOL Python 3.7 and added 3.12 in the test grid.\n\n2.0.0 (2023-06-22)\n~~~~~~~~~~~~~~~~~~\n\n* Removed support for legacy Pythons (2.7 and 3.6) and added Python 3.11 in the test grid.\n* Some cleanups and refactors (mostly from ruff).\n\n1.7.0 (2020-07-24)\n~~~~~~~~~~~~~~~~~~\n\n* Add more attributes to ``Frame`` and ``Code`` objects for pytest compatibility. Contributed by Ivanq in\n  `#58 <https://github.com/ionelmc/python-tblib/pull/58>`_.\n\n1.6.0 (2019-12-07)\n~~~~~~~~~~~~~~~~~~\n\n* When pickling an Exception, also pickle its traceback and the Exception chain\n  (``raise ... from ...``). Contributed by Guido Imperiale in\n  `#53 <https://github.com/ionelmc/python-tblib/issues/53>`_.\n\n1.5.0 (2019-10-23)\n~~~~~~~~~~~~~~~~~~\n\n* Added support for Python 3.8. Contributed by Victor Stinner in\n  `#42 <https://github.com/ionelmc/python-tblib/issues/42>`_.\n* Removed support for end of life Python 3.4.\n* Few CI improvements and fixes.\n\n1.4.0 (2019-05-02)\n~~~~~~~~~~~~~~~~~~\n\n* Removed support for end of life Python 3.3.\n* Fixed tests for Python 3.7. Contributed by Elliott Sales de Andrade in\n  `#36 <https://github.com/ionelmc/python-tblib/issues/36>`_.\n* Fixed compatibility issue with Twised (``twisted.python.failure.Failure`` expected a ``co_code`` attribute).\n\n1.3.2 (2017-04-09)\n~~~~~~~~~~~~~~~~~~\n\n* Add support for PyPy3.5-5.7.1-beta. Previously ``AttributeError:\n  'Frame' object has no attribute 'clear'``  could be raised. See PyPy\n  issue `#2532 <https://foss.heptapod.net/pypy/pypy/-/issues/2532>`_.\n\n1.3.1 (2017-03-27)\n~~~~~~~~~~~~~~~~~~\n\n* Fixed handling for tracebacks due to exceeding the recursion limit.\n  Fixes `#15 <https://github.com/ionelmc/python-tblib/issues/15>`_.\n\n1.3.0 (2016-03-08)\n~~~~~~~~~~~~~~~~~~\n\n* Added ``Traceback.from_string``.\n\n1.2.0 (2015-12-18)\n~~~~~~~~~~~~~~~~~~\n\n* Fixed handling for tracebacks from generators and other internal improvements\n  and optimizations. Contributed by DRayX in `#10 <https://github.com/ionelmc/python-tblib/issues/10>`_\n  and `#11 <https://github.com/ionelmc/python-tblib/pull/11>`_.\n\n1.1.0 (2015-07-27)\n~~~~~~~~~~~~~~~~~~\n\n* Added support for Python 2.6. Contributed by Arcadiy Ivanov in\n  `#8 <https://github.com/ionelmc/python-tblib/pull/8>`_.\n\n1.0.0 (2015-03-30)\n~~~~~~~~~~~~~~~~~~\n\n* Added ``to_dict`` method and ``from_dict`` classmethod on Tracebacks.\n  Contributed by beckjake in `#5 <https://github.com/ionelmc/python-tblib/pull/5>`_.\n",
    "bugtrack_url": null,
    "license": "BSD-2-Clause",
    "summary": "Traceback serialization library.",
    "version": "3.0.0",
    "project_urls": {
        "Changelog": "https://python-tblib.readthedocs.io/en/latest/changelog.html",
        "Documentation": "https://python-tblib.readthedocs.io/",
        "Homepage": "https://github.com/ionelmc/python-tblib",
        "Issue Tracker": "https://github.com/ionelmc/python-tblib/issues"
    },
    "split_keywords": [
        "traceback",
        "debugging",
        "exceptions"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9b87ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5",
                "md5": "a20cd4b91b1d1b8a58bf5bdc495dfda5",
                "sha256": "80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"
            },
            "downloads": -1,
            "filename": "tblib-3.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "a20cd4b91b1d1b8a58bf5bdc495dfda5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 12478,
            "upload_time": "2023-10-22T00:35:46",
            "upload_time_iso_8601": "2023-10-22T00:35:46.515838Z",
            "url": "https://files.pythonhosted.org/packages/9b/87/ce70db7cae60e67851eb94e1a2127d4abb573d3866d2efd302ceb0d4d2a5/tblib-3.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "1adf4f2cd7eaa6d41a7994d46527349569d46e34d9cdd07590b5c5b0dcf53de3",
                "md5": "7532a2d525bd0d5d4a78db5dafe4823f",
                "sha256": "93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"
            },
            "downloads": -1,
            "filename": "tblib-3.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "7532a2d525bd0d5d4a78db5dafe4823f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 30616,
            "upload_time": "2023-10-22T00:35:48",
            "upload_time_iso_8601": "2023-10-22T00:35:48.554954Z",
            "url": "https://files.pythonhosted.org/packages/1a/df/4f2cd7eaa6d41a7994d46527349569d46e34d9cdd07590b5c5b0dcf53de3/tblib-3.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-10-22 00:35:48",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "ionelmc",
    "github_project": "python-tblib",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "tox": true,
    "lcname": "tblib"
}
        
Elapsed time: 0.23678s