Skip to content

Commit 0e82529

Browse files
Move optional import stuff out to a separate module to avoid circular imports
1 parent c9d3a19 commit 0e82529

File tree

4 files changed

+57
-34
lines changed

4 files changed

+57
-34
lines changed

intake_esm/__init__.py

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,25 +11,12 @@
1111
from intake_esm.core import esm_datastore
1212
from intake_esm.derived import DerivedVariableRegistry, default_registry
1313
from intake_esm.utils import set_options, show_versions
14+
from intake_esm._imports import _to_opt_import_flag, _from_opt_import_flag
15+
from intake_esm import _imports as _import_module
1416

1517
from intake_esm._version import __version__
1618

17-
_optional_imports: dict[str, bool | None] = {'esmvalcore': None}
18-
19-
20-
def _to_opt_import_flag(name: str) -> str:
21-
"""Dynamically create import flags for optional imports."""
22-
return f'_{name.upper()}_AVAILABLE'
23-
24-
25-
def _from_opt_import_flag(name: str) -> str:
26-
"""Dynamically retrive the optional import name from its flag."""
27-
if name.startswith('_') and name.endswith('_AVAILABLE'):
28-
return name[1:-10].lower()
29-
raise ValueError(
30-
f"Invalid optional import flag '{name}'. Expected format: '_<import_name>_AVAILABLE'."
31-
)
32-
19+
import_flags = [_to_opt_import_flag(name) for name in _import_module._optional_imports]
3320

3421
__all__ = [
3522
'esm_datastore',
@@ -38,7 +25,8 @@ def _from_opt_import_flag(name: str) -> str:
3825
'set_options',
3926
'show_versions',
4027
'tutorial',
41-
] + [_to_opt_import_flag(name) for name in _optional_imports]
28+
'__version__',
29+
] + import_flags
4230

4331

4432
def __getattr__(attr: str) -> object:
@@ -49,17 +37,10 @@ def __getattr__(attr: str) -> object:
4937
if attr in (gl := globals()):
5038
return gl[attr]
5139

52-
import_flags = [_to_opt_import_flag(name) for name in _optional_imports]
53-
54-
if attr in import_flags:
55-
import_name = _from_opt_import_flag(attr)
56-
if _optional_imports.get(import_name, None) is None:
57-
_optional_imports[import_name] = bool(importlib.util.find_spec(import_name))
58-
return _optional_imports[import_name]
59-
else:
60-
return _optional_imports[import_name]
61-
62-
raise AttributeError(
63-
f"Module '{__name__}' has no attribute '{attr}'. "
64-
f'Did you mean one of {", ".join(import_flags)}?'
65-
)
40+
try:
41+
return getattr(_import_module, attr)
42+
except AttributeError:
43+
raise AttributeError(
44+
f"Module '{__name__}' has no attribute '{attr}'. "
45+
f'Did you mean one of {", ".join(import_flags)}?'
46+
)

intake_esm/_imports.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import importlib
2+
3+
_optional_imports: dict[str, bool | None] = {'esmvalcore': None}
4+
5+
6+
def _to_opt_import_flag(name: str) -> str:
7+
"""Dynamically create import flags for optional imports."""
8+
return f'_{name.upper()}_AVAILABLE'
9+
10+
11+
def _from_opt_import_flag(name: str) -> str:
12+
"""Dynamically retrive the optional import name from its flag."""
13+
if name.startswith('_') and name.endswith('_AVAILABLE'):
14+
return name[1:-10].lower()
15+
raise ValueError(
16+
f"Invalid optional import flag '{name}'. Expected format: '_<import_name>_AVAILABLE'."
17+
)
18+
19+
20+
def __getattr__(attr: str) -> object:
21+
"""
22+
Lazy load optional imports.
23+
"""
24+
25+
if attr in (gl := globals()):
26+
return gl[attr]
27+
28+
import_flags = [_to_opt_import_flag(name) for name in _optional_imports]
29+
30+
if attr in import_flags:
31+
import_name = _from_opt_import_flag(attr)
32+
if _optional_imports.get(import_name, None) is None:
33+
_optional_imports[import_name] = bool(importlib.util.find_spec(import_name))
34+
return _optional_imports[import_name]
35+
else:
36+
return _optional_imports[import_name]
37+
38+
raise AttributeError(
39+
f"Module '{__name__}' has no attribute '{attr}'. "
40+
f'Did you mean one of {", ".join(import_flags)}?'
41+
)

tests/conftest.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ def sample_bad_input():
2121
def cleanup_init():
2222
"""
2323
This resets the _optional_imports dictionary in intake_esm to it's default
24-
state, so we can test lazy loading and whatnot
24+
state before & after tests that use it so we can test lazy loading and whatnot
2525
"""
26+
intake_esm._imports._optional_imports = {'esmvalcore': None}
2627
yield
27-
28-
intake_esm._optional_imports = {'esmvalcore': None}
28+
intake_esm._imports._optional_imports = {'esmvalcore': None}

tests/test_init.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def test__all__(cleanup_init):
1313
'set_options',
1414
'show_versions',
1515
'tutorial',
16+
'__version__',
1617
'_ESMVALCORE_AVAILABLE',
1718
]
1819

0 commit comments

Comments
 (0)