[modutils] fix load_module_from_path (closes #100935)

A very old bug in modutils, that leads to doubly loading some modules. By chance on Linux (or the platform we typically use) it seems one the two module gets forgoten as soon as loaded.

Unfortunately on Windows, with CubicWeb, it was found that the first module instance was the one on which the mro was built and the second one hosted the super(...) call with a different class id for the litteral within super(ThisClass, self)....

The exact reason for the dual behaviour is still unknown but it could well be one of those 'platform dependant' bits that riddle Python.

authorAurelien Campeas <aurelien.campeas@logilab.fr>
changeset347549396655
branchstable
phasepublic
hiddenno
parent revision#d7f5380c5d23 [modutils] Fix modutils.modpath_from_file (closes #100757)
child revision#2ab4ad8b6714 0.58.2
files modified by this revision
modutils.py
test/data/lmfp/__init__.py
test/data/lmfp/foo.py
test/unittest_modutils.py
# HG changeset patch
# User Aurelien Campeas <aurelien.campeas@logilab.fr>
# Date 1343640123 -7200
# Mon Jul 30 11:22:03 2012 +0200
# Branch stable
# Node ID 347549396655d952b93081ffab107763acb2b6f0
# Parent d7f5380c5d235dbc74e3109bf7ad9a99ef5befbb
[modutils] fix load_module_from_path (closes #100935)

A very old bug in modutils, that leads to doubly loading some modules.
By chance on Linux (or the platform we typically use) it seems one the two module
gets forgoten as soon as loaded.

Unfortunately on Windows, with CubicWeb, it was found that the first module instance
was the one on which the mro was built and the second one hosted the super(...)
call with a different class id for the litteral within super(ThisClass, self)....

The exact reason for the dual behaviour is still unknown but it could well be
one of those 'platform dependant' bits that riddle Python.

diff --git a/modutils.py b/modutils.py
@@ -156,10 +156,13 @@
1          curname = '.'.join(modpath)
2          module = None
3          if len(modpath) != len(parts):
4              # even with use_sys=False, should try to get outer packages from sys.modules
5              module = sys.modules.get(curname)
6 +        elif use_sys:
7 +            # because it may have been indirectly loaded through a parent
8 +            module = sys.modules.get(curname)
9          if module is None:
10              mp_file, mp_filename, mp_desc = find_module(part, path)
11              module = load_module(curname, mp_file, mp_filename, mp_desc)
12          if prevmodule:
13              setattr(prevmodule, part, module)
diff --git a/test/data/lmfp/__init__.py b/test/data/lmfp/__init__.py
@@ -0,0 +1,2 @@
14 +# force a "direct" python import
15 +from . import foo
diff --git a/test/data/lmfp/foo.py b/test/data/lmfp/foo.py
@@ -0,0 +1,6 @@
16 +import sys
17 +if not getattr(sys, 'bar', None):
18 +    sys.just_once = []
19 +# there used to be two numbers here because
20 +# of a load_module_from_path bug
21 +sys.just_once.append(42)
diff --git a/test/unittest_modutils.py b/test/unittest_modutils.py
@@ -126,10 +126,20 @@
22 
23      def test_raise_modpath_from_file_Exception(self):
24          self.assertRaises(Exception, modutils.modpath_from_file, '/turlututu')
25 
26 
27 +class load_module_from_path_tc(ModutilsTestCase):
28 +
29 +    def test_do_not_load_twice(self):
30 +        sys.path.insert(0, self.datadir)
31 +        foo = modutils.load_module_from_modpath(['lmfp', 'foo'])
32 +        lmfp = modutils.load_module_from_modpath(['lmfp'])
33 +        self.assertEqual(len(sys.just_once), 1)
34 +        sys.path.pop(0)
35 +        del sys.just_once
36 +
37  class file_from_modpath_tc(ModutilsTestCase):
38      """given a mod path (i.e. splited module / package name), return the
39      corresponding file, giving priority to source file over precompiled file
40      if it exists"""
41