On this page

Managed spell limited quantity๏ƒ

๐ŸŸก Intermediate ยท Lesson 43

Cap how many objects exist at once - managed_spell_mode="limited_quantity". managed_spell_limit (required here) is the most objects ALIVE at once: idle plus out. Below the limit a meld builds; at the limit, with nothing idle, the meld is refused with ManagedSpellExhaustedError before anything is built. The error is a MeldExecutionError that names the mode and the limit, and says how to get out: release an object, or bind a higher limit.

Releasing frees a place: the next meld is served the released object, so the pool never grows past its limit. release_all(objects) gives many back at once.

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/43_managed_spell_limited_quantity.py
py -3.14t UX_and_AIX_experiences/02_intermediate/43_managed_spell_limited_quantity.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

md.Spellbook.bind (managed_spell_mode, managed_spell_limit), md.ManagedSpellMode.limited_quantity, md.Conduit.meld, md.ManagedSpellExhaustedError (mode, limit, timeout_seconds), md.MeldExecutionError, md.ReleaseManagedSpell (release, release_all, describe), md.Conduit.cleanup

Code๏ƒ

  1"""
  2TIER: intermediate (43)
  3GOAL: Cap how many objects exist at once - managed_spell_mode="limited_quantity".
  4      managed_spell_limit (required here) is the most objects ALIVE at once: idle plus
  5      out. Below the limit a meld builds; at the limit, with nothing idle, the meld is
  6      refused with ManagedSpellExhaustedError before anything is built. The error is a
  7      MeldExecutionError that names the mode and the limit, and says how to get out:
  8      release an object, or bind a higher limit.
  9
 10      Releasing frees a place: the next meld is served the released object, so the
 11      pool never grows past its limit. release_all(objects) gives many back at once.
 12SURFACE EXERCISED: md.Spellbook.bind (managed_spell_mode, managed_spell_limit),
 13                   md.ManagedSpellMode.limited_quantity, md.Conduit.meld,
 14                   md.ManagedSpellExhaustedError (mode, limit, timeout_seconds),
 15                   md.MeldExecutionError, md.ReleaseManagedSpell (release,
 16                   release_all, describe), md.Conduit.cleanup
 17"""
 18from typing import Tuple
 19
 20import melder as md
 21
 22
 23class LicenseSeat:
 24    """One seat of a licence that allows a fixed number of concurrent users.
 25
 26    `close` is its disposal method, run when the pool destroys the seat (root
 27    teardown here). `closed` is a lesson diagnostic.
 28    """
 29
 30    def __init__(self) -> None:
 31        """Claim the seat; the lesson only records that it is open."""
 32        self.closed: bool = False
 33
 34    def close(self) -> None:
 35        """Give the seat up for good; called by the pool when it destroys this object."""
 36        self.closed = True
 37
 38
 39def pool_counts(cls: type) -> Tuple[int, int, int]:
 40    """Return (alive, idle, out) for the one pool of a managed class."""
 41    (snapshot,) = md.ReleaseManagedSpell().describe(cls)
 42    return snapshot["count"], snapshot["idle_count"], snapshot["out_count"]
 43
 44
 45def main() -> None:
 46    """Fill a limited pool, read its refusal, then release and lease again.
 47
 48    Contract:
 49        The refusal is caught and its fields checked; the counts prove the refusal
 50        built nothing and that releases never let the pool grow past its limit.
 51    Returns:
 52        None.
 53    """
 54    book = md.Spellbook()
 55    try:
 56        book.bind(
 57            spell=LicenseSeat,
 58            existence=md.Existence.managed_spell_quantity,
 59            managed_spell_mode="limited_quantity",
 60            managed_spell_limit=2,
 61            disposal_method_names=["close"],
 62        )
 63        conduit = book.conjure()
 64        try:
 65            first: LicenseSeat = conduit.meld("LicenseSeat")
 66            second: LicenseSeat = conduit.meld("LicenseSeat")
 67            assert pool_counts(LicenseSeat) == (2, 0, 2)
 68            print("both seats taken (alive, idle, out):", pool_counts(LicenseSeat))
 69
 70            # At the limit with nothing idle: refused before anything is built.
 71            try:
 72                conduit.meld("LicenseSeat")
 73                raise AssertionError("a third seat was leased past the limit")
 74            except md.ManagedSpellExhaustedError as refused:
 75                assert isinstance(refused, md.MeldExecutionError)
 76                assert refused.mode == "limited_quantity" and refused.limit == 2
 77                assert refused.timeout_seconds is None
 78                assert "Release one with ReleaseManagedSpell().release(obj)" in str(refused)
 79                print("third seat refused:", type(refused).__name__, "- mode", refused.mode, "limit", refused.limit)
 80            assert pool_counts(LicenseSeat) == (2, 0, 2)
 81
 82            # A release frees a place, and the next meld is served the released seat.
 83            md.ReleaseManagedSpell().release(first)
 84            again: LicenseSeat = conduit.meld("LicenseSeat")
 85            assert again is first
 86            assert pool_counts(LicenseSeat) == (2, 0, 2)
 87            print("after a release the next user got the freed seat:", again is first)
 88
 89            # release_all gives every seat back; the pool still holds exactly two.
 90            md.ReleaseManagedSpell().release_all([second, again])
 91            assert pool_counts(LicenseSeat) == (2, 2, 0)
 92            reused = {id(conduit.meld("LicenseSeat")), id(conduit.meld("LicenseSeat"))}
 93            assert reused == {id(first), id(second)}
 94            assert pool_counts(LicenseSeat) == (2, 0, 2)
 95            print("two more leases reused the same two seats; alive stays at the limit:", pool_counts(LicenseSeat))
 96        finally:
 97            conduit.cleanup()
 98
 99        # Teardown disposes only what is idle; both seats were still out.
100        assert not first.closed and not second.closed
101        print("seats still out at teardown stay their holder's:", not first.closed)
102    finally:
103        book.cleanup()
104
105
106if __name__ == "__main__":
107    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๏ƒ