needed tests need to test when nested so that the toplevel path isn t sys path need to test dynamic path detection both at toplevel and nested with dynamic path check when a loader is returned on path reload that is trying to switch from a namespace package to a regular package make sure sys modules is the same object and has the same content when exiting the context as when entering similar to importlib test util uncache but doesn t require explicit names save import state and sys modules cache and restore it on exit typical usage with namespacetreecontextpath tmpxxyyportion1 tmpxxyyportion2 pass use default metapath and pathhooks unless specified otherwise subclasses should define self root and self paths under that root to be added to sys path make sure only foo one can be imported now modify sys path and make sure foo two is now importable we manipulate sys path before anything is imported to avoid accidental cache invalidation when changing it the module does not exist so it cannot be imported util createmodules manipulates sys path so we must create the modules manually instead the module is not known so it cannot be imported yet but after explicit cache invalidation it is importable now add project3 the paths dynamically get longer to include the new directories this will fail because missingdirectory zip contains length date time name 29 20120503 18 13 fooone py 0 20120503 20 57 bar 38 20120503 20 57 bartwo py 67 3 files because there is no foo the zipimporter currently doesn t know that foo is a namespace package this succeeds because there is a bar in the zip file make sure we find the module in preference to the namespace package now modify sys path and reload and make sure foo two is now importable bpo32303 bpo32305 bpo35843 needed tests need to test when nested so that the top level path isn t sys path need to test dynamic path detection both at top level and nested with dynamic path check when a loader is returned on path reload that is trying to switch from a namespace package to a regular package make sure sys modules is the same object and has the same content when exiting the context as when entering similar to importlib test util uncache but doesn t require explicit names save import state and sys modules cache and restore it on exit typical usage with namespace_tree_context path tmp xxyy portion1 tmp xxyy portion2 pass use default meta_path and path_hooks unless specified otherwise subclasses should define self root and self paths under that root to be added to sys path make sure only foo one can be imported now modify sys path and make sure foo two is now importable we manipulate sys path before anything is imported to avoid accidental cache invalidation when changing it the module does not exist so it cannot be imported util create_modules manipulates sys path so we must create the modules manually instead the module is not known so it cannot be imported yet but after explicit cache invalidation it is importable now add project3 the paths dynamically get longer to include the new directories this will fail because missing_directory zip contains length date time name 29 2012 05 03 18 13 foo one py 0 2012 05 03 20 57 bar 38 2012 05 03 20 57 bar two py 67 3 files because there is no foo the zipimporter currently doesn t know that foo is a namespace package this succeeds because there is a bar in the zip file make sure we find the module in preference to the namespace package now modify sys path and reload and make sure foo two is now importable bpo 32303 bpo 32305 bpo 35843
import contextlib
import importlib
import importlib.abc
import importlib.machinery
import os
import sys
import tempfile
import unittest

from test.test_importlib import util









@contextlib.contextmanager
def sys_modules_context():
    
    sys_modules_saved = sys.modules
    sys_modules_copy = sys.modules.copy()
    try:
        yield
    finally:
        sys.modules = sys_modules_saved
        sys.modules.clear()
        sys.modules.update(sys_modules_copy)


@contextlib.contextmanager
def namespace_tree_context(**kwargs):
    
    
    kwargs.setdefault('meta_path', sys.meta_path)
    kwargs.setdefault('path_hooks', sys.path_hooks)
    import_context = util.import_state(**kwargs)
    with import_context, sys_modules_context():
        yield

class NamespacePackageTest(unittest.TestCase):
    
    root = os.path.join(os.path.dirname(__file__), 'namespace_pkgs')

    def setUp(self):
        self.resolved_paths = [
            os.path.join(self.root, path) for path in self.paths
        ]
        self.enterContext(namespace_tree_context(path=self.resolved_paths))


class SingleNamespacePackage(NamespacePackageTest):
    paths = ['portion1']

    def test_simple_package(self):
        import foo.one
        self.assertEqual(foo.one.attr, 'portion1 foo one')

    def test_cant_import_other(self):
        with self.assertRaises(ImportError):
            import foo.two

    def test_simple_repr(self):
        import foo.one
        self.assertTrue(repr(foo).startswith("<module 'foo' (namespace) from ["))


class DynamicPathNamespacePackage(NamespacePackageTest):
    paths = ['portion1']

    def test_dynamic_path(self):
        
        import foo.one
        self.assertEqual(foo.one.attr, 'portion1 foo one')

        with self.assertRaises(ImportError):
            import foo.two

        
        sys.path.append(os.path.join(self.root, 'portion2'))

        
        import foo.two
        self.assertEqual(foo.two.attr, 'portion2 foo two')


class CombinedNamespacePackages(NamespacePackageTest):
    paths = ['both_portions']

    def test_imports(self):
        import foo.one
        import foo.two
        self.assertEqual(foo.one.attr, 'both_portions foo one')
        self.assertEqual(foo.two.attr, 'both_portions foo two')


class SeparatedNamespacePackages(NamespacePackageTest):
    paths = ['portion1', 'portion2']

    def test_imports(self):
        import foo.one
        import foo.two
        self.assertEqual(foo.one.attr, 'portion1 foo one')
        self.assertEqual(foo.two.attr, 'portion2 foo two')


class SeparatedNamespacePackagesCreatedWhileRunning(NamespacePackageTest):
    paths = ['portion1']

    def test_invalidate_caches(self):
        with tempfile.TemporaryDirectory() as temp_dir:
            
            
            sys.path.append(temp_dir)

            import foo.one
            self.assertEqual(foo.one.attr, 'portion1 foo one')

            
            with self.assertRaises(ImportError):
                import foo.just_created

            
            
            namespace_path = os.path.join(temp_dir, 'foo')
            os.mkdir(namespace_path)
            module_path = os.path.join(namespace_path, 'just_created.py')
            with open(module_path, 'w', encoding='utf-8') as file:
                file.write('attr = "just_created foo"')

            
            with self.assertRaises(ImportError):
                import foo.just_created

            
            importlib.invalidate_caches()
            import foo.just_created
            self.assertEqual(foo.just_created.attr, 'just_created foo')


class SeparatedOverlappingNamespacePackages(NamespacePackageTest):
    paths = ['portion1', 'both_portions']

    def test_first_path_wins(self):
        import foo.one
        import foo.two
        self.assertEqual(foo.one.attr, 'portion1 foo one')
        self.assertEqual(foo.two.attr, 'both_portions foo two')

    def test_first_path_wins_again(self):
        sys.path.reverse()
        import foo.one
        import foo.two
        self.assertEqual(foo.one.attr, 'both_portions foo one')
        self.assertEqual(foo.two.attr, 'both_portions foo two')

    def test_first_path_wins_importing_second_first(self):
        import foo.two
        import foo.one
        self.assertEqual(foo.one.attr, 'portion1 foo one')
        self.assertEqual(foo.two.attr, 'both_portions foo two')


class SingleZipNamespacePackage(NamespacePackageTest):
    paths = ['top_level_portion1.zip']

    def test_simple_package(self):
        import foo.one
        self.assertEqual(foo.one.attr, 'portion1 foo one')

    def test_cant_import_other(self):
        with self.assertRaises(ImportError):
            import foo.two


class SeparatedZipNamespacePackages(NamespacePackageTest):
    paths = ['top_level_portion1.zip', 'portion2']

    def test_imports(self):
        import foo.one
        import foo.two
        self.assertEqual(foo.one.attr, 'portion1 foo one')
        self.assertEqual(foo.two.attr, 'portion2 foo two')
        self.assertIn('top_level_portion1.zip', foo.one.__file__)
        self.assertNotIn('.zip', foo.two.__file__)


class SingleNestedZipNamespacePackage(NamespacePackageTest):
    paths = ['nested_portion1.zip/nested_portion1']

    def test_simple_package(self):
        import foo.one
        self.assertEqual(foo.one.attr, 'portion1 foo one')

    def test_cant_import_other(self):
        with self.assertRaises(ImportError):
            import foo.two


class SeparatedNestedZipNamespacePackages(NamespacePackageTest):
    paths = ['nested_portion1.zip/nested_portion1', 'portion2']

    def test_imports(self):
        import foo.one
        import foo.two
        self.assertEqual(foo.one.attr, 'portion1 foo one')
        self.assertEqual(foo.two.attr, 'portion2 foo two')
        fn = os.path.join('nested_portion1.zip', 'nested_portion1')
        self.assertIn(fn, foo.one.__file__)
        self.assertNotIn('.zip', foo.two.__file__)


class LegacySupport(NamespacePackageTest):
    paths = ['not_a_namespace_pkg', 'portion1', 'portion2', 'both_portions']

    def test_non_namespace_package_takes_precedence(self):
        import foo.one
        with self.assertRaises(ImportError):
            import foo.two
        self.assertIn('__init__', foo.__file__)
        self.assertNotIn('namespace', str(foo.__loader__).lower())


class DynamicPathCalculation(NamespacePackageTest):
    paths = ['project1', 'project2']

    def test_project3_fails(self):
        import parent.child.one
        self.assertEqual(len(parent.__path__), 2)
        self.assertEqual(len(parent.child.__path__), 2)
        import parent.child.two
        self.assertEqual(len(parent.__path__), 2)
        self.assertEqual(len(parent.child.__path__), 2)

        self.assertEqual(parent.child.one.attr, 'parent child one')
        self.assertEqual(parent.child.two.attr, 'parent child two')

        with self.assertRaises(ImportError):
            import parent.child.three

        self.assertEqual(len(parent.__path__), 2)
        self.assertEqual(len(parent.child.__path__), 2)

    def test_project3_succeeds(self):
        import parent.child.one
        self.assertEqual(len(parent.__path__), 2)
        self.assertEqual(len(parent.child.__path__), 2)
        import parent.child.two
        self.assertEqual(len(parent.__path__), 2)
        self.assertEqual(len(parent.child.__path__), 2)

        self.assertEqual(parent.child.one.attr, 'parent child one')
        self.assertEqual(parent.child.two.attr, 'parent child two')

        with self.assertRaises(ImportError):
            import parent.child.three

        
        sys.path.append(os.path.join(self.root, 'project3'))
        import parent.child.three

        
        self.assertEqual(len(parent.__path__), 3)
        self.assertEqual(len(parent.child.__path__), 3)

        self.assertEqual(parent.child.three.attr, 'parent child three')


class ZipWithMissingDirectory(NamespacePackageTest):
    paths = ['missing_directory.zip']

    @unittest.expectedFailure
    def test_missing_directory(self):
        
        
        
        
        
        
        
        

        
        

        import foo.one

    def test_present_directory(self):
        
        import bar.two
        self.assertEqual(bar.two.attr, 'missing_directory foo two')


class ModuleAndNamespacePackageInSameDir(NamespacePackageTest):
    paths = ['module_and_namespace_package']

    def test_module_before_namespace_package(self):
        
        
        import a_test
        self.assertEqual(a_test.attr, 'in module')


class ReloadTests(NamespacePackageTest):
    paths = ['portion1']

    def test_simple_package(self):
        import foo.one
        foo = importlib.reload(foo)
        self.assertEqual(foo.one.attr, 'portion1 foo one')

    def test_cant_import_other(self):
        import foo
        with self.assertRaises(ImportError):
            import foo.two
        foo = importlib.reload(foo)
        with self.assertRaises(ImportError):
            import foo.two

    def test_dynamic_path(self):
        import foo.one
        with self.assertRaises(ImportError):
            import foo.two

        
        sys.path.append(os.path.join(self.root, 'portion2'))
        foo = importlib.reload(foo)

        
        import foo.two
        self.assertEqual(foo.two.attr, 'portion2 foo two')


class LoaderTests(NamespacePackageTest):
    paths = ['portion1']

    def test_namespace_loader_consistency(self):
        
        import foo
        self.assertEqual(foo.__loader__, foo.__spec__.loader)
        self.assertIsNotNone(foo.__loader__)

    def test_namespace_origin_consistency(self):
        
        import foo
        self.assertIsNone(foo.__spec__.origin)
        self.assertIsNone(foo.__file__)

    def test_path_indexable(self):
        
        import foo
        expected_path = os.path.join(self.root, 'portion1', 'foo')
        self.assertEqual(foo.__path__[0], expected_path)

    def test_loader_abc(self):
        import foo
        self.assertTrue(isinstance(foo.__loader__, importlib.abc.Loader))
        self.assertTrue(isinstance(foo.__loader__, importlib.machinery.NamespaceLoader))


if __name__ == "__main__":
    unittest.main()