Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / cython / src / pyximport / pyximport.py
blob4fd7fe90e70b060d19a2675399d6267ba4ac66d2
1 """
2 Import hooks; when installed with the install() function, these hooks
3 allow importing .pyx files as if they were Python modules.
5 If you want the hook installed every time you run Python
6 you can add it to your Python version by adding these lines to
7 sitecustomize.py (which you can create from scratch in site-packages
8 if it doesn't exist there or somewhere else on your python path)::
10 import pyximport
11 pyximport.install()
13 For instance on the Mac with a non-system Python 2.3, you could create
14 sitecustomize.py with only those two lines at
15 /usr/local/lib/python2.3/site-packages/sitecustomize.py .
17 A custom distutils.core.Extension instance and setup() args
18 (Distribution) for for the build can be defined by a <modulename>.pyxbld
19 file like:
21 # examplemod.pyxbld
22 def make_ext(modname, pyxfilename):
23 from distutils.extension import Extension
24 return Extension(name = modname,
25 sources=[pyxfilename, 'hello.c'],
26 include_dirs=['/myinclude'] )
27 def make_setup_args():
28 return dict(script_args=["--compiler=mingw32"])
30 Extra dependencies can be defined by a <modulename>.pyxdep .
31 See README.
33 Since Cython 0.11, the :mod:`pyximport` module also has experimental
34 compilation support for normal Python modules. This allows you to
35 automatically run Cython on every .pyx and .py module that Python
36 imports, including parts of the standard library and installed
37 packages. Cython will still fail to compile a lot of Python modules,
38 in which case the import mechanism will fall back to loading the
39 Python source modules instead. The .py import mechanism is installed
40 like this::
42 pyximport.install(pyimport = True)
44 Running this module as a top-level script will run a test and then print
45 the documentation.
47 This code is based on the Py2.3+ import protocol as described in PEP 302.
48 """
50 import sys
51 import os
52 import glob
53 import imp
55 mod_name = "pyximport"
57 assert sys.hexversion >= 0x2030000, "need Python 2.3 or later"
59 PYX_EXT = ".pyx"
60 PYXDEP_EXT = ".pyxdep"
61 PYXBLD_EXT = ".pyxbld"
63 DEBUG_IMPORT = False
65 def _print(message, args):
66 if args:
67 message = message % args
68 print(message)
70 def _debug(message, *args):
71 if DEBUG_IMPORT:
72 _print(message, args)
74 def _info(message, *args):
75 _print(message, args)
77 # Performance problem: for every PYX file that is imported, we will
78 # invoke the whole distutils infrastructure even if the module is
79 # already built. It might be more efficient to only do it when the
80 # mod time of the .pyx is newer than the mod time of the .so but
81 # the question is how to get distutils to tell me the name of the .so
82 # before it builds it. Maybe it is easy...but maybe the peformance
83 # issue isn't real.
84 def _load_pyrex(name, filename):
85 "Load a pyrex file given a name and filename."
87 def get_distutils_extension(modname, pyxfilename, language_level=None):
88 # try:
89 # import hashlib
90 # except ImportError:
91 # import md5 as hashlib
92 # extra = "_" + hashlib.md5(open(pyxfilename).read()).hexdigest()
93 # modname = modname + extra
94 extension_mod,setup_args = handle_special_build(modname, pyxfilename)
95 if not extension_mod:
96 if not isinstance(pyxfilename, str):
97 # distutils is stupid in Py2 and requires exactly 'str'
98 # => encode accidentally coerced unicode strings back to str
99 pyxfilename = pyxfilename.encode(sys.getfilesystemencoding())
100 from distutils.extension import Extension
101 extension_mod = Extension(name = modname, sources=[pyxfilename])
102 if language_level is not None:
103 extension_mod.cython_directives = {'language_level': language_level}
104 return extension_mod,setup_args
106 def handle_special_build(modname, pyxfilename):
107 special_build = os.path.splitext(pyxfilename)[0] + PYXBLD_EXT
108 ext = None
109 setup_args={}
110 if os.path.exists(special_build):
111 # globls = {}
112 # locs = {}
113 # execfile(special_build, globls, locs)
114 # ext = locs["make_ext"](modname, pyxfilename)
115 mod = imp.load_source("XXXX", special_build, open(special_build))
116 make_ext = getattr(mod,'make_ext',None)
117 if make_ext:
118 ext = make_ext(modname, pyxfilename)
119 assert ext and ext.sources, ("make_ext in %s did not return Extension"
120 % special_build)
121 make_setup_args = getattr(mod,'make_setup_args',None)
122 if make_setup_args:
123 setup_args = make_setup_args()
124 assert isinstance(setup_args,dict), ("make_setup_args in %s did not return a dict"
125 % special_build)
126 assert set or setup_args, ("neither make_ext nor make_setup_args %s"
127 % special_build)
128 ext.sources = [os.path.join(os.path.dirname(special_build), source)
129 for source in ext.sources]
130 return ext, setup_args
132 def handle_dependencies(pyxfilename):
133 testing = '_test_files' in globals()
134 dependfile = os.path.splitext(pyxfilename)[0] + PYXDEP_EXT
136 # by default let distutils decide whether to rebuild on its own
137 # (it has a better idea of what the output file will be)
139 # but we know more about dependencies so force a rebuild if
140 # some of the dependencies are newer than the pyxfile.
141 if os.path.exists(dependfile):
142 depends = open(dependfile).readlines()
143 depends = [depend.strip() for depend in depends]
145 # gather dependencies in the "files" variable
146 # the dependency file is itself a dependency
147 files = [dependfile]
148 for depend in depends:
149 fullpath = os.path.join(os.path.dirname(dependfile),
150 depend)
151 files.extend(glob.glob(fullpath))
153 # only for unit testing to see we did the right thing
154 if testing:
155 _test_files[:] = [] #$pycheck_no
157 # if any file that the pyxfile depends upon is newer than
158 # the pyx file, 'touch' the pyx file so that distutils will
159 # be tricked into rebuilding it.
160 for file in files:
161 from distutils.dep_util import newer
162 if newer(file, pyxfilename):
163 _debug("Rebuilding %s because of %s", pyxfilename, file)
164 filetime = os.path.getmtime(file)
165 os.utime(pyxfilename, (filetime, filetime))
166 if testing:
167 _test_files.append(file)
169 def build_module(name, pyxfilename, pyxbuild_dir=None, inplace=False, language_level=None):
170 assert os.path.exists(pyxfilename), (
171 "Path does not exist: %s" % pyxfilename)
172 handle_dependencies(pyxfilename)
174 extension_mod,setup_args = get_distutils_extension(name, pyxfilename, language_level)
175 build_in_temp=pyxargs.build_in_temp
176 sargs=pyxargs.setup_args.copy()
177 sargs.update(setup_args)
178 build_in_temp=sargs.pop('build_in_temp',build_in_temp)
180 import pyxbuild
181 so_path = pyxbuild.pyx_to_dll(pyxfilename, extension_mod,
182 build_in_temp=build_in_temp,
183 pyxbuild_dir=pyxbuild_dir,
184 setup_args=sargs,
185 inplace=inplace,
186 reload_support=pyxargs.reload_support)
187 assert os.path.exists(so_path), "Cannot find: %s" % so_path
189 junkpath = os.path.join(os.path.dirname(so_path), name+"_*") #very dangerous with --inplace ? yes, indeed, trying to eat my files ;)
190 junkstuff = glob.glob(junkpath)
191 for path in junkstuff:
192 if path!=so_path:
193 try:
194 os.remove(path)
195 except IOError:
196 _info("Couldn't remove %s", path)
198 return so_path
200 def load_module(name, pyxfilename, pyxbuild_dir=None, is_package=False,
201 build_inplace=False, language_level=None, so_path=None):
202 try:
203 if so_path is None:
204 if is_package:
205 module_name = name + '.__init__'
206 else:
207 module_name = name
208 so_path = build_module(module_name, pyxfilename, pyxbuild_dir,
209 inplace=build_inplace, language_level=language_level)
210 mod = imp.load_dynamic(name, so_path)
211 if is_package and not hasattr(mod, '__path__'):
212 mod.__path__ = [os.path.dirname(so_path)]
213 assert mod.__file__ == so_path, (mod.__file__, so_path)
214 except Exception:
215 if pyxargs.load_py_module_on_import_failure and pyxfilename.endswith('.py'):
216 # try to fall back to normal import
217 mod = imp.load_source(name, pyxfilename)
218 assert mod.__file__ in (pyxfilename, pyxfilename+'c', pyxfilename+'o'), (mod.__file__, pyxfilename)
219 else:
220 import traceback
221 raise ImportError("Building module %s failed: %s" %
222 (name,
223 traceback.format_exception_only(*sys.exc_info()[:2]))), None, sys.exc_info()[2]
224 return mod
227 # import hooks
229 class PyxImporter(object):
230 """A meta-path importer for .pyx files.
232 def __init__(self, extension=PYX_EXT, pyxbuild_dir=None, inplace=False,
233 language_level=None):
234 self.extension = extension
235 self.pyxbuild_dir = pyxbuild_dir
236 self.inplace = inplace
237 self.language_level = language_level
239 def find_module(self, fullname, package_path=None):
240 if fullname in sys.modules and not pyxargs.reload_support:
241 return None # only here when reload()
242 try:
243 fp, pathname, (ext,mode,ty) = imp.find_module(fullname,package_path)
244 if fp: fp.close() # Python should offer a Default-Loader to avoid this double find/open!
245 if pathname and ty == imp.PKG_DIRECTORY:
246 pkg_file = os.path.join(pathname, '__init__'+self.extension)
247 if os.path.isfile(pkg_file):
248 return PyxLoader(fullname, pathname,
249 init_path=pkg_file,
250 pyxbuild_dir=self.pyxbuild_dir,
251 inplace=self.inplace,
252 language_level=self.language_level)
253 if pathname and pathname.endswith(self.extension):
254 return PyxLoader(fullname, pathname,
255 pyxbuild_dir=self.pyxbuild_dir,
256 inplace=self.inplace,
257 language_level=self.language_level)
258 if ty != imp.C_EXTENSION: # only when an extension, check if we have a .pyx next!
259 return None
261 # find .pyx fast, when .so/.pyd exist --inplace
262 pyxpath = os.path.splitext(pathname)[0]+self.extension
263 if os.path.isfile(pyxpath):
264 return PyxLoader(fullname, pyxpath,
265 pyxbuild_dir=self.pyxbuild_dir,
266 inplace=self.inplace,
267 language_level=self.language_level)
269 # .so/.pyd's on PATH should not be remote from .pyx's
270 # think no need to implement PyxArgs.importer_search_remote here?
272 except ImportError:
273 pass
275 # searching sys.path ...
277 #if DEBUG_IMPORT: print "SEARCHING", fullname, package_path
278 if '.' in fullname: # only when package_path anyway?
279 mod_parts = fullname.split('.')
280 module_name = mod_parts[-1]
281 else:
282 module_name = fullname
283 pyx_module_name = module_name + self.extension
284 # this may work, but it returns the file content, not its path
285 #import pkgutil
286 #pyx_source = pkgutil.get_data(package, pyx_module_name)
288 if package_path:
289 paths = package_path
290 else:
291 paths = sys.path
292 join_path = os.path.join
293 is_file = os.path.isfile
294 is_abs = os.path.isabs
295 abspath = os.path.abspath
296 #is_dir = os.path.isdir
297 sep = os.path.sep
298 for path in paths:
299 if not path:
300 path = os.getcwd()
301 elif not is_abs(path):
302 path = abspath(path)
303 if is_file(path+sep+pyx_module_name):
304 return PyxLoader(fullname, join_path(path, pyx_module_name),
305 pyxbuild_dir=self.pyxbuild_dir,
306 inplace=self.inplace,
307 language_level=self.language_level)
309 # not found, normal package, not a .pyx file, none of our business
310 _debug("%s not found" % fullname)
311 return None
313 class PyImporter(PyxImporter):
314 """A meta-path importer for normal .py files.
316 def __init__(self, pyxbuild_dir=None, inplace=False, language_level=None):
317 if language_level is None:
318 language_level = sys.version_info[0]
319 self.super = super(PyImporter, self)
320 self.super.__init__(extension='.py', pyxbuild_dir=pyxbuild_dir, inplace=inplace,
321 language_level=language_level)
322 self.uncompilable_modules = {}
323 self.blocked_modules = ['Cython', 'pyxbuild', 'pyximport.pyxbuild',
324 'distutils.extension', 'distutils.sysconfig']
326 def find_module(self, fullname, package_path=None):
327 if fullname in sys.modules:
328 return None
329 if fullname.startswith('Cython.'):
330 return None
331 if fullname in self.blocked_modules:
332 # prevent infinite recursion
333 return None
334 if _lib_loader.knows(fullname):
335 return _lib_loader
336 _debug("trying import of module '%s'", fullname)
337 if fullname in self.uncompilable_modules:
338 path, last_modified = self.uncompilable_modules[fullname]
339 try:
340 new_last_modified = os.stat(path).st_mtime
341 if new_last_modified > last_modified:
342 # import would fail again
343 return None
344 except OSError:
345 # module is no longer where we found it, retry the import
346 pass
348 self.blocked_modules.append(fullname)
349 try:
350 importer = self.super.find_module(fullname, package_path)
351 if importer is not None:
352 if importer.init_path:
353 path = importer.init_path
354 real_name = fullname + '.__init__'
355 else:
356 path = importer.path
357 real_name = fullname
358 _debug("importer found path %s for module %s", path, real_name)
359 try:
360 so_path = build_module(
361 real_name, path,
362 pyxbuild_dir=self.pyxbuild_dir,
363 language_level=self.language_level,
364 inplace=self.inplace)
365 _lib_loader.add_lib(fullname, path, so_path,
366 is_package=bool(importer.init_path))
367 return _lib_loader
368 except Exception:
369 if DEBUG_IMPORT:
370 import traceback
371 traceback.print_exc()
372 # build failed, not a compilable Python module
373 try:
374 last_modified = os.stat(path).st_mtime
375 except OSError:
376 last_modified = 0
377 self.uncompilable_modules[fullname] = (path, last_modified)
378 importer = None
379 finally:
380 self.blocked_modules.pop()
381 return importer
383 class LibLoader(object):
384 def __init__(self):
385 self._libs = {}
387 def load_module(self, fullname):
388 try:
389 source_path, so_path, is_package = self._libs[fullname]
390 except KeyError:
391 raise ValueError("invalid module %s" % fullname)
392 _debug("Loading shared library module '%s' from %s", fullname, so_path)
393 return load_module(fullname, source_path, so_path=so_path, is_package=is_package)
395 def add_lib(self, fullname, path, so_path, is_package):
396 self._libs[fullname] = (path, so_path, is_package)
398 def knows(self, fullname):
399 return fullname in self._libs
401 _lib_loader = LibLoader()
403 class PyxLoader(object):
404 def __init__(self, fullname, path, init_path=None, pyxbuild_dir=None,
405 inplace=False, language_level=None):
406 _debug("PyxLoader created for loading %s from %s (init path: %s)",
407 fullname, path, init_path)
408 self.fullname = fullname
409 self.path, self.init_path = path, init_path
410 self.pyxbuild_dir = pyxbuild_dir
411 self.inplace = inplace
412 self.language_level = language_level
414 def load_module(self, fullname):
415 assert self.fullname == fullname, (
416 "invalid module, expected %s, got %s" % (
417 self.fullname, fullname))
418 if self.init_path:
419 # package
420 #print "PACKAGE", fullname
421 module = load_module(fullname, self.init_path,
422 self.pyxbuild_dir, is_package=True,
423 build_inplace=self.inplace,
424 language_level=self.language_level)
425 module.__path__ = [self.path]
426 else:
427 #print "MODULE", fullname
428 module = load_module(fullname, self.path,
429 self.pyxbuild_dir,
430 build_inplace=self.inplace,
431 language_level=self.language_level)
432 return module
435 #install args
436 class PyxArgs(object):
437 build_dir=True
438 build_in_temp=True
439 setup_args={} #None
441 ##pyxargs=None
443 def _have_importers():
444 has_py_importer = False
445 has_pyx_importer = False
446 for importer in sys.meta_path:
447 if isinstance(importer, PyxImporter):
448 if isinstance(importer, PyImporter):
449 has_py_importer = True
450 else:
451 has_pyx_importer = True
453 return has_py_importer, has_pyx_importer
455 def install(pyximport=True, pyimport=False, build_dir=None, build_in_temp=True,
456 setup_args={}, reload_support=False,
457 load_py_module_on_import_failure=False, inplace=False,
458 language_level=None):
459 """Main entry point. Call this to install the .pyx import hook in
460 your meta-path for a single Python process. If you want it to be
461 installed whenever you use Python, add it to your sitecustomize
462 (as described above).
464 You can pass ``pyimport=True`` to also install the .py import hook
465 in your meta-path. Note, however, that it is highly experimental,
466 will not work for most .py files, and will therefore only slow
467 down your imports. Use at your own risk.
469 By default, compiled modules will end up in a ``.pyxbld``
470 directory in the user's home directory. Passing a different path
471 as ``build_dir`` will override this.
473 ``build_in_temp=False`` will produce the C files locally. Working
474 with complex dependencies and debugging becomes more easy. This
475 can principally interfere with existing files of the same name.
476 build_in_temp can be overriden by <modulename>.pyxbld/make_setup_args()
477 by a dict item of 'build_in_temp'
479 ``setup_args``: dict of arguments for Distribution - see
480 distutils.core.setup() . They are extended/overriden by those of
481 <modulename>.pyxbld/make_setup_args()
483 ``reload_support``: Enables support for dynamic
484 reload(<pyxmodulename>), e.g. after a change in the Cython code.
485 Additional files <so_path>.reloadNN may arise on that account, when
486 the previously loaded module file cannot be overwritten.
488 ``load_py_module_on_import_failure``: If the compilation of a .py
489 file succeeds, but the subsequent import fails for some reason,
490 retry the import with the normal .py module instead of the
491 compiled module. Note that this may lead to unpredictable results
492 for modules that change the system state during their import, as
493 the second import will rerun these modifications in whatever state
494 the system was left after the import of the compiled module
495 failed.
497 ``inplace``: Install the compiled module next to the source file.
499 ``language_level``: The source language level to use: 2 or 3.
500 The default is to use the language level of the current Python
501 runtime for .py files and Py2 for .pyx files.
503 if not build_dir:
504 build_dir = os.path.join(os.path.expanduser('~'), '.pyxbld')
506 global pyxargs
507 pyxargs = PyxArgs() #$pycheck_no
508 pyxargs.build_dir = build_dir
509 pyxargs.build_in_temp = build_in_temp
510 pyxargs.setup_args = (setup_args or {}).copy()
511 pyxargs.reload_support = reload_support
512 pyxargs.load_py_module_on_import_failure = load_py_module_on_import_failure
514 has_py_importer, has_pyx_importer = _have_importers()
515 py_importer, pyx_importer = None, None
517 if pyimport and not has_py_importer:
518 py_importer = PyImporter(pyxbuild_dir=build_dir, inplace=inplace,
519 language_level=language_level)
520 # make sure we import Cython before we install the import hook
521 import Cython.Compiler.Main, Cython.Compiler.Pipeline, Cython.Compiler.Optimize
522 sys.meta_path.insert(0, py_importer)
524 if pyximport and not has_pyx_importer:
525 pyx_importer = PyxImporter(pyxbuild_dir=build_dir, inplace=inplace,
526 language_level=language_level)
527 sys.meta_path.append(pyx_importer)
529 return py_importer, pyx_importer
531 def uninstall(py_importer, pyx_importer):
533 Uninstall an import hook.
535 try:
536 sys.meta_path.remove(py_importer)
537 except ValueError:
538 pass
540 try:
541 sys.meta_path.remove(pyx_importer)
542 except ValueError:
543 pass
545 # MAIN
547 def show_docs():
548 import __main__
549 __main__.__name__ = mod_name
550 for name in dir(__main__):
551 item = getattr(__main__, name)
552 try:
553 setattr(item, "__module__", mod_name)
554 except (AttributeError, TypeError):
555 pass
556 help(__main__)
558 if __name__ == '__main__':
559 show_docs()