[Jython-checkins] jython: Improve ClasspathPyImporter PEP302 optional methods. Fixes #2058, #1879.

jeff.allen jython-checkins at python.org
Sun May 17 10:11:29 CEST 2015


https://hg.python.org/jython/rev/dccf5d04d58e
changeset:   7711:dccf5d04d58e
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sat May 16 23:14:06 2015 +0100
summary:
  Improve ClasspathPyImporter PEP302 optional methods. Fixes #2058, #1879.

This correctly closes the resource InputStream in get_data, and ads
get_source with a similar pattern. test_classpathimporter is extended to
exercise these methods, including via pkgutil.

files:
  Lib/test/test_classpathimporter.py                |  44 ++++-
  NEWS                                              |   2 +
  src/org/python/core/ClasspathPyImporter.java      |  85 +++++++++-
  src/org/python/core/util/importer.java            |   7 +-
  src/org/python/modules/zipimport/zipimporter.java |  10 +-
  5 files changed, 130 insertions(+), 18 deletions(-)


diff --git a/Lib/test/test_classpathimporter.py b/Lib/test/test_classpathimporter.py
--- a/Lib/test/test_classpathimporter.py
+++ b/Lib/test/test_classpathimporter.py
@@ -78,7 +78,7 @@
                       os.path.join(compile_path, 'jar_pkg', compiled))
         return compiled
 
-    def addResourceToJar(self, jar, package='/jar_pkg'):
+    def addResourceToJar(self, jar, package='jar_pkg'):
         name = 'testdata.dat'
         with zipfile.ZipFile(jar, 'a') as zip:
             zip.writestr(package + '/' + name, self.RESOURCE_DATA)
@@ -141,15 +141,53 @@
         exec code in space
         self.assertEquals(space['compiled'], True)
 
-    @unittest.skipIf(test_support.is_jython, "FIXME: missing get_data.")
+    def test_pkgutil_get_data(self):
+        # Test loader.get_data used via pkgutil
+        jar = self.prepareJar('classimport.jar')
+        name = self.addResourceToJar(jar)
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        data = pkgutil.get_data('jar_pkg', name)
+        self.assertIsInstance(data, bytes)
+        self.assertEqual(data, self.RESOURCE_DATA)
+
     def test_loader_get_data(self):
+        # Test loader.get_data used via pkgutil.get_loader
         jar = self.prepareJar('classimport.jar')
         name = self.addResourceToJar(jar)
         Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
         loader = pkgutil.get_loader('jar_pkg')
-        data = loader.get_data('jar_pkg', name)
+        # path is a resource path (not file system path using os.path.sep)
+        path = 'jar_pkg/' + name
+        data = loader.get_data(path)
+        self.assertIsInstance(data, bytes)
         self.assertEqual(data, self.RESOURCE_DATA)
 
+    def test_importer_get_data(self):
+        # Test loader.get_data used via pkgutil.get_importer
+        jar = self.prepareJar('classimport.jar')
+        name = self.addResourceToJar(jar)
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        importer = pkgutil.get_importer('__pyclasspath__/')
+        # path is a resource path (may be file system path using os.path.sep)
+        path = os.path.join('jar_pkg', name)
+        data = importer.get_data(path)
+        self.assertIsInstance(data, bytes)
+        self.assertEqual(data, self.RESOURCE_DATA)
+        # Check works a second time (stream use internal to implementation)
+        data = importer.get_data(path)
+        self.assertEqual(data, self.RESOURCE_DATA)
+
+    def test_importer_get_source(self):
+        # Test loader.get_source used via pkgutil.get_importer
+        jar = self.prepareJar('classimport.jar')
+        Thread.currentThread().contextClassLoader = test_support.make_jar_classloader(jar)
+        importer = pkgutil.get_importer('__pyclasspath__/')
+        # In package
+        mod = 'jar_pkg.prefer_compiled'
+        source = importer.get_source(mod)
+        self.assertIsInstance(source, bytes)
+        self.assertEqual(source, 'compiled = False\n')
+
 
 def test_main():
     test_support.run_unittest(
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@
    - [ 2310 ] test_import runs on Windows (and passes with 4 skips).
    - [ 2347 ] failures in test_import_pep328 when run with -m
    - [ 2158, 2259 ] Fixed behaviour of relative from ... import *
+   - [ 1879 ] -m command now executes scripts from inside a jar file 
+   - [ 2058 ] ClasspathPyImporter implements PEP 302 get_data (and others)
 
 Jython 2.7rc3
   Bugs fixed
diff --git a/src/org/python/core/ClasspathPyImporter.java b/src/org/python/core/ClasspathPyImporter.java
--- a/src/org/python/core/ClasspathPyImporter.java
+++ b/src/org/python/core/ClasspathPyImporter.java
@@ -1,6 +1,7 @@
 /* Copyright (c) Jython Developers */
 package org.python.core;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Map;
@@ -42,15 +43,81 @@
         this.path = path;
     }
 
+    /**
+     * Return the contents of the jarred file at the specified path
+     * as bytes.
+     *
+     * @param path a String path name within the archive
+     * @return a String of data in binary mode (no CRLF)
+     */
+    @Override
     public String get_data(String path) {
-        InputStream is = entries.get(makeEntry(path.replace("__pyclasspath__/", "")));
-        byte[] data;
-        try {
-            data = FileUtil.readBytes(is);
+        return ClasspathPyImporter_get_data(path);
+    }
+
+    @ExposedMethod
+    final String ClasspathPyImporter_get_data(String path) {
+        // Strip any leading occurrence of the hook string
+        int len = PYCLASSPATH_PREFIX.length();
+        if (len < path.length() && path.startsWith(PYCLASSPATH_PREFIX)) {
+            path = path.substring(len);
+        }
+
+        // Bundle wraps the stream together with a close operation
+        try (Bundle bundle = makeBundle(path, makeEntry(path))) {
+            byte[] data = FileUtil.readBytes(bundle.inputStream);
+            return StringUtil.fromBytes(data);
         } catch (IOException ioe) {
             throw Py.IOError(ioe);
         }
-        return StringUtil.fromBytes(data);
+    }
+
+    /**
+     * Return the source code for the module as a string (using
+     * newline characters for line endings)
+     *
+     * @param fullname the fully qualified name of the module
+     * @return a String of the module's source code or null
+     */
+    public String get_source(String fullname) {
+        return ClasspathPyImporter_get_source(fullname);
+    }
+
+    @ExposedMethod
+    final String ClasspathPyImporter_get_source(String fullname) {
+
+        ModuleInfo moduleInfo = getModuleInfo(fullname);
+
+        if (moduleInfo == ModuleInfo.ERROR) {
+            return null;
+
+        } else if (moduleInfo == ModuleInfo.NOT_FOUND) {
+            throw Py.ImportError(String.format("can't find module '%s'", fullname));
+
+        } else {
+            // Turn the module name into a source file name
+            String path = makeFilename(fullname);
+            if (moduleInfo == ModuleInfo.PACKAGE) {
+                path += File.separator + "__init__.py";
+            } else {
+                path += ".py";
+            }
+
+            // Bundle wraps the stream together with a close operation
+            try (Bundle bundle = makeBundle(path, makeEntry(path))) {
+                InputStream is = bundle.inputStream;
+                if (is != null) {
+                    byte[] data = FileUtil.readBytes(is);
+                    return StringUtil.fromBytes(data);
+                } else {
+                    // we have the module, but no source
+                    return null;
+                }
+            } catch (IOException ioe) {
+                throw Py.IOError(ioe);
+            }
+        }
+
     }
 
     /**
@@ -126,14 +193,18 @@
 
     @Override
     protected String makeEntry(String filename) {
+        // In some contexts, the resource string arrives as from os.path.join(*parts)
+        if (!getSeparator().equals(File.separator)) {
+            filename = filename.replace(File.separator, getSeparator());
+        }
         if (entries.containsKey(filename)) {
             return filename;
         }
         InputStream is;
         if (Py.getSystemState().getClassLoader() != null) {
-        	is = tryClassLoader(filename, Py.getSystemState().getClassLoader(), "sys");
+            is = tryClassLoader(filename, Py.getSystemState().getClassLoader(), "sys");
         } else {
-        	is = tryClassLoader(filename, imp.getParentClassLoader(), "parent");
+            is = tryClassLoader(filename, imp.getParentClassLoader(), "parent");
         }
         if (is != null) {
             entries.put(filename, is);
diff --git a/src/org/python/core/util/importer.java b/src/org/python/core/util/importer.java
--- a/src/org/python/core/util/importer.java
+++ b/src/org/python/core/util/importer.java
@@ -4,6 +4,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.EnumSet;
+
 import org.python.core.BytecodeLoader;
 import org.python.core.Py;
 import org.python.core.PyCode;
@@ -39,6 +40,9 @@
         searchOrder = makeSearchOrder();
     }
 
+    /**
+     * Return the bytes for the data located at <code>path</code>.
+     */
     public abstract String get_data(String path);
 
     /**
@@ -127,7 +131,7 @@
      * Bundle is an InputStream, bundled together with a method that can close the input stream and
      * whatever resources are associated with it when the resource is imported.
      */
-    protected abstract static class Bundle {
+    protected abstract static class Bundle implements AutoCloseable {
         public InputStream inputStream;
 
         public Bundle(InputStream inputStream) {
@@ -137,6 +141,7 @@
         /**
          * Close the underlying resource if necessary. Raises an IOError if a problem occurs.
          */
+        @Override
         public abstract void close();
     }
 
diff --git a/src/org/python/modules/zipimport/zipimporter.java b/src/org/python/modules/zipimport/zipimporter.java
--- a/src/org/python/modules/zipimport/zipimporter.java
+++ b/src/org/python/modules/zipimport/zipimporter.java
@@ -165,11 +165,12 @@
 
     /**
      * Return the uncompressed data for the file at the specified path
-     * as a String.
+     * as bytes.
      *
      * @param path a String path name within the archive
      * @return a String of data in binary mode (no CRLF)
      */
+    @Override
     public String get_data(String path) {
         return zipimporter_get_data(path);
     }
@@ -280,12 +281,7 @@
         }
 
         PyObject tocEntry = files.__finditem__(path);
-        if (tocEntry != null) {
-            return get_data(path);
-        }
-
-        // we have the module, but no source
-        return null;
+        return (tocEntry == null) ? null : get_data(path);
     }
 
     /**

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list