diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py --- a/Lib/importlib/abc.py +++ b/Lib/importlib/abc.py @@ -10,6 +10,7 @@ import abc +# XXX Superfluous now with the various __subclasshook__ implementations? def _register(abstract_cls, *classes): for cls in classes: abstract_cls.register(cls) @@ -40,11 +41,20 @@ """Abstract base class for import finders on sys.meta_path.""" + @classmethod + def __subclasshook__(cls, C): + if cls is MetaPathFinder: + if any('find_module' in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + @abc.abstractmethod def find_module(self, fullname, path): - """Abstract method which, when implemented, should find a module. - The fullname is a str and the path is a list of strings or None. - Returns a Loader object or None. + """(abstract) Return a loader for the module. + + If no module is found, return None. The fullname is a str and + the path is a list of strings or None. + """ def invalidate_caches(self): @@ -60,14 +70,25 @@ """Abstract base class for path entry finders used by PathFinder.""" + @classmethod + def __subclasshook__(cls, C): + if cls is PathEntryFinder: + if any('find_module' in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + @abc.abstractmethod def find_loader(self, fullname): - """Abstract method which, when implemented, returns a module loader or - a possible part of a namespace. - The fullname is a str. Returns a 2-tuple of (Loader, portion) where - portion is a sequence of file system locations contributing to part of - a namespace package. The sequence may be empty and the loader may be - None. + """Return (loader, namespace portion) for the path entry. + + The fullname is a str. The namespace portion is a sequence of + path entries contributing to part of a namespace package. The + sequence may be empty. If loader is not None, the portion will + be ignored. + + The portion will be discarded if another path entry finder + locates the module as a normal module or package. + """ return None, [] @@ -91,12 +112,23 @@ """ + @classmethod + def __subclasshook__(cls, C): + if cls is Loader: + if (any('exec_module' in B.__dict__ for B in C.__mro__) or + any('load_module' in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + @abc.abstractmethod def load_module(self, fullname): - """Abstract method which when implemented should load a module. - The fullname is a str. + """Return the loaded module. + + The module must be added to sys.modules and have import-related + attributes set properly. The fullname is a str. ImportError is raised on failure. + """ raise ImportError @@ -105,6 +137,7 @@ Used by the module type when the method does not raise NotImplementedError. + """ raise NotImplementedError @@ -122,6 +155,14 @@ """ + @classmethod + def __subclasshook__(cls, C): + if cls is ResourceLoader: + if (Loader.__subclasshook__(C) and + any('get_data' in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + @abc.abstractmethod def get_data(self, path): """Abstract method which when implemented should return the bytes for @@ -138,12 +179,25 @@ """ + @classmethod + def __subclasshook__(cls, C): + if cls is InspectLoader: + if (Loader.__subclasshook__(C) and + any('is_package' in B.__dict__ for B in C.__mro__) and + any('get_code' in B.__dict__ for B in C.__mro__) and + any('get_source' in B.__dict__ for B in C.__mro__) and + any('source_to_code' in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + @abc.abstractmethod def is_package(self, fullname): - """Abstract method which when implemented should return whether the - module is a package. The fullname is a str. Returns a bool. + """(abstract) Return whether the module is a package. - Raises ImportError is the module cannot be found. + The fullname is a str. + + Raises ImportError if the module cannot be found. + """ raise ImportError @@ -181,6 +235,7 @@ The name of the module is gleaned from module.__name__. The __package__ attribute is set based on self.is_package(). + """ super().init_module_attrs(module) _bootstrap._init_package_attrs(self, module) @@ -200,6 +255,14 @@ """ + @classmethod + def __subclasshook__(cls, C): + if cls is ExecutionLoader: + if (InspectLoader.__subclasshook__(C) and + any('get_filename' in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + @abc.abstractmethod def get_filename(self, fullname): """Abstract method which should return the value that __file__ is to be @@ -245,6 +308,14 @@ """Abstract base class partially implementing the ResourceLoader and ExecutionLoader ABCs.""" + @classmethod + def __subclasshook__(cls, C): + if cls is FileLoader: + if (ResourceLoader.__subclasshook__(C) and + ExecutionLoader.__subclasshook__(C)): + return True + return NotImplemented + _register(FileLoader, machinery.SourceFileLoader, machinery.SourcelessFileLoader) @@ -266,6 +337,17 @@ """ + @classmethod + def __subclasshook__(cls, C): + if cls is SourceLoader: + if (ResourceLoader.__subclasshook__(C) and + ExecutionLoader.__subclasshook__(C) and + any('path_mtime' in B.__dict__ for B in C.__mro__) and + any('path_stats' in B.__dict__ for B in C.__mro__) and + any('set_data' in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + def path_mtime(self, path): """Return the (int) modification time for the path (str).""" if self.path_stats.__func__ is SourceLoader.path_stats: