Guido van Rossum | a4deda0 | 2002-12-23 16:30:00 +0000 | [diff] [blame] | 1 | """Utilities to support packages.""" |
| 2 | |
| 3 | import os |
| 4 | import sys |
| 5 | |
| 6 | def extend_path(path, name): |
| 7 | """Extend a package's path. |
| 8 | |
| 9 | Intended use is to place the following code in a package's __init__.py: |
| 10 | |
| 11 | from pkgutil import extend_path |
| 12 | __path__ = extend_path(__path__, __name__) |
| 13 | |
| 14 | This will add to the package's __path__ all subdirectories of |
| 15 | directories on sys.path named after the package. This is useful |
| 16 | if one wants to distribute different parts of a single logical |
| 17 | package as multiple directories. |
| 18 | |
| 19 | It also looks for *.pkg files beginning where * matches the name |
| 20 | argument. This feature is similar to *.pth files (see site.py), |
| 21 | except that it doesn't special-case lines starting with 'import'. |
| 22 | A *.pkg file is trusted at face value: apart from checking for |
| 23 | duplicates, all entries found in a *.pkg file are added to the |
| 24 | path, regardless of whether they are exist the filesystem. (This |
| 25 | is a feature.) |
| 26 | |
| 27 | If the input path is not a list (as is the case for frozen |
| 28 | packages) it is returned unchanged. The input path is not |
| 29 | modified; an extended copy is returned. Items are only appended |
| 30 | to the copy at the end. |
| 31 | |
| 32 | It is assumed that sys.path is a sequence. Items of sys.path that |
| 33 | are not (unicode or 8-bit) strings referring to existing |
| 34 | directories are ignored. Unicode items of sys.path that cause |
| 35 | errors when used as filenames may cause this function to raise an |
| 36 | exception (in line with os.path.isdir() behavior). |
| 37 | """ |
| 38 | |
| 39 | if not isinstance(path, list): |
| 40 | # This could happen e.g. when this is called from inside a |
| 41 | # frozen package. Return the path unchanged in that case. |
| 42 | return path |
| 43 | |
| 44 | pname = os.path.join(*name.split('.')) # Reconstitute as relative path |
| 45 | # Just in case os.extsep != '.' |
| 46 | sname = os.extsep.join(name.split('.')) |
| 47 | sname_pkg = sname + os.extsep + "pkg" |
| 48 | init_py = "__init__" + os.extsep + "py" |
| 49 | |
| 50 | path = path[:] # Start with a copy of the existing path |
| 51 | |
| 52 | for dir in sys.path: |
| 53 | if not isinstance(dir, (str, unicode)) or not os.path.isdir(dir): |
| 54 | continue |
| 55 | subdir = os.path.join(dir, pname) |
| 56 | # XXX This may still add duplicate entries to path on |
| 57 | # case-insensitive filesystems |
| 58 | initfile = os.path.join(subdir, init_py) |
| 59 | if subdir not in path and os.path.isfile(initfile): |
| 60 | path.append(subdir) |
| 61 | # XXX Is this the right thing for subpackages like zope.app? |
| 62 | # It looks for a file named "zope.app.pkg" |
| 63 | pkgfile = os.path.join(dir, sname_pkg) |
| 64 | if os.path.isfile(pkgfile): |
| 65 | try: |
| 66 | f = open(pkgfile) |
| 67 | except IOError, msg: |
| 68 | sys.stderr.write("Can't open %s: %s\n" % |
| 69 | (pkgfile, msg)) |
| 70 | else: |
| 71 | for line in f: |
| 72 | line = line.rstrip('\n') |
| 73 | if not line or line.startswith('#'): |
| 74 | continue |
| 75 | path.append(line) # Don't check for existence! |
| 76 | f.close() |
| 77 | |
| 78 | return path |