1from types import TracebackType 2from typing import Optional 3from typing import Tuple 4from typing import Type 5 6 7class _DeferredImportExceptionContextManager(object): 8 """Context manager to defer exceptions from imports. 9 10 Catches :exc:`ImportError` and :exc:`SyntaxError`. 11 If any exception is caught, this class raises an :exc:`ImportError` when being checked. 12 13 """ 14 15 def __init__(self) -> None: 16 self._deferred: Optional[Tuple[Exception, str]] = None 17 18 def __enter__(self) -> "_DeferredImportExceptionContextManager": 19 """Enter the context manager. 20 21 Returns: 22 Itself. 23 24 """ 25 return self 26 27 def __exit__( 28 self, 29 exc_type: Optional[Type[Exception]], 30 exc_value: Optional[Exception], 31 traceback: Optional[TracebackType], 32 ) -> Optional[bool]: 33 """Exit the context manager. 34 35 Args: 36 exc_type: 37 Raised exception type. :obj:`None` if nothing is raised. 38 exc_value: 39 Raised exception object. :obj:`None` if nothing is raised. 40 traceback: 41 Associated traceback. :obj:`None` if nothing is raised. 42 43 Returns: 44 :obj:`None` if nothing is deferred, otherwise :obj:`True`. 45 :obj:`True` will suppress any exceptions avoiding them from propagating. 46 47 """ 48 if isinstance(exc_value, (ImportError, SyntaxError)): 49 if isinstance(exc_value, ImportError): 50 message = ( 51 "Tried to import '{}' but failed. Please make sure that the package is " 52 "installed correctly to use this feature. Actual error: {}." 53 ).format(exc_value.name, exc_value) 54 elif isinstance(exc_value, SyntaxError): 55 message = ( 56 "Tried to import a package but failed due to a syntax error in {}. Please " 57 "make sure that the Python version is correct to use this feature. Actual " 58 "error: {}." 59 ).format(exc_value.filename, exc_value) 60 else: 61 assert False 62 63 self._deferred = (exc_value, message) 64 return True 65 return None 66 67 def is_successful(self) -> bool: 68 """Return whether the context manager has caught any exceptions. 69 70 Returns: 71 :obj:`True` if no exceptions are caught, :obj:`False` otherwise. 72 73 """ 74 return self._deferred is None 75 76 def check(self) -> None: 77 """Check whether the context manger has caught any exceptions. 78 79 Raises: 80 :exc:`ImportError`: 81 If any exception was caught from the caught exception. 82 83 """ 84 if self._deferred is not None: 85 exc_value, message = self._deferred 86 raise ImportError(message) from exc_value 87 88 89def try_import() -> _DeferredImportExceptionContextManager: 90 """Create a context manager that can wrap imports of optional packages to defer exceptions. 91 92 Returns: 93 Deferred import context manager. 94 95 """ 96 return _DeferredImportExceptionContextManager() 97