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/lazy-object-proxy/README.md
Original file line number Diff line number Diff line change
@@ -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.
68 changes: 68 additions & 0 deletions examples/lazy-object-proxy/deferred_construction/code.py
Original file line number Diff line number Diff line change
@@ -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"<strong>{build_calls['count']}</strong>"
)

# `__resolved__` tells us whether the factory has been called yet.
note(f"Has the proxy been resolved? <strong>{report.__resolved__}</strong>")

# First real use: indexing triggers the factory exactly once.
heading("First touch triggers the factory", level=3)
note(f"Top product: <strong>{report['top_product']}</strong>")
note(
f"Factory call count after first use: "
f"<strong>{build_calls['count']}</strong>"
)
note(f"Resolved now? <strong>{report.__resolved__}</strong>")

# Subsequent uses reuse the cached object; the factory is NOT called again.
heading("Subsequent uses reuse the cached value", level=3)
note(f"Revenue: <strong>${report['revenue']:,.2f}</strong>")
note(f"Units sold: <strong>{report['units_sold']}</strong>")
note(
f"Factory call count after several uses: "
f"<strong>{build_calls['count']}</strong> (still 1)"
)

# The proxy is indistinguishable from the underlying dict for most purposes.
note(f"isinstance(report, dict)? <strong>{isinstance(report, dict)}</strong>")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["lazy-object-proxy"]
36 changes: 36 additions & 0 deletions examples/lazy-object-proxy/deferred_construction/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
79 changes: 79 additions & 0 deletions examples/lazy-object-proxy/lazy_imports_and_configs/code.py
Original file line number Diff line number Diff line change
@@ -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? <strong>{json_lazy.__resolved__}</strong>")

payload = {"city": "Lisbon", "temp_c": 21, "skies": "clear"}
encoded = json_lazy.dumps(payload) # triggers the import
note(f"Encoded payload: <code>{encoded}</code>")
note(f"Resolved after use? <strong>{json_lazy.__resolved__}</strong>")


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: <strong>{build_log}</strong>")

# Touch only the 'cache' entry. The other two stay un-built.
cache_value = settings["cache"]["value"]
note(f"cache value: <code>{cache_value}</code>")
note(f"Builders run after reading 'cache': <strong>{build_log}</strong>")

# Reading 'cache' again does not rebuild; the proxy caches its result.
_ = settings["cache"]["ready"]
note(f"Builders run after re-reading 'cache': <strong>{build_log}</strong>")

# Now touch 'database'; 'search' remains untouched.
note(f"database url: <code>{settings['database']['value']}</code>")
note(f"Final build log: <strong>{build_log}</strong>")

resolved = {k: v.__resolved__ for k, v in settings.items()}
note(f"Per-entry resolved status: <strong>{resolved}</strong>")
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages = ["lazy-object-proxy"]
19 changes: 19 additions & 0 deletions examples/lazy-object-proxy/lazy_imports_and_configs/setup.py
Original file line number Diff line number Diff line change
@@ -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"<h{level}>{text}</h{level}>"), append=True)


def note(text):
display(HTML(f"<p>{text}</p>"), append=True)
4 changes: 4 additions & 0 deletions examples/lazy-object-proxy/order.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[
"deferred_construction",
"lazy_imports_and_configs"
]