Last active
June 27, 2017 10:54
-
-
Save asmodehn/d4d69374213be20002ec64ed7ab77dad to your computer and use it in GitHub Desktop.
PEP 420 for python 2.7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import absolute_import, division, print_function | |
# We need to be extra careful with python versions | |
# Ref : https://docs.python.org/dev/library/importlib.html#importlib.import_module | |
# Ref : http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path | |
import os | |
import sys | |
import logging | |
import contextlib | |
import importlib | |
import site | |
if (2, 7) <= sys.version_info < (3, 4): # valid until which py3 version ? | |
# from .importlib2 import machinery as importlib_machinery | |
# from .importlib2 import util as importlib_util | |
import pkg_resources # useful to have empty directory imply namespace package (like for py3) | |
import io | |
import errno | |
import imp | |
def _verbose_message(message, *args, **kwargs): | |
"""Print the message to stderr if -v/PYTHONVERBOSE is turned on.""" | |
verbosity = kwargs.pop('verbosity', 1) | |
if sys.flags.verbose >= verbosity: | |
if not message.startswith(('#', 'import ')): | |
message = '# ' + message | |
print(message.format(*args), file=sys.stderr) | |
try: | |
ImportError('msg', name='name', path='path') | |
except TypeError: | |
class _ImportError(ImportError): | |
def __init__(self, *args, **kwargs): | |
self.name = kwargs.pop('name', None) | |
self.path = kwargs.pop('path', None) | |
super(_ImportError, self).__init__(*args, **kwargs) | |
else: | |
_ImportError = ImportError | |
# Frame stripping magic ############################################### | |
def _call_with_frames_removed(f, *args, **kwds): | |
"""remove_importlib_frames in import.c will always remove sequences | |
of importlib frames that end with a call to this function | |
Use it instead of a normal call in places where including the importlib | |
frames introduces unwanted noise into the traceback (e.g. when executing | |
module code) | |
""" | |
return f(*args, **kwds) | |
def decode_source(source_bytes): | |
"""Decode bytes representing source code and return the string. | |
Universal newline support is used in the decoding. | |
""" | |
import tokenize # To avoid bootstrap issues. | |
source_bytes_readline = io.BytesIO(source_bytes).readline | |
encoding = tokenize.detect_encoding(source_bytes_readline) | |
newline_decoder = io.IncrementalNewlineDecoder(None, True) | |
return newline_decoder.decode(source_bytes.decode(encoding[0])) | |
# inspired from importlib2 | |
class FileLoader2(object): | |
"""Base file loader class which implements the loader protocol methods that | |
require file system usage. Also implements implicit namespace package PEP 420.""" | |
def __init__(self, fullname, path): | |
"""Cache the module name and the path to the file found by the | |
finder.""" | |
self.name = fullname | |
self.path = path | |
def __eq__(self, other): | |
return (self.__class__ == other.__class__ and | |
self.__dict__ == other.__dict__) | |
def __hash__(self): | |
return hash(self.name) ^ hash(self.path) | |
def get_source(self, fullname): | |
"""Concrete implementation of InspectLoader.get_source.""" | |
path = self.get_filename(fullname) | |
try: | |
source_bytes = self.get_data(path) | |
except OSError as exc: | |
e = _ImportError('source not available through get_data()', | |
name=fullname) | |
e.__cause__ = exc | |
raise e | |
return source_bytes | |
# return decode_source(source_bytes) | |
def load_module(self, name): | |
"""Load a module from a file. | |
""" | |
# Implementation inspired from pytest | |
# If there is an existing module object named 'fullname' in | |
# sys.modules, the loader must use that existing module. (Otherwise, | |
# the reload() builtin will not work correctly.) | |
if name in sys.modules: | |
return sys.modules[name] | |
source = self.get_source(name) | |
# I wish I could just call imp.load_compiled here, but __file__ has to | |
# be set properly. In Python 3.2+, this all would be handled correctly | |
# by load_compiled. | |
mod = sys.modules.setdefault(name, imp.new_module(name)) | |
try: | |
# Set a few properties required by PEP 302 | |
mod.__file__ = self.get_filename(name) | |
mod.__loader__ = self | |
if self.is_package(name): | |
mod.__path__ = [self.path] | |
mod.__package__ = name # PEP 366 | |
else: | |
mod.__path__ = self.path | |
mod.__package__ = '.'.join(name.split('.')[:-1]) # PEP 366 | |
exec(source, mod.__dict__) | |
except: | |
if name in sys.modules: | |
del sys.modules[name] | |
raise | |
return sys.modules[name] | |
def get_filename(self, fullname): | |
"""Return the path to the source file.""" | |
if os.path.isdir(self.path) and os.path.isfile(os.path.join(self.path, '__init__.py')): | |
return os.path.join(self.path, '__init__.py') # python package | |
else: | |
return self.path # module or namespace package case | |
def is_package(self, fullname): | |
# in case of package we have to always have the directory as self.path | |
# we can always compute init path dynamically when needed. | |
return os.path.isdir(self.path) | |
def get_data(self, path): | |
"""Return the data from path as raw bytes. | |
Implements PEP 420 using pkg_resources | |
""" | |
try: | |
with io.FileIO(path, 'r') as file: | |
return file.read() | |
except IOError as ioe: | |
if ioe.errno == errno.EISDIR: | |
# implicit namespace package | |
return """import pkg_resources; pkg_resources.declare_namespace(__name__)""" | |
else: | |
raise | |
class FileFinder2(object): | |
def __init__(self, path, *loader_details): | |
"""Initialize with the path to search on and a variable number of | |
2-tuples containing the loader and the file suffixes the loader | |
recognizes.""" | |
loaders = [] | |
for loader, suffixes in loader_details: | |
loaders.extend((suffix, loader) for suffix in suffixes) | |
self._loaders = loaders | |
# Base (directory) path | |
self.path = path or '.' | |
# Note : we are not playing with cache here (too complex to get right and not worth it for obsolete python) | |
def find_module(self, fullname, path=None): | |
"""Try to find a loader for the specified module, or the namespace | |
package portions. Returns loader.""" | |
path = path or self.path | |
tail_module = fullname.rpartition('.')[2] | |
base_path = os.path.join(path, tail_module) | |
for suffix, loader_class in self._loaders: | |
full_path = None # adjusting path for package or file | |
if os.path.isdir(base_path) and os.path.isfile(os.path.join(base_path, '__init__' + suffix)): | |
return loader_class(fullname, base_path) # __init__.py path will be computed by the loader when needed | |
elif os.path.isfile(base_path + suffix): | |
return loader_class(fullname, base_path + suffix) | |
else: | |
if os.path.isdir(base_path): | |
# If a namespace package, return the path if we don't | |
# find a module in the next section. | |
_verbose_message('possible namespace for {}'.format(base_path)) | |
return FileLoader2(fullname, base_path) | |
return None | |
@classmethod | |
def path_hook(cls, *loader_details): | |
"""A class method which returns a closure to use on sys.path_hook | |
which will return an instance using the specified loaders and the path | |
called on the closure. | |
If the path called on the closure is not a directory, ImportError is | |
raised. | |
""" | |
def path_hook_for_FileFinder(path): | |
"""Path hook for importlib.machinery.FileFinder.""" | |
if not os.path.isdir(path): | |
raise _ImportError('only directories are supported', path=path) | |
return cls(path, *loader_details) | |
return path_hook_for_FileFinder | |
def __repr__(self): | |
return 'FileFinder2({!r})'.format(self.path) | |
def _get_supported_ns_loaders(): | |
"""Returns a list of file-based module loaders. | |
Each item is a tuple (loader, suffixes). | |
""" | |
loader = FileLoader2, [suffix for suffix, mode, type in imp.get_suffixes()] | |
return [loader] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import absolute_import, division, print_function | |
import copy | |
""" | |
Testing rosmsg_import with import keyword. | |
CAREFUL : these tests should run with pytest --boxed in order to avoid polluting each other sys.modules | |
""" | |
import os | |
import sys | |
import runpy | |
import logging.config | |
logging.config.dictConfig({ | |
'version': 1, | |
'disable_existing_loggers': False, | |
'formatters': { | |
'default': { | |
'format': '%(asctime)s %(levelname)s %(name)s %(message)s' | |
}, | |
}, | |
'handlers': { | |
'console': { | |
'class': 'logging.StreamHandler', | |
} | |
}, | |
'root': { | |
'handlers': ['console'], | |
'level': 'DEBUG', | |
}, | |
}) | |
# Since test frameworks (like pytest) play with the import machinery, we cannot use it here... | |
import unittest | |
# Ref : http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path | |
import importlib | |
# Importing importer module | |
from pyros_msgs.importer import rosmsg_finder | |
# importlib | |
# https://pymotw.com/3/importlib/index.html | |
# https://pymotw.com/2/importlib/index.html | |
def print_importers(): | |
import sys | |
import pprint | |
print('PATH:'), | |
pprint.pprint(sys.path) | |
print() | |
print('IMPORTERS:') | |
for name, cache_value in sys.path_importer_cache.items(): | |
name = name.replace(sys.prefix, '...') | |
print('%s: %r' % (name, cache_value)) | |
# We need to test implicit namespace packages PEP 420 (especially for python 2.7) | |
# Since we rely on it for ros import. | |
# But we can only teet relative package structure | |
class TestImplicitNamespace(unittest.TestCase): | |
@classmethod | |
def setUpClass(cls): | |
# This is required only for old python | |
if sys.version_info < (3, 4): | |
supported_loaders = rosmsg_finder._get_supported_ns_loaders() | |
ns_hook = rosmsg_finder.FileFinder2.path_hook(*supported_loaders) | |
sys.path_hooks.insert(1, ns_hook) | |
# python3 implicit namespaces should work out of the box. | |
def test_import_relative_ns_subpkg(self): | |
"""Verify that package is importable relatively""" | |
print_importers() | |
from .nspkg import subpkg as test_pkg | |
self.assertTrue(test_pkg is not None) | |
self.assertTrue(test_pkg.TestClassInSubPkg is not None) | |
self.assertTrue(callable(test_pkg.TestClassInSubPkg)) | |
def test_import_relative_ns_subpkg_submodule(self): | |
"""Verify that package is importable relatively""" | |
print_importers() | |
from .nspkg.subpkg import submodule as test_mod | |
self.assertTrue(test_mod is not None) | |
self.assertTrue(test_mod.TestClassInSubModule is not None) | |
self.assertTrue(callable(test_mod.TestClassInSubModule)) | |
def test_import_class_from_relative_ns_subpkg(self): | |
"""Verify that message class is importable relatively""" | |
print_importers() | |
from .nspkg.subpkg import TestClassInSubPkg | |
self.assertTrue(TestClassInSubPkg is not None) | |
self.assertTrue(callable(TestClassInSubPkg)) | |
def test_import_class_from_relative_ns_subpkg_submodule(self): | |
"""Verify that package is importable relatively""" | |
print_importers() | |
from .nspkg.subpkg.submodule import TestClassInSubModule | |
self.assertTrue(TestClassInSubModule is not None) | |
self.assertTrue(callable(TestClassInSubModule)) | |
def test_import_relative_nonnspkg_raises(self): | |
"""Verify that package is importable relatively""" | |
print_importers() | |
with self.assertRaises(ImportError): | |
from .bad_nspkg import bad_subpkg |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage :