##// END OF EJS Templates
demandimport: delay loading for "from a import b" with absolute_import...
FUJIWARA Katsunori -
r29375:fcaf2017 default
parent child Browse files
Show More
@@ -188,15 +188,23 b' def _demandimport(name, globals=None, lo'
188 if globalname and isinstance(symbol, _demandmod):
188 if globalname and isinstance(symbol, _demandmod):
189 symbol._addref(globalname)
189 symbol._addref(globalname)
190
190
191 def chainmodules(rootmod, modname):
192 # recurse down the module chain, and return the leaf module
193 mod = rootmod
194 for comp in modname.split('.')[1:]:
195 if getattr(mod, comp, nothing) is nothing:
196 setattr(mod, comp,
197 _demandmod(comp, mod.__dict__, mod.__dict__))
198 mod = getattr(mod, comp)
199 return mod
200
191 if level >= 0:
201 if level >= 0:
192 # The "from a import b,c,d" or "from .a import b,c,d"
193 # syntax gives errors with some modules for unknown
194 # reasons. Work around the problem.
195 if name:
202 if name:
196 return _hgextimport(_origimport, name, globals, locals,
203 # "from a import b" or "from .a import b" style
197 fromlist, level)
204 rootmod = _hgextimport(_origimport, name, globals, locals,
198
205 level=level)
199 if _pypy:
206 mod = chainmodules(rootmod, name)
207 elif _pypy:
200 # PyPy's __import__ throws an exception if invoked
208 # PyPy's __import__ throws an exception if invoked
201 # with an empty name and no fromlist. Recreate the
209 # with an empty name and no fromlist. Recreate the
202 # desired behaviour by hand.
210 # desired behaviour by hand.
@@ -220,12 +228,7 b' def _demandimport(name, globals=None, lo'
220 # But, we still need to support lazy loading of standard library and 3rd
228 # But, we still need to support lazy loading of standard library and 3rd
221 # party modules. So handle level == -1.
229 # party modules. So handle level == -1.
222 mod = _hgextimport(_origimport, name, globals, locals)
230 mod = _hgextimport(_origimport, name, globals, locals)
223 # recurse down the module chain
231 mod = chainmodules(mod, name)
224 for comp in name.split('.')[1:]:
225 if getattr(mod, comp, nothing) is nothing:
226 setattr(mod, comp,
227 _demandmod(comp, mod.__dict__, mod.__dict__))
228 mod = getattr(mod, comp)
229
232
230 for x in fromlist:
233 for x in fromlist:
231 processfromitem(mod, x)
234 processfromitem(mod, x)
@@ -249,6 +249,191 b' Check absolute/relative import of extens'
249 $TESTTMP/a (glob)
249 $TESTTMP/a (glob)
250 #endif
250 #endif
251
251
252 #if absimport
253
254 Examine whether module loading is delayed until actual refering, even
255 though module is imported with "absolute_import" feature.
256
257 Files below in each packages are used for descirbed purpose:
258
259 - "called": examine whether "from MODULE import ATTR" works correctly
260 - "unused": examine whether loading is delayed correctly
261 - "used": examine whether "from PACKAGE import MODULE" works correctly
262
263 Package hierarchy is needed to examine whether demand importing works
264 as expected for "from SUB.PACK.AGE import MODULE".
265
266 Setup "external library" to be imported with "absolute_import"
267 feature.
268
269 $ mkdir -p $TESTTMP/extlibroot/lsub1/lsub2
270 $ touch $TESTTMP/extlibroot/__init__.py
271 $ touch $TESTTMP/extlibroot/lsub1/__init__.py
272 $ touch $TESTTMP/extlibroot/lsub1/lsub2/__init__.py
273
274 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/called.py <<EOF
275 > def func():
276 > return "this is extlibroot.lsub1.lsub2.called.func()"
277 > EOF
278 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/unused.py <<EOF
279 > raise Exception("extlibroot.lsub1.lsub2.unused is loaded unintentionally")
280 > EOF
281 $ cat > $TESTTMP/extlibroot/lsub1/lsub2/used.py <<EOF
282 > detail = "this is extlibroot.lsub1.lsub2.used"
283 > EOF
284
285 Setup sub-package of "external library", which causes instantiation of
286 demandmod in "recurse down the module chain" code path. Relative
287 importing with "absolute_import" feature isn't tested, because "level
288 >=1 " doesn't cause instantiation of demandmod.
289
290 $ mkdir -p $TESTTMP/extlibroot/recursedown/abs
291 $ cat > $TESTTMP/extlibroot/recursedown/abs/used.py <<EOF
292 > detail = "this is extlibroot.recursedown.abs.used"
293 > EOF
294 $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<EOF
295 > from __future__ import absolute_import
296 > from extlibroot.recursedown.abs.used import detail
297 > EOF
298
299 $ mkdir -p $TESTTMP/extlibroot/recursedown/legacy
300 $ cat > $TESTTMP/extlibroot/recursedown/legacy/used.py <<EOF
301 > detail = "this is extlibroot.recursedown.legacy.used"
302 > EOF
303 $ cat > $TESTTMP/extlibroot/recursedown/legacy/__init__.py <<EOF
304 > # legacy style (level == -1) import
305 > from extlibroot.recursedown.legacy.used import detail
306 > EOF
307
308 $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<EOF
309 > from __future__ import absolute_import
310 > from extlibroot.recursedown.abs import detail as absdetail
311 > from .legacy import detail as legacydetail
312 > EOF
313
314 Setup extension local modules to be imported with "absolute_import"
315 feature.
316
317 $ mkdir -p $TESTTMP/absextroot/xsub1/xsub2
318 $ touch $TESTTMP/absextroot/xsub1/__init__.py
319 $ touch $TESTTMP/absextroot/xsub1/xsub2/__init__.py
320
321 $ cat > $TESTTMP/absextroot/xsub1/xsub2/called.py <<EOF
322 > def func():
323 > return "this is absextroot.xsub1.xsub2.called.func()"
324 > EOF
325 $ cat > $TESTTMP/absextroot/xsub1/xsub2/unused.py <<EOF
326 > raise Exception("absextroot.xsub1.xsub2.unused is loaded unintentionally")
327 > EOF
328 $ cat > $TESTTMP/absextroot/xsub1/xsub2/used.py <<EOF
329 > detail = "this is absextroot.xsub1.xsub2.used"
330 > EOF
331
332 Setup extension local modules to examine whether demand importing
333 works as expected in "level > 1" case.
334
335 $ cat > $TESTTMP/absextroot/relimportee.py <<EOF
336 > detail = "this is absextroot.relimportee"
337 > EOF
338 $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<EOF
339 > from __future__ import absolute_import
340 > from ... import relimportee
341 > detail = "this relimporter imports %r" % (relimportee.detail)
342 > EOF
343
344 Setup modules, which actually import extension local modules at
345 runtime.
346
347 $ cat > $TESTTMP/absextroot/absolute.py << EOF
348 > from __future__ import absolute_import
349 >
350 > # import extension local modules absolutely (level = 0)
351 > from absextroot.xsub1.xsub2 import used, unused
352 > from absextroot.xsub1.xsub2.called import func
353 >
354 > def getresult():
355 > result = []
356 > result.append(used.detail)
357 > result.append(func())
358 > return result
359 > EOF
360
361 $ cat > $TESTTMP/absextroot/relative.py << EOF
362 > from __future__ import absolute_import
363 >
364 > # import extension local modules relatively (level == 1)
365 > from .xsub1.xsub2 import used, unused
366 > from .xsub1.xsub2.called import func
367 >
368 > # import a module, which implies "importing with level > 1"
369 > from .xsub1.xsub2 import relimporter
370 >
371 > def getresult():
372 > result = []
373 > result.append(used.detail)
374 > result.append(func())
375 > result.append(relimporter.detail)
376 > return result
377 > EOF
378
379 Setup main procedure of extension.
380
381 $ cat > $TESTTMP/absextroot/__init__.py <<EOF
382 > from __future__ import absolute_import
383 > from mercurial import cmdutil
384 > cmdtable = {}
385 > command = cmdutil.command(cmdtable)
386 >
387 > # "absolute" and "relative" shouldn't be imported before actual
388 > # command execution, because (1) they import same modules, and (2)
389 > # preceding import (= instantiate "demandmod" object instead of
390 > # real "module" object) might hide problem of succeeding import.
391 >
392 > @command('showabsolute', [], norepo=True)
393 > def showabsolute(ui, *args, **opts):
394 > from absextroot import absolute
395 > ui.write('ABS: %s\n' % '\nABS: '.join(absolute.getresult()))
396 >
397 > @command('showrelative', [], norepo=True)
398 > def showrelative(ui, *args, **opts):
399 > from . import relative
400 > ui.write('REL: %s\n' % '\nREL: '.join(relative.getresult()))
401 >
402 > # import modules from external library
403 > from extlibroot.lsub1.lsub2 import used as lused, unused as lunused
404 > from extlibroot.lsub1.lsub2.called import func as lfunc
405 > from extlibroot.recursedown import absdetail, legacydetail
406 >
407 > def uisetup(ui):
408 > result = []
409 > result.append(lused.detail)
410 > result.append(lfunc())
411 > result.append(absdetail)
412 > result.append(legacydetail)
413 > ui.write('LIB: %s\n' % '\nLIB: '.join(result))
414 > EOF
415
416 Examine module importing.
417
418 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showabsolute)
419 LIB: this is extlibroot.lsub1.lsub2.used
420 LIB: this is extlibroot.lsub1.lsub2.called.func()
421 LIB: this is extlibroot.recursedown.abs.used
422 LIB: this is extlibroot.recursedown.legacy.used
423 ABS: this is absextroot.xsub1.xsub2.used
424 ABS: this is absextroot.xsub1.xsub2.called.func()
425
426 $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}; hg --config extensions.absextroot=$TESTTMP/absextroot showrelative)
427 LIB: this is extlibroot.lsub1.lsub2.used
428 LIB: this is extlibroot.lsub1.lsub2.called.func()
429 LIB: this is extlibroot.recursedown.abs.used
430 LIB: this is extlibroot.recursedown.legacy.used
431 REL: this is absextroot.xsub1.xsub2.used
432 REL: this is absextroot.xsub1.xsub2.called.func()
433 REL: this relimporter imports 'this is absextroot.relimportee'
434
435 #endif
436
252 $ cd ..
437 $ cd ..
253
438
254 hide outer repo
439 hide outer repo
General Comments 0
You need to be logged in to leave comments. Login now