Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 1 | """Prototype of 'import' functionality enhanced to implement packages. |
| 2 | |
| 3 | Why packages? Packages enable module nesting and sibling module |
| 4 | imports. 'Til now, the python module namespace was flat, which |
| 5 | means every module had to have a unique name, in order to not |
| 6 | conflict with names of other modules on the load path. Furthermore, |
| 7 | suites of modules could not be structurally affiliated with one |
| 8 | another. |
| 9 | |
| 10 | With packages, a suite of, eg, email-oriented modules can include a |
| 11 | module named 'mailbox', without conflicting with the, eg, 'mailbox' |
| 12 | module of a shared-memory suite - 'email.mailbox' vs |
| 13 | 'shmem.mailbox'. Packages also enable modules within a suite to |
| 14 | load other modules within their package without having the package |
| 15 | name hard-coded. Similarly, package suites of modules can be loaded |
| 16 | as a unit, by loading the package that contains them. |
| 17 | |
| 18 | Usage: once installed (newimp.install(); newimp.revert() to revert to |
| 19 | the prior __import__ routine), 'import ...' and 'from ... import ...' |
| 20 | can be used to: |
| 21 | |
| 22 | - import modules from the search path, as before. |
| 23 | |
| 24 | - import modules from within other directory "packages" on the search |
| 25 | path using a '.' dot-delimited nesting syntax. The nesting is fully |
| 26 | recursive. |
| 27 | |
Guido van Rossum | 96044da | 1995-04-07 09:06:50 +0000 | [diff] [blame] | 28 | For example, 'import test.test_types' will import the test_types |
| 29 | module within the 'test' package. The calling environment would |
| 30 | then access the module as 'test.test_types', which is the name of |
| 31 | the fully-loaded 'test_types' module. It is found contained within |
| 32 | the stub (ie, only partially loaded) 'test' module, hence accessed as |
| 33 | 'test.test_types'. |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 34 | |
| 35 | - import siblings from modules within a package, using '__.' as a shorthand |
| 36 | prefix to refer to the parent package. This enables referential |
| 37 | transparency - package modules need not know their package name. |
| 38 | |
Guido van Rossum | 96044da | 1995-04-07 09:06:50 +0000 | [diff] [blame] | 39 | The '__' package references are actually names assigned within |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 40 | modules, to refer to their containing package. This means that |
Guido van Rossum | 96044da | 1995-04-07 09:06:50 +0000 | [diff] [blame] | 41 | variable references can be made to imported modules, or to variables |
| 42 | defined via 'import ... from', also using the '__.var' shorthand |
| 43 | notation. This establishes a proper equivalence between the import |
| 44 | reference '__.sibling' and the var reference '__.sibling'. |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 45 | |
Guido van Rossum | 96044da | 1995-04-07 09:06:50 +0000 | [diff] [blame] | 46 | - import an entire package as a unit, by importing the package directory. |
| 47 | If there is a module named '__main__.py' in the package, it controls the |
| 48 | load. Otherwise, all the modules in the dir, including packages, are |
| 49 | inherently loaded into the package module's namespace. |
| 50 | |
| 51 | For example, 'import test' will load the modules of the entire 'test' |
| 52 | package, at least until a test failure is encountered. |
| 53 | |
| 54 | In a package, a module with the name '__main__' has a special role. |
| 55 | If present in a package directory, then it is loaded into the package |
| 56 | module, instead of loading the contents of the directory. This |
| 57 | enables the __main__ module to control the load, possibly loading |
| 58 | the entire directory deliberately (using 'import __', or even |
| 59 | 'from __ import *', to load all the module contents directly into the |
| 60 | package module). |
| 61 | |
| 62 | - perform any combination of the above - have a package that contains |
| 63 | packages, etc. |
| 64 | |
| 65 | Modules have a few new attributes in support of packages. As mentioned |
| 66 | above, '__' is a shorthand attribute denoting the modules' parent package, |
| 67 | also denoted in the module by '__package__'. Additionally, modules have |
| 68 | associated with them a '__pkgpath__', a path by which sibling modules are |
| 69 | found.""" |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 70 | |
Guido van Rossum | f4ef7e6 | 1995-06-22 18:50:15 +0000 | [diff] [blame^] | 71 | __version__ = "Revision: 1.14" |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 72 | |
Guido van Rossum | f4ef7e6 | 1995-06-22 18:50:15 +0000 | [diff] [blame^] | 73 | # Id: newimp.py,v 1.14 1995/05/10 19:15:07 klm Exp |
| 74 | # First release: Ken.Manheimer@nist.gov, 5-Apr-1995, for python 1.2 |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 75 | |
| 76 | # Developers Notes: |
| 77 | # |
| 78 | # - 'sys.stub_modules' registers "incidental" (partially loaded) modules. |
| 79 | # A stub module is promoted to the fully-loaded 'sys.modules' list when it is |
| 80 | # explicitly loaded as a unit. |
| 81 | # - The __main__ loads of '__' have not yet been tested. |
| 82 | # - The test routines are cool, including a transient directory |
| 83 | # hierarchy facility, and a means of skipping to later tests by giving |
| 84 | # the test routine a numeric arg. |
Guido van Rossum | f4ef7e6 | 1995-06-22 18:50:15 +0000 | [diff] [blame^] | 85 | # - This could be substantially optimized, and there are some loose ends |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 86 | # lying around, since i wanted to get this released for 1.2. |
| 87 | |
| 88 | VERBOSE = 0 |
| 89 | |
| 90 | import sys, string, regex, types, os, marshal, new, __main__ |
| 91 | try: |
| 92 | import imp # Build on this recent addition |
| 93 | except ImportError: |
| 94 | raise ImportError, 'Pkg import module depends on optional "imp" module' |
| 95 | |
| 96 | from imp import SEARCH_ERROR, PY_SOURCE, PY_COMPILED, C_EXTENSION |
| 97 | PY_PACKAGE = 4 # In addition to above PY_* |
| 98 | |
| 99 | modes = {SEARCH_ERROR: 'SEARCH_ERROR', |
| 100 | PY_SOURCE: 'PY_SOURCE', |
| 101 | PY_COMPILED: 'PY_COMPILED', |
| 102 | C_EXTENSION: 'C_EXTENSION', |
| 103 | PY_PACKAGE: 'PY_PACKAGE'} |
| 104 | |
| 105 | # sys.stub_modules tracks modules partially loaded modules, ie loaded only |
| 106 | # incidental to load of nested components. |
| 107 | |
| 108 | try: sys.stub_modules |
| 109 | except AttributeError: |
| 110 | sys.stub_modules = {} |
| 111 | |
| 112 | # Environment setup - "root" module, '__python__' |
| 113 | |
| 114 | # Establish root package '__python__' in __main__ and newimp envs. |
| 115 | |
| 116 | PKG_MAIN_NM = '__main__' # 'pkg/__main__.py' master, if present. |
| 117 | PKG_NM = '__package__' # Longhand for module's container. |
| 118 | PKG_SHORT_NM = '__' # Shorthand for module's container. |
| 119 | PKG_SHORT_NM_LEN = len(PKG_SHORT_NM) |
| 120 | PKG_PATH = '__pkgpath__' # Var holding package search path, |
| 121 | # usually just the path of the pkg dir. |
| 122 | __python__ = __main__ |
| 123 | sys.modules['__python__'] = __python__ # Register as an importable module. |
| 124 | __python__.__dict__[PKG_PATH] = sys.path |
| 125 | |
| 126 | origImportFunc = None |
| 127 | def install(): |
| 128 | """Install newimp import_module() routine, for package support. |
| 129 | |
| 130 | newimp.revert() reverts to __import__ routine that was superceded.""" |
Guido van Rossum | 96044da | 1995-04-07 09:06:50 +0000 | [diff] [blame] | 131 | import __builtin__ |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 132 | global origImportFunc |
| 133 | if not origImportFunc: |
| 134 | try: |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 135 | origImportFunc = __builtin__.__import__ |
| 136 | except AttributeError: |
| 137 | pass |
| 138 | __builtin__.__import__ = import_module |
| 139 | print 'Enhanced import functionality installed.' |
| 140 | def revert(): |
| 141 | """Revert to original __builtin__.__import__ func, if newimp.install() has |
| 142 | been executed.""" |
| 143 | if origImportFunc: |
| 144 | import __builtin__ |
| 145 | __builtin__.__import__ = origImportFunc |
| 146 | print 'Original import routine back in place.' |
| 147 | |
| 148 | def import_module(name, |
| 149 | envLocals=None, envGlobals=None, |
| 150 | froms=None, |
| 151 | inPkg=None): |
| 152 | """Primary service routine implementing 'import' with package nesting.""" |
| 153 | |
| 154 | # The job is divided into a few distinct steps: |
| 155 | # |
| 156 | # - Look for either an already loaded module or a file to be loaded. |
| 157 | # * if neither loaded module nor prospect file is found, raise an error. |
| 158 | # - If we have a file, not an already loaded module: |
| 159 | # - Load the file into a module. |
| 160 | # - Register the new module and intermediate package stubs. |
| 161 | # (We have a module at this point...) |
| 162 | # - Bind requested syms (module or specified 'from' defs) in calling env. |
| 163 | # - Return the appropriate component. |
| 164 | |
| 165 | note("import_module: seeking '%s'%s" % |
| 166 | (name, ((inPkg and ' (in package %s)' % inPkg.__name__) or ''))) |
| 167 | |
| 168 | # We need callers environment dict for local path and resulting module |
| 169 | # binding. |
| 170 | if not (envLocals or envGlobals): |
| 171 | envLocals, envGlobals = exterior() |
| 172 | |
| 173 | modList = theMod = absNm = container = None |
| 174 | |
| 175 | # Get module obj if one already established, or else module file if not: |
| 176 | |
| 177 | if inPkg: |
| 178 | # We've been invoked with a specific containing package: |
| 179 | pkg, pkgPath, pkgNm = inPkg, inPkg.__dict__[PKG_PATH], inPkg.__name__ |
| 180 | relNm = name |
| 181 | absNm = pkgNm + '.' + name |
| 182 | |
| 183 | elif name[:PKG_SHORT_NM_LEN+1] != PKG_SHORT_NM + '.': |
| 184 | # name is NOT '__.something' - setup to seek according to specified |
| 185 | # absolute name. |
| 186 | pkg = __python__ |
| 187 | pkgPath = sys.path |
| 188 | absNm = name |
| 189 | relNm = absNm |
| 190 | |
| 191 | else: |
| 192 | # name IS '__.' + something - setup to seek according to relative name, |
| 193 | # in current package. |
| 194 | |
| 195 | relNm = name[len(PKG_SHORT_NM)+1:] # Relative portion of name. |
| 196 | try: |
| 197 | pkg = envGlobals[PKG_NM] # The immediately containing package. |
| 198 | pkgPath = pkg.__dict__[PKG_PATH] |
| 199 | if pkg == __python__: # At outermost package. |
| 200 | absNm = relNm |
| 201 | else: |
| 202 | absNm = (pkg.__name__ + '.' + relNm) |
| 203 | except KeyError: # Missing package, path, or name. |
| 204 | note("Can't identify parent package, package name, or pkgpath") |
| 205 | pass # ==v |
| 206 | |
| 207 | # Try to find existing module: |
| 208 | if sys.modules.has_key(absNm): |
| 209 | note('found ' + absNm + ' already imported') |
| 210 | theMod = sys.modules[absNm] |
| 211 | else: |
| 212 | # Try for builtin or frozen first: |
| 213 | theMod = imp.init_builtin(absNm) |
| 214 | if theMod: |
| 215 | note('found builtin ' + absNm) |
| 216 | else: |
| 217 | theMod = imp.init_frozen(absNm) |
| 218 | if theMod: |
| 219 | note('found frozen ' + absNm) |
| 220 | if not theMod: |
| 221 | if type(pkgPath) == types.StringType: |
| 222 | pkgPath = [pkgPath] |
| 223 | modList = find_module(relNm, pkgPath, absNm) |
| 224 | if not modList: |
| 225 | raise ImportError, "module '%s' not found" % absNm # ===X |
| 226 | # We have a list of successively nested files leading to the |
| 227 | # module, register them as stubs: |
| 228 | container = register_module_nesting(modList, pkg) |
| 229 | |
| 230 | # Load from file if necessary and possible: |
| 231 | modNm, modf, path, ty = modList[-1] |
| 232 | note('found type ' + modes[ty[2]] + ' - ' + absNm) |
| 233 | |
| 234 | # Do the load: |
| 235 | theMod = load_module(absNm, ty[2], modf, inPkg) |
| 236 | |
| 237 | # Loaded successfully - promote module to full module status: |
| 238 | register_module(theMod, theMod.__name__, pkgPath, pkg) |
| 239 | |
| 240 | # Have a loaded module, impose designated components, and return |
| 241 | # appropriate thing - according to guido: |
| 242 | # "Note that for "from spam.ham import bacon" your function should |
| 243 | # return the object denoted by 'spam.ham', while for "import |
| 244 | # spam.ham" it should return the object denoted by 'spam' -- the |
| 245 | # STORE instructions following the import statement expect it this |
| 246 | # way." |
| 247 | if not froms: |
| 248 | # Establish the module defs in the importing name space: |
| 249 | (envLocals or envGlobals)[name] = theMod |
| 250 | return (container or theMod) |
| 251 | else: |
| 252 | # Implement 'from': Populate immediate env with module defs: |
Guido van Rossum | b1c1315 | 1995-05-05 15:50:56 +0000 | [diff] [blame] | 253 | while froms: |
| 254 | item = froms[0]; del froms[0] |
| 255 | if item == '*': |
| 256 | froms = theMod.__dict__.keys() + froms |
| 257 | else: |
| 258 | try: |
| 259 | (envLocals or envGlobals)[item] = theMod.__dict__[item] |
| 260 | except KeyError: |
| 261 | raise ImportError, ("name '%s' not found in module %s" % |
| 262 | (item, theMod.__name__)) |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 263 | return theMod |
| 264 | |
| 265 | def unload(module): |
| 266 | """Remove registration for a module, so import will do a fresh load.""" |
| 267 | if type(module) == types.ModuleType: |
| 268 | module = module.__name__ |
| 269 | for m in [sys.modules, sys.stub_modules]: |
| 270 | try: |
| 271 | del m[module] |
| 272 | except KeyError: |
| 273 | pass |
| 274 | |
| 275 | def find_module(name, path, absNm=''): |
| 276 | """Locate module NAME on PATH. PATH is pathname string or a list of them. |
| 277 | |
| 278 | Note that up-to-date compiled versions of a module are preferred to plain |
| 279 | source, and compilation is automatically performed when necessary and |
| 280 | possible. |
| 281 | |
| 282 | Returns a list of the tuples returned by 'find_module_file' (cf), one for |
| 283 | each nested level, deepest last.""" |
| 284 | |
| 285 | checked = [] # For avoiding redundant dir lists. |
| 286 | |
| 287 | if not absNm: absNm = name |
| 288 | |
| 289 | # Parse name into list of nested components, |
| 290 | expNm = string.splitfields(name, '.') |
| 291 | |
| 292 | for curPath in path: |
| 293 | |
| 294 | if (type(curPath) != types.StringType) or (curPath in checked): |
| 295 | # Disregard bogus or already investigated path elements: |
| 296 | continue # ==^ |
| 297 | else: |
| 298 | # Register it for subsequent disregard. |
| 299 | checked.append(curPath) |
| 300 | |
| 301 | if len(expNm) == 1: |
| 302 | |
| 303 | # Non-nested module name: |
| 304 | |
| 305 | got = find_module_file(curPath, absNm) |
| 306 | if got: |
| 307 | note('using %s' % got[2], 2) |
| 308 | return [got] # ===> |
| 309 | |
| 310 | else: |
| 311 | |
| 312 | # Composite name specifying nested module: |
| 313 | |
| 314 | gotList = []; nameAccume = expNm[0] |
| 315 | |
| 316 | got = find_module_file(curPath, nameAccume) |
| 317 | if not got: # Continue to next prospective path. |
| 318 | continue # ==^ |
| 319 | else: |
| 320 | gotList.append(got) |
| 321 | nm, file, fullPath, ty = got |
| 322 | |
| 323 | # Work on successively nested components: |
| 324 | for component in expNm[1:]: |
| 325 | # 'ty'pe of containing component must be package: |
| 326 | if ty[2] != PY_PACKAGE: |
| 327 | gotList, got = [], None |
| 328 | break # ==v^ |
| 329 | if nameAccume: |
| 330 | nameAccume = nameAccume + '.' + component |
| 331 | else: |
| 332 | nameAccume = component |
| 333 | got = find_module_file(fullPath, nameAccume) |
| 334 | if got: |
| 335 | gotList.append(got) |
| 336 | # ** have to return the *full* name here: |
| 337 | nm, file, fullPath, ty = got |
| 338 | else: |
| 339 | # Clear state vars: |
| 340 | gotList, got, nameAccume = [], None, '' |
| 341 | break # ==v^ |
| 342 | # Found nesting all the way to the specified tip: |
| 343 | if got: |
| 344 | return gotList # ===> |
| 345 | |
| 346 | # Failed. |
| 347 | return None |
| 348 | |
| 349 | def find_module_file(pathNm, modname): |
| 350 | """Find module file given dir PATHNAME and module NAME. |
| 351 | |
| 352 | If successful, returns quadruple consisting of a mod name, file object, |
| 353 | PATHNAME for the found file, and a description triple as contained in the |
| 354 | list returned by get_suffixes. |
| 355 | |
| 356 | Otherwise, returns None. |
| 357 | |
| 358 | Note that up-to-date compiled versions of a module are preferred to plain |
| 359 | source, and compilation is automatically performed, when necessary and |
| 360 | possible.""" |
| 361 | |
| 362 | relNm = string.splitfields(modname,'.')[-1] |
| 363 | |
| 364 | if pathNm[-1] != '/': pathNm = pathNm + '/' |
| 365 | |
| 366 | for suff, mode, ty in get_suffixes(): |
| 367 | note('trying ' + pathNm + relNm + suff + '...', 3) |
| 368 | fullPath = pathNm + relNm + suff |
| 369 | try: |
| 370 | modf = open(fullPath, mode) |
| 371 | except IOError: |
| 372 | # ?? Skip unreadable ones. |
| 373 | continue # ==^ |
| 374 | |
| 375 | if ty == PY_PACKAGE: |
| 376 | # Enforce directory characteristic: |
| 377 | if not os.path.isdir(fullPath): |
| 378 | note('Skipping non-dir match ' + fullPath) |
| 379 | continue # ==^ |
| 380 | else: |
| 381 | return (modname, modf, fullPath, (suff, mode, ty)) # ===> |
| 382 | |
| 383 | |
| 384 | elif ty == PY_SOURCE: |
| 385 | # Try for a compiled version: |
| 386 | note('found source ' + fullPath, 2) |
| 387 | pyc = fullPath + 'c' # Sadly, we're presuming '.py' suff. |
| 388 | if (not os.path.exists(pyc) or |
| 389 | (os.stat(fullPath)[8] > os.stat(pyc)[8])): |
| 390 | # Try to compile: |
| 391 | pyc = compile_source(fullPath, modf) |
| 392 | if pyc and (os.stat(fullPath)[8] < os.stat(pyc)[8]): |
| 393 | # Either pyc was already newer or we just made it so; in either |
| 394 | # case it's what we crave: |
| 395 | return (modname, open(pyc, 'rb'), pyc, # ===> |
| 396 | ('.pyc', 'rb', PY_COMPILED)) |
| 397 | # Couldn't get a compiled version - return the source: |
| 398 | return (modname, modf, fullPath, (suff, mode, ty)) # ===> |
| 399 | |
| 400 | elif ty == PY_COMPILED: |
| 401 | # Make sure it is current, trying to compile if necessary, and |
| 402 | # prefer source failing that: |
| 403 | note('found compiled ' + fullPath, 2) |
| 404 | py = fullPath[:-1] # Sadly again, presuming '.pyc' suff. |
| 405 | if not os.path.exists(py): |
| 406 | note('found pyc sans py: ' + fullPath) |
| 407 | return (modname, modf, fullPath, (suff, mode, ty)) # ===> |
| 408 | elif (os.stat(py)[8] > os.stat(fullPath)[8]): |
| 409 | note('forced to try compiling: ' + py) |
| 410 | pyc = compile_source(py, modf) |
| 411 | if pyc: |
| 412 | return (modname, modf, fullPath, (suff, mode, ty)) # ===> |
| 413 | else: |
| 414 | note('failed compile - must use more recent .py') |
| 415 | return (modname, # ===> |
| 416 | open(py, 'r'), py, ('.py', 'r', PY_SOURCE)) |
| 417 | else: |
| 418 | return (modname, modf, fullPath, (suff, mode, ty)) # ===> |
| 419 | |
| 420 | elif ty == C_EXTENSION: |
| 421 | note('found extension ' + fullPath, 2) |
| 422 | return (modname, modf, fullPath, (suff, mode, ty)) # ===> |
| 423 | |
| 424 | else: |
| 425 | raise SystemError, 'Unanticipated (new?) module type encountered' |
| 426 | |
| 427 | return None |
| 428 | |
| 429 | |
| 430 | def load_module(name, ty, theFile, fromMod=None): |
| 431 | """Load module NAME, type TYPE, from file FILE. |
| 432 | |
| 433 | Optional arg fromMod indicated the module from which the load is being done |
| 434 | - necessary for detecting import of __ from a package's __main__ module. |
| 435 | |
| 436 | Return the populated module object.""" |
| 437 | |
| 438 | # Note: we mint and register intermediate package directories, as necessary |
| 439 | |
| 440 | # Determine packagepath extension: |
| 441 | |
| 442 | # Establish the module object in question: |
| 443 | theMod = procure_module(name) |
| 444 | nameTail = string.splitfields(name, '.')[-1] |
| 445 | thePath = theFile.name |
| 446 | |
| 447 | if ty == PY_SOURCE: |
| 448 | exec_into(theFile, theMod, theFile.name) |
| 449 | |
| 450 | elif ty == PY_COMPILED: |
| 451 | pyc = open(theFile.name, 'rb').read() |
| 452 | if pyc[0:4] != imp.get_magic(): |
| 453 | raise ImportError, 'bad magic number: ' + theFile.name # ===> |
| 454 | code = marshal.loads(pyc[8:]) |
| 455 | exec_into(code, theMod, theFile.name) |
| 456 | |
| 457 | elif ty == C_EXTENSION: |
| 458 | try: |
| 459 | theMod = imp.load_dynamic(nameTail, thePath, theFile) |
| 460 | except: |
| 461 | # ?? Ok to embellish the error message? |
| 462 | raise sys.exc_type, ('%s (from %s)' % |
| 463 | (str(sys.exc_value), theFile.name)) |
| 464 | |
| 465 | elif ty == PY_PACKAGE: |
| 466 | # Load constituents: |
| 467 | if (os.path.exists(thePath + '/' + PKG_MAIN_NM) and |
| 468 | # pkg has a __main__, and this import not already from __main__, so |
| 469 | # __main__ can 'import __', or even better, 'from __ import *' |
| 470 | ((theMod.__name__ != PKG_MAIN_NM) and (fromMod.__ == theMod))): |
| 471 | exec_into(thePath + '/' + PKG_MAIN_NM, theMod, theFile.name) |
| 472 | else: |
| 473 | # ... or else recursively load constituent modules. |
| 474 | prospects = mod_prospects(thePath) |
| 475 | for item in prospects: |
| 476 | theMod.__dict__[item] = import_module(item, |
| 477 | theMod.__dict__, |
| 478 | theMod.__dict__, |
| 479 | None, |
| 480 | theMod) |
| 481 | |
| 482 | else: |
| 483 | raise ImportError, 'Unimplemented import type: %s' % ty # ===> |
| 484 | |
| 485 | return theMod |
| 486 | |
| 487 | def exec_into(obj, module, path): |
| 488 | """Helper for load_module, execfile/exec path or code OBJ within MODULE.""" |
| 489 | |
| 490 | # This depends on ability of exec and execfile to mutilate, erhm, mutate |
| 491 | # the __dict__ of a module. It will not work if/when this becomes |
| 492 | # disallowed, as it is for normal assignments. |
| 493 | |
| 494 | try: |
| 495 | if type(obj) == types.FileType: |
| 496 | execfile(path, module.__dict__, module.__dict__) |
| 497 | elif type(obj) in [types.CodeType, types.StringType]: |
| 498 | exec obj in module.__dict__, module.__dict__ |
| 499 | except: |
| 500 | # ?? Ok to embellish the error message? |
| 501 | raise sys.exc_type, ('%s (from %s)' % |
| 502 | (str(sys.exc_value), path)) |
| 503 | |
| 504 | |
| 505 | def mod_prospects(path): |
| 506 | """Return a list of prospective modules within directory PATH. |
| 507 | |
| 508 | We actually return the distinct names resulting from stripping the dir |
| 509 | entries (excluding '.' and '..') of their suffixes (as represented by |
| 510 | 'get_suffixes'). |
| 511 | |
| 512 | (Note that matches for the PY_PACKAGE type with null suffix are |
| 513 | implicitly constrained to be directories.)""" |
| 514 | |
| 515 | # We actually strip the longest matching suffixes, so eg 'dbmmodule.so' |
| 516 | # mates with 'module.so' rather than '.so'. |
| 517 | |
| 518 | dirList = os.listdir(path) |
| 519 | excludes = ['.', '..'] |
| 520 | sortedSuffs = sorted_suffixes() |
| 521 | entries = [] |
| 522 | for item in dirList: |
| 523 | if item in excludes: continue # ==^ |
| 524 | for suff in sortedSuffs: |
| 525 | sub = -1 * len(suff) |
| 526 | if sub == 0: |
| 527 | if os.path.isdir(os.path.join(path, item)): |
| 528 | entries.append(item) |
| 529 | elif item[sub:] == suff: |
| 530 | it = item[:sub] |
| 531 | if not it in entries: |
| 532 | entries.append(it) |
| 533 | break # ==v^ |
| 534 | return entries |
| 535 | |
| 536 | |
| 537 | |
| 538 | def procure_module(name): |
| 539 | """Return an established or else new module object having NAME. |
| 540 | |
| 541 | First checks sys.modules, then sys.stub_modules.""" |
| 542 | |
| 543 | if sys.modules.has_key(name): |
| 544 | it = sys.modules[name] |
| 545 | elif sys.stub_modules.has_key(name): |
| 546 | it = sys.stub_modules[name] |
| 547 | else: |
| 548 | it = new.module(name) |
| 549 | return it # ===> |
| 550 | |
| 551 | def register_module_nesting(modList, pkg): |
| 552 | """Given a find_module()-style NESTING and a parent PACKAGE, register |
| 553 | components as stub modules.""" |
| 554 | container = None |
| 555 | for stubModNm, stubModF, stubPath, stubTy in modList: |
| 556 | relStubNm = string.splitfields(stubModNm, '.')[-1] |
| 557 | if sys.modules.has_key(stubModNm): |
| 558 | # Nestle in containing package: |
| 559 | stubMod = sys.modules[stubModNm] |
| 560 | pkg.__dict__[relStubNm] = stubMod |
| 561 | pkg = stubMod # will be parent for next in sequence. |
| 562 | elif sys.stub_modules.has_key(stubModNm): |
| 563 | stubMod = sys.stub_modules[stubModNm] |
| 564 | pkg.__dict__[relStubNm] = stubMod |
| 565 | pkg = stubMod |
| 566 | else: |
| 567 | stubMod = procure_module(stubModNm) |
| 568 | # Register as a stub: |
| 569 | register_module(stubMod, stubModNm, stubPath, pkg, 1) |
| 570 | pkg.__dict__[relStubNm] = stubMod |
| 571 | pkg = stubMod |
| 572 | if not container: |
| 573 | container = stubMod |
| 574 | return container |
| 575 | |
| 576 | def register_module(theMod, name, path, package, stub=0): |
| 577 | """Properly register MODULE, w/ name, path, package, opt, as stub.""" |
| 578 | |
| 579 | if stub: |
| 580 | sys.stub_modules[name] = theMod |
| 581 | else: |
| 582 | sys.modules[name] = theMod |
| 583 | if sys.stub_modules.has_key(name): |
| 584 | del sys.stub_modules[name] |
| 585 | theMod.__ = theMod.__dict__[PKG_NM] = package |
| 586 | theMod.__dict__[PKG_PATH] = path |
| 587 | |
| 588 | |
| 589 | def compile_source(sourcePath, sourceFile): |
| 590 | """Given python code source path and file obj, Create a compiled version. |
| 591 | |
| 592 | Return path of compiled version, or None if file creation is not |
| 593 | successful. (Compilation errors themselves are passed without restraint.) |
| 594 | |
| 595 | This is an import-private interface, and not well-behaved for general use. |
| 596 | |
| 597 | In particular, we presume the validity of the sourcePath, and that it |
| 598 | includes a '.py' extension.""" |
| 599 | |
| 600 | compiledPath = sourcePath[:-3] + '.pyc' |
| 601 | try: |
| 602 | compiledFile = open(compiledPath, 'wb') |
| 603 | except IOError: |
| 604 | note("write permission denied to " + compiledPath) |
| 605 | return None |
| 606 | mtime = os.stat(sourcePath)[8] |
| 607 | sourceFile.seek(0) # rewind |
| 608 | try: |
Guido van Rossum | f4ef7e6 | 1995-06-22 18:50:15 +0000 | [diff] [blame^] | 609 | compiled = compile(sourceFile.read(), sourcePath, 'exec') |
| 610 | except SyntaxError: |
| 611 | # Doctor the exception a bit, to include the source file name in the |
| 612 | # report, and then reraise the doctored version. |
| 613 | os.unlink(compiledFile.name) |
| 614 | sys.exc_value = ((sys.exc_value[0] + ' in ' + sourceFile.name,) |
| 615 | + sys.exc_value[1:]) |
| 616 | raise sys.exc_type, sys.exc_value |
| 617 | try: |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 618 | compiledFile.write(imp.get_magic()) # compiled magic number |
| 619 | compiledFile.seek(8, 0) # mtime space holder |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 620 | marshal.dump(compiled, compiledFile) # write the code obj |
| 621 | compiledFile.seek(4, 0) # position for mtime |
| 622 | compiledFile.write(marshal.dumps(mtime)[1:]) # register mtime |
| 623 | compiledFile.flush() |
| 624 | compiledFile.close() |
| 625 | return compiledPath |
| 626 | except IOError: |
Guido van Rossum | f4ef7e6 | 1995-06-22 18:50:15 +0000 | [diff] [blame^] | 627 | return None # ===> |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 628 | |
| 629 | |
| 630 | def PathExtension(locals, globals): # Probably obsolete. |
| 631 | """Determine import search path extension vis-a-vis __pkgpath__ entries. |
| 632 | |
| 633 | local dict __pkgpath__ will preceed global dict __pkgpath__.""" |
| 634 | |
| 635 | pathadd = [] |
| 636 | if globals and globals.has_key(PKG_PATH): |
| 637 | pathadd = PrependPath(pathadd, globals[PKG_PATH], 'global') |
| 638 | if locals and locals.has_key(PKG_PATH): |
| 639 | pathadd = PrependPath(pathadd, locals[PKG_PATH], 'local') |
| 640 | if pathadd: |
| 641 | note(PKG_PATH + ' extension: ' + pathadd) |
| 642 | return pathadd |
| 643 | |
| 644 | def PrependPath(path, pre, whence): # Probably obsolete |
| 645 | """Return copy of PATH list with string or list-of-strings PRE prepended. |
| 646 | |
| 647 | If PRE is neither a string nor list-of-strings, print warning that |
| 648 | locality WHENCE has malformed value.""" |
| 649 | |
| 650 | # (There is probably a better way to handle malformed PREs, but raising an |
| 651 | # error seems too severe - in that case, a bad setting for |
| 652 | # sys.__pkgpath__ would prevent any imports!) |
| 653 | |
| 654 | if type(pre) == types.StringType: return [pre] + path[:] # ===> |
| 655 | elif type(pre) == types.ListType: return pre + path[:] # ===> |
| 656 | else: |
| 657 | print "**Ignoring '%s' bad %s value**" % (whence, PKG_PATH) |
| 658 | return path # ===> |
| 659 | |
| 660 | got_suffixes = None |
| 661 | def get_suffixes(): |
| 662 | """Produce a list of triples, each describing a type of import file. |
| 663 | |
| 664 | Triples have the form '(SUFFIX, MODE, TYPE)', where: |
| 665 | |
| 666 | SUFFIX is a string found appended to a module name to make a filename for |
| 667 | that type of import file. |
| 668 | |
| 669 | MODE is the mode string to be passed to the built-in 'open' function - "r" |
| 670 | for text files, "rb" for binary. |
| 671 | |
| 672 | TYPE is the file type: |
| 673 | |
| 674 | PY_SOURCE: python source code, |
| 675 | PY_COMPILED: byte-compiled python source, |
| 676 | C_EXTENSION: compiled-code object file, |
| 677 | PY_PACKAGE: python library directory, or |
| 678 | SEARCH_ERROR: no module found. """ |
| 679 | |
| 680 | # Note: sorted_suffixes() depends on this function's value being invariant. |
| 681 | # sorted_suffixes() must be revised if this becomes untrue. |
| 682 | |
| 683 | global got_suffixes |
| 684 | |
| 685 | if got_suffixes: |
| 686 | return got_suffixes |
| 687 | else: |
| 688 | # Ensure that the .pyc suffix precedes the .py: |
| 689 | got_suffixes = [('', 'r', PY_PACKAGE)] |
| 690 | py = pyc = None |
| 691 | for suff in imp.get_suffixes(): |
| 692 | if suff[0] == '.py': |
| 693 | py = suff |
| 694 | elif suff[0] == '.pyc': |
| 695 | pyc = suff |
| 696 | else: |
| 697 | got_suffixes.append(suff) |
| 698 | got_suffixes.append(pyc) |
| 699 | got_suffixes.append(py) |
| 700 | return got_suffixes |
| 701 | |
| 702 | |
| 703 | sortedSuffs = [] # State vars for sorted_suffixes(). Go |
| 704 | def sorted_suffixes(): |
| 705 | """Helper function ~efficiently~ tracks sorted list of module suffixes.""" |
| 706 | |
| 707 | # Produce sortedSuffs once - this presumes that get_suffixes does not |
| 708 | # change from call to call during a python session. Needs to be |
| 709 | # corrected if that becomes no longer true. |
| 710 | |
| 711 | global sortedsuffs |
| 712 | if not sortedSuffs: # do compute only the "first" time |
| 713 | for item in get_suffixes(): |
| 714 | sortedSuffs.append(item[0]) |
| 715 | # Sort them in descending order: |
| 716 | sortedSuffs.sort(lambda x, y: (((len(x) > len(y)) and 1) or |
| 717 | ((len(x) < len(y)) and -1))) |
| 718 | sortedSuffs.reverse() |
| 719 | return sortedSuffs |
| 720 | |
| 721 | |
| 722 | # exterior(): Utility routine, obtain local and global dicts of environment |
| 723 | # containing/outside the callers environment, ie that of the |
| 724 | # caller's caller. Routines can use exterior() to determine the |
| 725 | # environment from which they were called. |
| 726 | |
| 727 | def exterior(): |
| 728 | """Return dyad containing locals and globals of caller's caller. |
| 729 | |
| 730 | Locals will be None if same as globals, ie env is global env.""" |
| 731 | |
| 732 | bogus = 'bogus' # A locally usable exception |
| 733 | try: raise bogus # Force an exception object |
| 734 | except bogus: |
| 735 | at = sys.exc_traceback.tb_frame.f_back # The external frame. |
| 736 | if at.f_back: at = at.f_back # And further, if any. |
| 737 | globals, locals = at.f_globals, at.f_locals |
| 738 | if locals == globals: # Exterior is global? |
| 739 | locals = None |
| 740 | return (locals, globals) |
| 741 | |
| 742 | ######################################################################### |
| 743 | # TESTING FACILITIES # |
| 744 | |
| 745 | def note(msg, threshold=1): |
Guido van Rossum | 96044da | 1995-04-07 09:06:50 +0000 | [diff] [blame] | 746 | if VERBOSE >= threshold: sys.stderr.write('(import: ' + msg + ')\n') |
Guido van Rossum | 5232590 | 1995-04-07 09:03:10 +0000 | [diff] [blame] | 747 | |
| 748 | class TestDirHier: |
| 749 | """Populate a transient directory hierarchy according to a definition |
| 750 | template - so we can create package/module hierarchies with which to |
| 751 | exercise the new import facilities...""" |
| 752 | |
| 753 | def __init__(self, template, where='/var/tmp'): |
| 754 | """Establish a dir hierarchy, according to TEMPLATE, that will be |
| 755 | deleted upon deletion of this object (or deliberate invocation of the |
| 756 | __del__ method).""" |
| 757 | self.PKG_NM = 'tdh_' |
| 758 | rev = 0 |
| 759 | while os.path.exists(os.path.join(where, self.PKG_NM+str(rev))): |
| 760 | rev = rev + 1 |
| 761 | sys.exc_traceback = None # Ensure Discard of try/except obj ref |
| 762 | self.PKG_NM = self.PKG_NM + str(rev) |
| 763 | self.root = os.path.join(where, self.PKG_NM) |
| 764 | self.createDir(self.root) |
| 765 | self.add(template) |
| 766 | |
| 767 | def __del__(self): |
| 768 | """Cleanup the test hierarchy.""" |
| 769 | self.remove() |
| 770 | def add(self, template, root=None): |
| 771 | """Populate directory according to template dictionary. |
| 772 | |
| 773 | Keys indicate file names, possibly directories themselves. |
| 774 | |
| 775 | String values dictate contents of flat files. |
| 776 | |
| 777 | Dictionary values dictate recursively embedded dictionary templates.""" |
| 778 | if root == None: root = self.root |
| 779 | for key, val in template.items(): |
| 780 | name = os.path.join(root, key) |
| 781 | if type(val) == types.StringType: # flat file |
| 782 | self.createFile(name, val) |
| 783 | elif type(val) == types.DictionaryType: # embedded dir |
| 784 | self.createDir(name) |
| 785 | self.add(val, name) |
| 786 | else: |
| 787 | raise ValueError, 'invalid file-value type, %s' % type(val) |
| 788 | def remove(self, name=''): |
| 789 | """Dispose of the NAME (or keys in dictionary), using 'rm -r'.""" |
| 790 | name = os.path.join(self.root, name) |
| 791 | sys.exc_traceback = None # Ensure Discard of try/except obj ref |
| 792 | if os.path.exists(name): |
| 793 | print '(TestDirHier: deleting %s)' % name |
| 794 | os.system('rm -r ' + name) |
| 795 | else: |
| 796 | raise IOError, "can't remove non-existent " + name |
| 797 | def createFile(self, name, contents=None): |
| 798 | """Establish file NAME with CONTENTS. |
| 799 | |
| 800 | If no contents specfied, contents will be 'print NAME'.""" |
| 801 | f = open(name, 'w') |
| 802 | if not contents: |
| 803 | f.write("print '" + name + "'\n") |
| 804 | else: |
| 805 | f.write(contents) |
| 806 | f.close |
| 807 | def createDir(self, name): |
| 808 | """Create dir with NAME.""" |
| 809 | return os.mkdir(name, 0755) |
| 810 | |
| 811 | skipToTest = 0 |
| 812 | atTest = 1 |
| 813 | def testExec(msg, execList, locals, globals): |
| 814 | global skipToTest, atTest |
| 815 | print 'Import Test:', '(' + str(atTest) + ')', msg, '...' |
| 816 | atTest = atTest + 1 |
| 817 | if skipToTest > (atTest - 1): |
| 818 | print ' ... skipping til test', skipToTest |
| 819 | return |
| 820 | else: |
| 821 | print '' |
| 822 | for stmt in execList: |
| 823 | exec stmt in locals, globals |
| 824 | |
| 825 | def test(number=0): |
| 826 | """Exercise import functionality, creating a transient dir hierarchy for |
| 827 | the purpose. |
| 828 | |
| 829 | We actually install the new import functionality, temporarily, resuming the |
| 830 | existing function on cleanup.""" |
| 831 | |
| 832 | import __builtin__ |
| 833 | |
| 834 | global skipToTest, atTest |
| 835 | skipToTest = number |
| 836 | hier = None |
| 837 | |
| 838 | def confPkgVars(mod, locals, globals): |
| 839 | if not sys.modules.has_key(mod): |
| 840 | print 'import test: missing module "%s"' % mod |
| 841 | else: |
| 842 | modMod = sys.modules[mod] |
| 843 | if not modMod.__dict__.has_key(PKG_SHORT_NM): |
| 844 | print ('import test: module "%s" missing %s pkg shorthand' % |
| 845 | (mod, PKG_SHORT_NM)) |
| 846 | if not modMod.__dict__.has_key(PKG_PATH): |
| 847 | print ('import test: module "%s" missing %s package path' % |
| 848 | (mod, PKG_PATH)) |
| 849 | def unloadFull(mod): |
| 850 | """Unload module and offspring submodules, if any.""" |
| 851 | modMod = '' |
| 852 | if type(mod) == types.StringType: |
| 853 | modNm = mod |
| 854 | elif type(mod) == types.ModuleType: |
| 855 | modNm = modMod.__name__ |
| 856 | for subj in sys.modules.keys() + sys.stub_modules.keys(): |
| 857 | if subj[0:len(modNm)] == modNm: |
| 858 | unload(subj) |
| 859 | |
| 860 | # First, get the globals and locals to pass to our testExec(): |
| 861 | exec 'import ' + __name__ |
| 862 | globals, locals = eval(__name__ + '.__dict__'), vars() |
| 863 | |
| 864 | try: |
| 865 | __main__.testMods |
| 866 | except AttributeError: |
| 867 | __main__.testMods = [] |
| 868 | testMods = __main__.testMods |
| 869 | |
| 870 | |
| 871 | # Install the newimp routines, within a try/finally: |
| 872 | try: |
| 873 | sys.exc_traceback = None |
| 874 | wasImport = __builtin__.__import__ # Stash default |
| 875 | wasPath = sys.path |
| 876 | except AttributeError: |
| 877 | wasImport = None |
| 878 | try: |
| 879 | hiers = []; modules = [] |
| 880 | global VERBOSE |
| 881 | wasVerbose, VERBOSE = VERBOSE, 2 |
| 882 | __builtin__.__import__ = import_module # Install new version |
| 883 | |
| 884 | if testMods: # Clear out imports from previous tests |
| 885 | for m in testMods[:]: |
| 886 | unloadFull(m) |
| 887 | testMods.remove(m) |
| 888 | |
| 889 | testExec("already imported module: %s" % sys.modules.keys()[0], |
| 890 | ['import ' + sys.modules.keys()[0]], |
| 891 | locals, globals) |
| 892 | try: |
| 893 | no_sirree = 'no_sirree_does_not_exist' |
| 894 | testExec("non-existent module: %s" % no_sirree, |
| 895 | ['import ' + no_sirree], |
| 896 | locals, globals) |
| 897 | except ImportError: |
| 898 | testExec("ok", ['pass'], locals, globals) |
| 899 | got = None |
| 900 | for mod in ['Complex', 'UserDict', 'UserList', 'calendar', |
| 901 | 'cmd', 'dis', 'mailbox', 'profile', 'random', 'rfc822']: |
| 902 | if not (mod in sys.modules.keys()): |
| 903 | got = mod |
| 904 | break # ==v |
| 905 | if got: |
| 906 | testExec("not-yet loaded module: %s" % mod, |
| 907 | ['import ' + mod, 'modules.append(got)'], |
| 908 | locals, globals) |
| 909 | else: |
| 910 | print "Import Test: couldn't find unimported module from list" |
| 911 | |
| 912 | # Now some package stuff. |
| 913 | |
| 914 | # First change the path to include our temp dir, copying so the |
| 915 | # addition can be revoked on cleanup in the finally, below: |
| 916 | sys.path = ['/var/tmp'] + sys.path[:] |
| 917 | # Now create a trivial package: |
| 918 | stmts = ["hier1 = TestDirHier({'a.py': 'print \"a.py executing\"'})", |
| 919 | "hiers.append(hier1)", |
| 920 | "root = hier1.PKG_NM", |
| 921 | "exec 'import ' + root", |
| 922 | "testMods.append(root)", |
| 923 | "confPkgVars(sys.modules[root].__name__, locals, globals)", |
| 924 | "confPkgVars(sys.modules[root].__name__+'.a',locals,globals)"] |
| 925 | testExec("trivial package, with one module, a.py", |
| 926 | stmts, locals, globals) |
| 927 | # Slightly less trivial package - reference to '__': |
| 928 | stmts = [("hier2 = TestDirHier({'ref.py': 'print \"Pkg __:\", __'})"), |
| 929 | "root = hier2.PKG_NM", |
| 930 | "hiers.append(hier2)", |
| 931 | "exec 'import ' + root", |
| 932 | "testMods.append(root)"] |
| 933 | testExec("trivial package, with module that has pkg shorthand ref", |
| 934 | stmts, locals, globals) |
| 935 | # Nested package, plus '__' references: |
| 936 | |
| 937 | complexTemplate = {'ref.py': 'print "ref.py loading..."', |
| 938 | 'suite': {'s1.py': 'print "s1.py, in pkg:", __', |
| 939 | 'subsuite': {'sub1.py': |
| 940 | 'print "sub1.py"'}}} |
| 941 | stmts = [('print """%s\n%s\n%s\n%s\n%s\n%s"""' % |
| 942 | ('.../', |
| 943 | ' ref.py\t\t\t"ref.py loading..."', |
| 944 | ' suite/', |
| 945 | ' s1.py \t\t"s1.py, in pkg: xxxx.suite"', |
| 946 | ' subsuite/', |
| 947 | ' sub1.py "sub1.py" ')), |
| 948 | "hier3 = TestDirHier(complexTemplate)", |
| 949 | "root = hier3.PKG_NM", |
| 950 | "hiers.append(hier3)", |
| 951 | "exec 'import ' + root", |
| 952 | "testMods.append(root)"] |
| 953 | testExec("Significantly nestled package:", |
| 954 | stmts, locals, globals) |
| 955 | |
| 956 | # Now try to do an embedded sibling import, using '__' shorthand - |
| 957 | # alter our complexTemplate for a new dirHier: |
| 958 | complexTemplate['suite']['s1.py'] = 'import __.subsuite' |
| 959 | stmts = ["hier4 = TestDirHier(complexTemplate)", |
| 960 | "root = hier4.PKG_NM", |
| 961 | "testMods.append(root)", |
| 962 | "hiers.append(hier4)", |
| 963 | "exec 'import %s.suite.s1' % root", |
| 964 | "testMods.append(root)"] |
| 965 | testExec("Similar structure, but suite/s1.py imports '__.subsuite'", |
| 966 | stmts, locals, globals) |
| 967 | |
| 968 | sys.exc_traceback = None # Signify clean conclusion. |
| 969 | |
| 970 | finally: |
| 971 | if sys.exc_traceback: |
| 972 | print ' ** Import test FAILURE... cleanup.' |
| 973 | else: |
| 974 | print ' Import test SUCCESS... cleanup' |
| 975 | VERBOSE = wasVerbose |
| 976 | skipToTest = 0 |
| 977 | atTest = 1 |
| 978 | sys.path = wasPath |
| 979 | for h in hiers: h.remove(); del h # Dispose of test directories |
| 980 | if wasImport: # Resurrect prior routine |
| 981 | __builtin__.__import__ = wasImport |
| 982 | else: |
| 983 | del __builtin__.__import__ |
Guido van Rossum | fa486a2 | 1995-04-07 09:04:01 +0000 | [diff] [blame] | 984 | |
| 985 | if __name__ == '__main__': |
| 986 | test() |