1. Ronald Oussoren
  2. modulegraph

Commits

Ronald Oussoren  committed c743aeb

Implement support for PEP 420 (with tests)

PEP 420 "Implicit namespace packages" is new in Python 3.3, see <http://www.python.org/dev/peps/pep-0420>;
for more information.

This patch ensures that modulegraph supports these packages (but only on python 3.3 or later)

  • Participants
  • Parent commits 2b68f09
  • Branches default

Comments (0)

Files changed (4)

File doc/changelog.rst

View file
 * Ensure modulegraph works with changes introduced after
   Python 3.3b1.
 
+* Implement support for PEP 420 ("Implicit namespace packages")
+  in Python 3.3.
+
 * ``modulegraph.util.imp_walk`` is deprecated and will be 
   removed in the next release of this package.
 

File modulegraph/modulegraph.py

View file
         except ImportError:
             ImpImporter = pkg_resources.ImpWrapper
 
+    namespace_path =[] 
     for entry in path:
         importer = pkg_resources.get_importer(entry)
         if importer is None:
             continue
-        loader = importer.find_module(name)
+
+        if sys.version_info[:2] >= (3,3) and hasattr(importer, 'find_loader'):
+            loader, portions = importer.find_loader(name)
+
+        else:
+            loader = importer.find_module(name)
+            portions = []
+
+        namespace_path.extend(portions)
+
         if loader is None: continue
 
         if isinstance(importer, ImpImporter):
             else:
                 return (fp, pathname + '.pyc', ('.pyc', 'rb', imp.PY_COMPILED))
 
+    if namespace_path:
+        return (None, namespace_path[0], ('', namespace_path, imp.PKG_DIRECTORY))
+
     raise ImportError(name)
 
 def moduleInfoForPath(path):
         self.msgin(2, "load_module", fqname, fp and "fp", pathname)
 
         if typ == imp.PKG_DIRECTORY:
-            m = self.load_package(fqname, pathname)
+            if isinstance(mode, (list, tuple)):
+                packagepath = mode
+            else:
+                packagepath = []
+
+            m = self.load_package(fqname, pathname, packagepath)
             self.msgout(2, "load_module ->", m)
             return m
 
             if isinstance(c, cotype):
                 self.scan_code(c, m)
 
-    def load_package(self, fqname, pathname):
+    def load_package(self, fqname, pathname, pkgpath):
         """
         Called only when an imp.PACKAGE_DIRECTORY is found
         """
-        self.msgin(2, "load_package", fqname, pathname)
+        self.msgin(2, "load_package", fqname, pathname, pkgpath)
         newname = _replacePackageMap.get(fqname)
         if newname:
             fqname = newname
         m.filename = pathname
         m.packagepath = _namespace_package_path(fqname, [pathname])
 
+        if pkgpath:
+            m.filename = '-'
+            m.packagepath = _namespace_package_path(fqname, pkgpath)
+
         # As per comment at top of file, simulate runtime packagepath additions.
         m.packagepath = m.packagepath + _packagePathMap.get(fqname, [])
 
         
 
-        fp, buf, stuff = self.find_module("__init__", m.packagepath)
-        try:
-            self.load_module(fqname, fp, buf, stuff)
-        finally:
-            if fp is not None:
-                fp.close()
+        if not pkgpath:
+            # XXX: pkgpath is only set for PEP420 packages, which don't have
+            # an __init__ module. Need a cleaner interface for this.
+
+            fp, buf, stuff = self.find_module("__init__", m.packagepath)
+            try:
+                self.load_module(fqname, fp, buf, stuff)
+            finally:
+                if fp is not None:
+                    fp.close()
         self.msgout(2, "load_package ->", m)
         return m
 

File modulegraph_tests/test_pep420_nspkg.py

View file
             m = self.importModule('package.subpackage.sub')
             self.assertEqual(m, 'package.subpackage.sub')
 
+            m = self.importModule('package.nspkg.mod')
+            self.assertEqual(m, 'package.nspkg.mod')
+
     class TestModuleGraphImport (unittest.TestCase):
         if not hasattr(unittest.TestCase, 'assertIsInstance'):
             def assertIsInstance(self, value, types):
             self.assertIsInstance(node, modulegraph.SourceModule)
             self.assertEqual(node.identifier, 'package.sub1')
 
+            self.mf.import_hook('package.sub2')
             node = self.mf.findNode('package.sub2')
             self.assertIsInstance(node, modulegraph.SourceModule)
             self.assertEqual(node.identifier, 'package.sub2')
             node = self.mf.findNode('package')
             self.assertIsInstance(node, modulegraph.Package)
 
+            self.mf.import_hook('package.nspkg.mod')
+            node = self.mf.findNode('package.nspkg.mod')
+            self.assertIsInstance(node, modulegraph.SourceModule)
+            self.assertEqual(node.identifier, 'package.nspkg.mod')
+
+else:
+    # Check that PEP 420 is not implemented in python 3.2 and earlier
+    # (and that modulegraph also doesn't do this)
+
+    class TestPythonBehaviour (unittest.TestCase):
+        def importModule(self, name):
+            test_dir1 = os.path.join(gSrcDir, 'path1')
+            test_dir2 = os.path.join(gSrcDir, 'path2')
+            if '.' in name:
+                script = textwrap.dedent("""\
+                    import site
+                    site.addsitedir(%r)
+                    site.addsitedir(%r)
+                    try:
+                        import %s
+                    except ImportError:
+                        import %s
+                    print (%s.__name__)
+                """) %(test_dir1, test_dir2, name, name.rsplit('.', 1)[0], name)
+            else:
+                script = textwrap.dedent("""\
+                    import site
+                    site.addsitedir(%r)
+                    site.addsitedir(%r)
+                    import %s
+                    print (%s.__name__)
+                """) %(test_dir1, test_dir2, name, name)
+
+            p = subprocess.Popen([sys.executable, '-c', script], 
+                    stdout=subprocess.PIPE,
+                    stderr=subprocess.STDOUT,
+                    cwd=os.path.join(
+                        os.path.dirname(os.path.abspath(__file__)),
+                        'testpkg-relimport'),
+            )
+            data = p.communicate()[0]
+            if sys.version_info[0] != 2:
+                data = data.decode('UTF-8')
+            data = data.strip()
+            if data.endswith(' refs]'):
+                data = data.rsplit('\n', 1)[0].strip()
+
+            sts = p.wait()
+
+            if sts != 0:
+                raise ImportError(name)
+
+            return data
+
+        def testToplevel(self):
+            m = self.importModule('sys')
+            self.assertEqual(m, 'sys')
+
+            self.assertRaises(ImportError, self.importModule, 'package.sub1')
+            self.assertRaises(ImportError, self.importModule, 'package.sub2')
+
+        def testSub(self):
+            self.assertRaises(ImportError, self.importModule, 'package.subpackage.sub')
+
+    class TestModuleGraphImport (unittest.TestCase):
+        if not hasattr(unittest.TestCase, 'assertIsInstance'):
+            def assertIsInstance(self, value, types):
+                if not isinstance(value, types):
+                    self.fail("%r is not an instance of %r", value, types)
+
+        def setUp(self):
+            self.mf = modulegraph.ModuleGraph(path=[
+                    os.path.join(gSrcDir, 'path1'),
+                    os.path.join(gSrcDir, 'path2'),
+                ] + sys.path)
+
+
+        def testRootPkg(self):
+            self.assertRaises(ImportError, self.mf.import_hook, 'package')
+
+            node = self.mf.findNode('package')
+            self.assertIs(node, None)
+
+        def testRootPkgModule(self):
+            self.assertRaises(ImportError, self.mf.import_hook, 'package.sub1')
+
+            node = self.mf.findNode('package.sub1')
+            self.assertIs(node, None)
+
+            node = self.mf.findNode('package.sub2')
+            self.assertIs(node, None)
 
 if __name__ == "__main__":
     unittest.main()

File modulegraph_tests/testpkg-pep420-namespace/path2/package/nspkg/mod.py

View file
+""" package.nspkg.mod """