diff --git a/examples/lazy-object-proxy/README.md b/examples/lazy-object-proxy/README.md
new file mode 100644
index 0000000..57771a5
--- /dev/null
+++ b/examples/lazy-object-proxy/README.md
@@ -0,0 +1,18 @@
+# lazy-object-proxy 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/lazy-object-proxy/deferred_construction/code.py b/examples/lazy-object-proxy/deferred_construction/code.py
new file mode 100644
index 0000000..c321695
--- /dev/null
+++ b/examples/lazy-object-proxy/deferred_construction/code.py
@@ -0,0 +1,68 @@
+"""
+Deferred construction with `lazy_object_proxy.Proxy`.
+
+A `Proxy` wraps a zero-argument callable (the "factory") and looks
+and feels like the eventual return value, but the factory is only
+invoked the first time the proxy is actually used. After that the
+result is cached and the proxy transparently forwards every
+attribute access, method call, and operator to the real object.
+
+Docs: https://python-lazy-object-proxy.readthedocs.io/
+"""
+from IPython.core.display import display, HTML
+
+import lazy_object_proxy
+
+
+heading("A proxy that pretends to be an expensive report")
+note(
+ "Imagine loading a large report from disk or the network. We "
+ "don't want to pay that cost unless someone actually reads it."
+)
+
+# A counter we can inspect to see when (and how often) the factory runs.
+build_calls = {"count": 0}
+
+
+def build_quarterly_report():
+ """Pretend to do expensive work and return a dict 'report'."""
+ build_calls["count"] += 1
+ return {
+ "quarter": "Q3",
+ "revenue": 184_500.00,
+ "top_product": "Aurora Notebook",
+ "units_sold": 1273,
+ }
+
+
+# Wrap the factory. No work happens here.
+report = lazy_object_proxy.Proxy(build_quarterly_report)
+
+note(
+ f"Proxy created. Factory call count so far: "
+ f"{build_calls['count']}"
+)
+
+# `__resolved__` tells us whether the factory has been called yet.
+note(f"Has the proxy been resolved? {report.__resolved__}")
+
+# First real use: indexing triggers the factory exactly once.
+heading("First touch triggers the factory", level=3)
+note(f"Top product: {report['top_product']}")
+note(
+ f"Factory call count after first use: "
+ f"{build_calls['count']}"
+)
+note(f"Resolved now? {report.__resolved__}")
+
+# Subsequent uses reuse the cached object; the factory is NOT called again.
+heading("Subsequent uses reuse the cached value", level=3)
+note(f"Revenue: ${report['revenue']:,.2f}")
+note(f"Units sold: {report['units_sold']}")
+note(
+ f"Factory call count after several uses: "
+ f"{build_calls['count']} (still 1)"
+)
+
+# The proxy is indistinguishable from the underlying dict for most purposes.
+note(f"isinstance(report, dict)? {isinstance(report, dict)}")
diff --git a/examples/lazy-object-proxy/deferred_construction/config.toml b/examples/lazy-object-proxy/deferred_construction/config.toml
new file mode 100644
index 0000000..89c494b
--- /dev/null
+++ b/examples/lazy-object-proxy/deferred_construction/config.toml
@@ -0,0 +1 @@
+packages = ["lazy-object-proxy"]
diff --git a/examples/lazy-object-proxy/deferred_construction/setup.py b/examples/lazy-object-proxy/deferred_construction/setup.py
new file mode 100644
index 0000000..050df73
--- /dev/null
+++ b/examples/lazy-object-proxy/deferred_construction/setup.py
@@ -0,0 +1,36 @@
+"""Shim setup for the first example: full IPython compatibility shim."""
+import sys
+import types
+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__,
+ )
+
+
+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) diff --git a/examples/lazy-object-proxy/lazy_imports_and_configs/code.py b/examples/lazy-object-proxy/lazy_imports_and_configs/code.py new file mode 100644 index 0000000..fb5164f --- /dev/null +++ b/examples/lazy-object-proxy/lazy_imports_and_configs/code.py @@ -0,0 +1,79 @@ +# --------------------------------------------------------------------- +# Two practical patterns: lazy imports and a lazy settings registry. +# --------------------------------------------------------------------- + +import lazy_object_proxy + + +heading("Pattern 1: lazy imports") +note( + "Wrap a module import in a Proxy so the cost of importing is " + "paid only when the module is first touched. Useful for heavy " + "optional dependencies." +) + + +def _import_json(): + """Factory that imports and returns the json module.""" + import json + return json + + +# `json_lazy` looks and behaves like the json module, but isn't loaded yet. +json_lazy = lazy_object_proxy.Proxy(_import_json) + +note(f"Resolved before use? {json_lazy.__resolved__}") + +payload = {"city": "Lisbon", "temp_c": 21, "skies": "clear"} +encoded = json_lazy.dumps(payload) # triggers the import +note(f"Encoded payload:{encoded}")
+note(f"Resolved after use? {json_lazy.__resolved__}")
+
+
+heading("Pattern 2: a registry of lazily-built settings")
+note(
+ "Each entry is a Proxy wrapping a builder. Iterating the "
+ "registry's keys is cheap; only the entries you read get built."
+)
+
+build_log = []
+
+
+def make_builder(name, value):
+ """Return a zero-arg factory that 'builds' a settings record."""
+ def _build():
+ build_log.append(name)
+ return {"name": name, "value": value, "ready": True}
+ return _build
+
+
+settings = {
+ "database": lazy_object_proxy.Proxy(
+ make_builder("database", "postgres://example/db")
+ ),
+ "cache": lazy_object_proxy.Proxy(
+ make_builder("cache", "redis://example/0")
+ ),
+ "search": lazy_object_proxy.Proxy(
+ make_builder("search", "https://search.example/api")
+ ),
+}
+
+note(f"Registry keys (no builders run yet): {list(settings)}")
+note(f"Builders run so far: {build_log}")
+
+# Touch only the 'cache' entry. The other two stay un-built.
+cache_value = settings["cache"]["value"]
+note(f"cache value: {cache_value}")
+note(f"Builders run after reading 'cache': {build_log}")
+
+# Reading 'cache' again does not rebuild; the proxy caches its result.
+_ = settings["cache"]["ready"]
+note(f"Builders run after re-reading 'cache': {build_log}")
+
+# Now touch 'database'; 'search' remains untouched.
+note(f"database url: {settings['database']['value']}")
+note(f"Final build log: {build_log}")
+
+resolved = {k: v.__resolved__ for k, v in settings.items()}
+note(f"Per-entry resolved status: {resolved}")
diff --git a/examples/lazy-object-proxy/lazy_imports_and_configs/config.toml b/examples/lazy-object-proxy/lazy_imports_and_configs/config.toml
new file mode 100644
index 0000000..89c494b
--- /dev/null
+++ b/examples/lazy-object-proxy/lazy_imports_and_configs/config.toml
@@ -0,0 +1 @@
+packages = ["lazy-object-proxy"]
diff --git a/examples/lazy-object-proxy/lazy_imports_and_configs/setup.py b/examples/lazy-object-proxy/lazy_imports_and_configs/setup.py
new file mode 100644
index 0000000..b38ee6c
--- /dev/null
+++ b/examples/lazy-object-proxy/lazy_imports_and_configs/setup.py
@@ -0,0 +1,19 @@
+"""Setup for example 2. Mirrors example 1's namespace, no 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) diff --git a/examples/lazy-object-proxy/order.json b/examples/lazy-object-proxy/order.json new file mode 100644 index 0000000..214adcf --- /dev/null +++ b/examples/lazy-object-proxy/order.json @@ -0,0 +1,4 @@ +[ + "deferred_construction", + "lazy_imports_and_configs" +]