[Zope3-dev] Zip import and sys.path manipulation (was Re: directory hierarchy proposal)

Guido van Rossum guido@python.org
Mon, 16 Dec 2002 12:47:34 -0500


> Minor nit: .pth semantics should be identical to site.py .pth
> semantics.  I.e. lines that start with "import" should be exec'd.

Barry & I had an offline discussion about this.

Some conclusions:

- pth files for sys.path and pth-ish files for packages should
  probably have different extensions; otherwise e.g. paths named in
  zope.pth would be added both to sys.path and to zope.__path__, which
  is a bad idea.  I propose using pkg as the extension for package.s

- Nobody remembers why there is a special case in site.py for lines in
  the .pth file that start with 'import', except that Martin von
  Loewis once wanted this.  I propose not to duplicate this
  functionality in .pkg files.

- We need to look both for subdirectories and for .pkg files --
  different styles of administering a system call for both approaches.

- We should only accept subdirectories that have an __init__.py.

- I'm punting for now on an extension to cope with finding things in
  other importers.

Current draft, incorporating all these changes:

######################################################################
"""Utilities to support packages."""

import os
import sys

def extend_path(path, name):
    """Extend a package's path.

    Intended use is to place the following code in a package's __init__.py:

        from pkgutil import extend_path
        __path__ = extend_path(__path__, __name__)

    This will add to the package's __path__ all subdirectories of
    directories on sys.path named after the package.  This is useful
    if one wants to distribute different parts of a single logical
    package as multiple directories.

    It also looks for *.pkg files beginning with __name__.  This
    feature is similar to *.pth files, except it doesn't special-case
    lines starting with 'import'.
    """
    pname = os.path.join(*name.split('.')) # Reconstitute as relative path
    sname = os.extsep.join(name.split('.')) # Just in case os.extsep != '.'
    sname_pkg = sname + os.extsep + "pkg"
    init_py = "__init__" + os.extsep + "py"
    path = path[:]
    for dir in sys.path:
        if isinstance(dir, (str, unicode)) and os.path.isdir(dir):
            subdir = os.path.join(dir, pname)
            # XXX This may still add duplicate entries to path on
            # case-insensitive filesystems
            initfile = os.path.join(subdir, init_py)
            if subdir not in path and os.path.isfile(initfile):
                path.append(subdir)
            # XXX Is this the right thing for subpackages like
            # zope.app?  It looks for a file named "zope.app.pkg"
            pkgfile = os.path.join(dir, sname_pkg)
            if os.path.isfile(pkgfile):
                try:
                    f = open(pkgfile)
                except IOError, msg:
                    sys.stderr.write("Can't open %s: %s\n" %
                                     (pkgfile, msg))
                else:
                    for line in f:
                        line = line.rstrip('\n')
                        if not line or line.startswith('#'):
                            continue
                        path.append(line) # Don't check for existence!
                    f.close()
    return path
######################################################################

--Guido van Rossum (home page: http://www.python.org/~guido/)