From ef57348d498cb3bd63712a5070c43a2d93b596cb Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 29 May 2026 15:47:06 +0100 Subject: [PATCH 1/2] Add PyScript examples for executing Generated by apply_llm_response.py from prompts/executing/response.toml. Examples included: - find_calling_node: Find the AST node that called you - show_argument_source: Print debugging: show the source of each argument - qualname_and_source: Qualified names and source ranges Generated-By: apply_llm_response.py --- examples/executing/README.md | 18 +++++ examples/executing/find_calling_node/code.py | 48 ++++++++++++ .../executing/find_calling_node/config.toml | 1 + examples/executing/find_calling_node/setup.py | 46 ++++++++++++ examples/executing/order.json | 5 ++ .../executing/qualname_and_source/code.py | 74 +++++++++++++++++++ .../executing/qualname_and_source/config.toml | 1 + .../executing/qualname_and_source/setup.py | 22 ++++++ .../executing/show_argument_source/code.py | 50 +++++++++++++ .../show_argument_source/config.toml | 1 + .../executing/show_argument_source/setup.py | 25 +++++++ 11 files changed, 291 insertions(+) create mode 100644 examples/executing/README.md create mode 100644 examples/executing/find_calling_node/code.py create mode 100644 examples/executing/find_calling_node/config.toml create mode 100644 examples/executing/find_calling_node/setup.py create mode 100644 examples/executing/order.json create mode 100644 examples/executing/qualname_and_source/code.py create mode 100644 examples/executing/qualname_and_source/config.toml create mode 100644 examples/executing/qualname_and_source/setup.py create mode 100644 examples/executing/show_argument_source/code.py create mode 100644 examples/executing/show_argument_source/config.toml create mode 100644 examples/executing/show_argument_source/setup.py diff --git a/examples/executing/README.md b/examples/executing/README.md new file mode 100644 index 0000000..faf6016 --- /dev/null +++ b/examples/executing/README.md @@ -0,0 +1,18 @@ +# executing Examples + +Each sub-directory contains a self-contained example. The order in +which the examples are to appear is specified in `order.json` (an +array of directory names in the expected order). + +In each example directory you'll find: + +* `config.toml` - must conform to the specification outlined here: + https://docs.pyscript.net/latest/user-guide/configuration/ This is + parsed and ultimately turned into a JSON representation as part of + the package's API object. +* `setup.py` - Python code for contextual and environmental setup, + NOT SEEN BY THE END USER, but is run before the `code.py` code is + evaluated. Allows us to create useful (IPython) shims, avoid + repeating boilerplate and whatnot. +* `code.py` - the actual code added to the editor which forms the + practical example of using the package. diff --git a/examples/executing/find_calling_node/code.py b/examples/executing/find_calling_node/code.py new file mode 100644 index 0000000..a8b02c2 --- /dev/null +++ b/examples/executing/find_calling_node/code.py @@ -0,0 +1,48 @@ +""" +A first taste of `executing`: discover, at runtime, exactly which +piece of source code triggered the current function call. + +`executing` looks at the calling frame's bytecode and matches it back +to a node in the parsed AST of the source file. This is the magic +behind libraries like `icecream`, `snoop`, and `stack_data`. + +See: https://github.com/alexmojaki/executing +""" +from IPython.core.display import display, HTML + + +def whoami(): + """Report the AST node and source text of our caller.""" + # The caller's frame is one level up the stack. + caller_frame = inspect.currentframe().f_back + node = executing.Source.executing(caller_frame).node + if node is None: + return "(could not identify the calling node)" + # ast.dump shows the structural shape; ast.unparse recovers source. + return f"{type(node).__name__}: {ast.unparse(node)!r}" + + +heading("What called me?") +note( + "Each line below calls whoami() in a different " + "syntactic context. executing tells us which AST " + "node corresponds to each call." +) + +# A bare call expression. +result_simple = whoami() + +# A call inside a binary operation. +result_in_binop = "prefix: " + whoami() + +# A call used as a subscript index. +labels = {"a": "alpha", "b": "beta"} +result_as_key = labels["a"], whoami() + +display(HTML( + "" +), append=True) diff --git a/examples/executing/find_calling_node/config.toml b/examples/executing/find_calling_node/config.toml new file mode 100644 index 0000000..5e50b6e --- /dev/null +++ b/examples/executing/find_calling_node/config.toml @@ -0,0 +1 @@ +packages = ["executing"] diff --git a/examples/executing/find_calling_node/setup.py b/examples/executing/find_calling_node/setup.py new file mode 100644 index 0000000..2154b66 --- /dev/null +++ b/examples/executing/find_calling_node/setup.py @@ -0,0 +1,46 @@ +""" +Shim IPython's display API onto PyScript so example code written in a +Jupyter/IPython idiom runs unmodified in the browser. +""" + +import sys +import types +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + """Wrap pyscript.display so output lands in the example target.""" + return _display( + *args, **kwargs, target=__pyscript_display_target__, + ) + + +ipython = types.ModuleType("IPython") +core = types.ModuleType("IPython.core") +core_display = types.ModuleType("IPython.core.display") +core_display.display = display +core_display.HTML = HTML +ipython.core = core +core.display = core_display +ipython.get_ipython = lambda: None +ipython.display = core_display +sys.modules["IPython"] = ipython +sys.modules["IPython.core"] = core +sys.modules["IPython.core.display"] = core_display +sys.modules["IPython.display"] = core_display + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +import ast +import inspect +import executing diff --git a/examples/executing/order.json b/examples/executing/order.json new file mode 100644 index 0000000..b44c1bf --- /dev/null +++ b/examples/executing/order.json @@ -0,0 +1,5 @@ +[ + "find_calling_node", + "show_argument_source", + "qualname_and_source" +] diff --git a/examples/executing/qualname_and_source/code.py b/examples/executing/qualname_and_source/code.py new file mode 100644 index 0000000..a31d441 --- /dev/null +++ b/examples/executing/qualname_and_source/code.py @@ -0,0 +1,74 @@ +# --------------------------------------------------------------------- +# Beyond the AST node: ask `executing` for the qualified name of the +# currently executing function, and for the line/column range of the +# specific node within the source. +# --------------------------------------------------------------------- + +heading("Qualified names from any frame") +note( + "Source.for_frame(frame).code_qualname(frame.f_code) " + "returns the dotted __qualname__ of the function " + "currently running in that frame — including nested " + "functions and methods." +) + + +class Telescope: + def observe(self, target): + return self._record(target) + + def _record(self, target): + frame = inspect.currentframe() + source = executing.Source.for_frame(frame) + return source.code_qualname(frame.f_code) + + +def outer(): + def inner(): + frame = inspect.currentframe() + source = executing.Source.for_frame(frame) + return source.code_qualname(frame.f_code) + return inner() + + +display(HTML( + "" +), append=True) + + +# --------------------------------------------------------------------- +# Locate the calling expression's exact position in the source. +# --------------------------------------------------------------------- + +heading("Where in the source did this call happen?") +note( + "AST nodes carry line and column information. We can combine " + "executing's node identification with those " + "attributes to point at the precise span of code." +) + + +def locate(): + """Return a description of where the caller invoked us.""" + caller_frame = inspect.currentframe().f_back + executing_info = executing.Source.executing(caller_frame) + node = executing_info.node + if node is None: + return "unknown location" + filename = caller_frame.f_code.co_filename + return ( + f"{type(node).__name__} in {filename} " + f"at line {node.lineno}, cols {node.col_offset}" + f"–{node.end_col_offset}: {ast.unparse(node)}" + ) + + +# Two distinct call sites; each gets its own location report. +report_a = locate() +report_b = locate() + +display(HTML(f"

First call: {report_a}

"), append=True) +display(HTML(f"

Second call: {report_b}

"), append=True) diff --git a/examples/executing/qualname_and_source/config.toml b/examples/executing/qualname_and_source/config.toml new file mode 100644 index 0000000..5e50b6e --- /dev/null +++ b/examples/executing/qualname_and_source/config.toml @@ -0,0 +1 @@ +packages = ["executing"] diff --git a/examples/executing/qualname_and_source/setup.py b/examples/executing/qualname_and_source/setup.py new file mode 100644 index 0000000..8d68d3c --- /dev/null +++ b/examples/executing/qualname_and_source/setup.py @@ -0,0 +1,22 @@ +"""Same lightweight setup as the previous cell.""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display(*args, **kwargs, target=__pyscript_display_target__) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +import ast +import inspect +import executing diff --git a/examples/executing/show_argument_source/code.py b/examples/executing/show_argument_source/code.py new file mode 100644 index 0000000..21376dc --- /dev/null +++ b/examples/executing/show_argument_source/code.py @@ -0,0 +1,50 @@ +# --------------------------------------------------------------------- +# Build a tiny `icecream`-style debug helper. +# +# When you call `show(some_expr, other_expr)`, it prints both the +# *source text* of each argument and its runtime value. This is the +# core trick behind libraries like `icecream` and `snoop`. +# --------------------------------------------------------------------- + +heading("A mini print-debugger powered by executing") +note( + "show(...) identifies its own Call " + "node in the caller's source, then walks the argument AST nodes " + "to recover the original text of each expression." +) + + +def show(*values): + """Display each argument as `source = value`.""" + caller_frame = inspect.currentframe().f_back + call_node = executing.Source.executing(caller_frame).node + + rows = [] + if isinstance(call_node, ast.Call): + # Pair each AST argument node with its evaluated value. + for arg_node, value in zip(call_node.args, values): + source_text = ast.unparse(arg_node) + rows.append((source_text, repr(value))) + else: + # Fallback if we couldn't identify the call (e.g. inside an + # expression the parser handles unusually). + for value in values: + rows.append(("?", repr(value))) + + body = "".join( + f"{src}" + f"{val}" + for src, val in rows + ) + display(HTML( + "" + f"{body}
expressionvalue
" + ), append=True) + + +# A small story: a basket of fruit and a few derived quantities. +basket = {"apples": 4, "pears": 2, "plums": 7} +total = sum(basket.values()) +heaviest = max(basket, key=basket.get) + +show(basket, total, heaviest, total * 1.5, basket["apples"] + basket["pears"]) diff --git a/examples/executing/show_argument_source/config.toml b/examples/executing/show_argument_source/config.toml new file mode 100644 index 0000000..5e50b6e --- /dev/null +++ b/examples/executing/show_argument_source/config.toml @@ -0,0 +1 @@ +packages = ["executing"] diff --git a/examples/executing/show_argument_source/setup.py b/examples/executing/show_argument_source/setup.py new file mode 100644 index 0000000..c959c37 --- /dev/null +++ b/examples/executing/show_argument_source/setup.py @@ -0,0 +1,25 @@ +""" +Lighter setup: re-establish the names from the first cell without +re-running the IPython shim. +""" +import js +from pyscript import window, HTML, display as _display + +js.alert = window.alert + + +def display(*args, **kwargs): + return _display(*args, **kwargs, target=__pyscript_display_target__) + + +def heading(text, level=2): + display(HTML(f"{text}"), append=True) + + +def note(text): + display(HTML(f"

{text}

"), append=True) + + +import ast +import inspect +import executing From a86a45e5d70ae2874b4ba0850b244098aa1b45df Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Wed, 3 Jun 2026 17:49:24 +0100 Subject: [PATCH 2/2] Fix imports. --- examples/executing/find_calling_node/code.py | 4 ++++ examples/executing/find_calling_node/setup.py | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/executing/find_calling_node/code.py b/examples/executing/find_calling_node/code.py index a8b02c2..3974fc4 100644 --- a/examples/executing/find_calling_node/code.py +++ b/examples/executing/find_calling_node/code.py @@ -9,6 +9,10 @@ See: https://github.com/alexmojaki/executing """ from IPython.core.display import display, HTML +import ast +import inspect +import executing + def whoami(): diff --git a/examples/executing/find_calling_node/setup.py b/examples/executing/find_calling_node/setup.py index 2154b66..07879f9 100644 --- a/examples/executing/find_calling_node/setup.py +++ b/examples/executing/find_calling_node/setup.py @@ -40,7 +40,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -import ast -import inspect -import executing