سؤال

أحاول هذا لمدة ساعتين تقريبًا الآن ، دون أي حظ.

لدي وحدة تبدو هكذا:

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

لاحقًا في الكود:

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

أيه أفكار؟

تعديل:

لا يبدو أن اقتراح أليكس يعمل:

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

عند إجراء الاختبارات:

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 "<doctest shorturl.txt[10]>", line 4, in fakeimport
NameError: global name 'realimport' is not defined

ومع ذلك ، هو يفعل اعمل عندما أقوم بتشغيل نفس الرمز من وحدة التحكم التفاعلية Python.

المزيد من التحرير:

أنا استخدم zope.testing وملف اختبار ، shorturl.txt هذا له كل الاختبارات الخاصة لهذا الجزء من الوحدة النمطية الخاصة بي. أولا أقوم باستيراد الوحدة مع zope.component متاح ، لإظهار واختبار الاستخدام المعتاد. غياب zope.* تعتبر الحزم حالة حافة ، لذلك أنا أختبرها لاحقًا. وهكذا ، لا بد لي من reload() الوحدة النمطية الخاصة بي بعد صنعها zope.* غير متوفر ، بطريقة ما.

حتى الآن حاولت استخدام tempfile.mktempdir() وفارغة zope/__init__.py و zope/component/__init__.py الملفات في tempdir ، ثم إدخال tempdir sys.path[0], وإزالة القديم zope.* حزم من sys.modules.

لم ينجح أيضًا.

أكثر تحريرًا:

في غضون ذلك ، جربت هذا:

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

>>> import sys
>>> sys.path.insert(0, NoZope())

ويعمل بشكل جيد على مساحة اسم جناح الاختبار (= لجميع الواردات في shorturl.txt) ، لكنها لم يتم تنفيذها في الوحدة الرئيسية الخاصة بي ، ao.shorturl. ولا حتى عندما أنا reload() هو - هي. أي فكرة لماذا؟

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

الاستيراد zope.interfaces يرفع ImportError, ، لذلك لا يصل إلى الجزء الذي أستورد فيه zope.component, ، و يبقى في مساحة اسم AO.Shorturl. لماذا؟!

>>> ao.shorturl.zope.component  # why?! 
<module ...>
هل كانت مفيدة؟

المحلول

فقط monkeypatch في builtins نسختك الخاصة من __import__ - يمكن أن يرفع ما تريده عندما يدرك أنه يتم استدعاؤه على الوحدات النمطية المحددة التي تريد أن تسخر من الأخطاء. يرى المستندات للحصول على تفاصيل وفيرة. بقسوة:

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

بدلا من ..., ، يمكنك الترميز المتشددين name == 'zope.component', ، أو ترتيب الأشياء بشكل أكثر مرونة مع رد اتصال خاص بك يمكن أن تجعل الواردات ترتفع عند الطلب في حالات مختلفة ، اعتمادًا على احتياجات الاختبار المحددة ، دون مطالمك بترميز متعددة __import__-وظائف متشابهة ؛-).

لاحظ أيضًا أنه إذا كان ما تستخدمه بدلاً من import zope.component أو from zope.component import something, ، هو from zope import component, ، ال name ثم سيكون 'zope', ، و 'component' سيكون العنصر الوحيد في fromlist.

تعديل: مستندات __import__ الوظيفة قل أن الاسم للاستيراد builtin (كما في بيثون 3) ، ولكن في الواقع تحتاج __builtins__ - لقد قمت بتحرير الكود أعلاه بحيث يعمل في كلتا الحالتين.

نصائح أخرى

هذا هو ما أدت إلى تقديري.

يستخدم PEP-302 "خطافات استيراد جديدة". (تحذير: وثيقة PEP-302 وملاحظات الإصدار الأكثر إيجازًا التي ربطتها ليست بالضبط دقيق.)

أنا أستعمل meta_path لأنه في أقرب وقت ممكن في تسلسل الاستيراد.

إذا تم بالفعل استيراد الوحدة النمطية (كما في حالتي ، لأنه في وقت سابق يسخر من الاختبارات ضدها) ، فمن الضروري إزالتها من sys.modules قبل القيام reload على الوحدة التابعة.

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
 <module 'pif.index' from '...'>

 >>> pif.index.CONFIG_DIR == os.path.expanduser('~/.pif')
 True

 >>> sys.meta_path.remove(fail_loader)

حيث يشبه الرمز داخل 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')

للإجابة على السؤال حول سبب وجود وحدة تم إعادة تحميلها حديثًا خصائص للأحمال القديمة والجديدة ، إليك ملفان مثال.

الأول هو وحدة نمطية y مع حالة فشل الاستيراد.

# y.py

try:
    import sys

    _loaded_with = 'sys'
except ImportError:
    import os

    _loaded_with = 'os'

والثاني هو x مما يوضح كيف يمكن أن تؤثر ترك المقابض للوحدة النمطية على خصائصها عند إعادة تحميلها.

# 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

إذا كنت لا تمانع في تغيير البرنامج نفسه ، فيمكنك أيضًا وضع مكالمة الاستيراد في وظيفة وتصحيح في الاختبارات الخاصة بك.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top