Get information about what a Python frame is currently doing, particularly the AST node being executed

Related tags

Miscellaneouspython
Overview

executing

Build Status Coverage Status Supports Python versions 2.7 and 3.4+, including PyPy

This mini-package lets you get information about what a frame is currently doing, particularly the AST node being executed.

Usage

Getting the AST node

import executing

node = executing.Source.executing(frame).node

Then node will be an AST node (from the ast standard library module) or None if the node couldn't be identified (which may happen often and should always be checked).

node will always be the same instance for multiple calls with frames at the same point of execution.

If you have a traceback object, pass it directly to Source.executing() rather than the tb_frame attribute to get the correct node.

Getting the source code of the node

For this you will need to separately install the asttokens library, then obtain an ASTTokens object:

executing.Source.executing(frame).source.asttokens()

or:

executing.Source.for_frame(frame).asttokens()

or use one of the convenience methods:

executing.Source.executing(frame).text()
executing.Source.executing(frame).text_range()

Getting the __qualname__ of the current function

executing.Source.executing(frame).code_qualname()

or:

executing.Source.for_frame(frame).code_qualname(frame.f_code)

The Source class

Everything goes through the Source class. Only one instance of the class is created for each filename. Subclassing it to add more attributes on creation or methods is recommended. The classmethods such as executing will respect this. See the source code and docstrings for more detail.

Installation

pip install executing

If you don't like that you can just copy the file executing.py, there are no dependencies (but of course you won't get updates).

How does it work?

Suppose the frame is executing this line:

self.foo(bar.x)

and in particular it's currently obtaining the attribute self.foo. Looking at the bytecode, specifically frame.f_code.co_code[frame.f_lasti], we can tell that it's loading an attribute, but it's not obvious which one. We can narrow down the statement being executed using frame.f_lineno and find the two ast.Attribute nodes representing self.foo and bar.x. How do we find out which one it is, without recreating the entire compiler in Python?

The trick is to modify the AST slightly for each candidate expression and observe the changes in the bytecode instructions. We change the AST to this:

(self.foo ** 'longuniqueconstant')(bar.x)

and compile it, and the bytecode will be almost the same but there will be two new instructions:

LOAD_CONST 'longuniqueconstant'
BINARY_POWER

and just before that will be a LOAD_ATTR instruction corresponding to self.foo. Seeing that it's in the same position as the original instruction lets us know we've found our match.

Is it reliable?

Yes - if it identifies a node, you can trust that it's identified the correct one. The tests are very thorough - in addition to unit tests which check various situations directly, there are property tests against a large number of files (see the filenames printed in this build) with real code. Specifically, for each file, the tests:

  1. Identify as many nodes as possible from all the bytecode instructions in the file, and assert that they are all distinct
  2. Find all the nodes that should be identifiable, and assert that they were indeed identified somewhere

In other words, it shows that there is a one-to-one mapping between the nodes and the instructions that can be handled. This leaves very little room for a bug to creep in.

Furthermore, executing checks that the instructions compiled from the modified AST exactly match the original code save for a few small known exceptions. This accounts for all the quirks and optimisations in the interpreter.

Which nodes can it identify?

Currently it works in almost all cases for the following ast nodes:

  • Call, e.g. self.foo(bar)
  • Attribute, e.g. point.x
  • Subscript, e.g. lst[1]
  • BinOp, e.g. x + y (doesn't include and and or)
  • UnaryOp, e.g. -n (includes not but only works sometimes)
  • Compare e.g. a < b (not for chains such as 0 < p < 1)

The plan is to extend to more operations in the future.

Libraries that use this

My libraries

  • stack_data: Extracts data from stack frames and tracebacks, particularly to display more useful tracebacks than the default. Also uses another related library of mine: pure_eval.
  • snoop: A feature-rich and convenient debugging library. Uses executing to show the operation which caused an exception and to allow the pp function to display the source of its arguments.
  • heartrate: A simple real time visualisation of the execution of a Python program. Uses executing to highlight currently executing operations, particularly in each frame of the stack trace.
  • sorcery: Dark magic delights in Python. Uses executing to let special callables called spells know where they're being called from.

Libraries I've contributed to

  • IPython: Highlights the executing node in tracebacks using executing via stack_data.
  • icecream: 🍦 Sweet and creamy print debugging. Uses executing to identify where ic is called and print its arguments.
  • python-devtools: Uses executing for print debugging similar to icecream.
  • sentry_sdk: Add the integration sentry_sdk.integrations.executingExecutingIntegration() to show the function __qualname__ in each frame in sentry events. Highlighting the executing node is hopefully coming soon.
  • varname: Dark magics about variable names in python. Uses executing to find where its various magical functions like varname and nameof are called from.
Comments
  • feat: support for python 3.11

    feat: support for python 3.11

    • This provides a new implementation, which uses co_positions() to lookup the ast node.
    • It has a simpler implementation and better performance.
    • Some limitations in the unit tests are removed for 3.11.
      • support for and and or
      • no ambiguities for generators
      • fixes #30
    opened by 15r10nk 80
  • Let GH index who's using this package

    Let GH index who's using this package

    After experimenting a little bit, I found that for python packages, including a requirements.txt in the repo would enable GH to index who's using the package.

    The list can be seen on both the main page of the repo or Insights -> Dependency graph -> Dependents

    opened by pwwang 20
  • Use only pytest

    Use only pytest

    I was able to execute all tests with pytest. However assertion rewriting is still disabled for test_main.py.

    But this has already several benefits:

    • tox -e py310 -- --sw works
    • no need to maintain two test environments

    I also converted the test_sample_files to an parametrized test which makes it more useful for pytest --sw

    I also don't know how I should handle the timeouts. I removed them for test_sample_files because they are not really useful for single file checks. I kept them for test_module_files because this takes already a bit longer. What's your opinion there?

    I wanted to get your feedback before I continue.

    opened by 15r10nk 12
  • Small samples

    Small samples

    generate small samples to improve test coverage.

    • The samples where generated with tox -e mutmut (which calls tests/mutmut_workflow.py, which class tests/generate_small_sample.py).
    • The samples are placed in tests/small_samples

    Every sample is the result of a code mutation detected by mutmut which was not covered by any other test (excluding the long running tests).

    generate_small_sample.py tries to find a source file with some code which fails with the mutated implementation and removes code from this file with pysource_minimize.

    I have also found some issues:

    • The ExceptHandler cleanup is now an KnownIssue. Before it was verified as an valid mapping, which is not ok.
    • DELETE/STORE_DEREF had some issues.

    I had to reimplement the Deadcode analysis. The problem was that I always tried to reimplement the deadcode analysis of the python interpreter, which did not scale very well. The new implementation replaces the node with an sentinel compiles the source and checks if the sentinel is in the bytecode. The downside is that I had to remove the inverse deadcode check, because the new implementation is a bit more complex ( compiles the whole file for every node to test).

    Let me know what you think.

    closes #55

    opened by 15r10nk 10
  • fix decorator detection bug

    fix decorator detection bug

    try to fix #49

    I also reported frame.f_lasti and the problem is that the instruction is a CACHE instruction in the deco case (f_lasti == 130).

    The Tester case gets the expected instruction and maps the bytecode correctly.

    You can find my source here https://github.com/15r10nk/executing/tree/decorator-detection-bug

    grafik

    I could ignore CACHE instructions until now. I will try to figure out what they are good for.

    opened by 15r10nk 9
  • Support for Python 3.11?

    Support for Python 3.11?

    With today's release of Python 3.11.0a3, I suddenly have many failing tests for friendly-traceback that were still passing with 3.11.0a2. It looks like the nodes are no longer properly identified.

    For example, if I have

    a = (1, 2)(3, 4)
    

    the node was previously identified as (1, 2)(3, 4) whereas now it is the entire line that is identified as the problematic node.

    I can provide more examples if needed, but I suspect that your own unit tests would likely give you some results. It might be that the problem is upstream (with asttoken) instead of in executing.

    opened by aroberge 9
  • Bug:  Incorrect node text when __slots__ and class variables are used (3.11)

    Bug: Incorrect node text when __slots__ and class variables are used (3.11)

    Apologies for not providing a self-contained test with executing for the following, as I don't know how. The following raises a ValueError exception:

    class F:
        __slots__ = ["a", "b"]
        a = 1
    

    With Python 3.10 (and prior), the text of the node identified causing the exception is:

    class F:
    

    With Python 3.11, we get this:

    class F: __slots__ = ["a", "b"] a = 1
    

    This is not a typo: the text of the node identified a single line, with no \n, which is clearly a SyntaxError.

    This is the only bug I found when running the unit tests for friendly_traceback with executing 1.1.0 and Python 3.11. Everything still works for me with Python 3.6 to 3.10, although I found that the tests I run take about 2 to 3 times as long as compared with using executing 0.8.3. The slowdown seemed to be already present with executing 1.0.0.

    opened by aroberge 7
  • Executing cannot find node inside Jupyter lab/notebooks

    Executing cannot find node inside Jupyter lab/notebooks

    Using friendly, I found that excuting could not locate the correct location of a node causing a problem inside Jupyter notebooks (or Jupyter lab) but could do so with IPython. First, here's a screen capture showing the correct result using IPython, with some additional outputs from print statements inserted for this specific report.

    image

    Next, the same code run within a Jupyter notebook image

    Perhaps this is is caused by the new way that Jupyter runs code, using temporary files instead of using exec() on code.

    With the "old" friendly-traceback (before it made use of stack_data), when Jupyter was not using temporary files, I see from screen captures on https://aroberge.github.io/friendly-traceback-docs/docs/html/jupyter.html that the location was correctly picked up.

    opened by aroberge 6
  • no source distribution on pypi

    no source distribution on pypi

    Would it be possible to upload the sdist in addition to the wheel file on pypi? Currently the conda-forge distribution does not automatically pick up updates unless the sources are present.

    opened by Anthchirp 6
  • 0.8.3: pytest is failing

    0.8.3: pytest is failing

    I'm trying to package your module as an rpm package. So I'm using the typical PEP517 based build, install and test cycle used on building packages from non-root account.

    • python3 -sBm build -w --no-isolation
    • because I'm calling build with --no-isolation I'm using during all processes only locally installed modules
    • install .whl file in </install/prefix>
    • run pytest with PYTHONPATH pointing to sitearch and sitelib inside </install/prefix>

    Here is pytest output:

    + PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-executing-0.8.3-2.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-executing-0.8.3-2.fc35.x86_64/usr/lib/python3.8/site-packages
    + /usr/bin/pytest -ra
    =========================================================================== test session starts ============================================================================
    platform linux -- Python 3.8.13, pytest-7.1.1, pluggy-1.0.0
    rootdir: /home/tkloczko/rpmbuild/BUILD/executing-0.8.3
    collected 2 items / 1 error
    
    ================================================================================== ERRORS ==================================================================================
    ___________________________________________________________________ ERROR collecting tests/test_main.py ____________________________________________________________________
    executing/executing.py:317: in executing
        args = executing_cache[key]
    E   KeyError: (<code object <module> at 0x7f98855a0ea0, file "/home/tkloczko/rpmbuild/BUILD/executing-0.8.3/tests/test_main.py", line 2>, 140293049028256, 418)
    
    During handling of the above exception, another exception occurred:
    executing/executing.py:346: in find
        node_finder = NodeFinder(frame, stmts, tree, lasti)
    executing/executing.py:636: in __init__
        matching = list(self.matching_nodes(exprs))
    executing/executing.py:702: in matching_nodes
        original_index = only(
    executing/executing.py:164: in only
        raise NotOneValueFound('Expected one value, found 0')
    E   executing.executing.NotOneValueFound: Expected one value, found 0
    
    During handling of the above exception, another exception occurred:
    tests/test_main.py:682: in <module>
        assert tester([1, 2, 3]) == [1, 2, 3]
    tests/utils.py:40: in __call__
        ex = self.get_executing(inspect.currentframe().f_back)
    tests/utils.py:28: in get_executing
        return Source.executing(frame)
    executing/executing.py:369: in executing
        args = find(source=cls.for_frame(frame), retry_cache=True)
    executing/executing.py:355: in find
        return find(
    executing/executing.py:346: in find
        node_finder = NodeFinder(frame, stmts, tree, lasti)
    executing/executing.py:636: in __init__
        matching = list(self.matching_nodes(exprs))
    executing/executing.py:702: in matching_nodes
        original_index = only(
    executing/executing.py:164: in only
        raise NotOneValueFound('Expected one value, found 0')
    E   executing.executing.NotOneValueFound: Expected one value, found 0
    ========================================================================= short test summary info ==========================================================================
    ERROR tests/test_main.py - executing.executing.NotOneValueFound: Expected one value, found 0
    !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    ============================================================================= 1 error in 0.44s =============================================================================
    
    opened by kloczek 5
  • Is it possible to know whether a frame is from a method call?

    Is it possible to know whether a frame is from a method call?

    For example

    def do_func(self):
        frame = sys._getframe()
    
    class A:
        def do_method(self):
            frame = sys._getframe()
    

    Is it possible to know whether frame is inside a regular function call or method call? From https://stackoverflow.com/q/2203424/2142577 it seems this is no way except relying on the convention that self is the first argument of method signature. Do we have a better way?

    I also tried using gc.get_referrers(frame.f_code)), but it turns out the code is referrenced by the unbound function, no matter whether it is actually a method.

    opened by laike9m 4
  • Index node finder

    Index node finder

    This is still work in progress.

    The goal is an NodeFinder with similar capabilities like PositionNodeFinder.

    todo:

    • [ ] workaround for https://github.com/python/cpython/issues/100537
    • [ ] integrate node verification ( create Verifier which can be used for python 3.11 and 3.10)
    • expand to:
      • [ ] 3.9
      • [ ] 3.8
      • [ ] 3.7
      • [ ] 3.6 (if possible)
    opened by 15r10nk 0
  • parent pointers in AST nodes hurt deepcopy performance

    parent pointers in AST nodes hurt deepcopy performance

    Obligatory "I'm a huge fan of your work".

    Background: I maintain a library called ipyflow, which uses another library I maintain called pyccolo. I noticed that on ipython >= 8.0, which uses executing for better stack traces, ipyflow would have really bad performance regressions the first time after a cell throws some exception.

    Eventually I traced it to pyccolo's use of copy.deepcopy in a few places -- performance was bad because the parent pointers that executing added to AST nodes were causing deepcopy to do a lot of extra unnecessary work.

    I ended up working around it on the pyccolo side, but I figured you may be interested in this for other libraries that may want to use executing and get surprised when deepcopy has bad perf. The way we maintain deepcopy-ability in pyccolo is to maintain a mapping from id(node) to parent for parent pointers, which avoids setting an attribute on the AST node directly.

    Thanks for this great library!

    opened by smacke 0
  • Include new library files in coverage measurement

    Include new library files in coverage measurement

    Currently GHA has coverage run --include=executing/executing.py <run tests> which doesn't measure coverage in the recently added files in the executing folder. The original motivation was lines in __init__.py that would never be covered, but that should be handled by a # pragma: no cover comment or something.

    opened by alexmojaki 3
Releases(v0.5.4)
Owner
Alex Hall
Python metaprogrammer
Alex Hall
- Auto join teams teams ( from calendar invite )

Auto Join Teams Meetings Requirements: Python 3.7 or higher Latest Google Chrome This script automatically logins to your account and joins the meetin

Prajin Khadka 10 Aug 20, 2022
Bitflip Fault Simulation Platform by Daniele Rizzieri (2021)

SEE Injection Framework 2021 This repository contains two Single Event Effect (SEE) injection platforms. The first one is called BFSP - "Bitflip Fault

Daniele Rizzieri 2 Nov 05, 2022
PSP (Python Starter Package) is meant for those who want to start coding in python but are new to the coding scene.

Python Starter Package PSP (Python Starter Package) is meant for those who want to start coding in python, but are new to the coding scene. We include

Giter/ 1 Nov 20, 2021
A 3D Slicer Extension to view data from the flywheel heirarchy

flywheel-connect A 3D Slicer Extension to view, select, and download images from a Flywheel instance to 3D Slicer and storing Slicer outputs back to F

4 Nov 05, 2022
Linux Backlight Manager

Is a program to manage your laptop keyboard backlights in linux. Tested on Tuxedo / Clevo / Monste models. Must be tested on other devices

Arshia Ihammi 4 Jan 14, 2022
For Tok-k passages that have passed through the Bi-Encoder Retrival, ReRank is performed using CrossEncoder.

Cross-Encoder-with-Bi-Encoder For Tok-k passages that have passed through the Bi-Encoder Retrival, ReRank is performed using CrossEncoder. Data Data u

7 Feb 09, 2022
BDD base project: Python + Behave

BDD base project: Python + Behave Basic example of using Python with Behave (BDD). This Gherkin example includes: Basic Scenario Scenario Outline Tagg

eccanto 1 Dec 08, 2021
The first Python 1v1.lol triggerbot working with colors !

1v1.lol TriggerBot Afin d'utiliser mon triggerbot, vous devez activer le plein écran sur 1v1.lol sur votre naviguateur (quelque-soit ce dernier). Vous

Venax 5 Jul 25, 2022
Backups made easy, automated, monitored and SECURED with an audited encryption

Backup Controller Backups made easy, automated, monitored and SECURED with an audited encryption. Schedules backup tasks executed by Backup Maker, upl

RiotKit 1 Jan 30, 2022
Library support get vocabulary from MEM

Features: Support scraping the courses in MEM to take the vocabulary Translate the words to your own language Get the IPA for the English course Insta

Joseph Quang 4 Aug 13, 2022
Our product DrLeaf which not only makes the work easier but also reduces the effort and expenditure of the farmer to identify the disease and its treatment methods.

Our product DrLeaf which not only makes the work easier but also reduces the effort and expenditure of the farmer to identify the disease and its treatment methods. We have to upload the image of an

Aniruddha Jana 2 Feb 02, 2022
A VirtualBox manager with interactive mode

A VirtualBox manager with interactive mode

Luis Gerardo 1 Nov 21, 2021
Auto check in via GitHub Actions

因为本人毕业离校,本项目交由在校的@hfut-xyc同学接手,请访问hfut-xyc/hfut_auto_check-in获得最新的脚本 本项目遵从GPLv2协定,Copyright (C) 2021, Fw[a]rd 免责声明 根据GPL协定,我、本项目的作者,不会对您使用这个脚本带来的任何后果

Fw[a]rd 3 Jun 27, 2021
Get information about what a Python frame is currently doing, particularly the AST node being executed

executing This mini-package lets you get information about what a frame is currently doing, particularly the AST node being executed. Usage Getting th

Alex Hall 211 Jan 01, 2023
Python library and cli util for https://www.zerochan.net/

Zerochan Library for Zerochan.net with pics parsing and downloader included! Features CLI utility for pics downloading from zerochan.net Library for c

kiriharu 10 Oct 11, 2022
🌲 Um simples criador de arvore de items feito em Python para o Prompt 🐍

Esse projeto foi feito em Python com, intuito de fortificar meu aprendizado de programação. Sobre • Tecnologias • Pré Requisitos • Licença • Autor 📄

Kawan Henrique 1 Aug 02, 2021
Kubernetes-native workflow automation platform for complex, mission-critical data and ML processes at scale. It has been battle-tested at Lyft, Spotify, Freenome, and others and is truly open-source.

Flyte Flyte is a workflow automation platform for complex, mission-critical data, and ML processes at scale Home Page · Quick Start · Documentation ·

Flyte 3k Jan 01, 2023
Sublime Text 2/3 style auto completion for ST4

Hippie Autocompletion Sublime Text 2/3 style auto completion for ST4: cycle through words, do not show popup. Simply hit Tab to insert completion, hit

Alexander Schepanovski 20 May 19, 2022
The official Repository wherein newbies into Open Source can Contribute during the Hacktoberfest 2021

Hacktoberfest 2021 Get Started With your first Contrinution/Pull Request : Fork/Copy the repo by clicking the right most button on top of the page. Go

HacOkars 25 Aug 20, 2022
Karte der Allgemeinverfügungen zu Schulschließungen oder eingeschränktem Regelbetrieb in Sachsen

SNSZ Karte Datenquelle: Allgemeinverfügungen zu Schulschließungen oder eingeschränktem Regelbetrieb in Sachsen Sächsisches Staatsministerium für Kultu

Jannis Leidel 3 Sep 26, 2022