Files
Max Isbey 1f3fbc7ca3 Use one lazy-attribute helper for the packages, invisible to type checkers
Three implementations of lazy module attributes had grown: the
hand-rolled __getattr__/__dir__ in mcp/__init__.py, the submodule
fallback factory used by the four package inits, and a one-name
__getattr__ in mcp.server.elicitation. They shared two problems.

An unconditional module-level __getattr__ is visible to type checkers,
so pyright typed every misspelled `mcp.<name>` (and `mcp.client.<name>`,
...) as `object` instead of reporting it. And the submodule fallback ran
a filesystem find_spec on every attribute miss and speculatively imported
whatever matched, so a name sweep (cloudpickle's whichmodule, hasattr
probes) could import real submodules as a side effect, while
dir(mcp.client) no longer listed submodules it would happily resolve.

mcp.shared._lazy.lazy_module_attrs now serves all of them: lazy exports
(a `(module, attr)` pair or a zero-argument loader, resolved once and
cached in the namespace) plus known submodules (an explicit set, or the
package's real submodules listed once on first need). A miss is a plain
AttributeError with no search and no import, and __dir__ reports the
exports and submodules. Every caller binds the pair under
`if not TYPE_CHECKING:`, so attribute typos are pyright errors again
while the TYPE_CHECKING mirrors keep the real names typed. The
mcp.server.auth package gets the same fallback so qualified annotations
such as `mcp.server.auth.provider.TokenVerifier` resolve on demand.

The elicitation gate's wire-schema type is resolved through the same
mechanism from a cached accessor, so validating rendered schemas no
longer re-executes the wire-package import on every call.
2026-07-29 21:59:46 +00:00

23 lines
617 B
Python

"""A package that installs the lazy-attribute helper, for tests/shared/test_lazy.py."""
from typing import TYPE_CHECKING
from mcp.shared._lazy import lazy_module_attrs as _lazy_module_attrs
load_calls: int = 0
if TYPE_CHECKING:
# Type checkers see the lazy export as a plain module attribute.
ANSWER: int
def _load_answer() -> int:
"""A lazily-resolved re-export: a zero-argument loader that must run at most once."""
global load_calls
load_calls += 1
return 42
if not TYPE_CHECKING:
__getattr__, __dir__ = _lazy_module_attrs(__name__, globals(), exports={"ANSWER": _load_answer})