__init__.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104
  1. from __future__ import annotations
  2. from importlib.machinery import ModuleSpec
  3. import importlib.util
  4. import sys
  5. from types import ModuleType
  6. from typing import Iterable, Sequence
  7. class VendorImporter:
  8. """
  9. A PEP 302 meta path importer for finding optionally-vendored
  10. or otherwise naturally-installed packages from root_name.
  11. """
  12. def __init__(
  13. self,
  14. root_name: str,
  15. vendored_names: Iterable[str] = (),
  16. vendor_pkg: str | None = None,
  17. ):
  18. self.root_name = root_name
  19. self.vendored_names = set(vendored_names)
  20. self.vendor_pkg = vendor_pkg or root_name.replace('extern', '_vendor')
  21. @property
  22. def search_path(self):
  23. """
  24. Search first the vendor package then as a natural package.
  25. """
  26. yield self.vendor_pkg + '.'
  27. yield ''
  28. def _module_matches_namespace(self, fullname):
  29. """Figure out if the target module is vendored."""
  30. root, base, target = fullname.partition(self.root_name + '.')
  31. return not root and any(map(target.startswith, self.vendored_names))
  32. def load_module(self, fullname: str):
  33. """
  34. Iterate over the search path to locate and load fullname.
  35. """
  36. root, base, target = fullname.partition(self.root_name + '.')
  37. for prefix in self.search_path:
  38. extant = prefix + target
  39. try:
  40. __import__(extant)
  41. except ImportError:
  42. continue
  43. mod = sys.modules[extant]
  44. sys.modules[fullname] = mod
  45. return mod
  46. else:
  47. raise ImportError(
  48. "The '{target}' package is required; "
  49. "normally this is bundled with this package so if you get "
  50. "this warning, consult the packager of your "
  51. "distribution.".format(**locals())
  52. )
  53. def create_module(self, spec: ModuleSpec):
  54. return self.load_module(spec.name)
  55. def exec_module(self, module: ModuleType):
  56. pass
  57. def find_spec(
  58. self,
  59. fullname: str,
  60. path: Sequence[str] | None = None,
  61. target: ModuleType | None = None,
  62. ):
  63. """Return a module spec for vendored names."""
  64. return (
  65. # This should fix itself next mypy release https://github.com/python/typeshed/pull/11890
  66. importlib.util.spec_from_loader(fullname, self) # type: ignore[arg-type]
  67. if self._module_matches_namespace(fullname)
  68. else None
  69. )
  70. def install(self):
  71. """
  72. Install this importer into sys.meta_path if not already present.
  73. """
  74. if self not in sys.meta_path:
  75. sys.meta_path.append(self)
  76. # [[[cog
  77. # import cog
  78. # from tools.vendored import yield_top_level
  79. # names = "\n".join(f" {x!r}," for x in yield_top_level('pkg_resources'))
  80. # cog.outl(f"names = (\n{names}\n)")
  81. # ]]]
  82. names = (
  83. 'backports',
  84. 'importlib_resources',
  85. 'jaraco',
  86. 'more_itertools',
  87. 'packaging',
  88. 'platformdirs',
  89. 'zipp',
  90. )
  91. # [[[end]]]
  92. VendorImporter(__name__, names).install()