On this page

Managed spell settings and transfer๏ƒ

๐ŸŸก Intermediate ยท Lesson 48

Change a pool's settings and hand a pool to another root (dynamic mode). A managed spell's mode, limit, queue timeout and reset methods are SETTINGS of the spell, not part of its spell_id. To change them, cleanup_spell the spell and bind it again: the spell_id stays the same, and so does its pool - every object alive stays alive and counted, and the pool takes the new settings.

An ownership transfer moves the spell to another root, and the pool goes with it, idle objects included. The new owner leases from it; releases keep routing by class, so holders change nothing.

Before you run๏ƒ

Use the Intermediate guide for prerequisite concepts. Run from a checkout with Melder installed and Python 3.14 free-threading selected. The collection download includes the level's local helper modules.

Run the saved script๏ƒ

python UX_and_AIX_experiences/02_intermediate/48_managed_spell_settings_and_transfer.py
py -3.14t UX_and_AIX_experiences/02_intermediate/48_managed_spell_settings_and_transfer.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

md.Spellbook.conjure(dynamic=True), md.Conduit.bind (managed options), md.Conduit.cleanup_spell, md.Conduit.get_spell_by_spell_id, md.Conduit.link, md.Conduit.transfer_spell_ownership, md.ManagedSpellExhaustedError, md.ReleaseManagedSpell (release, release_all, describe), md.Conduit.cleanup

Code๏ƒ

  1"""
  2TIER: intermediate (48)
  3GOAL: Change a pool's settings and hand a pool to another root (dynamic mode).
  4      A managed spell's mode, limit, queue timeout and reset methods are SETTINGS of
  5      the spell, not part of its spell_id. To change them, cleanup_spell the spell and
  6      bind it again: the spell_id stays the same, and so does its pool - every object
  7      alive stays alive and counted, and the pool takes the new settings.
  8
  9      An ownership transfer moves the spell to another root, and the pool goes with it,
 10      idle objects included. The new owner leases from it; releases keep routing by
 11      class, so holders change nothing.
 12SURFACE EXERCISED: md.Spellbook.conjure(dynamic=True), md.Conduit.bind (managed
 13                   options), md.Conduit.cleanup_spell, md.Conduit.get_spell_by_spell_id,
 14                   md.Conduit.link, md.Conduit.transfer_spell_ownership,
 15                   md.ManagedSpellExhaustedError, md.ReleaseManagedSpell (release,
 16                   release_all, describe), md.Conduit.cleanup
 17"""
 18import sys
 19from pathlib import Path
 20from typing import Any, Dict, List, Tuple
 21
 22sys.path.insert(0, str(Path(__file__).parent))  # local helper (see _dynamic_world)
 23from _dynamic_world import dynamic_spellbook
 24
 25import melder as md
 26
 27
 28class ExportWorker:
 29    """A pooled export worker; `close` disposes it when its pool destroys it."""
 30
 31    def __init__(self) -> None:
 32        """Start the worker."""
 33        self.closed: bool = False
 34
 35    def close(self) -> None:
 36        """Stop the worker for good."""
 37        self.closed = True
 38
 39
 40def pool_snapshot(cls: type) -> Dict[str, Any]:
 41    """Return the one pool snapshot of a managed class (this lesson binds it once)."""
 42    (snapshot,) = md.ReleaseManagedSpell().describe(cls)
 43    return snapshot
 44
 45
 46def pool_counts(cls: type) -> Tuple[int, int, int]:
 47    """Return (alive, idle, out) for the one pool of a managed class."""
 48    snapshot = pool_snapshot(cls)
 49    return snapshot["count"], snapshot["idle_count"], snapshot["out_count"]
 50
 51
 52def raise_the_limit(reports: md.Conduit, spell_id: str) -> List[ExportWorker]:
 53    """Fill a limit-1 pool, rebind it with limit 3, lease up to the new limit.
 54
 55    Contract:
 56        The rebind keeps the spell_id and the pool: the worker that was out stays
 57        alive and counted under the new limit. Every worker is released at the end.
 58    Args:
 59        reports: The dynamic root that owns the managed spell.
 60        spell_id: The spell_id the first bind returned.
 61    Returns:
 62        List[ExportWorker]: The three workers the pool now holds, all idle.
 63    """
 64    busy: ExportWorker = reports.meld("ExportWorker")
 65    try:
 66        reports.meld("ExportWorker")
 67        raise AssertionError("limit 1 leased a second worker")
 68    except md.ManagedSpellExhaustedError:
 69        print("limit 1: a second worker is refused while one is out")
 70
 71    # Settings are not identity: rebinding with a higher limit keeps the spell_id and the pool.
 72    reports.cleanup_spell(spell=reports.get_spell_by_spell_id(spell_id))
 73    rebound_id = reports.bind(
 74        spell=ExportWorker,
 75        existence=md.Existence.managed_spell_quantity,
 76        managed_spell_mode="limited_quantity",
 77        managed_spell_limit=3,
 78        disposal_method_names=["close"],
 79    )
 80    assert rebound_id == spell_id
 81    assert pool_snapshot(ExportWorker)["limit"] == 3
 82    assert pool_counts(ExportWorker) == (1, 0, 1) and not busy.closed
 83    print("rebound with limit 3, same spell_id; the busy worker still counts:", pool_counts(ExportWorker))
 84
 85    workers = [busy, reports.meld("ExportWorker"), reports.meld("ExportWorker")]
 86    assert pool_counts(ExportWorker) == (3, 0, 3)
 87    md.ReleaseManagedSpell().release_all(workers)
 88    assert pool_counts(ExportWorker) == (3, 3, 0)
 89    print("three workers under the new limit, all back:", pool_counts(ExportWorker))
 90    return workers
 91
 92
 93def transfer_the_pool(reports: md.Conduit, archive: md.Conduit, spell_id: str, workers: List[ExportWorker]) -> None:
 94    """Transfer the managed spell to another root and lease from the moved pool.
 95
 96    Contract:
 97        After the transfer the pool lives in the archive root's store, with its idle
 98        workers; the archive leases them, the old owner can no longer meld the spell.
 99    Args:
100        reports: The current owner.
101        archive: The new owner, linked from `reports`.
102        spell_id: The managed spell's spell_id.
103        workers: The pool's idle workers before the transfer.
104    Returns:
105        None.
106    """
107    assert pool_snapshot(ExportWorker)["holding_conduit_id"] == reports.id
108    reports.link(archive)
109    reports.transfer_spell_ownership(spell=spell_id, target_conduit=archive)
110    assert pool_snapshot(ExportWorker)["holding_conduit_id"] == archive.id
111    assert pool_counts(ExportWorker) == (3, 3, 0)
112    moved: ExportWorker = archive.meld("ExportWorker")
113    assert any(moved is worker for worker in workers)
114    print("the archive root now holds the pool and leases its idle workers:", pool_counts(ExportWorker))
115
116    try:
117        reports.meld("ExportWorker")
118        raise AssertionError("the old owner still melds the transferred spell")
119    except KeyError as gone:
120        print("the old owner can no longer meld it:", type(gone).__name__)
121    md.ReleaseManagedSpell().release(moved)
122    assert pool_counts(ExportWorker) == (3, 3, 0)
123
124
125def main() -> None:
126    """Raise a pool's limit by rebinding, then transfer the spell and its pool.
127
128    Contract:
129        Both roots and both books are cleaned in finally blocks; the archive root's
130        teardown disposes the idle workers its moved pool holds.
131    Returns:
132        None.
133    """
134    reports_book = dynamic_spellbook()
135    try:
136        spell_id = reports_book.bind(
137            spell=ExportWorker,
138            existence=md.Existence.managed_spell_quantity,
139            managed_spell_mode="limited_quantity",
140            managed_spell_limit=1,
141            disposal_method_names=["close"],
142        )
143        reports = reports_book.conjure(dynamic=True, name="reports")
144        try:
145            archive_book = dynamic_spellbook()
146            try:
147                archive = archive_book.conjure(dynamic=True, name="archive")
148                try:
149                    workers = raise_the_limit(reports, spell_id)
150                    transfer_the_pool(reports, archive, spell_id, workers)
151                finally:
152                    archive.cleanup()
153                # The new owner's teardown disposed the idle workers its pool held.
154                assert all(worker.closed for worker in workers)
155                print("the new owner's teardown closed the pooled workers:", len(workers))
156            finally:
157                archive_book.cleanup()
158        finally:
159            reports.cleanup()
160    finally:
161        reports_book.cleanup()
162
163
164if __name__ == "__main__":
165    main()

Check the outcome๏ƒ

The script contains its own assertions or demonstrated refusal paths. Run it to evaluate those checks against your installed version. The code above is taken directly from the saved file; no run output is invented here.

More intermediate examples ยท Level guide

API contracts๏ƒ