Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/executing/README.md
Original file line number Diff line number Diff line change
@@ -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.
52 changes: 52 additions & 0 deletions examples/executing/find_calling_node/code.py
Original file line number Diff line number Diff line change
@@ -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 <code>whoami()</code> in a different "
"syntactic context. <code>executing</code> 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(
"<ul>"
f"<li>Bare call &rarr; {result_simple}</li>"
f"<li>Inside concatenation &rarr; {result_in_binop}</li>"
f"<li>Inside a tuple &rarr; {result_as_key[1]}</li>"
"</ul>"
), append=True)
1 change: 1 addition & 0 deletions examples/executing/find_calling_node/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["executing"]
42 changes: 42 additions & 0 deletions examples/executing/find_calling_node/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)

5 changes: 5 additions & 0 deletions examples/executing/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
"find_calling_node",
"show_argument_source",
"qualname_and_source"
]
74 changes: 74 additions & 0 deletions examples/executing/qualname_and_source/code.py
Original file line number Diff line number Diff line change
@@ -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(
"<code>Source.for_frame(frame).code_qualname(frame.f_code)</code> "
"returns the dotted <code>__qualname__</code> of the function "
"currently running in that frame &mdash; 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(
"<ul>"
f"<li>Method qualname: <code>{Telescope().observe('Vega')}</code></li>"
f"<li>Nested function qualname: <code>{outer()}</code></li>"
"</ul>"
), 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 "
"<code>executing</code>'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"&ndash;{node.end_col_offset}: <code>{ast.unparse(node)}</code>"
)


# Two distinct call sites; each gets its own location report.
report_a = locate()
report_b = locate()

display(HTML(f"<p>First call: {report_a}</p>"), append=True)
display(HTML(f"<p>Second call: {report_b}</p>"), append=True)
1 change: 1 addition & 0 deletions examples/executing/qualname_and_source/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["executing"]
22 changes: 22 additions & 0 deletions examples/executing/qualname_and_source/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)


import ast
import inspect
import executing
50 changes: 50 additions & 0 deletions examples/executing/show_argument_source/code.py
Original file line number Diff line number Diff line change
@@ -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(
"<code>show(...)</code> identifies its own <code>Call</code> "
"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"<tr><td><code>{src}</code></td>"
f"<td><code>{val}</code></td></tr>"
for src, val in rows
)
display(HTML(
"<table><thead><tr><th>expression</th><th>value</th>"
f"</tr></thead><tbody>{body}</tbody></table>"
), 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"])
1 change: 1 addition & 0 deletions examples/executing/show_argument_source/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["executing"]
25 changes: 25 additions & 0 deletions examples/executing/show_argument_source/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)


import ast
import inspect
import executing