On this page

SpellMap๏ƒ

Use md.SpellMap from the public package namespace.

Implementation source

class SpellMap(spell: Any | None = None, *, spellframe: Any | None = None, binding_name: str | None = None, spell_override: dict | list | tuple | None = None)[source]๏ƒ

Declarative DI descriptor for normal spellbook-local resolution.

SpellMap is the explicit descriptor form of ordinary Melder DI intent. It tells SpellCrafter and Meld which spell or frame/binding identity should be injected at a given parameter or attribute location, optionally along with an override payload that should be carried into runtime construction.

Unlike SpellContract, this descriptor does not declare a cross-conduit hole that will be satisfied later. It stays inside the current resolution world. SpellCrafter and the runtime planning path inspect it, derive the canonical lookup identity, and then resolve a local spell through the usual Spellbook, SpellIndex, and Existence machinery.

Supported shapes:

  • SpellMap(MyService) Direct type-based DI where the spell itself supplies the lookup identity.

  • SpellMap(ILogic) Frame-based DI where a Protocol / interface acts as the lookup frame.

  • SpellMap(MyService, spellframe=ILogic, binding_name="primary") Fully explicit spell + frame + binding declaration.

  • SpellMap(spell=None, spellframe=ILogic, binding_name="primary") Frame-only descriptor where the DI key is derived entirely from frame and binding.

Typical usage:

class MyService:
    def __init__(self, repo=SpellMap(MyRepo)):
        self.repo = repo

class UsesLogic:
    def __init__(self, logic=SpellMap(ILogic, binding_name="primary")):
        self.logic = logic

class UsesConfig:
    def __init__(
        self,
        cfg=SpellMap(
            spell=None,
            spellframe=IAppConfig,
            binding_name="primary",
        ),
    ):
        self.cfg = cfg

Contract:

  • Pure intent object; it does not resolve providers by itself.

  • Used for normal in-graph DI, not late conduit linking.

  • Carries spell/frame/binding identity plus optional override payload.

  • Cleanable and invalid after cleanup.

  • Should not be subclassed or treated like a runtime-resolved object.

Threading:

Value-shaped and effectively immutable after construction: the four slots are set in __init__ and read thereafter. No lock is taken, because a descriptor written into a constructor default is shared read-only across every resolution that reads it.

Lifecycle / Cleanup:

Cleanable. The descriptor lives as long as the class default that declares it, which in practice is the lifetime of the defining module.

Registration:

MELDER KERNEL, USER-INSTANTIATED but NOT user-bindable. A user writes SpellMap(MyRepo) in their own constructor default constantly, so instances are authored outside melder - but binding the SpellMap CLASS itself is refused, which would be meaningless because a descriptor is a statement of intent, not a service to resolve.

Subsystem Context:

One of the two declarative DI descriptors, paired with SpellContract. The division is scope: SpellMap stays INSIDE the current resolution world and resolves through the ordinary Spellbook / SpellIndex / Existence machinery, while SpellContract declares a hole a future linked conduit may fill. Phase 1 classifies each parameter into a ParameterDIShape; a SpellMap default lands as SPELLMAP_DEFAULT, and Phase 3 performs the actual candidate resolution.

System Context:

The four supported shapes exist because DI identity in Melder is a (spell, frame, binding) triple rather than a single type key, and different call sites know different parts of it. Passing a concrete class supplies the whole identity; passing a Protocol supplies a frame and asks the graph to find the implementation; the frame-only form (spell=None) exists for the case where the caller deliberately knows NOTHING but the contract and the binding name. Ambiguity is a build-time failure, not a runtime one: a SpellMap default that resolves to zero or to multiple candidates raises rather than silently picking. That is the whole reason the explicit binding_name form exists - it is the documented way to disambiguate when several spells legitimately satisfy the same frame.

AGENT_ACCESS: public

AGENT_PURPOSE:

access: public. Declarative DI descriptor for normal in-graph resolution. Write it as a constructor default: SpellMap(MyRepo), SpellMap(ILogic, binding_name='primary'), or frame-only with spell=None. Zero or multiple matches raise at build time.

cleanup() None[source]๏ƒ

Release descriptor references and invalidate the object.

Contract:

  • Idempotent and safe to call multiple times.

  • Clears mutable override payloads before dropping references.

  • After cleanup the descriptor should be treated as unusable and callers should rely on check_cleaned() before reading it.

Returns:

None.

property lookup_triplet: tuple[Any, Any | None, str | None]๏ƒ

Return the raw descriptor identity tuple.

This is the shape SpellCrafter and the runtime planning path consume before they derive the normalized key or resolve the underlying spell.

Contract:

  • Returns the RAW, AS-SUPPLIED triplet - the spell, spellframe and binding name exactly as the caller gave them, WITHOUT normalization.

  • This is NOT the registry key. Use canonical_key when you need the normalized identity the spellbook actually indexes by; the two can differ for the same SpellMap.

Threading:

Pure computation over immutable fields; safe from any thread.

Lifecycle / Cleanup:

Carries no cleaned-state guard.

Returns:

(spell, spellframe, binding_name) exactly as stored on the descriptor.

Return type:

tuple[Any, Optional[Any], Optional[str]]

Notes

  • For frame-only SpellMaps (spell is None), only spellframe and binding_name contribute to the runtime identity.

  • For fully explicit SpellMaps, higher layers may use both spell and spellframe when enforcing resolution rules.

  • Binding names are already normalized when provided.

property canonical_key: Tuple[str, str]๏ƒ

Return the normalized (frame_key, binding_key) pair.

This is the canonical Spellbook-style key derived through SpellInputUtils.normalize_spell_key(...). It is the stable string pair higher layers use when indexing or matching SpellMap intent.

Shape rules:

  • when spellframe is present, the frame key is derived from spellframe

  • when spellframe is absent, the frame key is derived from spell

  • binding defaults normalize to "__default__" inside the helper path

Contract:

  • NORMALIZED identity: it runs the raw triplet through the shared key normalizer, so equivalent-but-differently-spelled inputs collapse to the same key. This is what the spellbook indexes by.

  • Recomputed on every access rather than cached, so it always reflects the current field values.

Threading:

Pure computation over immutable fields; safe from any thread.

Lifecycle / Cleanup:

Carries no cleaned-state guard.

Returns:

Normalized (frame_key, binding_key) pair.

Return type:

Tuple[str, str]

property spell_key: Tuple[str, str]๏ƒ

Compatibility alias for canonical_key.

Contract:

  • ALIAS for canonical_key, kept for call-site readability. Identical behaviour - it is the NORMALIZED key, not the raw triplet.

Threading:

Pure computation over immutable fields; safe from any thread.

Lifecycle / Cleanup:

Carries no cleaned-state guard.

Returns:

The same normalized key pair returned by canonical_key.

Return type:

Tuple[str, str]

Topic reference ยท Full contents