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