123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- import sys
- import warnings
- from typing import Any, Dict, Iterable, Optional
- __all__ = ["lazy_import"]
- def import_name(name: str, source: str, namespace: Dict[str, Any]) -> Any:
- """
- Import <name> from <source> in <namespace>.
- There are two cases:
- - <name> is an object defined in <source>
- - <name> is a submodule of source
- Neither __import__ nor importlib.import_module does exactly this.
- __import__ is closer to the intended behavior.
- """
- level = 0
- while source[level] == ".":
- level += 1
- assert level < len(source), "importing from parent isn't supported"
- module = __import__(source[level:], namespace, None, [name], level)
- return getattr(module, name)
- def lazy_import(
- namespace: Dict[str, Any],
- aliases: Optional[Dict[str, str]] = None,
- deprecated_aliases: Optional[Dict[str, str]] = None,
- ) -> None:
- """
- Provide lazy, module-level imports.
- Typical use::
- __getattr__, __dir__ = lazy_import(
- globals(),
- aliases={
- "<name>": "<source module>",
- ...
- },
- deprecated_aliases={
- ...,
- }
- )
- This function defines __getattr__ and __dir__ per PEP 562.
- On Python 3.6 and earlier, it falls back to non-lazy imports and doesn't
- raise deprecation warnings.
- """
- if aliases is None:
- aliases = {}
- if deprecated_aliases is None:
- deprecated_aliases = {}
- namespace_set = set(namespace)
- aliases_set = set(aliases)
- deprecated_aliases_set = set(deprecated_aliases)
- assert not namespace_set & aliases_set, "namespace conflict"
- assert not namespace_set & deprecated_aliases_set, "namespace conflict"
- assert not aliases_set & deprecated_aliases_set, "namespace conflict"
- package = namespace["__name__"]
- if sys.version_info[:2] >= (3, 7):
- def __getattr__(name: str) -> Any:
- assert aliases is not None # mypy cannot figure this out
- try:
- source = aliases[name]
- except KeyError:
- pass
- else:
- return import_name(name, source, namespace)
- assert deprecated_aliases is not None # mypy cannot figure this out
- try:
- source = deprecated_aliases[name]
- except KeyError:
- pass
- else:
- warnings.warn(
- f"{package}.{name} is deprecated",
- DeprecationWarning,
- stacklevel=2,
- )
- return import_name(name, source, namespace)
- raise AttributeError(f"module {package!r} has no attribute {name!r}")
- namespace["__getattr__"] = __getattr__
- def __dir__() -> Iterable[str]:
- return sorted(namespace_set | aliases_set | deprecated_aliases_set)
- namespace["__dir__"] = __dir__
- else: # pragma: no cover
- for name, source in aliases.items():
- namespace[name] = import_name(name, source, namespace)
- for name, source in deprecated_aliases.items():
- namespace[name] = import_name(name, source, namespace)
|