New functions: uninstall(), is_installed()

Plus some minor changes:
- added unittests to test reset() functionality
- fixed a minor bug in __init__.py where py2 module was import first when running on py3
- prevent pyc files from being written during unittests, to protect later tests from finding pyc files written by earlier tests

NOTE: reset() and uninstall() don't fully work in py2, and their tests currently fail on py2,
until issue#2 is resolved (https://github.com/elfsternberg/polyloader/issues/2)
This commit is contained in:
shx2 2016-11-10 11:05:51 +02:00
parent 39e93c354f
commit a2d87aa59f
5 changed files with 142 additions and 5 deletions

View File

@ -5,10 +5,9 @@ __author__ = 'Kenneth M. "Elf" Sternberg'
__email__ = 'elf.sternberg@gmail.com' __email__ = 'elf.sternberg@gmail.com'
__version__ = '0.1.0' __version__ = '0.1.0'
if sys.version_info[0:2] >= (2, 6):
from ._python2 import install, reset
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
from ._python3 import install, reset from ._python3 import install, uninstall, is_installed, reset
elif sys.version_info[0:2] >= (2, 6):
from ._python2 import install, uninstall, is_installed, reset
__all__ = ['install', 'reset'] __all__ = ['install', 'uninstall', 'is_installed', 'reset']

View File

@ -111,6 +111,21 @@ class PolyFinder(object):
return return
cls._loader_handlers += [Loader(suf, compiler) for suf in suffixes] cls._loader_handlers += [Loader(suf, compiler) for suf in suffixes]
@classmethod
def _uninstall(cls, suffixes):
if isinstance(suffixes, basestring):
suffixes = [suffixes]
suffixes = set(suffixes)
overlap = suffixes.intersection(set([suf[0] for suf in imp.get_suffixes()]))
if overlap:
raise RuntimeError("Removing a native Python extensions is not permitted.")
cls._loader_handlers = [ loader for loader in cls._loader_handlers if loader.suffix not in suffixes ]
@classmethod
def _is_installed(cls, suffix):
return any( loader.suffix == suffix for loader in cls._loader_handlers )
@classmethod @classmethod
def getmodulename(cls, path): def getmodulename(cls, path):
filename = os.path.basename(path) filename = os.path.basename(path)
@ -174,6 +189,13 @@ def install(compiler, suffixes):
PolyFinder._installed = True PolyFinder._installed = True
PolyFinder._install(compiler, suffixes) PolyFinder._install(compiler, suffixes)
def uninstall(suffixes):
if not PolyFinder._installed:
return
PolyFinder._uninstall(suffixes)
def is_installed(suffix):
return PolyFinder._is_installed(suffix)
def reset(): def reset():
PolyFinder._loader_handlers = [] PolyFinder._loader_handlers = []

View File

@ -152,6 +152,25 @@ class PolyFileFinder(FileFinder):
dict(_compiler = compiler)) dict(_compiler = compiler))
cls._custom_loaders += [(EXS + suffix, newloader) for suffix in suffixset] cls._custom_loaders += [(EXS + suffix, newloader) for suffix in suffixset]
@classmethod
def _uninstall(cls, suffixes):
if not suffixes:
return
if isinstance(suffixes, str):
suffixes = [suffixes]
suffixset = set(suffixes)
overlap = suffixset.intersection(set([suf[0] for suf in cls._native_loaders]))
if overlap:
raise RuntimeError("Removing a native Python extensions is not permitted.")
exs_suffixset = set( EXS + suffix for suffix in suffixset )
cls._custom_loaders = [ (suffix, loader) for suffix, loader in cls._custom_loaders if suffix not in exs_suffixset ]
@classmethod
def _is_installed(cls, suffix):
suffix = EXS + suffix
return any( suf == suffix for suf, loader in cls._custom_loaders )
@classmethod @classmethod
def getmodulename(cls, path): def getmodulename(cls, path):
filename = os.path.basename(path) filename = os.path.basename(path)
@ -268,6 +287,11 @@ def install(compiler, suffixes):
PolyFileFinder._install(compiler, suffixes) PolyFileFinder._install(compiler, suffixes)
def uninstall(suffixes):
PolyFileFinder._uninstall(suffixes)
def is_installed(suffix):
return PolyFileFinder._is_installed(suffix)
def reset(): def reset():
PolyFileFinder._custom_loaders = [] PolyFileFinder._custom_loaders = []

View File

@ -11,6 +11,7 @@ Tests for `polyloader` module.
import polyloader import polyloader
import copy import copy
import sys import sys
import pytest
# Note that these compilers don't actually load much out of the # Note that these compilers don't actually load much out of the
# source files. That's not the point. The point is to show that the # source files. That's not the point. The point is to show that the
@ -28,6 +29,8 @@ class ImportEnvironment(object):
self.meta_path = copy.copy(sys.meta_path) self.meta_path = copy.copy(sys.meta_path)
self.modules = copy.copy(sys.modules) self.modules = copy.copy(sys.modules)
self.path_importer_cache = copy.copy(sys.path_importer_cache) self.path_importer_cache = copy.copy(sys.path_importer_cache)
self.dont_write_bytecode = sys.dont_write_bytecode
sys.dont_write_bytecode = True
return sys return sys
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
@ -36,6 +39,7 @@ class ImportEnvironment(object):
sys.meta_path = self.meta_path sys.meta_path = self.meta_path
sys.modules = self.modules sys.modules = self.modules
sys.path_importer_cache = self.path_importer_cache sys.path_importer_cache = self.path_importer_cache
sys.dont_write_bytecode = self.dont_write_bytecode
class Compiler: class Compiler:
@ -99,6 +103,52 @@ class Test_Polymorph_Module(object):
assert(result2 == "Success for 2: Test Two") assert(result2 == "Success for 2: Test Two")
assert(result3 == "Success for 3: Test Three") assert(result3 == "Success for 3: Test Three")
class Test_Polymorph_Reset(object):
def test_reset_after_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("2"), ['2'])
polyloader.install(compiler("3"), ['3'])
from tests_py2.polytestmix.test3 import result as result3
polyloader.reset()
with pytest.raises(ImportError):
from tests_py2.polytestmix.test2 import result as result2
def test_reset_before_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("3"), ['3'])
polyloader.reset()
with pytest.raises(ImportError):
from tests_py2.polytestmix.test3 import result as result3
class Test_Polymorph_Uninstall(object):
def test_uninstall_after_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("2"), ['2'])
polyloader.install(compiler("3"), ['3'])
import tests_py2.polytestmix.test3
assert(polyloader.is_installed('2'))
assert(polyloader.is_installed('3'))
polyloader.uninstall('2')
assert(not polyloader.is_installed('2'))
assert(polyloader.is_installed('3'))
with pytest.raises(ImportError):
import tests_py2.polytestmix.test2
import tests_py2.polytestmix.test1
def test_uninstall_before_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("2"), ['2'])
polyloader.install(compiler("3"), ['3'])
assert(polyloader.is_installed('2'))
assert(polyloader.is_installed('3'))
polyloader.uninstall('2')
polyloader.uninstall('3')
assert(not polyloader.is_installed('2'))
assert(not polyloader.is_installed('3'))
with pytest.raises(ImportError):
import tests_py2.polytestmix.test2
with pytest.raises(ImportError):
import tests_py2.polytestmix.test3
import tests_py2.polytestmix.test1
class Test_Polymorph_Iterator(object): class Test_Polymorph_Iterator(object):
''' The Django Compatibility test: Can we load arbitrary modules from a package? ''' ''' The Django Compatibility test: Can we load arbitrary modules from a package? '''
def test_iterator(self): def test_iterator(self):

View File

@ -11,6 +11,7 @@ Tests for `polyloader` module.
import polyloader import polyloader
import copy import copy
import sys import sys
import pytest
# Note that these compilers don't actually load much out of the # Note that these compilers don't actually load much out of the
# source files. That's not the point. The point is to show that the # source files. That's not the point. The point is to show that the
@ -28,6 +29,8 @@ class ImportEnvironment(object):
self.meta_path = copy.copy(sys.meta_path) self.meta_path = copy.copy(sys.meta_path)
self.modules = copy.copy(sys.modules) self.modules = copy.copy(sys.modules)
self.path_importer_cache = copy.copy(sys.path_importer_cache) self.path_importer_cache = copy.copy(sys.path_importer_cache)
self.dont_write_bytecode = sys.dont_write_bytecode
sys.dont_write_bytecode = True
return sys return sys
def __exit__(self, type, value, traceback): def __exit__(self, type, value, traceback):
@ -36,6 +39,7 @@ class ImportEnvironment(object):
sys.meta_path = self.meta_path sys.meta_path = self.meta_path
sys.modules = self.modules sys.modules = self.modules
sys.path_importer_cache = self.path_importer_cache sys.path_importer_cache = self.path_importer_cache
sys.dont_write_bytecode = self.dont_write_bytecode
class Compiler: class Compiler:
@ -99,6 +103,44 @@ class Test_Polymorph_Module(object):
assert(result2 == "Success for 2: Test Two") assert(result2 == "Success for 2: Test Two")
assert(result3 == "Success for 3: Test Three") assert(result3 == "Success for 3: Test Three")
class Test_Polymorph_Reset(object):
def test_reset_after_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("2"), ['2'])
polyloader.install(compiler("3"), ['3'])
from tests_py2.polytestmix.test3 import result as result3
polyloader.reset()
with pytest.raises(ImportError):
from tests_py2.polytestmix.test2 import result as result2
def test_reset_before_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("3"), ['3'])
polyloader.reset()
with pytest.raises(ImportError):
from tests_py2.polytestmix.test3 import result as result3
class Test_Polymorph_Uninstall(object):
def test_uninstall_after_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("3"), ['3'])
polyloader.install(compiler("2"), ['2'])
import tests_py2.polytestmix.test3
polyloader.uninstall('2')
with pytest.raises(ImportError):
import tests_py2.polytestmix.test2
import tests_py2.polytestmix.test1
def test_uninstall_before_import(self):
with ImportEnvironment() as sys:
polyloader.install(compiler("3"), ['3'])
polyloader.install(compiler("2"), ['2'])
polyloader.uninstall('3')
polyloader.uninstall('2')
with pytest.raises(ImportError):
import tests_py2.polytestmix.test3
with pytest.raises(ImportError):
import tests_py2.polytestmix.test2
import tests_py2.polytestmix.test1
class Test_Polymorph_Iterator(object): class Test_Polymorph_Iterator(object):
''' The Django Compatibility test: Can we load arbitrary modules from a package? ''' ''' The Django Compatibility test: Can we load arbitrary modules from a package? '''
def test_iterator(self): def test_iterator(self):