Re: PEP proposal: Per-Module Import Path

Brett Cannon <[email protected]>
Newsgroups gmane.comp.python.import
Message-ID <CAP1=2W4BuwOe1OxRg8t7jZfTEiRHP204QoQ8tOaVo8OadXMu1g@mail.gmail.com>
On Wed, Jul 31, 2013 at 2:41 AM, Eric Snow <[email protected]>wrote:

>
>
>
> On Fri, Jul 19, 2013 at 8:51 AM, Brett Cannon <[email protected]> wrote:
>
>> If this can lead to the deprecation of .pth files then I support the
>> idea, but I think there are technical issues in terms of implementation
>> that have not been throught through yet. This is going to require an
>> implementation (even if it isn't in importlib._bootstrap but as a subclass
>> of importlib.machinery.FileFinder or something) to see how you plan to make
>> all of this work before this PEP can move beyond this SIG.
>>
>
> I'll address any outstanding concerns separately and update the PEP
> pursuant to outstanding recommendations.  In the meantime, below is a rough
> draft of the implementation.  We can factor out any artificial complexity
> I've introduced <wink/>, but it should reflect the approach that came to
> mind first for me.  You'll notice that I call _find_module() directly (for
> sys.meta_path traversal).
>
> As already noted, the whole issue of how to populate __indirect__ is
> tricky, both in how to feed it to the loader and how it should look for
> namespace packages.  I just stuck a placeholder in there for the moment.
>

You also changed the return value signature of find_loader() which is
really cheating since you can't do that (I'm really kicking myself for not
thinking through the implications of returning a tuple for find_loader()).


> The rest of the implementation is pretty trivial in comparison.  However
> it does reflect my approach to handling cycles and to aggregating the list
> for __indirect__.
>
> I'll make it more clear in the PEP that refpath resolution is
> depth-first--a consequence of doing normal loader lookup.  This means that
> in the face of a cycle the normal package/module/ns package handling
> happens rather than acting like there was an empty .ref file (but only if
> no other path entries in the .ref file pan out first).  Would it be better
> to treat this case the same as an empty .ref file?
>

I would argue a cycle is an error and you should raise ImportError.

-Brett


>
> -eric
>
>
> diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
> --- a/Lib/importlib/_bootstrap.py
> +++ b/Lib/importlib/_bootstrap.py
> @@ -1338,7 +1338,9 @@
>          sys.path_importer_cache."""
>          if path is None:
>              path = sys.path
> -        loader, namespace_path = cls._get_loader(fullname, path)
> +        loader, namespace_path, indirect = cls._get_loader(fullname, path)
> +        # XXX What to do with indirect?
> +        # XXX How to handle __indirect__ for namespace packages?
>          if loader is not None:
>              return loader
>          else:
> @@ -1372,6 +1374,7 @@
>          self._path_mtime = -1
>          self._path_cache = set()
>          self._relaxed_path_cache = set()
> +        self._visited_refnames = {}
>
>      def invalidate_caches(self):
>          """Invalidate the directory mtime."""
> @@ -1379,6 +1382,25 @@
>
>      find_module = _find_module_shim
>
> +    def process_ref_file(self, fullname, refname):
> +        """Return the path specified in a .ref file."""
> +        path = []
> +        with open(refname, encoding='UTF-8') as reffile:
> +            for line in reffile:
> +                # XXX Be more lenient on leading/trailing whitespace?
> +                line = line.strip()
> +                # ignore comments and blank lines
> +                if line.startswith("#"):
> +                    continue
> +                if not line:
> +                    continue
> +                # resolve the target
> +                if not line.startswith("/"):
> +                    line = self.path + "/" + line
> +                target = os.path.join(*line.split("/"))
> +                path.append(target)
> +        return path
> +
>      def find_loader(self, fullname):
>          """Try to find a loader for the specified module, or the namespace
>          package portions. Returns (loader, list-of-portions)."""
> @@ -1398,6 +1420,29 @@
>          else:
>              cache = self._path_cache
>              cache_module = tail_module
> +        # Handle indirection files first.
> +        if fullname not in self._visited_refnames:
> +            indirect = []
> +            visited = set()
> +            self._visited_refnames[fullname] = (indirect, visited)
> +        else:
> +            indirect, visited = self._visited_refnames[fullname]
> +        refname = _path_join(self.path, tail_module) + '.ref'
> +        if refname not in visited and refname in cache:
> +            visited.add(refname)
> +            indirect.append(refname)
> +            refpath = self.process_ref_file(fullname, refname)
> +            if not refpath:
> +                # An empty ref file is a marker for skipping this path
> entry.
> +                indirect.pop()
> +                return (None, [])
> +            loader = _find_module(fullname, refpath)
> +            if isinstance(loader, NamespaceLoader):
> +                return (None, loader._path, indirect)
> +            else:
> +                return (loader, [], indirect)
> +        if indirect and indirect[0] == refname:
> +            del self._visited_refnames[fullname]
>          # Check if the module is the name of a directory (and thus a
> package).
>          if cache_module in cache:
>              base_path = _path_join(self.path, tail_module)
>

_______________________________________________
Import-SIG mailing list
[email protected]
http://mail.python.org/mailman/listinfo/import-sig
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.