Commit | Line | Data |
---|---|---|
86530b38 AT |
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, basestring) 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 |