Fix import check interaction with namespace modules

When running hacking from an oslo.* lib, all other oslo.* libs get
categorized as project because the import check was only looking at
the base module.  So when running in oslo.db, oslo.db should be
categorized as project and oslo.config as third-party, but both were
being seen as project because _get_import_type was only looking at
the 'oslo' part of the import.

This change extends _get_import_type to look at the entire module
path and adds the necessary logic for handling namespace modules
and non-module imports.  Since this is fairly complex and can't be
tested with doctests, standalone test cases are also added to verify
the behavior of the new code.

Note that prior to this change, oslo.db had 5 apparent H305
violations and 3 H307, which was incorrect.  After this change,
hacking correctly reports 3 H305 violations and 1 H307.

Also, because some of the stdlib modules don't play nice with the
new lookup method, this also includes a static list of stdlib
modules that will also be used for py2/3 compatibility in a
followup commit.

Change-Id: Ibb8c982bafcd69c9d642e86aa357a3a11b4395af
This commit is contained in:
Ben Nemec
2014-07-10 16:11:56 +00:00
parent b1fe19ebeb
commit 22f37a1d39
3 changed files with 367 additions and 31 deletions

View File

@@ -156,51 +156,329 @@ def hacking_import_rules(logical_line, physical_line, filename, noqa):
" '%s' is a relative import" % logical_line)
# Get the location of a known stdlib module
_, p, _ = imp.find_module('imp')
stdlib_path_prefix = os.path.dirname(p)
module_cache = dict()
def _find_module(module, path=None):
mod_base = module
parent_path = None
while '.' in mod_base:
first, _, mod_base = mod_base.partition('.')
parent_path = path
_, path, _ = imp.find_module(first, path)
path = [path]
try:
_, path, _ = imp.find_module(mod_base, path)
except ImportError:
# NOTE(bnemec): There are two reasons we might get here: 1) A
# non-module import and 2) an import of a namespace module that is
# in the same namespace as the current project, which caused us to
# recurse into the project namespace but fail to find the third-party
# module. For 1), we won't be able to import it as a module, so we
# return the parent module's path, but for 2) the import below should
# succeed, so we re-raise the ImportError because the module was
# legitimately not found in this path.
try:
__import__(module)
except ImportError:
# Non-module import, return the parent path if we have it
if parent_path:
return parent_path
raise
raise
return path
# List of all Python 2 stdlib modules - anything not in this list will be
# allowed in either the stdlib or third-party groups to allow for Python 3
# stdlib additions.
# The list was generated via the following script, which is a variation on
# the one found here:
# http://stackoverflow.com/questions/6463918/how-can-i-get-a-list-of-all-the-python-standard-library-modules
"""
from distutils import sysconfig
import os
import sys
std_lib = sysconfig.get_python_lib(standard_lib=True)
prefix_len = len(std_lib) + 1
modules = ''
line = '['
mod_list = []
for top, dirs, files in os.walk(std_lib):
for name in files:
if 'site-packages' not in top:
if name == '__init__.py':
full_name = top[prefix_len:].replace('/', '.')
mod_list.append(full_name)
elif name.endswith('.py'):
full_name = top.replace('/', '.') + '.'
full_name += name[:-3]
full_name = full_name[prefix_len:]
mod_list.append(full_name)
elif name.endswith('.so') and top.endswith('lib-dynload'):
full_name = name[:-3]
if full_name.endswith('module'):
full_name = full_name[:-6]
mod_list.append(full_name)
for name in sys.builtin_module_names:
mod_list.append(name)
mod_list.sort()
for mod in mod_list:
if len(line + mod) + 8 > 79:
modules += '\n' + line
line = ' '
line += "'%s', " % mod
print modules + ']'
"""
py2_stdlib = [
'BaseHTTPServer', 'Bastion', 'CGIHTTPServer', 'ConfigParser', 'Cookie',
'DocXMLRPCServer', 'HTMLParser', 'MimeWriter', 'Queue',
'SimpleHTTPServer', 'SimpleXMLRPCServer', 'SocketServer', 'StringIO',
'UserDict', 'UserList', 'UserString', '_LWPCookieJar',
'_MozillaCookieJar', '__builtin__', '__future__', '__main__',
'__phello__.foo', '_abcoll', '_ast', '_bisect', '_bsddb', '_codecs',
'_codecs_cn', '_codecs_hk', '_codecs_iso2022', '_codecs_jp',
'_codecs_kr', '_codecs_tw', '_collections', '_crypt', '_csv',
'_ctypes', '_curses', '_curses_panel', '_elementtree', '_functools',
'_hashlib', '_heapq', '_hotshot', '_io', '_json', '_locale',
'_lsprof', '_multibytecodec', '_multiprocessing', '_osx_support',
'_pyio', '_random', '_socket', '_sqlite3', '_sre', '_ssl',
'_strptime', '_struct', '_symtable', '_sysconfigdata',
'_threading_local', '_warnings', '_weakref', '_weakrefset', 'abc',
'aifc', 'antigravity', 'anydbm', 'argparse', 'array', 'ast',
'asynchat', 'asyncore', 'atexit', 'audiodev', 'audioop', 'base64',
'bdb', 'binascii', 'binhex', 'bisect', 'bsddb', 'bsddb.db',
'bsddb.dbobj', 'bsddb.dbrecio', 'bsddb.dbshelve', 'bsddb.dbtables',
'bsddb.dbutils', 'bz2', 'cPickle', 'cProfile', 'cStringIO',
'calendar', 'cgi', 'cgitb', 'chunk', 'cmath', 'cmd', 'code', 'codecs',
'codeop', 'collections', 'colorsys', 'commands', 'compileall',
'compiler', 'compiler.ast', 'compiler.consts', 'compiler.future',
'compiler.misc', 'compiler.pyassem', 'compiler.pycodegen',
'compiler.symbols', 'compiler.syntax', 'compiler.transformer',
'compiler.visitor', 'contextlib', 'cookielib', 'copy', 'copy_reg',
'crypt', 'csv', 'ctypes', 'ctypes._endian', 'ctypes.macholib',
'ctypes.macholib.dyld', 'ctypes.macholib.dylib',
'ctypes.macholib.framework', 'ctypes.util', 'ctypes.wintypes',
'curses', 'curses.ascii', 'curses.has_key', 'curses.panel',
'curses.textpad', 'curses.wrapper', 'datetime', 'dbhash', 'dbm',
'decimal', 'difflib', 'dircache', 'dis', 'distutils',
'distutils.archive_util', 'distutils.bcppcompiler',
'distutils.ccompiler', 'distutils.cmd', 'distutils.command',
'distutils.command.bdist', 'distutils.command.bdist_dumb',
'distutils.command.bdist_msi', 'distutils.command.bdist_rpm',
'distutils.command.bdist_wininst', 'distutils.command.build',
'distutils.command.build_clib', 'distutils.command.build_ext',
'distutils.command.build_py', 'distutils.command.build_scripts',
'distutils.command.check', 'distutils.command.clean',
'distutils.command.config', 'distutils.command.install',
'distutils.command.install_data',
'distutils.command.install_egg_info',
'distutils.command.install_headers', 'distutils.command.install_lib',
'distutils.command.install_scripts', 'distutils.command.register',
'distutils.command.sdist', 'distutils.command.upload',
'distutils.config', 'distutils.core', 'distutils.cygwinccompiler',
'distutils.debug', 'distutils.dep_util', 'distutils.dir_util',
'distutils.dist', 'distutils.emxccompiler', 'distutils.errors',
'distutils.extension', 'distutils.fancy_getopt',
'distutils.file_util', 'distutils.filelist', 'distutils.log',
'distutils.msvc9compiler', 'distutils.msvccompiler',
'distutils.spawn', 'distutils.sysconfig', 'distutils.text_file',
'distutils.unixccompiler', 'distutils.util', 'distutils.version',
'distutils.versionpredicate', 'dl', 'doctest', 'dumbdbm',
'dummy_thread', 'dummy_threading', 'email', 'email._parseaddr',
'email.base64mime', 'email.charset', 'email.encoders', 'email.errors',
'email.feedparser', 'email.generator', 'email.header',
'email.iterators', 'email.message', 'email.mime',
'email.mime.application', 'email.mime.audio', 'email.mime.base',
'email.mime.image', 'email.mime.message', 'email.mime.multipart',
'email.mime.nonmultipart', 'email.mime.text', 'email.parser',
'email.quoprimime', 'email.utils', 'encodings', 'encodings.aliases',
'encodings.ascii', 'encodings.base64_codec', 'encodings.big5',
'encodings.big5hkscs', 'encodings.bz2_codec', 'encodings.charmap',
'encodings.cp037', 'encodings.cp1006', 'encodings.cp1026',
'encodings.cp1140', 'encodings.cp1250', 'encodings.cp1251',
'encodings.cp1252', 'encodings.cp1253', 'encodings.cp1254',
'encodings.cp1255', 'encodings.cp1256', 'encodings.cp1257',
'encodings.cp1258', 'encodings.cp424', 'encodings.cp437',
'encodings.cp500', 'encodings.cp720', 'encodings.cp737',
'encodings.cp775', 'encodings.cp850', 'encodings.cp852',
'encodings.cp855', 'encodings.cp856', 'encodings.cp857',
'encodings.cp858', 'encodings.cp860', 'encodings.cp861',
'encodings.cp862', 'encodings.cp863', 'encodings.cp864',
'encodings.cp865', 'encodings.cp866', 'encodings.cp869',
'encodings.cp874', 'encodings.cp875', 'encodings.cp932',
'encodings.cp949', 'encodings.cp950', 'encodings.euc_jis_2004',
'encodings.euc_jisx0213', 'encodings.euc_jp', 'encodings.euc_kr',
'encodings.gb18030', 'encodings.gb2312', 'encodings.gbk',
'encodings.hex_codec', 'encodings.hp_roman8', 'encodings.hz',
'encodings.idna', 'encodings.iso2022_jp', 'encodings.iso2022_jp_1',
'encodings.iso2022_jp_2', 'encodings.iso2022_jp_2004',
'encodings.iso2022_jp_3', 'encodings.iso2022_jp_ext',
'encodings.iso2022_kr', 'encodings.iso8859_1', 'encodings.iso8859_10',
'encodings.iso8859_11', 'encodings.iso8859_13',
'encodings.iso8859_14', 'encodings.iso8859_15',
'encodings.iso8859_16', 'encodings.iso8859_2', 'encodings.iso8859_3',
'encodings.iso8859_4', 'encodings.iso8859_5', 'encodings.iso8859_6',
'encodings.iso8859_7', 'encodings.iso8859_8', 'encodings.iso8859_9',
'encodings.johab', 'encodings.koi8_r', 'encodings.koi8_u',
'encodings.latin_1', 'encodings.mac_arabic', 'encodings.mac_centeuro',
'encodings.mac_croatian', 'encodings.mac_cyrillic',
'encodings.mac_farsi', 'encodings.mac_greek', 'encodings.mac_iceland',
'encodings.mac_latin2', 'encodings.mac_roman',
'encodings.mac_romanian', 'encodings.mac_turkish', 'encodings.mbcs',
'encodings.palmos', 'encodings.ptcp154', 'encodings.punycode',
'encodings.quopri_codec', 'encodings.raw_unicode_escape',
'encodings.rot_13', 'encodings.shift_jis', 'encodings.shift_jis_2004',
'encodings.shift_jisx0213', 'encodings.string_escape',
'encodings.tis_620', 'encodings.undefined',
'encodings.unicode_escape', 'encodings.unicode_internal',
'encodings.utf_16', 'encodings.utf_16_be', 'encodings.utf_16_le',
'encodings.utf_32', 'encodings.utf_32_be', 'encodings.utf_32_le',
'encodings.utf_7', 'encodings.utf_8', 'encodings.utf_8_sig',
'encodings.uu_codec', 'encodings.zlib_codec', 'errno', 'exceptions',
'fcntl', 'filecmp', 'fileinput', 'fnmatch', 'formatter', 'fpformat',
'fractions', 'ftplib', 'functools', 'future_builtins', 'gc', 'gdbm',
'genericpath', 'getopt', 'getpass', 'gettext', 'glob', 'grp', 'gzip',
'hashlib', 'heapq', 'hmac', 'hotshot', 'hotshot.log', 'hotshot.stats',
'hotshot.stones', 'htmlentitydefs', 'htmllib', 'httplib', 'idlelib',
'idlelib.AutoComplete', 'idlelib.AutoCompleteWindow',
'idlelib.AutoExpand', 'idlelib.Bindings', 'idlelib.CallTipWindow',
'idlelib.CallTips', 'idlelib.ClassBrowser', 'idlelib.CodeContext',
'idlelib.ColorDelegator', 'idlelib.Debugger', 'idlelib.Delegator',
'idlelib.EditorWindow', 'idlelib.FileList', 'idlelib.FormatParagraph',
'idlelib.GrepDialog', 'idlelib.HyperParser', 'idlelib.IOBinding',
'idlelib.IdleHistory', 'idlelib.MultiCall', 'idlelib.MultiStatusBar',
'idlelib.ObjectBrowser', 'idlelib.OutputWindow', 'idlelib.ParenMatch',
'idlelib.PathBrowser', 'idlelib.Percolator', 'idlelib.PyParse',
'idlelib.PyShell', 'idlelib.RemoteDebugger',
'idlelib.RemoteObjectBrowser', 'idlelib.ReplaceDialog',
'idlelib.RstripExtension', 'idlelib.ScriptBinding',
'idlelib.ScrolledList', 'idlelib.SearchDialog',
'idlelib.SearchDialogBase', 'idlelib.SearchEngine',
'idlelib.StackViewer', 'idlelib.ToolTip', 'idlelib.TreeWidget',
'idlelib.UndoDelegator', 'idlelib.WidgetRedirector',
'idlelib.WindowList', 'idlelib.ZoomHeight', 'idlelib.aboutDialog',
'idlelib.configDialog', 'idlelib.configHandler',
'idlelib.configHelpSourceEdit', 'idlelib.configSectionNameDialog',
'idlelib.dynOptionMenuWidget', 'idlelib.idle', 'idlelib.idlever',
'idlelib.keybindingDialog', 'idlelib.macosxSupport', 'idlelib.rpc',
'idlelib.run', 'idlelib.tabbedpages', 'idlelib.textView', 'ihooks',
'imageop', 'imaplib', 'imghdr', 'imp', 'importlib', 'imputil',
'inspect', 'io', 'itertools', 'json', 'json.decoder', 'json.encoder',
'json.scanner', 'json.tool', 'keyword', 'lib2to3', 'lib2to3.__main__',
'lib2to3.btm_matcher', 'lib2to3.btm_utils', 'lib2to3.fixer_base',
'lib2to3.fixer_util', 'lib2to3.fixes', 'lib2to3.fixes.fix_apply',
'lib2to3.fixes.fix_basestring', 'lib2to3.fixes.fix_buffer',
'lib2to3.fixes.fix_callable', 'lib2to3.fixes.fix_dict',
'lib2to3.fixes.fix_except', 'lib2to3.fixes.fix_exec',
'lib2to3.fixes.fix_execfile', 'lib2to3.fixes.fix_exitfunc',
'lib2to3.fixes.fix_filter', 'lib2to3.fixes.fix_funcattrs',
'lib2to3.fixes.fix_future', 'lib2to3.fixes.fix_getcwdu',
'lib2to3.fixes.fix_has_key', 'lib2to3.fixes.fix_idioms',
'lib2to3.fixes.fix_import', 'lib2to3.fixes.fix_imports',
'lib2to3.fixes.fix_imports2', 'lib2to3.fixes.fix_input',
'lib2to3.fixes.fix_intern', 'lib2to3.fixes.fix_isinstance',
'lib2to3.fixes.fix_itertools', 'lib2to3.fixes.fix_itertools_imports',
'lib2to3.fixes.fix_long', 'lib2to3.fixes.fix_map',
'lib2to3.fixes.fix_metaclass', 'lib2to3.fixes.fix_methodattrs',
'lib2to3.fixes.fix_ne', 'lib2to3.fixes.fix_next',
'lib2to3.fixes.fix_nonzero', 'lib2to3.fixes.fix_numliterals',
'lib2to3.fixes.fix_operator', 'lib2to3.fixes.fix_paren',
'lib2to3.fixes.fix_print', 'lib2to3.fixes.fix_raise',
'lib2to3.fixes.fix_raw_input', 'lib2to3.fixes.fix_reduce',
'lib2to3.fixes.fix_renames', 'lib2to3.fixes.fix_repr',
'lib2to3.fixes.fix_set_literal', 'lib2to3.fixes.fix_standarderror',
'lib2to3.fixes.fix_sys_exc', 'lib2to3.fixes.fix_throw',
'lib2to3.fixes.fix_tuple_params', 'lib2to3.fixes.fix_types',
'lib2to3.fixes.fix_unicode', 'lib2to3.fixes.fix_urllib',
'lib2to3.fixes.fix_ws_comma', 'lib2to3.fixes.fix_xrange',
'lib2to3.fixes.fix_xreadlines', 'lib2to3.fixes.fix_zip',
'lib2to3.main', 'lib2to3.patcomp', 'lib2to3.pgen2',
'lib2to3.pgen2.conv', 'lib2to3.pgen2.driver', 'lib2to3.pgen2.grammar',
'lib2to3.pgen2.literals', 'lib2to3.pgen2.parse', 'lib2to3.pgen2.pgen',
'lib2to3.pgen2.token', 'lib2to3.pgen2.tokenize', 'lib2to3.pygram',
'lib2to3.pytree', 'lib2to3.refactor', 'linecache', 'linuxaudiodev',
'locale', 'logging', 'logging.config', 'logging.handlers', 'macpath',
'macurl2path', 'mailbox', 'mailcap', 'markupbase', 'marshal', 'math',
'md5', 'mhlib', 'mimetools', 'mimetypes', 'mimify', 'mmap',
'modulefinder', 'multifile', 'multiprocessing',
'multiprocessing.connection', 'multiprocessing.dummy',
'multiprocessing.dummy.connection', 'multiprocessing.forking',
'multiprocessing.heap', 'multiprocessing.managers',
'multiprocessing.pool', 'multiprocessing.process',
'multiprocessing.queues', 'multiprocessing.reduction',
'multiprocessing.sharedctypes', 'multiprocessing.synchronize',
'multiprocessing.util', 'mutex', 'netrc', 'new', 'nis', 'nntplib',
'ntpath', 'nturl2path', 'numbers', 'opcode', 'operator', 'optparse',
'os', 'os2emxpath', 'ossaudiodev', 'parser', 'pdb', 'pickle',
'pickletools', 'pipes', 'pkgutil', 'plat-linux2.CDROM',
'plat-linux2.DLFCN', 'plat-linux2.IN', 'plat-linux2.TYPES',
'platform', 'plistlib', 'popen2', 'poplib', 'posix', 'posixfile',
'posixpath', 'pprint', 'profile', 'pstats', 'pty', 'pwd',
'py_compile', 'pyclbr', 'pydoc', 'pydoc_data', 'pydoc_data.topics',
'pyexpat', 'quopri', 'random', 're', 'readline', 'repr', 'resource',
'rexec', 'rfc822', 'rlcompleter', 'robotparser', 'runpy', 'sched',
'select', 'sets', 'sgmllib', 'sha', 'shelve', 'shlex', 'shutil',
'signal', 'site', 'smtpd', 'smtplib', 'sndhdr', 'socket', 'spwd',
'sqlite3', 'sqlite3.dbapi2', 'sqlite3.dump', 'sre', 'sre_compile',
'sre_constants', 'sre_parse', 'ssl', 'stat', 'statvfs', 'string',
'stringold', 'stringprep', 'strop', 'struct', 'subprocess', 'sunau',
'sunaudio', 'symbol', 'symtable', 'sys', 'sysconfig', 'syslog',
'tabnanny', 'tarfile', 'telnetlib', 'tempfile', 'termios', 'test',
'test.test_support', 'textwrap', 'this', 'thread', 'threading',
'time', 'timeit', 'timing', 'toaiff', 'token', 'tokenize', 'trace',
'traceback', 'tty', 'types', 'unicodedata', 'unittest',
'unittest.__main__', 'unittest.case', 'unittest.loader',
'unittest.main', 'unittest.result', 'unittest.runner',
'unittest.signals', 'unittest.suite', 'unittest.test',
'unittest.test.dummy', 'unittest.test.support',
'unittest.test.test_assertions', 'unittest.test.test_break',
'unittest.test.test_case', 'unittest.test.test_discovery',
'unittest.test.test_functiontestcase', 'unittest.test.test_loader',
'unittest.test.test_program', 'unittest.test.test_result',
'unittest.test.test_runner', 'unittest.test.test_setups',
'unittest.test.test_skipping', 'unittest.test.test_suite',
'unittest.util', 'urllib', 'urllib2', 'urlparse', 'user', 'uu',
'uuid', 'warnings', 'wave', 'weakref', 'webbrowser', 'whichdb',
'wsgiref', 'wsgiref.handlers', 'wsgiref.headers',
'wsgiref.simple_server', 'wsgiref.util', 'wsgiref.validate', 'xdrlib',
'xml', 'xml.dom', 'xml.dom.NodeFilter', 'xml.dom.domreg',
'xml.dom.expatbuilder', 'xml.dom.minicompat', 'xml.dom.minidom',
'xml.dom.pulldom', 'xml.dom.xmlbuilder', 'xml.etree',
'xml.etree.ElementInclude', 'xml.etree.ElementPath',
'xml.etree.ElementTree', 'xml.etree.cElementTree', 'xml.parsers',
'xml.parsers.expat', 'xml.sax', 'xml.sax._exceptions',
'xml.sax.expatreader', 'xml.sax.handler', 'xml.sax.saxutils',
'xml.sax.xmlreader', 'xmllib', 'xmlrpclib', 'xxsubtype', 'zipfile', ]
# Dynamic modules that can't be auto-discovered by the script above
manual_stdlib = ['os.path', ]
py2_stdlib.extend(manual_stdlib)
def _get_import_type(module):
mod_base, _, _ = module.partition('.')
if mod_base in module_cache:
return module_cache[mod_base]
if module in module_cache:
return module_cache[module]
def cache_type(module_type):
module_cache[mod_base] = module_type
module_cache[module] = module_type
return module_type
# First check if the module is local
# Check static stdlib list
if module in py2_stdlib:
return cache_type('stdlib')
# Check if the module is local
try:
imp.find_module(mod_base, ['.'])
_find_module(module, ['.'])
# If the previous line succeeded then it must be a project module
return cache_type('project')
except ImportError:
pass
try:
_, path, _ = imp.find_module(mod_base)
except ImportError:
return cache_type('third-party')
if path is None:
# NOTE(imelnikov): python 3 returns None for path of builtin
# modules, like sys or builtin; they are definitely stdlib
return cache_type('stdlib')
if 'site-packages' in path or 'dist-packages' in path:
return cache_type('third-party')
std_paths = [stdlib_path_prefix, sys.prefix]
# NOTE(imelnikov): if we are in virtualenv, we should consider
# real prefix too, as python 3 copies some stdlib modules to
# virtualenv, but not all of them.
real_prefix = getattr(sys, 'real_prefix', None)
if real_prefix is not None:
std_paths.append(real_prefix)
if path == module or any(path.startswith(p) for p in std_paths):
return cache_type('stdlib')
# Otherwise treat it as third-party - this means we may treat some stdlib
# modules as third-party, but that's okay because we are allowing
# third-party libs in the stdlib section.
return cache_type('third-party')

View File

@@ -0,0 +1,57 @@
# Copyright (c) 2014 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import mock
import six
from hacking.checks import imports
from hacking import tests
def fake_find_module(module, path):
# Behave as though we're running in the oslo.db project
if path is None and module == 'oslo':
# The oslo namespace module won't be found
raise ImportError
elif module == 'oslo' and path[0] == '.':
return (None, './oslo', None)
elif module == 'db' and path[0] == './oslo':
return (None, './oslo/db', None)
elif './oslo/db' in path[0] and module != '_':
return (None, '%s/%s' % (path[0], module), None)
raise ImportError
if six.PY3:
import_name = 'builtins.__import__'
else:
import_name = '__builtin__.__import__'
class ImportTestCase(tests.TestCase):
@mock.patch(import_name)
@mock.patch('imp.find_module')
def test_namespace_module(self, find_module, mock_import):
find_module.side_effect = fake_find_module
self.assertEqual('third-party', imports._get_import_type('oslo.i18n'))
self.assertEqual('project', imports._get_import_type('oslo.db'))
@mock.patch(import_name)
@mock.patch('imp.find_module')
def test_non_module(self, find_module, mock_import):
find_module.side_effect = fake_find_module
mock_import.side_effect = ImportError
mod_name = 'oslo.db.openstack.common.gettextutils._'
self.assertEqual('project', imports._get_import_type(mod_name))

View File

@@ -1,6 +1,7 @@
coverage>=3.6
discover
fixtures>=0.3.14
mock
python-subunit>=0.0.18
sphinx>=1.1.2,!=1.2.0,<1.3
oslosphinx