diff -r f6b8b1e5d24d Doc/library/unittest.mock.rst --- a/Doc/library/unittest.mock.rst Wed Jun 22 22:46:51 2016 -0400 +++ b/Doc/library/unittest.mock.rst Thu Jun 23 20:10:40 2016 +0300 @@ -2127,6 +2127,32 @@ >>> assert result == 'bibble' +mock_import +~~~~~~~~~ + +.. function:: mock_import(do_not_mock=None) + + A helper function to mask ``ImportError``s on a scoped code, using the ``with`` + statement, or in method a method used as a decorator. + Failed imports will be ignored, unless specified by the *do_not_mock* argument. + + The *do_not_mock* argument is a package or module name, or package or module + names list. When specified, and imported in the scoped mocked code, importing + them must succeed. If ``None`` (the default) then no import must succeed. + + +Mocking import for a code block: + + >>> with mock_import(): + ... import do_not_exists + + +Mocking import as a decorator: + >>> @mock_import() + ... def method(): + ... import do_not_exists + + .. _auto-speccing: Autospeccing diff -r f6b8b1e5d24d Lib/unittest/mock.py --- a/Lib/unittest/mock.py Wed Jun 22 22:46:51 2016 -0400 +++ b/Lib/unittest/mock.py Thu Jun 23 20:10:40 2016 +0300 @@ -33,6 +33,7 @@ _builtins = {name for name in dir(builtins) if not name.startswith('_')} +_builtins_import = builtins.__import__ BaseExceptions = (BaseException,) if 'java' in sys.platform: @@ -2387,6 +2388,36 @@ return mock +def mock_import(do_not_mock=None): + """ + Mocks import statement, and disable ImportError if a module + could not be imported. + :param do_not_mock: a list of prefixes of modules that should + exists, and an ImportError could be raised for. + :return: patch object + """ + + # convert do_not_mock to a list + if do_not_mock is None: + do_not_mock = [] + elif isinstance(do_not_mock, str): + do_not_mock = [do_not_mock] + + def try_import(module_name, *args, **kwargs): + try: + return _builtins_import(module_name, *args, **kwargs) + except: + if any((module_name == prefix or module_name.startswith(prefix + '.') + for prefix in do_not_mock)): + # This is a module we need to import, so we don't mock it + # and raise the exception + raise + # Mock external module so we can peacefully create our client + return MagicMock() + + return patch('builtins.__import__', try_import) + + class PropertyMock(Mock): """ A mock intended to be used as a property, or other descriptor, on a class. diff -r f6b8b1e5d24d Lib/unittest/test/testmock/testmock.py --- a/Lib/unittest/test/testmock/testmock.py Wed Jun 22 22:46:51 2016 -0400 +++ b/Lib/unittest/test/testmock/testmock.py Thu Jun 23 20:10:40 2016 +0300 @@ -1470,6 +1470,97 @@ self.assertEqual([], h.readlines()) self.assertEqual([], h.readlines()) + def test_mock_import_empty(self): + """ + Basic test case of mock_import + """ + try: + with mock.mock_import(): + import no_such_module + no_such_module.not_such_function() + except (ImportError, AttributeError): + self.fail('mock_import failed') + + @mock.mock_import() + def some_method(): + import no_such_module2 + no_such_module2.not_such_function() + + try: + some_method() + except (ImportError, AttributeError): + self.fail('mock_import failed') + + + def test_mock_import_scoping(self): + """ + Use module outside of scope should raise a name error. + """ + with mock.mock_import(): + import no_such_module + + self.assertIsInstance(no_such_module, mock.MagicMock) + + with self.assertRaises(ImportError): + import no_such_module + + def test_mock_import_outside_scope(self): + """ + Import module outside of scope should raise an import error. + """ + with self.assertRaises(ImportError): + with mock.mock_import(): + pass + import no_such_module + + def test_mock_import_do_not_mock(self): + """ + Importing non-existing module explicitly + asked not to mock, should raise import error + """ + with self.assertRaises(ImportError): + with mock.mock_import(do_not_mock='no_such_module'): + import no_such_module + with self.assertRaises(ImportError): + with mock.mock_import(do_not_mock='no_such_module'): + import no_such_module.inner_module + + try: + with mock.mock_import(do_not_mock='no_such_module'): + import no_such + import no_such_module_not + import no_such_module_not.inner_module + except ImportError: + self.fail('mock_import do_not_mock failed') + + def test_mock_import_do_not_mock_list(self): + """ + Importing non-existing module explicitly + asked not to mock, by list, should raise import error + """ + do_not_mock = ['no_such_module1', 'no_such_module2'] + with self.assertRaises(ImportError): + with mock.mock_import(do_not_mock=do_not_mock): + import no_such_module1 + + with self.assertRaises(ImportError): + with mock.mock_import(do_not_mock=do_not_mock): + import no_such_module2 + + with self.assertRaises(ImportError): + with mock.mock_import(do_not_mock=do_not_mock): + import no_such_module1.inner + + try: + with mock.mock_import(do_not_mock=do_not_mock): + import no_such + import no_such_module1_not + import no_such_module2_not + import no_such_module1_not.inner_module + import no_such_module2_not.inner_module + except ImportError: + self.fail('mock_import do_not_mock failed') + def test_mock_parents(self): for Klass in Mock, MagicMock: m = Klass() diff -r f6b8b1e5d24d Misc/ACKS --- a/Misc/ACKS Wed Jun 22 22:46:51 2016 -0400 +++ b/Misc/ACKS Thu Jun 23 20:10:40 2016 +0300 @@ -1678,3 +1678,4 @@ Doug Zongker Peter Åstrand evilzero +Eyal Posener