Skip to content

How it works

  • Importing wishful installs a MagicFinder at the front of sys.meta_path.
  • Imports under wishful.static.* and wishful.dynamic.* get routed to MagicLoader.
  • Internal modules (wishful.core, wishful.config, …) bypass the hook to avoid recursion.

discover() looks around the call site:

  • Parses the import to know which symbols are wanted.
  • Scrapes nearby code/comments (radius configurable via WISHFUL_CONTEXT_RADIUS or wishful.set_context_radius).
  • Pulls registered type schemas and output bindings from @wishful.type.

That bundle becomes prompt seasoning for the LLM.

  • Static: check cache → use if present; otherwise generate once and write to .wishful/<module>.py.
  • Dynamic: always regenerate (runtime-context-aware), and also write a snapshot under .wishful/_dynamic/.
  • Safety gate: validate_code blocks dangerous imports/calls unless allow_unsafe=True.
  • Missing symbols? Static loader regenerates with the expanded symbol set. Dynamic loader proxies __getattr__ and call-time regeneration.

Attribute access on a dynamic module is lazy and free: it returns a callable wrapper without spending an LLM call. Generation happens only when you invoke the wrapper. Three consequences are intentional:

  • hasattr(mod, name) is True for every public name — the module can’t know what the model would generate without paying for a generation, so it doesn’t try.
  • dir(mod) only lists what previous generations produced, not what’s possible.
  • Dynamic modules expose functions only. If a call’s regeneration produces the name as a non-callable (a constant), the call raises AttributeError; if the model fails to produce the requested symbol at all, the existing module and snapshot are left untouched. Use wishful.static.* for modules with data attributes.
  • wishful.settings is stored on builtins so it survives module reloads (handy for tests).
  • install_finder() is idempotent: if a finder from wishful.core.finder is already present, it won’t stack duplicates.
  • MagicLoader resolves generate_module_code each call, so monkeypatches work even after reloads.
  • wishful.static.foo.wishful/foo.py
  • wishful.dynamic.foo.wishful/_dynamic/foo.py (a disjoint namespace — dynamic snapshots never collide with static cache files)
  • CLI helpers (wishful inspect/clear/regen) and Python helpers (inspect_cache, clear_cache, regenerate) manage these paths.

That’s the core loop. Everything else (CLI, types, logging) builds on this.

wishful.explore() is a smarter way to populate the cache:

  • Generates multiple implementations of a function
  • Tests each one against your criteria
  • Benchmarks them (optional) to find the fastest
  • Caches the winner to .wishful/ like a regular import
# Generate 5 variants, test each, cache the winner
parser = wishful.explore(
"wishful.static.text.extract_emails",
variants=5,
test=lambda fn: fn("test@example.com") == ["test@example.com"]
)
# Future imports use the proven winner
from wishful.static.text import extract_emails # ← No re-exploration

Uses async internally (litellm.acompletion()) for responsive UI with a beautiful Rich progress display. See Explore for the full tour.

For knobs you can turn, see Configuration.