From f41cb408d2265a99bb59498bb5be9ac226b9a923 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Fri, 29 May 2026 15:47:10 +0100 Subject: [PATCH 1/2] Add PyScript examples for munch Generated by apply_llm_response.py from prompts/munch/response.toml. Examples included: - dot_access_basics: Dot-access dictionaries - json_round_trip: JSON round-trip - default_munch: Defaults for missing keys Generated-By: apply_llm_response.py --- examples/munch/README.md | 18 ++++++ examples/munch/default_munch/code.py | 64 +++++++++++++++++++ examples/munch/default_munch/config.toml | 1 + examples/munch/default_munch/setup.py | 23 +++++++ examples/munch/dot_access_basics/code.py | 66 ++++++++++++++++++++ examples/munch/dot_access_basics/config.toml | 1 + examples/munch/dot_access_basics/setup.py | 45 +++++++++++++ examples/munch/json_round_trip/code.py | 57 +++++++++++++++++ examples/munch/json_round_trip/config.toml | 1 + examples/munch/json_round_trip/setup.py | 24 +++++++ examples/munch/order.json | 5 ++ 11 files changed, 305 insertions(+) create mode 100644 examples/munch/README.md create mode 100644 examples/munch/default_munch/code.py create mode 100644 examples/munch/default_munch/config.toml create mode 100644 examples/munch/default_munch/setup.py create mode 100644 examples/munch/dot_access_basics/code.py create mode 100644 examples/munch/dot_access_basics/config.toml create mode 100644 examples/munch/dot_access_basics/setup.py create mode 100644 examples/munch/json_round_trip/code.py create mode 100644 examples/munch/json_round_trip/config.toml create mode 100644 examples/munch/json_round_trip/setup.py create mode 100644 examples/munch/order.json diff --git a/examples/munch/README.md b/examples/munch/README.md new file mode 100644 index 0000000..96f228e --- /dev/null +++ b/examples/munch/README.md @@ -0,0 +1,18 @@ +# munch 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/munch/default_munch/code.py b/examples/munch/default_munch/code.py new file mode 100644 index 0000000..0986bbb --- /dev/null +++ b/examples/munch/default_munch/code.py @@ -0,0 +1,64 @@ +# --------------------------------------------------------------------- +# DefaultMunch and DefaultFactoryMunch: graceful handling of missing keys. +# --------------------------------------------------------------------- + +heading("DefaultMunch: a sentinel for missing attributes") +note( + "A regular Munch raises AttributeError for unknown keys. " + "DefaultMunch instead returns a value of your choosing, which is " + "perfect for optional configuration fields." +) + +# A common pattern: use a sentinel object so you can tell "missing" apart +# from a legitimate None value. +MISSING = object() + +config = DefaultMunch.fromDict( + { + "host": "localhost", + "port": 5432, + "database": {"name": "shop", "user": "ada"}, + }, + MISSING, +) + +note(f"config.host → {config.host}") +note(f"config.database.user → {config.database.user}") +note(f"config.database.password is MISSING → {config.database.password is MISSING}") +note(f"config.tls_cert is MISSING → {config.tls_cert is MISSING}") + +heading("DefaultFactoryMunch: build values on demand") +note( + "When you'd rather create a fresh value for each missing key (think " + "collections.defaultdict), reach for DefaultFactoryMunch. Below we " + "tally word frequencies in a tiny corpus." +) + +word_counts = DefaultFactoryMunch(int) +poem = ( + "the sea the sky the gull " + "the wind the wave the gull" +) +for word in poem.split(): + word_counts[word] += 1 + +# Attribute-style access works on accumulated keys. +note(f"word_counts.the → {word_counts.the}") +note(f"word_counts.gull → {word_counts.gull}") +note(f"Unseen word starts at zero: word_counts.dolphin = {word_counts.dolphin}") + +display(dict(word_counts), append=True) + +# A factory can be any zero-argument callable. Here, lists for grouping. +inbox = DefaultFactoryMunch(list) +messages = [ + ("ada", "lunch?"), + ("grace", "shipped the build"), + ("ada", "see you at 1"), + ("linus", "patch attached"), +] +for sender, body in messages: + inbox[sender].append(body) + +note("Messages grouped by sender:") +display(dict(inbox), append=True) diff --git a/examples/munch/default_munch/config.toml b/examples/munch/default_munch/config.toml new file mode 100644 index 0000000..d3c7ac1 --- /dev/null +++ b/examples/munch/default_munch/config.toml @@ -0,0 +1 @@ +packages = ["munch"] diff --git a/examples/munch/default_munch/setup.py b/examples/munch/default_munch/setup.py new file mode 100644 index 0000000..9dde593 --- /dev/null +++ b/examples/munch/default_munch/setup.py @@ -0,0 +1,23 @@ +"""Setup for the DefaultMunch example.""" + +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) + + +from munch import Munch, DefaultMunch, DefaultFactoryMunch diff --git a/examples/munch/dot_access_basics/code.py b/examples/munch/dot_access_basics/code.py new file mode 100644 index 0000000..4dcd29a --- /dev/null +++ b/examples/munch/dot_access_basics/code.py @@ -0,0 +1,66 @@ +""" +Getting started with Munch: a dict that you can also poke at with dots. + +A Munch behaves exactly like a regular dictionary, but its keys can be +accessed (and assigned) as attributes. Handy for config objects, parsed +JSON, or anything where `config["database"]["host"]` starts to feel +clunky compared to `config.database.host`. + +Docs and source: https://github.com/Infinidat/munch +""" + +from IPython.core.display import display, HTML + +heading("1. A Munch is just a dict with attribute access") +note( + "Build a small profile for an imaginary coffee-shop customer. " + "Notice how we can mix bracket-style and dot-style access freely." +) + +customer = Munch() +customer.name = "Ada Lovelace" +customer.favorite_drink = "flat white" +customer["loyalty_points"] = 142 + +note(f"customer.name → {customer.name}") +note(f'customer["favorite_drink"] → {customer["favorite_drink"]}') +note(f"customer.loyalty_points → {customer.loyalty_points}") + +# Munch is a real dict subclass, so all the usual methods work. +note(f"Keys: {list(customer.keys())}") +note(f"isinstance(customer, dict) → {isinstance(customer, dict)}") + +heading("2. Nesting works naturally") +note( + "Assign a Munch as a value and you can chain dot access all the " + "way down." +) + +customer.address = Munch(city="London", postcode="EC1A 1BB") +note(f"customer.address.city → {customer.address.city}") +note(f"customer.address.postcode → {customer.address.postcode}") + +heading("3. Convert to/from plain dicts with munchify / unmunchify") +note( + "Got a nested dict from somewhere (parsed JSON, a config file)? " + "Pass it through munchify and the whole tree becomes dot-accessible." +) + +raw_order = { + "order_id": "A-1042", + "items": [ + {"name": "croissant", "qty": 2}, + {"name": "flat white", "qty": 1}, + ], + "totals": {"subtotal": 8.50, "tax": 0.68}, +} + +order = munchify(raw_order) +note(f"order.order_id → {order.order_id}") +note(f"order.items[0].name → {order.items[0].name}") +note(f"order.totals.subtotal → {order.totals.subtotal}") + +# Going back is just as easy. +plain = unmunchify(order) +note(f"unmunchify gives back a plain dict: {type(plain).__name__}") +display(plain, append=True) diff --git a/examples/munch/dot_access_basics/config.toml b/examples/munch/dot_access_basics/config.toml new file mode 100644 index 0000000..d3c7ac1 --- /dev/null +++ b/examples/munch/dot_access_basics/config.toml @@ -0,0 +1 @@ +packages = ["munch"] diff --git a/examples/munch/dot_access_basics/setup.py b/examples/munch/dot_access_basics/setup.py new file mode 100644 index 0000000..9dddb53 --- /dev/null +++ b/examples/munch/dot_access_basics/setup.py @@ -0,0 +1,45 @@ +""" +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) + + +# Package imports for this example. +from munch import Munch, munchify, unmunchify diff --git a/examples/munch/json_round_trip/code.py b/examples/munch/json_round_trip/code.py new file mode 100644 index 0000000..4de60f8 --- /dev/null +++ b/examples/munch/json_round_trip/code.py @@ -0,0 +1,57 @@ +# --------------------------------------------------------------------- +# Munch + JSON: a comfortable way to work with API-shaped data. +# --------------------------------------------------------------------- + +heading("Parsing JSON into a Munch") +note( + "Imagine this JSON came back from a weather API. We parse it as " + "usual, then munchify so we can navigate the response with dots " + "instead of a thicket of brackets and quotes." +) + +api_response_text = """ +{ + "location": {"city": "Reykjavik", "country": "IS"}, + "current": {"temperature_c": 4.2, "wind_kph": 22.0, "condition": "cloudy"}, + "forecast": [ + {"day": "Mon", "high_c": 5, "low_c": -1}, + {"day": "Tue", "high_c": 6, "low_c": 0}, + {"day": "Wed", "high_c": 3, "low_c": -2} + ] +} +""" + +raw = json.loads(api_response_text) +weather = munchify(raw) + +note(f"weather.location.city → {weather.location.city}") +note( + f"weather.current.temperature_c → " + f"{weather.current.temperature_c} °C" +) + +# Iterate over a list of nested Munches just like a list of dicts. +forecast_lines = [ + f"{day.day}: {day.low_c}° to {day.high_c}°" + for day in weather.forecast +] +note("Three-day forecast: " + " · ".join(forecast_lines)) + +heading("Serializing back out with toJSON()") +note( + "Every Munch has a toJSON() helper. It produces a JSON string just " + "like json.dumps would, so a Munch can travel through any JSON-aware " + "boundary unchanged." +) + +# Mutate via dot access, then dump. +weather.current.condition = "snow" +weather.forecast.append(munchify({"day": "Thu", "high_c": 2, "low_c": -3})) + +dumped = weather.toJSON() +note("Round-tripped JSON (truncated):") +display(HTML(f"
{dumped[:200]}...
"), append=True) + +# And of course, it's still a dict, so json.dumps works directly too. +also_dumped = json.dumps(weather, indent=2) +note(f"json.dumps and toJSON agree: {json.loads(dumped) == json.loads(also_dumped)}") diff --git a/examples/munch/json_round_trip/config.toml b/examples/munch/json_round_trip/config.toml new file mode 100644 index 0000000..d3c7ac1 --- /dev/null +++ b/examples/munch/json_round_trip/config.toml @@ -0,0 +1 @@ +packages = ["munch"] diff --git a/examples/munch/json_round_trip/setup.py b/examples/munch/json_round_trip/setup.py new file mode 100644 index 0000000..05a75c7 --- /dev/null +++ b/examples/munch/json_round_trip/setup.py @@ -0,0 +1,24 @@ +"""Setup for the JSON round-trip example.""" + +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) + + +from munch import Munch, munchify, unmunchify +import json diff --git a/examples/munch/order.json b/examples/munch/order.json new file mode 100644 index 0000000..ee5f39f --- /dev/null +++ b/examples/munch/order.json @@ -0,0 +1,5 @@ +[ + "dot_access_basics", + "json_round_trip", + "default_munch" +] From fd711029c5ab0c9b2b940420ecc9ad6320fd3bd0 Mon Sep 17 00:00:00 2001 From: "Nicholas H.Tollervey" Date: Thu, 4 Jun 2026 17:58:29 +0100 Subject: [PATCH 2/2] Fix. --- examples/munch/default_munch/code.py | 2 ++ examples/munch/default_munch/setup.py | 2 -- examples/munch/dot_access_basics/code.py | 8 ++++++-- examples/munch/dot_access_basics/setup.py | 4 ---- examples/munch/json_round_trip/code.py | 3 +++ examples/munch/json_round_trip/setup.py | 3 --- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/munch/default_munch/code.py b/examples/munch/default_munch/code.py index 0986bbb..a02744a 100644 --- a/examples/munch/default_munch/code.py +++ b/examples/munch/default_munch/code.py @@ -1,6 +1,8 @@ # --------------------------------------------------------------------- # DefaultMunch and DefaultFactoryMunch: graceful handling of missing keys. # --------------------------------------------------------------------- +from munch import Munch, DefaultMunch, DefaultFactoryMunch + heading("DefaultMunch: a sentinel for missing attributes") note( diff --git a/examples/munch/default_munch/setup.py b/examples/munch/default_munch/setup.py index 9dde593..83a6e26 100644 --- a/examples/munch/default_munch/setup.py +++ b/examples/munch/default_munch/setup.py @@ -19,5 +19,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -from munch import Munch, DefaultMunch, DefaultFactoryMunch diff --git a/examples/munch/dot_access_basics/code.py b/examples/munch/dot_access_basics/code.py index 4dcd29a..2bbdf46 100644 --- a/examples/munch/dot_access_basics/code.py +++ b/examples/munch/dot_access_basics/code.py @@ -11,6 +11,10 @@ from IPython.core.display import display, HTML +# Package imports for this example. +from munch import Munch, munchify, unmunchify + + heading("1. A Munch is just a dict with attribute access") note( "Build a small profile for an imaginary coffee-shop customer. " @@ -48,7 +52,7 @@ raw_order = { "order_id": "A-1042", - "items": [ + "line_items": [ # renamed from "items" {"name": "croissant", "qty": 2}, {"name": "flat white", "qty": 1}, ], @@ -57,7 +61,7 @@ order = munchify(raw_order) note(f"order.order_id → {order.order_id}") -note(f"order.items[0].name → {order.items[0].name}") +note(f"order.line_items[0].name → {order.line_items[0].name}") note(f"order.totals.subtotal → {order.totals.subtotal}") # Going back is just as easy. diff --git a/examples/munch/dot_access_basics/setup.py b/examples/munch/dot_access_basics/setup.py index 9dddb53..b4f3ee1 100644 --- a/examples/munch/dot_access_basics/setup.py +++ b/examples/munch/dot_access_basics/setup.py @@ -39,7 +39,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - - -# Package imports for this example. -from munch import Munch, munchify, unmunchify diff --git a/examples/munch/json_round_trip/code.py b/examples/munch/json_round_trip/code.py index 4de60f8..08d8fd1 100644 --- a/examples/munch/json_round_trip/code.py +++ b/examples/munch/json_round_trip/code.py @@ -1,6 +1,9 @@ # --------------------------------------------------------------------- # Munch + JSON: a comfortable way to work with API-shaped data. # --------------------------------------------------------------------- +from munch import Munch, munchify, unmunchify +import json + heading("Parsing JSON into a Munch") note( diff --git a/examples/munch/json_round_trip/setup.py b/examples/munch/json_round_trip/setup.py index 05a75c7..138fc83 100644 --- a/examples/munch/json_round_trip/setup.py +++ b/examples/munch/json_round_trip/setup.py @@ -19,6 +19,3 @@ def heading(text, level=2): def note(text): display(HTML(f"

{text}

"), append=True) - -from munch import Munch, munchify, unmunchify -import json