stackscope


Namestackscope JSON
Version 0.2.1 PyPI version JSON
download
home_pagehttps://github.com/oremanj/stackscope
SummaryUnusually detailed Python stack introspection
upload_time2024-02-02 20:44:05
maintainer
docs_urlNone
authorJoshua Oreman
requires_python>=3.8
licenseMIT -or- Apache License 2.0
keywords async debugging trio asyncio
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage
            stackscope: unusually detailed Python stack introspection
=========================================================

.. image:: https://img.shields.io/pypi/v/stackscope.svg
   :target: https://pypi.org/project/stackscope
   :alt: Latest PyPI version

.. image:: https://img.shields.io/badge/docs-read%20now-blue.svg
   :target: https://stackscope.readthedocs.io/en/latest/?badge=latest
   :alt: Documentation status

.. image:: https://codecov.io/gh/oremanj/stackscope/branch/master/graph/badge.svg
   :target: https://codecov.io/gh/oremanj/stackscope
   :alt: Test coverage

.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
   :target: https://github.com/ambv/black
   :alt: Code style: black

.. image:: http://www.mypy-lang.org/static/mypy_badge.svg
   :target: http://www.mypy-lang.org/
   :alt: Checked with mypy

``stackscope`` is a library that helps you tell what your running
Python program is doing and how it got there. It can provide detailed
stack traces, similar to what you get in an exception traceback, but
without needing to throw an exception first. Compared to standard
library facilities such as ``traceback.extract_stack()``, it is far
more versatile. It supports async tasks, generators, threads, and
greenlets; provides information about active context managers in each
stack frame; and includes a customization interface that library
authors can use to teach it to improve the stack extraction logic for
code that touches their library. (As an example of the latter, the
stack of an async task blocked in a ``run_in_thread()`` function could
be made to cover the code that's running in the thread as well.)

``stackscope`` is loosely affiliated with the `Trio
<https://trio.readthedocs.io/>`__ async framework, and shares Trio's
obsessive focus on usability and correctness. The context manager
analysis is especially helpful with Trio since you can use it to
understand where the nurseries are. You don't have to use ``stackscope``
with Trio, though; it requires only the Python standard library, 3.8
or later, and the `ExceptionGroup backport <https://pypi.org/project/exceptiongroup/>`__
on versions below 3.11.

``stackscope`` is mostly intended as a building block for other
debugging and introspection tools. You can use it directly, but
there's only rudimentary support for end-user-facing niceties such as
pretty-printed output. On the other hand, the core logic is (believed
to be) robust and flexible, exposing customization points that
third-party libraries can use to help ``stackscope`` make better
tracebacks for their code.  ``stackscope`` ships out of the box with such
"glue" for `Trio <https://trio.readthedocs.io/en/stable/>`__, `greenback
<https://greenback.readthedocs.io/en/latest/>`__, and some of their
lower-level dependencies.

``stackscope`` requires Python 3.8 or later. It is fully
type-annotated and is tested with CPython (every minor version through
3.12) and PyPy, on Linux, Windows, and macOS. It will probably
work on other operating systems.  Basic features will work on other
Python implementations, but the context manager decoding will be less
intelligent, and won't work at all without a usable
``gc.get_referents()``.

Quickstart
----------

Call ``stackscope.extract()`` to obtain a ``stackscope.Stack``
describing the stack of a coroutine object, generator iterator (sync
or async), greenlet, or thread. If you want to extract part of the
stack that led to the ``extract()`` call, then either pass a
``stackscope.StackSlice`` or use the convenience aliases
``stackscope.extract_since()`` and ``stackscope.extract_until()``.

Trio users: Try ``print(stackscope.extract(trio.lowlevel.current_root_task(),
recurse_child_tasks=True))`` to print the entire task tree of your
Trio program.

Once you have a ``Stack``, you can:

* Format it for human consumption: ``str()`` obtains a tree view as
  shown in the example below, or use ``stack.format()`` to customize
  it or ``stack.format_flat()`` to get an alternate format that
  resembles a standard Python traceback.

* Iterate over it (or equivalently, its ``frames`` attribute) to
  obtain a series of ``stackscope.Frame``\s for programmatic
  inspection.  Each frame represents one function call. In addition to
  the interpreter-level frame object, it lets you access information
  about the active context managers in that function.

* Look at its ``leaf`` attribute to see what's left once you
  peel away all the frames. For example, this might be some atomic
  awaitable such as an ``asyncio.Future``. It will be ``None`` if the
  frames tell the whole story.

* Use its ``as_stdlib_summary()`` method to get a standard library
  ``traceback.StackSummary`` object (with some loss of information),
  which can be pickled or passed to non-``stackscope``\-aware tools.

See the documentation for more details.

Example
-------

This code uses a number of context managers::

    from contextlib import contextmanager, ExitStack

    @contextmanager
    def null_context():
        yield

    def some_cb(*a, **kw):
        pass

    @contextmanager
    def inner_context():
        stack = ExitStack()
        with stack:
            stack.enter_context(null_context())
            stack.callback(some_cb, 10, "hi", answer=42)
            yield "inner"

    @contextmanager
    def outer_context():
        with inner_context() as inner:
            yield "outer"

    def example():
        with outer_context():
            yield

    def call_example():
        yield from example()

    gen = call_example()
    next(gen)

You can use ``stackscope`` to inspect the state of the partially-consumed generator
*gen*, showing the tree structure of all of those context managers::

    $ python3 -i example.py
    >>> import stackscope
    >>> stack = stackscope.extract(gen)
    >>> print(stack)
    stackscope.Stack (most recent call last):
    ╠ call_example in __main__ at [...]/stackscope/example.py:28
    ║ └ yield from example()
    ╠ example in __main__ at [...]/stackscope/example.py:25
    ║ ├ with outer_context():  # _: _GeneratorContextManager (line 24)
    ║ │ ╠ outer_context in __main__ at [...]/stackscope/example.py:21
    ║ │ ║ ├ with inner_context() as inner:  # inner: _GeneratorContextManager (line 20)
    ║ │ ║ │ ╠ inner_context in __main__ at [...]/stackscope/example.py:16
    ║ │ ║ │ ║ ├ with stack:  # stack: ExitStack (line 13)
    ║ │ ║ │ ║ ├── stack.enter_context(null_context(...))  # stack[0]: _GeneratorContextManager
    ║ │ ║ │ ║ │   ╠ null_context in __main__ at [...]/stackscope/example.py:5
    ║ │ ║ │ ║ │   ║ └ yield
    ║ │ ║ │ ║ ├── stack.callback(__main__.some_cb, 10, 'hi', answer=42)  # stack[1]: function
    ║ │ ║ │ ║ └ yield "inner"
    ║ │ ║ └ yield "outer"
    ║ └ yield

That full tree structure is exposed for programmatic inspection as well::

    >>> print(stack.frames[1].contexts[0].inner_stack.frames[0].contexts[0])
    inner_context(...)  # inner: _GeneratorContextManager (line 20)
    ╠ inner_context in __main__ at /Users/oremanj/dev/stackscope/example.py:16
    ║ ├ with stack:  # stack: ExitStack (line 13)
    ║ ├── stack.enter_context(null_context(...))  # stack[0]: _GeneratorContextManager
    ║ │   ╠ null_context in __main__ at /Users/oremanj/dev/stackscope/example.py:5
    ║ │   ║ └ yield
    ║ ├── stack.callback(__main__.some_cb, 10, 'hi', answer=42)  # stack[1]: function
    ║ └ yield "inner"

Of course, if you just want a "normal" stack trace without the added information,
you can get that too::

    >>> print("".join(stack.format_flat()))
    stackscope.Stack (most recent call last):
      File "/Users/oremanj/dev/stackscope/example.py", line 28, in call_example
        yield from example()
      File "/Users/oremanj/dev/stackscope/example.py", line 25, in example
        yield

Development status
------------------

While ``stackscope`` is a young project that deals with some obscure Python internals,
it is written quite defensively including 99%+ test coverage and type hints.
The author is using it (cautiously) in production and thinks you might want to as well.

License
-------

``stackscope`` is licensed under your choice of the MIT or Apache 2.0
license. See `LICENSE <https://github.com/oremanj/stackscope/blob/master/LICENSE>`__
for details.

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/oremanj/stackscope",
    "name": "stackscope",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "",
    "keywords": "async,debugging,trio,asyncio",
    "author": "Joshua Oreman",
    "author_email": "oremanj@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/bc/1c/5d977ddf735543dc05c4c77e205e26c5bbf1bca9c43a4d926cb7d413d64d/stackscope-0.2.1.tar.gz",
    "platform": null,
    "description": "stackscope: unusually detailed Python stack introspection\n=========================================================\n\n.. image:: https://img.shields.io/pypi/v/stackscope.svg\n   :target: https://pypi.org/project/stackscope\n   :alt: Latest PyPI version\n\n.. image:: https://img.shields.io/badge/docs-read%20now-blue.svg\n   :target: https://stackscope.readthedocs.io/en/latest/?badge=latest\n   :alt: Documentation status\n\n.. image:: https://codecov.io/gh/oremanj/stackscope/branch/master/graph/badge.svg\n   :target: https://codecov.io/gh/oremanj/stackscope\n   :alt: Test coverage\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg\n   :target: https://github.com/ambv/black\n   :alt: Code style: black\n\n.. image:: http://www.mypy-lang.org/static/mypy_badge.svg\n   :target: http://www.mypy-lang.org/\n   :alt: Checked with mypy\n\n``stackscope`` is a library that helps you tell what your running\nPython program is doing and how it got there. It can provide detailed\nstack traces, similar to what you get in an exception traceback, but\nwithout needing to throw an exception first. Compared to standard\nlibrary facilities such as ``traceback.extract_stack()``, it is far\nmore versatile. It supports async tasks, generators, threads, and\ngreenlets; provides information about active context managers in each\nstack frame; and includes a customization interface that library\nauthors can use to teach it to improve the stack extraction logic for\ncode that touches their library. (As an example of the latter, the\nstack of an async task blocked in a ``run_in_thread()`` function could\nbe made to cover the code that's running in the thread as well.)\n\n``stackscope`` is loosely affiliated with the `Trio\n<https://trio.readthedocs.io/>`__ async framework, and shares Trio's\nobsessive focus on usability and correctness. The context manager\nanalysis is especially helpful with Trio since you can use it to\nunderstand where the nurseries are. You don't have to use ``stackscope``\nwith Trio, though; it requires only the Python standard library, 3.8\nor later, and the `ExceptionGroup backport <https://pypi.org/project/exceptiongroup/>`__\non versions below 3.11.\n\n``stackscope`` is mostly intended as a building block for other\ndebugging and introspection tools. You can use it directly, but\nthere's only rudimentary support for end-user-facing niceties such as\npretty-printed output. On the other hand, the core logic is (believed\nto be) robust and flexible, exposing customization points that\nthird-party libraries can use to help ``stackscope`` make better\ntracebacks for their code.  ``stackscope`` ships out of the box with such\n\"glue\" for `Trio <https://trio.readthedocs.io/en/stable/>`__, `greenback\n<https://greenback.readthedocs.io/en/latest/>`__, and some of their\nlower-level dependencies.\n\n``stackscope`` requires Python 3.8 or later. It is fully\ntype-annotated and is tested with CPython (every minor version through\n3.12) and PyPy, on Linux, Windows, and macOS. It will probably\nwork on other operating systems.  Basic features will work on other\nPython implementations, but the context manager decoding will be less\nintelligent, and won't work at all without a usable\n``gc.get_referents()``.\n\nQuickstart\n----------\n\nCall ``stackscope.extract()`` to obtain a ``stackscope.Stack``\ndescribing the stack of a coroutine object, generator iterator (sync\nor async), greenlet, or thread. If you want to extract part of the\nstack that led to the ``extract()`` call, then either pass a\n``stackscope.StackSlice`` or use the convenience aliases\n``stackscope.extract_since()`` and ``stackscope.extract_until()``.\n\nTrio users: Try ``print(stackscope.extract(trio.lowlevel.current_root_task(),\nrecurse_child_tasks=True))`` to print the entire task tree of your\nTrio program.\n\nOnce you have a ``Stack``, you can:\n\n* Format it for human consumption: ``str()`` obtains a tree view as\n  shown in the example below, or use ``stack.format()`` to customize\n  it or ``stack.format_flat()`` to get an alternate format that\n  resembles a standard Python traceback.\n\n* Iterate over it (or equivalently, its ``frames`` attribute) to\n  obtain a series of ``stackscope.Frame``\\s for programmatic\n  inspection.  Each frame represents one function call. In addition to\n  the interpreter-level frame object, it lets you access information\n  about the active context managers in that function.\n\n* Look at its ``leaf`` attribute to see what's left once you\n  peel away all the frames. For example, this might be some atomic\n  awaitable such as an ``asyncio.Future``. It will be ``None`` if the\n  frames tell the whole story.\n\n* Use its ``as_stdlib_summary()`` method to get a standard library\n  ``traceback.StackSummary`` object (with some loss of information),\n  which can be pickled or passed to non-``stackscope``\\-aware tools.\n\nSee the documentation for more details.\n\nExample\n-------\n\nThis code uses a number of context managers::\n\n    from contextlib import contextmanager, ExitStack\n\n    @contextmanager\n    def null_context():\n        yield\n\n    def some_cb(*a, **kw):\n        pass\n\n    @contextmanager\n    def inner_context():\n        stack = ExitStack()\n        with stack:\n            stack.enter_context(null_context())\n            stack.callback(some_cb, 10, \"hi\", answer=42)\n            yield \"inner\"\n\n    @contextmanager\n    def outer_context():\n        with inner_context() as inner:\n            yield \"outer\"\n\n    def example():\n        with outer_context():\n            yield\n\n    def call_example():\n        yield from example()\n\n    gen = call_example()\n    next(gen)\n\nYou can use ``stackscope`` to inspect the state of the partially-consumed generator\n*gen*, showing the tree structure of all of those context managers::\n\n    $ python3 -i example.py\n    >>> import stackscope\n    >>> stack = stackscope.extract(gen)\n    >>> print(stack)\n    stackscope.Stack (most recent call last):\n    \u2560 call_example in __main__ at [...]/stackscope/example.py:28\n    \u2551 \u2514 yield from example()\n    \u2560 example in __main__ at [...]/stackscope/example.py:25\n    \u2551 \u251c with outer_context():  # _: _GeneratorContextManager (line 24)\n    \u2551 \u2502 \u2560 outer_context in __main__ at [...]/stackscope/example.py:21\n    \u2551 \u2502 \u2551 \u251c with inner_context() as inner:  # inner: _GeneratorContextManager (line 20)\n    \u2551 \u2502 \u2551 \u2502 \u2560 inner_context in __main__ at [...]/stackscope/example.py:16\n    \u2551 \u2502 \u2551 \u2502 \u2551 \u251c with stack:  # stack: ExitStack (line 13)\n    \u2551 \u2502 \u2551 \u2502 \u2551 \u251c\u2500\u2500 stack.enter_context(null_context(...))  # stack[0]: _GeneratorContextManager\n    \u2551 \u2502 \u2551 \u2502 \u2551 \u2502   \u2560 null_context in __main__ at [...]/stackscope/example.py:5\n    \u2551 \u2502 \u2551 \u2502 \u2551 \u2502   \u2551 \u2514 yield\n    \u2551 \u2502 \u2551 \u2502 \u2551 \u251c\u2500\u2500 stack.callback(__main__.some_cb, 10, 'hi', answer=42)  # stack[1]: function\n    \u2551 \u2502 \u2551 \u2502 \u2551 \u2514 yield \"inner\"\n    \u2551 \u2502 \u2551 \u2514 yield \"outer\"\n    \u2551 \u2514 yield\n\nThat full tree structure is exposed for programmatic inspection as well::\n\n    >>> print(stack.frames[1].contexts[0].inner_stack.frames[0].contexts[0])\n    inner_context(...)  # inner: _GeneratorContextManager (line 20)\n    \u2560 inner_context in __main__ at /Users/oremanj/dev/stackscope/example.py:16\n    \u2551 \u251c with stack:  # stack: ExitStack (line 13)\n    \u2551 \u251c\u2500\u2500 stack.enter_context(null_context(...))  # stack[0]: _GeneratorContextManager\n    \u2551 \u2502   \u2560 null_context in __main__ at /Users/oremanj/dev/stackscope/example.py:5\n    \u2551 \u2502   \u2551 \u2514 yield\n    \u2551 \u251c\u2500\u2500 stack.callback(__main__.some_cb, 10, 'hi', answer=42)  # stack[1]: function\n    \u2551 \u2514 yield \"inner\"\n\nOf course, if you just want a \"normal\" stack trace without the added information,\nyou can get that too::\n\n    >>> print(\"\".join(stack.format_flat()))\n    stackscope.Stack (most recent call last):\n      File \"/Users/oremanj/dev/stackscope/example.py\", line 28, in call_example\n        yield from example()\n      File \"/Users/oremanj/dev/stackscope/example.py\", line 25, in example\n        yield\n\nDevelopment status\n------------------\n\nWhile ``stackscope`` is a young project that deals with some obscure Python internals,\nit is written quite defensively including 99%+ test coverage and type hints.\nThe author is using it (cautiously) in production and thinks you might want to as well.\n\nLicense\n-------\n\n``stackscope`` is licensed under your choice of the MIT or Apache 2.0\nlicense. See `LICENSE <https://github.com/oremanj/stackscope/blob/master/LICENSE>`__\nfor details.\n",
    "bugtrack_url": null,
    "license": "MIT -or- Apache License 2.0",
    "summary": "Unusually detailed Python stack introspection",
    "version": "0.2.1",
    "project_urls": {
        "Homepage": "https://github.com/oremanj/stackscope"
    },
    "split_keywords": [
        "async",
        "debugging",
        "trio",
        "asyncio"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "c555c9cadfbc038a0cb1cd7c38b5f2acc0b50f3979171f1886e381a7bd5e56ec",
                "md5": "157c4e4b2dc3240f12a55fc6dd109d9e",
                "sha256": "2f7ab6f6fc821c83e0f0780ae068518ce9b2a9da50ee10ce293fc86276ef07ce"
            },
            "downloads": -1,
            "filename": "stackscope-0.2.1-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "157c4e4b2dc3240f12a55fc6dd109d9e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 80458,
            "upload_time": "2024-02-02T20:44:04",
            "upload_time_iso_8601": "2024-02-02T20:44:04.151032Z",
            "url": "https://files.pythonhosted.org/packages/c5/55/c9cadfbc038a0cb1cd7c38b5f2acc0b50f3979171f1886e381a7bd5e56ec/stackscope-0.2.1-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bc1c5d977ddf735543dc05c4c77e205e26c5bbf1bca9c43a4d926cb7d413d64d",
                "md5": "5e891eeb74f128b18b01e68c5b8fd56f",
                "sha256": "29f09caabc3eec18faa35bf238c96a24cc2156dd9d49e087e3a3a0ea5cf20626"
            },
            "downloads": -1,
            "filename": "stackscope-0.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "5e891eeb74f128b18b01e68c5b8fd56f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 90021,
            "upload_time": "2024-02-02T20:44:05",
            "upload_time_iso_8601": "2024-02-02T20:44:05.519481Z",
            "url": "https://files.pythonhosted.org/packages/bc/1c/5d977ddf735543dc05c4c77e205e26c5bbf1bca9c43a4d926cb7d413d64d/stackscope-0.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-02-02 20:44:05",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "oremanj",
    "github_project": "stackscope",
    "travis_ci": false,
    "coveralls": true,
    "github_actions": true,
    "tox": true,
    "lcname": "stackscope"
}
        
Elapsed time: 0.28785s