asmodehn / filefinder2

PEP420 - implicit namespace packages for python 2.7
MIT License
3 stars 1 forks source link

Test PathFinder/FileFinder/*Loaders as an API #47

Open asmodehn opened 6 years ago

asmodehn commented 6 years ago

The simplest way to make your custom importer, should also be the easiest : reusing existing classes directly should not come with surprises.

We should integrate the pattern I use in most FileFinder even if I they dont do much, only to integrate with existing python and work around some pitfalls :


class MyFileFinder(FileFinder):

    def __init__(self, path, *loader_details):
        super(MyFileFinder, self).__init__(path, *loader_details)

    def __repr__(self):
        return 'MyFileFinder({!r})'.format(self.path)

    @classmethod
    def path_hook(cls, *loader_details):
        """A class method which returns a closure to use on sys.path_hook
        which will return an instance using the specified loaders and the path
        called on the closure.

        If the path called on the closure is not a directory, or doesnt contain
         any files with the supported extension, ImportError is raised.

         This is different from default python behavior
         but prevent polluting the cache with custom finders
        """
        def path_hook_for_MyFileFinder(path):
            """Path hook for importlib.machinery.FileFinder."""

            if not (os.path.isdir(path)):
                raise ImportError('only directories are supported')

            exts = [x for ld in loader_details for x in ld[1]]
            if not any(fname.endswith(ext) for fname in os.listdir(path) for ext in exts):
                raise ImportError(
                    'only directories containing {ext} files are supported'.format(ext=", ".join(exts)))
            return cls(path, *loader_details)
        return path_hook_for_MyFileFinder

    def find_spec(self, fullname, target=None):
        """
        Try to find a spec for the specified module.
        :param fullname: the name of the package we are trying to import
        :return: the matching spec, or None if not found.
        """

        # We attempt to load a .my file as a module
        tail_module = fullname.rpartition('.')[2]
        base_path = os.path.join(self.path, tail_module)
        for suffix, loader_class in self._loaders:
            full_path = base_path + suffix
            if os.path.isfile(full_path):  # maybe we need more checks here (importlib filefinder checks its cache...)
                return self._get_spec(loader_class, fullname, full_path, None, target)

        # Otherwise, we try find python modules
        return super(MyFileFinder, self).find_spec(fullname=fullname, target=target)

[...]

There are probably similar changes that would be useful in PathFinder. I haven't used it enough yet to know which ones would be useful.

asmodehn commented 6 years ago

For clarity : This issue is about testing the behavior when one makes a custom importer (without defining his own class). It is important, especially in this case, to add tests before starting to change existing behavior.