• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1"""Abstract base classes related to import."""
2from . import _bootstrap_external
3from . import machinery
4try:
5    import _frozen_importlib
6except ImportError as exc:
7    if exc.name != '_frozen_importlib':
8        raise
9    _frozen_importlib = None
10try:
11    import _frozen_importlib_external
12except ImportError:
13    _frozen_importlib_external = _bootstrap_external
14from ._abc import Loader
15import abc
16import warnings
17from typing import BinaryIO, Iterable, Text
18from typing import Protocol, runtime_checkable
19
20
21def _register(abstract_cls, *classes):
22    for cls in classes:
23        abstract_cls.register(cls)
24        if _frozen_importlib is not None:
25            try:
26                frozen_cls = getattr(_frozen_importlib, cls.__name__)
27            except AttributeError:
28                frozen_cls = getattr(_frozen_importlib_external, cls.__name__)
29            abstract_cls.register(frozen_cls)
30
31
32class Finder(metaclass=abc.ABCMeta):
33
34    """Legacy abstract base class for import finders.
35
36    It may be subclassed for compatibility with legacy third party
37    reimplementations of the import system.  Otherwise, finder
38    implementations should derive from the more specific MetaPathFinder
39    or PathEntryFinder ABCs.
40
41    Deprecated since Python 3.3
42    """
43
44    def __init__(self):
45        warnings.warn("the Finder ABC is deprecated and "
46                       "slated for removal in Python 3.12; use MetaPathFinder "
47                       "or PathEntryFinder instead",
48                       DeprecationWarning)
49
50    @abc.abstractmethod
51    def find_module(self, fullname, path=None):
52        """An abstract method that should find a module.
53        The fullname is a str and the optional path is a str or None.
54        Returns a Loader object or None.
55        """
56        warnings.warn("importlib.abc.Finder along with its find_module() "
57                      "method are deprecated and "
58                       "slated for removal in Python 3.12; use "
59                       "MetaPathFinder.find_spec() or "
60                       "PathEntryFinder.find_spec() instead",
61                       DeprecationWarning)
62
63
64class MetaPathFinder(metaclass=abc.ABCMeta):
65
66    """Abstract base class for import finders on sys.meta_path."""
67
68    # We don't define find_spec() here since that would break
69    # hasattr checks we do to support backward compatibility.
70
71    def find_module(self, fullname, path):
72        """Return a loader for the module.
73
74        If no module is found, return None.  The fullname is a str and
75        the path is a list of strings or None.
76
77        This method is deprecated since Python 3.4 in favor of
78        finder.find_spec(). If find_spec() exists then backwards-compatible
79        functionality is provided for this method.
80
81        """
82        warnings.warn("MetaPathFinder.find_module() is deprecated since Python "
83                      "3.4 in favor of MetaPathFinder.find_spec() and is "
84                      "slated for removal in Python 3.12",
85                      DeprecationWarning,
86                      stacklevel=2)
87        if not hasattr(self, 'find_spec'):
88            return None
89        found = self.find_spec(fullname, path)
90        return found.loader if found is not None else None
91
92    def invalidate_caches(self):
93        """An optional method for clearing the finder's cache, if any.
94        This method is used by importlib.invalidate_caches().
95        """
96
97_register(MetaPathFinder, machinery.BuiltinImporter, machinery.FrozenImporter,
98          machinery.PathFinder, machinery.WindowsRegistryFinder)
99
100
101class PathEntryFinder(metaclass=abc.ABCMeta):
102
103    """Abstract base class for path entry finders used by PathFinder."""
104
105    # We don't define find_spec() here since that would break
106    # hasattr checks we do to support backward compatibility.
107
108    def find_loader(self, fullname):
109        """Return (loader, namespace portion) for the path entry.
110
111        The fullname is a str.  The namespace portion is a sequence of
112        path entries contributing to part of a namespace package. The
113        sequence may be empty.  If loader is not None, the portion will
114        be ignored.
115
116        The portion will be discarded if another path entry finder
117        locates the module as a normal module or package.
118
119        This method is deprecated since Python 3.4 in favor of
120        finder.find_spec(). If find_spec() is provided than backwards-compatible
121        functionality is provided.
122        """
123        warnings.warn("PathEntryFinder.find_loader() is deprecated since Python "
124                      "3.4 in favor of PathEntryFinder.find_spec() "
125                      "(available since 3.4)",
126                      DeprecationWarning,
127                      stacklevel=2)
128        if not hasattr(self, 'find_spec'):
129            return None, []
130        found = self.find_spec(fullname)
131        if found is not None:
132            if not found.submodule_search_locations:
133                portions = []
134            else:
135                portions = found.submodule_search_locations
136            return found.loader, portions
137        else:
138            return None, []
139
140    find_module = _bootstrap_external._find_module_shim
141
142    def invalidate_caches(self):
143        """An optional method for clearing the finder's cache, if any.
144        This method is used by PathFinder.invalidate_caches().
145        """
146
147_register(PathEntryFinder, machinery.FileFinder)
148
149
150class ResourceLoader(Loader):
151
152    """Abstract base class for loaders which can return data from their
153    back-end storage.
154
155    This ABC represents one of the optional protocols specified by PEP 302.
156
157    """
158
159    @abc.abstractmethod
160    def get_data(self, path):
161        """Abstract method which when implemented should return the bytes for
162        the specified path.  The path must be a str."""
163        raise OSError
164
165
166class InspectLoader(Loader):
167
168    """Abstract base class for loaders which support inspection about the
169    modules they can load.
170
171    This ABC represents one of the optional protocols specified by PEP 302.
172
173    """
174
175    def is_package(self, fullname):
176        """Optional method which when implemented should return whether the
177        module is a package.  The fullname is a str.  Returns a bool.
178
179        Raises ImportError if the module cannot be found.
180        """
181        raise ImportError
182
183    def get_code(self, fullname):
184        """Method which returns the code object for the module.
185
186        The fullname is a str.  Returns a types.CodeType if possible, else
187        returns None if a code object does not make sense
188        (e.g. built-in module). Raises ImportError if the module cannot be
189        found.
190        """
191        source = self.get_source(fullname)
192        if source is None:
193            return None
194        return self.source_to_code(source)
195
196    @abc.abstractmethod
197    def get_source(self, fullname):
198        """Abstract method which should return the source code for the
199        module.  The fullname is a str.  Returns a str.
200
201        Raises ImportError if the module cannot be found.
202        """
203        raise ImportError
204
205    @staticmethod
206    def source_to_code(data, path='<string>'):
207        """Compile 'data' into a code object.
208
209        The 'data' argument can be anything that compile() can handle. The'path'
210        argument should be where the data was retrieved (when applicable)."""
211        return compile(data, path, 'exec', dont_inherit=True)
212
213    exec_module = _bootstrap_external._LoaderBasics.exec_module
214    load_module = _bootstrap_external._LoaderBasics.load_module
215
216_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter)
217
218
219class ExecutionLoader(InspectLoader):
220
221    """Abstract base class for loaders that wish to support the execution of
222    modules as scripts.
223
224    This ABC represents one of the optional protocols specified in PEP 302.
225
226    """
227
228    @abc.abstractmethod
229    def get_filename(self, fullname):
230        """Abstract method which should return the value that __file__ is to be
231        set to.
232
233        Raises ImportError if the module cannot be found.
234        """
235        raise ImportError
236
237    def get_code(self, fullname):
238        """Method to return the code object for fullname.
239
240        Should return None if not applicable (e.g. built-in module).
241        Raise ImportError if the module cannot be found.
242        """
243        source = self.get_source(fullname)
244        if source is None:
245            return None
246        try:
247            path = self.get_filename(fullname)
248        except ImportError:
249            return self.source_to_code(source)
250        else:
251            return self.source_to_code(source, path)
252
253_register(ExecutionLoader, machinery.ExtensionFileLoader)
254
255
256class FileLoader(_bootstrap_external.FileLoader, ResourceLoader, ExecutionLoader):
257
258    """Abstract base class partially implementing the ResourceLoader and
259    ExecutionLoader ABCs."""
260
261_register(FileLoader, machinery.SourceFileLoader,
262            machinery.SourcelessFileLoader)
263
264
265class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLoader):
266
267    """Abstract base class for loading source code (and optionally any
268    corresponding bytecode).
269
270    To support loading from source code, the abstractmethods inherited from
271    ResourceLoader and ExecutionLoader need to be implemented. To also support
272    loading from bytecode, the optional methods specified directly by this ABC
273    is required.
274
275    Inherited abstractmethods not implemented in this ABC:
276
277        * ResourceLoader.get_data
278        * ExecutionLoader.get_filename
279
280    """
281
282    def path_mtime(self, path):
283        """Return the (int) modification time for the path (str)."""
284        if self.path_stats.__func__ is SourceLoader.path_stats:
285            raise OSError
286        return int(self.path_stats(path)['mtime'])
287
288    def path_stats(self, path):
289        """Return a metadata dict for the source pointed to by the path (str).
290        Possible keys:
291        - 'mtime' (mandatory) is the numeric timestamp of last source
292          code modification;
293        - 'size' (optional) is the size in bytes of the source code.
294        """
295        if self.path_mtime.__func__ is SourceLoader.path_mtime:
296            raise OSError
297        return {'mtime': self.path_mtime(path)}
298
299    def set_data(self, path, data):
300        """Write the bytes to the path (if possible).
301
302        Accepts a str path and data as bytes.
303
304        Any needed intermediary directories are to be created. If for some
305        reason the file cannot be written because of permissions, fail
306        silently.
307        """
308
309_register(SourceLoader, machinery.SourceFileLoader)
310
311
312class ResourceReader(metaclass=abc.ABCMeta):
313    """Abstract base class for loaders to provide resource reading support."""
314
315    @abc.abstractmethod
316    def open_resource(self, resource: Text) -> BinaryIO:
317        """Return an opened, file-like object for binary reading.
318
319        The 'resource' argument is expected to represent only a file name.
320        If the resource cannot be found, FileNotFoundError is raised.
321        """
322        # This deliberately raises FileNotFoundError instead of
323        # NotImplementedError so that if this method is accidentally called,
324        # it'll still do the right thing.
325        raise FileNotFoundError
326
327    @abc.abstractmethod
328    def resource_path(self, resource: Text) -> Text:
329        """Return the file system path to the specified resource.
330
331        The 'resource' argument is expected to represent only a file name.
332        If the resource does not exist on the file system, raise
333        FileNotFoundError.
334        """
335        # This deliberately raises FileNotFoundError instead of
336        # NotImplementedError so that if this method is accidentally called,
337        # it'll still do the right thing.
338        raise FileNotFoundError
339
340    @abc.abstractmethod
341    def is_resource(self, path: Text) -> bool:
342        """Return True if the named 'path' is a resource.
343
344        Files are resources, directories are not.
345        """
346        raise FileNotFoundError
347
348    @abc.abstractmethod
349    def contents(self) -> Iterable[str]:
350        """Return an iterable of entries in `package`."""
351        raise FileNotFoundError
352
353
354@runtime_checkable
355class Traversable(Protocol):
356    """
357    An object with a subset of pathlib.Path methods suitable for
358    traversing directories and opening files.
359    """
360
361    @abc.abstractmethod
362    def iterdir(self):
363        """
364        Yield Traversable objects in self
365        """
366
367    def read_bytes(self):
368        """
369        Read contents of self as bytes
370        """
371        with self.open('rb') as strm:
372            return strm.read()
373
374    def read_text(self, encoding=None):
375        """
376        Read contents of self as text
377        """
378        with self.open(encoding=encoding) as strm:
379            return strm.read()
380
381    @abc.abstractmethod
382    def is_dir(self) -> bool:
383        """
384        Return True if self is a dir
385        """
386
387    @abc.abstractmethod
388    def is_file(self) -> bool:
389        """
390        Return True if self is a file
391        """
392
393    @abc.abstractmethod
394    def joinpath(self, child):
395        """
396        Return Traversable child in self
397        """
398
399    def __truediv__(self, child):
400        """
401        Return Traversable child in self
402        """
403        return self.joinpath(child)
404
405    @abc.abstractmethod
406    def open(self, mode='r', *args, **kwargs):
407        """
408        mode may be 'r' or 'rb' to open as text or binary. Return a handle
409        suitable for reading (same as pathlib.Path.open).
410
411        When opening as text, accepts encoding parameters such as those
412        accepted by io.TextIOWrapper.
413        """
414
415    @abc.abstractproperty
416    def name(self) -> str:
417        """
418        The base name of this object without any parent references.
419        """
420
421
422class TraversableResources(ResourceReader):
423    """
424    The required interface for providing traversable
425    resources.
426    """
427
428    @abc.abstractmethod
429    def files(self):
430        """Return a Traversable object for the loaded package."""
431
432    def open_resource(self, resource):
433        return self.files().joinpath(resource).open('rb')
434
435    def resource_path(self, resource):
436        raise FileNotFoundError(resource)
437
438    def is_resource(self, path):
439        return self.files().joinpath(path).is_file()
440
441    def contents(self):
442        return (item.name for item in self.files().iterdir())
443