OLD | NEW |
(Empty) | |
| 1 """ |
| 2 Import hooks; when installed with the install() function, these hooks |
| 3 allow importing .pyx files as if they were Python modules. |
| 4 |
| 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):: |
| 9 |
| 10 import pyximport |
| 11 pyximport.install() |
| 12 |
| 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 . |
| 16 |
| 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: |
| 20 |
| 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"]) |
| 29 |
| 30 Extra dependencies can be defined by a <modulename>.pyxdep . |
| 31 See README. |
| 32 |
| 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:: |
| 41 |
| 42 pyximport.install(pyimport = True) |
| 43 |
| 44 Running this module as a top-level script will run a test and then print |
| 45 the documentation. |
| 46 |
| 47 This code is based on the Py2.3+ import protocol as described in PEP 302. |
| 48 """ |
| 49 |
| 50 import sys |
| 51 import os |
| 52 import glob |
| 53 import imp |
| 54 |
| 55 mod_name = "pyximport" |
| 56 |
| 57 assert sys.hexversion >= 0x2030000, "need Python 2.3 or later" |
| 58 |
| 59 PYX_EXT = ".pyx" |
| 60 PYXDEP_EXT = ".pyxdep" |
| 61 PYXBLD_EXT = ".pyxbld" |
| 62 |
| 63 DEBUG_IMPORT = False |
| 64 |
| 65 def _print(message, args): |
| 66 if args: |
| 67 message = message % args |
| 68 print(message) |
| 69 |
| 70 def _debug(message, *args): |
| 71 if DEBUG_IMPORT: |
| 72 _print(message, args) |
| 73 |
| 74 def _info(message, *args): |
| 75 _print(message, args) |
| 76 |
| 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." |
| 86 |
| 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 |
| 105 |
| 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 Extensio
n" |
| 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 |
| 131 |
| 132 def handle_dependencies(pyxfilename): |
| 133 testing = '_test_files' in globals() |
| 134 dependfile = os.path.splitext(pyxfilename)[0] + PYXDEP_EXT |
| 135 |
| 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) |
| 138 |
| 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] |
| 144 |
| 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)) |
| 152 |
| 153 # only for unit testing to see we did the right thing |
| 154 if testing: |
| 155 _test_files[:] = [] #$pycheck_no |
| 156 |
| 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) |
| 168 |
| 169 def build_module(name, pyxfilename, pyxbuild_dir=None, inplace=False, language_l
evel=None): |
| 170 assert os.path.exists(pyxfilename), ( |
| 171 "Path does not exist: %s" % pyxfilename) |
| 172 handle_dependencies(pyxfilename) |
| 173 |
| 174 extension_mod,setup_args = get_distutils_extension(name, pyxfilename, langua
ge_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) |
| 179 |
| 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 |
| 188 |
| 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) |
| 197 |
| 198 return so_path |
| 199 |
| 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=languag
e_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('.p
y'): |
| 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 |
| 225 |
| 226 |
| 227 # import hooks |
| 228 |
| 229 class PyxImporter(object): |
| 230 """A meta-path importer for .pyx files. |
| 231 """ |
| 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 |
| 238 |
| 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 t
his 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 |
| 260 |
| 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) |
| 268 |
| 269 # .so/.pyd's on PATH should not be remote from .pyx's |
| 270 # think no need to implement PyxArgs.importer_search_remote here? |
| 271 |
| 272 except ImportError: |
| 273 pass |
| 274 |
| 275 # searching sys.path ... |
| 276 |
| 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) |
| 287 |
| 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) |
| 308 |
| 309 # not found, normal package, not a .pyx file, none of our business |
| 310 _debug("%s not found" % fullname) |
| 311 return None |
| 312 |
| 313 class PyImporter(PyxImporter): |
| 314 """A meta-path importer for normal .py files. |
| 315 """ |
| 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'] |
| 325 |
| 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 |
| 347 |
| 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 |
| 382 |
| 383 class LibLoader(object): |
| 384 def __init__(self): |
| 385 self._libs = {} |
| 386 |
| 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) |
| 394 |
| 395 def add_lib(self, fullname, path, so_path, is_package): |
| 396 self._libs[fullname] = (path, so_path, is_package) |
| 397 |
| 398 def knows(self, fullname): |
| 399 return fullname in self._libs |
| 400 |
| 401 _lib_loader = LibLoader() |
| 402 |
| 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 |
| 413 |
| 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 |
| 433 |
| 434 |
| 435 #install args |
| 436 class PyxArgs(object): |
| 437 build_dir=True |
| 438 build_in_temp=True |
| 439 setup_args={} #None |
| 440 |
| 441 ##pyxargs=None |
| 442 |
| 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 |
| 452 |
| 453 return has_py_importer, has_pyx_importer |
| 454 |
| 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). |
| 463 |
| 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. |
| 468 |
| 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. |
| 472 |
| 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' |
| 478 |
| 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() |
| 482 |
| 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. |
| 487 |
| 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. |
| 496 |
| 497 ``inplace``: Install the compiled module next to the source file. |
| 498 |
| 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. |
| 502 """ |
| 503 if not build_dir: |
| 504 build_dir = os.path.join(os.path.expanduser('~'), '.pyxbld') |
| 505 |
| 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 |
| 513 |
| 514 has_py_importer, has_pyx_importer = _have_importers() |
| 515 py_importer, pyx_importer = None, None |
| 516 |
| 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.O
ptimize |
| 522 sys.meta_path.insert(0, py_importer) |
| 523 |
| 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) |
| 528 |
| 529 return py_importer, pyx_importer |
| 530 |
| 531 def uninstall(py_importer, pyx_importer): |
| 532 """ |
| 533 Uninstall an import hook. |
| 534 """ |
| 535 try: |
| 536 sys.meta_path.remove(py_importer) |
| 537 except ValueError: |
| 538 pass |
| 539 |
| 540 try: |
| 541 sys.meta_path.remove(pyx_importer) |
| 542 except ValueError: |
| 543 pass |
| 544 |
| 545 # MAIN |
| 546 |
| 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__) |
| 557 |
| 558 if __name__ == '__main__': |
| 559 show_docs() |
OLD | NEW |