Coverage for python/lsst/utils/doImport.py: 12%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of utils.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# Use of this source code is governed by a 3-clause BSD-style
10# license that can be found in the LICENSE file.
12__all__ = ("doImport",)
14import importlib
15import types
16from typing import Type, List, Optional, Union
19def doImport(importable: str) -> Union[types.ModuleType, Type]:
20 """Import a python object given an importable string and return it.
22 Parameters
23 ----------
24 importable : `str`
25 String containing dot-separated path of a Python class, module,
26 or member function.
28 Returns
29 -------
30 type : `type`
31 Type object. Either a module or class or a function.
33 Raises
34 ------
35 TypeError
36 ``importable`` is not a `str`.
37 ModuleNotFoundError
38 No module in the supplied import string could be found.
39 ImportError
40 ``importable`` is found but can not be imported or the requested
41 item could not be retrieved from the imported module.
42 """
43 if not isinstance(importable, str):
44 raise TypeError(f"Unhandled type of importable, val: {importable}")
46 def tryImport(module: str, fromlist: List[str],
47 previousError: Optional[str]) -> Union[types.ModuleType, Type]:
48 pytype = importlib.import_module(module)
49 # Can have functions inside classes inside modules
50 for f in fromlist:
51 try:
52 pytype = getattr(pytype, f)
53 except AttributeError:
54 extra = f"({previousError})" if previousError is not None else ""
55 raise ImportError(f"Could not get attribute '{f}' from '{module}' {extra}")
56 return pytype
58 # Go through the import path attempting to load the module
59 # and retrieve the class or function as an attribute. Shift components
60 # from the module list to the attribute list until something works.
61 moduleComponents = importable.split(".")
62 infileComponents: List[str] = []
63 previousError = None
65 while moduleComponents:
66 try:
67 pytype = tryImport(".".join(moduleComponents), infileComponents, previousError)
68 if not infileComponents and hasattr(pytype, moduleComponents[-1]):
69 # This module has an attribute with the same name as the
70 # module itself (like doImport.doImport, actually!).
71 # If that attribute was lifted to the package, we should
72 # return the attribute, not the module.
73 try:
74 return tryImport(".".join(moduleComponents[:-1]), moduleComponents[-1:], previousError)
75 except ModuleNotFoundError:
76 pass
77 return pytype
78 except ModuleNotFoundError as e:
79 previousError = str(e)
80 # Move element from module to file and try again
81 infileComponents.insert(0, moduleComponents.pop())
83 raise ModuleNotFoundError(f"Unable to import {importable}")