pyximport.py 23 KB

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