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/frozenlist/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# frozenlist 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.
54 changes: 54 additions & 0 deletions examples/frozenlist/freeze_a_playlist/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
A first look at frozenlist.

`FrozenList` behaves like a regular list, but once you call
`freeze()` on it, any further mutation raises `RuntimeError`.
This is handy when you want to assemble a collection during
setup and then guarantee it won't be modified afterwards.

Docs: https://frozenlist.aio-libs.org
"""
from IPython.core.display import display, HTML

# Package import for this example.
from frozenlist import FrozenList


# Build a playlist mutably, the same way you'd build a regular list.
playlist = FrozenList()
playlist.append("Take Five")
playlist.append("So What")
playlist.append("Blue in Green")
playlist.extend(["All Blues", "Freddie Freeloader"])

heading("Building the playlist")
note(
"Before freezing, the FrozenList accepts all the usual "
"MutableSequence operations: append, extend, insert, "
"__setitem__, and so on."
)
note(f"Tracks so far: {list(playlist)}")
note(f"Frozen? <strong>{playlist.frozen}</strong>")

# Lock it down. There is no thaw -- this is a one-way trip.
playlist.freeze()

heading("After freeze()")
note(f"Frozen? <strong>{playlist.frozen}</strong>")

# Read access still works perfectly.
note(f"First track: <em>{playlist[0]}</em>")
note(f"Number of tracks: {len(playlist)}")
note(f"Contains 'So What'? {'So What' in playlist}")

# Any attempt to mutate now raises RuntimeError.
heading("Mutation is now an error")
try:
playlist.append("Milestones")
except RuntimeError as exc:
note(f"playlist.append(...) raised: <code>{exc}</code>")

try:
playlist[0] = "Kind of Blue"
except RuntimeError as exc:
note(f"playlist[0] = ... raised: <code>{exc}</code>")
1 change: 1 addition & 0 deletions examples/frozenlist/freeze_a_playlist/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["frozenlist"]
37 changes: 37 additions & 0 deletions examples/frozenlist/freeze_a_playlist/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Shim IPython's display API onto PyScript and import frozenlist."""
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)
62 changes: 62 additions & 0 deletions examples/frozenlist/hashable_when_frozen/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ---------------------------------------------------------------------
# Frozen FrozenLists are hashable, so they can be dict keys or set
# members. Unfrozen ones are not -- hashing them raises RuntimeError.
# ---------------------------------------------------------------------

heading("Why hashability matters")
note(
"Imagine tagging recipes by their ingredient lists. If the "
"ingredient list is a regular Python list, you can't use it "
"as a dict key. A frozen FrozenList works as a key, because "
"freezing makes it hashable."
)

# A few recipes, each described by an ordered ingredient list.
raw_recipes = {
"Margherita pizza": ["flour", "tomato", "mozzarella", "basil"],
"Caprese salad": ["tomato", "mozzarella", "basil"],
"Tomato bruschetta":["bread", "tomato", "basil", "olive oil"],
"Cheese toastie": ["bread", "mozzarella"],
}


def freeze_ingredients(items):
"""Build a FrozenList from items and freeze it before returning."""
fl = FrozenList(items)
fl.freeze()
return fl


# Try hashing an unfrozen list to show it fails.
unfrozen = FrozenList(["flour", "water"])
try:
hash(unfrozen)
except RuntimeError as exc:
note(f"Hashing an unfrozen FrozenList raised: <code>{exc}</code>")

# Now invert the mapping: ingredient list -> recipe name.
by_ingredients = {
freeze_ingredients(ingredients): name
for name, ingredients in raw_recipes.items()
}

heading("Looking up a recipe by its ingredients")
query = freeze_ingredients(["tomato", "mozzarella", "basil"])
note(f"Query (frozen): <code>{query}</code>")
note(f"Hash of query: <code>{hash(query)}</code>")
note(f"Recipe found: <strong>{by_ingredients.get(query)}</strong>")

# Frozen FrozenLists also work happily inside sets, which lets us
# deduplicate ingredient lists across many recipes.
heading("Deduplicating with a set")
all_ingredient_lists = {
freeze_ingredients(ings) for ings in raw_recipes.values()
}
all_ingredient_lists.add(freeze_ingredients(["tomato", "mozzarella", "basil"]))
note(
f"Started with {len(raw_recipes)} recipes, "
f"set contains {len(all_ingredient_lists)} unique "
f"ingredient lists (the duplicate Caprese was collapsed)."
)
for ings in sorted(all_ingredient_lists, key=len):
display(HTML(f"<li><code>{list(ings)}</code></li>"), append=True)
1 change: 1 addition & 0 deletions examples/frozenlist/hashable_when_frozen/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["frozenlist"]
22 changes: 22 additions & 0 deletions examples/frozenlist/hashable_when_frozen/setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Lightweight setup for example 2: re-establish names from cell 1."""
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)


from frozenlist import FrozenList
4 changes: 4 additions & 0 deletions examples/frozenlist/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"freeze_a_playlist",
"hashable_when_frozen"
]