[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