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..3974fc4
--- /dev/null
+++ b/examples/executing/find_calling_node/code.py
@@ -0,0 +1,52 @@
+"""
+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
+import ast
+import inspect
+import executing
+
+
+
+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(
+ "
{text}
"), append=True) + 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(
+ "{Telescope().observe('Vega')}{outer()}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) + + +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}{val}| expression | value | " + f"
|---|
{text}
"), append=True) + + +import ast +import inspect +import executing