_compat.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. # flake8: noqa
  2. import abc
  3. import os
  4. import sys
  5. import pathlib
  6. import warnings
  7. from contextlib import suppress
  8. from typing import Union
  9. if sys.version_info >= (3, 10):
  10. from zipfile import Path as ZipPath # type: ignore
  11. else:
  12. from zipp import Path as ZipPath # type: ignore
  13. try:
  14. from typing import runtime_checkable # type: ignore
  15. except ImportError:
  16. def runtime_checkable(cls): # type: ignore
  17. return cls
  18. try:
  19. from typing import Protocol # type: ignore
  20. except ImportError:
  21. Protocol = abc.ABC # type: ignore
  22. class TraversableResourcesLoader:
  23. """
  24. Adapt loaders to provide TraversableResources and other
  25. compatibility.
  26. Used primarily for Python 3.9 and earlier where the native
  27. loaders do not yet implement TraversableResources.
  28. """
  29. def __init__(self, spec):
  30. self.spec = spec
  31. @property
  32. def path(self):
  33. return self.spec.origin
  34. def get_resource_reader(self, name):
  35. from . import readers, _adapters
  36. def _zip_reader(spec):
  37. with suppress(AttributeError):
  38. return readers.ZipReader(spec.loader, spec.name)
  39. def _namespace_reader(spec):
  40. with suppress(AttributeError, ValueError):
  41. return readers.NamespaceReader(spec.submodule_search_locations)
  42. def _available_reader(spec):
  43. with suppress(AttributeError):
  44. return spec.loader.get_resource_reader(spec.name)
  45. def _native_reader(spec):
  46. reader = _available_reader(spec)
  47. return reader if hasattr(reader, 'files') else None
  48. def _file_reader(spec):
  49. try:
  50. path = pathlib.Path(self.path)
  51. except TypeError:
  52. return None
  53. if path.exists():
  54. return readers.FileReader(self)
  55. return (
  56. # local ZipReader if a zip module
  57. _zip_reader(self.spec)
  58. or
  59. # local NamespaceReader if a namespace module
  60. _namespace_reader(self.spec)
  61. or
  62. # local FileReader
  63. _file_reader(self.spec)
  64. or
  65. # native reader if it supplies 'files'
  66. _native_reader(self.spec)
  67. or
  68. # fallback - adapt the spec ResourceReader to TraversableReader
  69. _adapters.CompatibilityFiles(self.spec)
  70. )
  71. def wrap_spec(package):
  72. """
  73. Construct a package spec with traversable compatibility
  74. on the spec/loader/reader.
  75. Supersedes _adapters.wrap_spec to use TraversableResourcesLoader
  76. from above for older Python compatibility (<3.10).
  77. """
  78. from . import _adapters
  79. return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)
  80. if sys.version_info >= (3, 9):
  81. StrPath = Union[str, os.PathLike[str]]
  82. else:
  83. # PathLike is only subscriptable at runtime in 3.9+
  84. StrPath = Union[str, "os.PathLike[str]"]
  85. def ensure_traversable(path):
  86. """
  87. Convert deprecated string arguments to traversables (pathlib.Path).
  88. """
  89. if not isinstance(path, str):
  90. return path
  91. warnings.warn(
  92. "String arguments are deprecated. Pass a Traversable instead.",
  93. DeprecationWarning,
  94. stacklevel=3,
  95. )
  96. return pathlib.Path(path)