Burlándose de ImportError en Python

Estoy intentando esto durante casi dos horas, sin suerte.

Tengo un módulo que se parece a esto:

try: from zope.component import queryUtility # and things like this except ImportError: # do some fallback operations <-- how to test this? 

Más adelante en el código:

 try: queryUtility(foo) except NameError: # do some fallback actions <-- this one is easy with mocking # zope.component.queryUtility to raise a NameError 

¿Algunas ideas?

EDITAR:

La sugerencia de Alex no parece funcionar:

 >>> import __builtin__ >>> realimport = __builtin__.__import__ >>> def fakeimport(name, *args, **kw): ... if name == 'zope.component': ... raise ImportError ... realimport(name, *args, **kw) ... >>> __builtin__.__import__ = fakeimport 

Al ejecutar las pruebas:

 aatiis@aiur ~/work/ao.shorturl $ ./bin/test --coverage . Running zope.testing.testrunner.layer.UnitTests tests: Set up zope.testing.testrunner.layer.UnitTests in 0.000 seconds. Error in test /home/aatiis/work/ao.shorturl/src/ao/shorturl/shorturl.txt Traceback (most recent call last): File "/usr/lib64/python2.5/unittest.py", line 260, in run testMethod() File "/usr/lib64/python2.5/doctest.py", line 2123, in runTest test, out=new.write, clear_globs=False) File "/usr/lib64/python2.5/doctest.py", line 1361, in run return self.__run(test, compileflags, out) File "/usr/lib64/python2.5/doctest.py", line 1282, in __run exc_info) File "/usr/lib64/python2.5/doctest.py", line 1148, in report_unexpected_exception 'Exception raised:\n' + _indent(_exception_traceback(exc_info))) File "/usr/lib64/python2.5/doctest.py", line 1163, in _failure_header out.append(_indent(source)) File "/usr/lib64/python2.5/doctest.py", line 224, in _indent return re.sub('(?m)^(?!$)', indent*' ', s) File "/usr/lib64/python2.5/re.py", line 150, in sub return _compile(pattern, 0).sub(repl, string, count) File "/usr/lib64/python2.5/re.py", line 239, in _compile p = sre_compile.compile(pattern, flags) File "/usr/lib64/python2.5/sre_compile.py", line 507, in compile p = sre_parse.parse(p, flags) AttributeError: 'NoneType' object has no attribute 'parse' Error in test BaseShortUrlHandler (ao.shorturl) Traceback (most recent call last): File "/usr/lib64/python2.5/unittest.py", line 260, in run testMethod() File "/usr/lib64/python2.5/doctest.py", line 2123, in runTest test, out=new.write, clear_globs=False) File "/usr/lib64/python2.5/doctest.py", line 1351, in run self.debugger = _OutputRedirectingPdb(save_stdout) File "/usr/lib64/python2.5/doctest.py", line 324, in __init__ pdb.Pdb.__init__(self, stdout=out) File "/usr/lib64/python2.5/pdb.py", line 57, in __init__ cmd.Cmd.__init__(self, completekey, stdin, stdout) File "/usr/lib64/python2.5/cmd.py", line 90, in __init__ import sys File "", line 4, in fakeimport NameError: global name 'realimport' is not defined 

Sin embargo, funciona cuando ejecuto el mismo código desde la consola interactiva de Python.

MÁS EDITAR:

Estoy usando zope.testing y un archivo de prueba, shorturl.txt que tiene todas las pruebas específicas de esta parte de mi módulo. Primero, estoy importando el módulo con zope.component disponible, para demostrar y probar el uso habitual. La ausencia de paquetes zope.* Se considera un caso extremo, por lo que lo probaré más adelante. Por lo tanto, tengo que reload() mi módulo, después de hacer que zope.* esté disponible, de alguna manera.

Hasta ahora he intentado usar tempfile.mktempdir() y vaciar los zope/__init__.py y zope/component/__init__.py en el tempdir, luego insertar tempdir en sys.path[0] y eliminar el zope.* antiguo zope.* Paquetes desde sys.modules .

Tampoco funcionó.

AÚN MÁS EDITAR:

Mientras tanto, he intentado esto:

 >>> class NoZope(object): ... def find_module(self, fullname, path): ... if fullname.startswith('zope'): ... raise ImportError ... >>> import sys >>> sys.path.insert(0, NoZope()) 

Y funciona bien para el espacio de nombres del conjunto de pruebas (= para todas las importaciones en shorturl.txt ), pero no se ejecuta en mi módulo principal, ao.shorturl . Ni siquiera cuando lo reload() . ¿Alguna idea de por qué?

 >>> import zope # ok, this raises an ImportError >>> reload(ao.shorturl)  

Importar zope.interfaces genera un ImportError , por lo que no llega a la parte donde importo zope.component , y permanece en el espacio de nombres ao.shorturl . ¡¿Por qué?!

 >>> ao.shorturl.zope.component # why?!  

Solo haga clic en MonkeyPatch en su propia versión de __import__ – puede boost lo que desee cuando reconoce que se le está llamando en los módulos específicos para los que desea simular errores. Ver los documentos para el detalle copioso. Aproximadamente:

 try: import builtins except ImportError: import __builtin__ as builtins realimport = builtins.__import__ def myimport(name, globals, locals, fromlist, level): if ...: raise ImportError return realimport(name, globals, locals, fromlist, level) builtins.__import__ = myimport 

En lugar de ... , puede codificar el name == 'zope.component' , o organizar las cosas de manera más flexible con una callback propia que puede hacer que las importaciones aumenten a pedido en diferentes casos, dependiendo de sus necesidades específicas de prueba, sin requiriendo que codifiques múltiples funciones __import__ -alike ;-).

Tenga en cuenta también que si lo que usa, en lugar de import zope.component o from zope.component import something , es from zope import component , el name será 'zope' , y 'component' será el único elemento en la fromlist .

Edición : los documentos para la función __import__ dicen que el nombre para importar está builtin (como en Python 3), pero en realidad necesita __builtins__ – He editado el código anterior para que funcione de cualquier manera.

Esto es lo que justé en mis pruebas de unidad.

Utiliza PEP-302 “Nuevos ganchos de importación” . (Advertencia: el documento PEP-302 y las notas de publicación más concisas que he vinculado no son exactamente exactas ).

Uso meta_path porque es lo más temprano posible en la secuencia de importación.

Si el módulo ya se ha importado (como en mi caso, porque las pruebas de unidad anteriores se burlan de él), entonces es necesario eliminarlo de sys.modules antes de realizar la reload en el módulo dependiente.

 Ensure we fallback to using ~/.pif if XDG doesn't exist. >>> import sys >>> class _(): ... def __init__(self, modules): ... self.modules = modules ... ... def find_module(self, fullname, path=None): ... if fullname in self.modules: ... raise ImportError('Debug import failure for %s' % fullname) >>> fail_loader = _(['xdg.BaseDirectory']) >>> sys.meta_path.append(fail_loader) >>> del sys.modules['xdg.BaseDirectory'] >>> reload(pif.index) #doctest: +ELLIPSIS  >>> pif.index.CONFIG_DIR == os.path.expanduser('~/.pif') True >>> sys.meta_path.remove(fail_loader) 

Donde se ve el código dentro de pif.index:

 try: import xdg.BaseDirectory CONFIG_DIR = os.path.join(xdg.BaseDirectory.xdg_data_home, 'pif') except ImportError: CONFIG_DIR = os.path.expanduser('~/.pif') 

Para responder a la pregunta de por qué el módulo recién recargado tiene propiedades de las cargas antiguas y nuevas, aquí hay dos archivos de ejemplo.

El primero es un módulo y con un caso de error de importación.

 # y.py try: import sys _loaded_with = 'sys' except ImportError: import os _loaded_with = 'os' 

El segundo es x que muestra cómo el hecho de dejar los manejadores para un módulo puede afectar sus propiedades cuando se vuelve a cargar.

 # x.py import sys import y assert y._loaded_with == 'sys' assert y.sys class _(): def __init__(self, modules): self.modules = modules def find_module(self, fullname, path=None): if fullname in self.modules: raise ImportError('Debug import failure for %s' % fullname) # Importing sys will not raise an ImportError. fail_loader = _(['sys']) sys.meta_path.append(fail_loader) # Demonstrate that reloading doesn't work if the module is already in the # cache. reload(y) assert y._loaded_with == 'sys' assert y.sys # Now we remove sys from the modules cache, and try again. del sys.modules['sys'] reload(y) assert y._loaded_with == 'os' assert y.sys assert y.os # Now we remove the handles to the old y so it can get garbage-collected. del sys.modules['y'] del y import y assert y._loaded_with == 'os' try: assert y.sys except AttributeError: pass assert y.os 

Si no le importa cambiar su propio progtwig, también puede poner la llamada de importación en una función y parche que en sus pruebas.