0
# Lazy Import Errors
1
2
Context manager and function for deferring ImportError exceptions until modules are actually used, preventing crashes from missing optional dependencies during import time.
3
4
## Capabilities
5
6
### Lazy Import Error Context Manager
7
8
Enables lazy import error handling that captures ImportError exceptions and defers them until the module is actually used, allowing imports of optional dependencies to succeed even when the dependency is not installed.
9
10
```python { .api }
11
def lazy_import_errors(
12
*,
13
get_extras_modules: Optional[Callable[[], Set[str]]] = None,
14
make_error_message: Optional[Callable[[str], str]] = None,
15
):
16
"""
17
Enable lazy import errors.
18
19
When enabled, lazy import errors will capture imports that would otherwise
20
raise ImportErrors and defer those errors until the last possible moment
21
when the functionality is needed.
22
23
This function may be used either as a function directly or as a
24
contextmanager which will disable lazy errors upon exit.
25
26
Args:
27
get_extras_modules: Optional callable that fetches the list of module names
28
that are managed as extras using setup_tools.parse_requirements.
29
(Mutually exclusive with make_error_message)
30
make_error_message: Optional callable that takes the name of the module which
31
failed to import and returns an error message string.
32
(Mutually exclusive with get_extras_modules)
33
"""
34
```
35
36
### Global Lazy Import Errors
37
38
Enable lazy import errors globally for all subsequent imports:
39
40
```python
41
from import_tracker import lazy_import_errors
42
43
# Enable globally - all failed imports will be deferred
44
lazy_import_errors()
45
46
# Now these imports won't crash even if modules are missing
47
import optional_module
48
from some_package import maybe_missing_submodule
49
50
# Error will be raised only when you try to use the module
51
try:
52
optional_module.some_function() # ModuleNotFoundError raised here
53
except ModuleNotFoundError as e:
54
print(f"Module not available: {e}")
55
```
56
57
### Context Manager Usage
58
59
Use as a context manager to enable lazy errors only for specific imports:
60
61
```python
62
from import_tracker import lazy_import_errors
63
64
# Required imports - will fail immediately if missing
65
import requests
66
import json
67
68
# Optional imports - errors deferred until usage
69
with lazy_import_errors():
70
import matplotlib.pyplot as plt
71
import seaborn as sns
72
from some_package import optional_feature
73
74
# These will work fine even if matplotlib/seaborn are not installed
75
print("Imports completed successfully")
76
77
# Error raised only when trying to use missing modules
78
try:
79
plt.plot([1, 2, 3]) # ModuleNotFoundError raised here if matplotlib missing
80
except ModuleNotFoundError:
81
print("Matplotlib not available, using basic plotting")
82
```
83
84
### Custom Error Messages
85
86
Provide custom error messages for missing dependencies:
87
88
```python
89
def custom_error_message(module_name: str) -> str:
90
return f"Missing optional dependency: {module_name}. Install with: pip install {module_name}"
91
92
with lazy_import_errors(make_error_message=custom_error_message):
93
import some_optional_module
94
95
# When used, will show custom message:
96
# ModuleNotFoundError: Missing optional dependency: some_optional_module. Install with: pip install some_optional_module
97
```
98
99
### Extras Integration
100
101
Integration with setuptools extras_require for helpful installation messages:
102
103
```python
104
def get_extras_modules():
105
return {'my_package.plotting', 'my_package.advanced_features'}
106
107
with lazy_import_errors(get_extras_modules=get_extras_modules):
108
from my_package import plotting
109
110
# When used within an extras module, provides installation instructions:
111
# ModuleNotFoundError: No module named 'matplotlib'.
112
# To install the missing dependencies, run `pip install my_package[my_package.plotting]`
113
```
114
115
### Practical Usage Patterns
116
117
#### Optional Dependency Handling
118
119
```python
120
from import_tracker import lazy_import_errors
121
122
# Enable lazy errors for optional dependencies
123
with lazy_import_errors():
124
try:
125
import pandas as pd
126
HAS_PANDAS = True
127
except ImportError:
128
HAS_PANDAS = False
129
130
def process_data(data):
131
if HAS_PANDAS:
132
return pd.DataFrame(data).describe()
133
else:
134
return {"error": "pandas not available"}
135
```
136
137
#### Hierarchical Wild Imports
138
139
Particularly useful for packages with hierarchical wild imports:
140
141
```python
142
# In my_package/__init__.py
143
from import_tracker import lazy_import_errors
144
145
# Enable lazy errors globally for this package
146
lazy_import_errors()
147
148
# These won't crash the entire package if submodules have missing dependencies
149
from .core import *
150
from .utils import *
151
from .optional_features import * # Won't crash if dependencies missing
152
```
153
154
#### Decorator Compatibility
155
156
Lazy errors work with decorators from missing dependencies:
157
158
```python
159
with lazy_import_errors():
160
from some_optional_package import optional_decorator
161
162
# This works even if some_optional_package is not installed
163
@optional_decorator
164
def my_function():
165
pass
166
167
# Error only raised when decorator functionality is actually invoked
168
```
169
170
## Internal Implementation
171
172
The lazy import error system is implemented using several internal classes:
173
174
### Meta Path Finder System
175
176
```python { .api }
177
class _LazyErrorMetaFinder(importlib.abc.MetaPathFinder):
178
"""Meta path finder for intercepting import failures"""
179
180
def find_spec(self, fullname, path, *args, **kwargs):
181
"""Returns lazy loader spec for missing modules"""
182
183
class _LazyErrorLoader(importlib.abc.Loader):
184
"""Loader for creating lazy error modules"""
185
186
def create_module(self, spec):
187
"""Creates _LazyErrorModule instances"""
188
189
def exec_module(self, *_, **__):
190
"""No-op execution"""
191
```
192
193
### Lazy Error Objects
194
195
```python { .api }
196
class _LazyErrorModule(ModuleType):
197
"""Module that defers ImportError until attribute access"""
198
199
def __getattr__(self, name: str) -> _LazyErrorAttr:
200
"""Returns lazy error attributes"""
201
202
class _LazyErrorAttr(type):
203
"""Lazy error object that raises on any meaningful usage"""
204
205
def __call__(self, *_, **__):
206
"""Handles decorator usage at import time"""
207
208
def __getattr__(self, name: str) -> "_LazyErrorAttr":
209
"""Recursive attribute access"""
210
```
211
212
## Error Behavior
213
214
### Import Time vs Usage Time
215
216
```python
217
# Import time - no error raised
218
with lazy_import_errors():
219
import missing_module
220
221
# Usage time - error raised on meaningful operations
222
missing_module.function() # Raises ModuleNotFoundError
223
str(missing_module) # Raises ModuleNotFoundError
224
missing_module == something # Returns False (import time operation)
225
missing_module.attr.nested # Returns another lazy error object
226
```
227
228
### Shell Environment Note
229
230
When using extras integration, be aware of shell escaping requirements:
231
232
```bash
233
# In bash/sh
234
pip install my_package[my_package.feature]
235
236
# In zsh - requires escaping
237
pip install my_package\[my_package.feature\]
238
```
239
240
## Types
241
242
```python { .api }
243
from typing import Callable, Optional, Set
244
from contextlib import AbstractContextManager
245
246
# Function parameter types
247
GetExtrasModulesFunc = Callable[[], Set[str]]
248
MakeErrorMessageFunc = Callable[[str], str]
249
250
# Context manager type
251
LazyImportErrorContext = AbstractContextManager[None]
252
```