[modutils] Add basic support for namespace packages

Nested namespaces (zope is a big fan) probably don't work.

Closes #122443.

authorRémi Cardona <remi.cardona@logilab.fr>
changeset5e15c1218a55
branchdefault
phasepublic
hiddenno
parent revision#3475c6df3af3 [modutils] Use True/False instead of 1/0
child revision#1536b89f3f58 [modutils] Use dict.fromkeys instead of convoluted dict+zip+listrepeat
files modified by this revision
logilab/common/modutils.py
# HG changeset patch
# User Rémi Cardona <remi.cardona@logilab.fr>
# Date 1432820642 -7200
# Thu May 28 15:44:02 2015 +0200
# Node ID 5e15c1218a55e1e81299df06005852f14ec6402e
# Parent 3475c6df3af3dc41c044a3d40b5b4175d3ebe95a
[modutils] Add basic support for namespace packages

Nested namespaces (zope is a big fan) probably don't work.

Closes #122443.

diff --git a/logilab/common/modutils.py b/logilab/common/modutils.py
@@ -168,14 +168,16 @@
1              mp_file, mp_filename, mp_desc = find_module(part, path)
2              module = load_module(curname, mp_file, mp_filename, mp_desc)
3          if prevmodule:
4              setattr(prevmodule, part, module)
5          _file = getattr(module, '__file__', '')
6 +        prevmodule = module
7 +        if not _file and _is_namespace(curname):
8 +            continue
9          if not _file and len(modpath) != len(parts):
10              raise ImportError('no module in %s' % '.'.join(parts[len(modpath):]) )
11          path = [dirname( _file )]
12 -        prevmodule = module
13      return module
14 
15 
16  def load_module_from_file(filepath, path=None, use_sys=True, extrapath=None):
17      """Load a Python module from it's path.
@@ -203,13 +205,15 @@
18      return load_module_from_modpath(modpath, path, use_sys)
19 
20 
21  def _check_init(path, mod_path):
22      """check there are some __init__.py all along the way"""
23 +    modpath = []
24      for part in mod_path:
25 +        modpath.append(part)
26          path = join(path, part)
27 -        if not _has_init(path):
28 +        if not _is_namespace('.'.join(modpath)) and not _has_init(path):
29              return False
30      return True
31 
32 
33  def modpath_from_file(filename, extrapath=None):
@@ -474,11 +478,10 @@
34      return: True if the filename is a python source file
35      """
36      return splitext(filename)[1][1:] in PY_SOURCE_EXTS
37 
38 
39 -
40  def is_standard_module(modname, std_path=(STD_LIB_DIR,)):
41      """try to guess if a module is a standard python module (by default,
42      see `std_path` parameter's description)
43 
44      :type modname: str
@@ -502,11 +505,12 @@
45          # not standard...
46          return False
47      # modules which are not living in a file are considered standard
48      # (sys and __builtin__ for instance)
49      if filename is None:
50 -        return True
51 +        # we assume there are no namespaces in stdlib
52 +        return not _is_namespace(modname)
53      filename = abspath(filename)
54      if filename.startswith(EXT_LIB_DIR):
55          return False
56      for path in std_path:
57          if filename.startswith(abspath(path)):
@@ -583,10 +587,16 @@
58  try:
59      import pkg_resources
60  except ImportError:
61      pkg_resources = None
62 
63 +
64 +def _is_namespace(modname):
65 +    return (pkg_resources is not None
66 +            and modname in pkg_resources._namespace_packages)
67 +
68 +
69  def _module_file(modpath, path=None):
70      """get a module type / file path
71 
72      :type modpath: list or tuple
73      :param modpath:
@@ -614,18 +624,17 @@
74                      pic[__path] = None
75          checkeggs = True
76      except AttributeError:
77          checkeggs = False
78      # pkg_resources support (aka setuptools namespace packages)
79 -    if (pkg_resources is not None
80 -            and modpath[0] in pkg_resources._namespace_packages
81 -            and modpath[0] in sys.modules
82 -            and len(modpath) > 1):
83 +    if (_is_namespace(modpath[0]) and modpath[0] in sys.modules):
84          # setuptools has added into sys.modules a module object with proper
85          # __path__, get back information from there
86          module = sys.modules[modpath.pop(0)]
87          path = module.__path__
88 +        if not modpath:
89 +            return C_BUILTIN, None
90      imported = []
91      while modpath:
92          modname = modpath[0]
93          # take care to changes in find_module implementation wrt builtin modules
94          #