1import functools 
    2import pathlib 
    3from contextlib import suppress 
    4from types import SimpleNamespace 
    5 
    6from .. import readers, _adapters 
    7 
    8 
    9def _block_standard(reader_getter): 
    10    """ 
    11    Wrap _adapters.TraversableResourcesLoader.get_resource_reader 
    12    and intercept any standard library readers. 
    13    """ 
    14 
    15    @functools.wraps(reader_getter) 
    16    def wrapper(*args, **kwargs): 
    17        """ 
    18        If the reader is from the standard library, return None to allow 
    19        allow likely newer implementations in this library to take precedence. 
    20        """ 
    21        try: 
    22            reader = reader_getter(*args, **kwargs) 
    23        except NotADirectoryError: 
    24            # MultiplexedPath may fail on zip subdirectory 
    25            return 
    26        # Python 3.10+ 
    27        mod_name = reader.__class__.__module__ 
    28        if mod_name.startswith('importlib.') and mod_name.endswith('readers'): 
    29            return 
    30        # Python 3.8, 3.9 
    31        if isinstance(reader, _adapters.CompatibilityFiles) and ( 
    32            reader.spec.loader.__class__.__module__.startswith('zipimport') 
    33            or reader.spec.loader.__class__.__module__.startswith( 
    34                '_frozen_importlib_external' 
    35            ) 
    36        ): 
    37            return 
    38        return reader 
    39 
    40    return wrapper 
    41 
    42 
    43def _skip_degenerate(reader): 
    44    """ 
    45    Mask any degenerate reader. Ref #298. 
    46    """ 
    47    is_degenerate = ( 
    48        isinstance(reader, _adapters.CompatibilityFiles) and not reader._reader 
    49    ) 
    50    return reader if not is_degenerate else None 
    51 
    52 
    53class TraversableResourcesLoader(_adapters.TraversableResourcesLoader): 
    54    """ 
    55    Adapt loaders to provide TraversableResources and other 
    56    compatibility. 
    57 
    58    Ensures the readers from importlib_resources are preferred 
    59    over stdlib readers. 
    60    """ 
    61 
    62    def get_resource_reader(self, name): 
    63        return ( 
    64            _skip_degenerate(_block_standard(super().get_resource_reader)(name)) 
    65            or self._standard_reader() 
    66            or super().get_resource_reader(name) 
    67        ) 
    68 
    69    def _standard_reader(self): 
    70        return self._zip_reader() or self._namespace_reader() or self._file_reader() 
    71 
    72    def _zip_reader(self): 
    73        with suppress(AttributeError): 
    74            return readers.ZipReader(self.spec.loader, self.spec.name) 
    75 
    76    def _namespace_reader(self): 
    77        with suppress(AttributeError, ValueError): 
    78            return readers.NamespaceReader(self.spec.submodule_search_locations) 
    79 
    80    def _file_reader(self): 
    81        try: 
    82            path = pathlib.Path(self.spec.origin) 
    83        except TypeError: 
    84            return None 
    85        if path.exists(): 
    86            return readers.FileReader(SimpleNamespace(path=path)) 
    87 
    88 
    89def wrap_spec(package): 
    90    """ 
    91    Override _adapters.wrap_spec to use TraversableResourcesLoader 
    92    from above. Ensures that future behavior is always available on older 
    93    Pythons. 
    94    """ 
    95    return _adapters.SpecLoaderAdapter(package.__spec__, TraversableResourcesLoader)