Skip to content

Instantly share code, notes, and snippets.

@asmodehn
Last active June 27, 2017 10:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asmodehn/d4d69374213be20002ec64ed7ab77dad to your computer and use it in GitHub Desktop.
Save asmodehn/d4d69374213be20002ec64ed7ab77dad to your computer and use it in GitHub Desktop.
PEP 420 for python 2.7
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]
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
@asmodehn
Copy link
Author

Usage :

import FileFinder2
if sys.version_info < (3, 4):
    supported_loaders = rosmsg_finder._get_supported_ns_loaders()
    ns_hook = FileFinder2.FileFinder2.path_hook(*supported_loaders)
    sys.path_hooks.insert(1, ns_hook)

@asmodehn
Copy link
Author

Actually went to the trouble to make a package for this : https://github.com/asmodehn/filefinder2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment