Coverage for python/lsst/utils/doImport.py: 13%
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", "doImportType")
14import importlib
15import types
16from typing import List, Optional, Type, 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(
47 module: str, fromlist: List[str], previousError: Optional[str]
48 ) -> Union[types.ModuleType, Type]:
49 pytype = importlib.import_module(module)
50 # Can have functions inside classes inside modules
51 for f in fromlist:
52 try:
53 pytype = getattr(pytype, f)
54 except AttributeError:
55 extra = f"({previousError})" if previousError is not None else ""
56 raise ImportError(f"Could not get attribute '{f}' from '{module}' {extra}")
57 return pytype
59 # Go through the import path attempting to load the module
60 # and retrieve the class or function as an attribute. Shift components
61 # from the module list to the attribute list until something works.
62 moduleComponents = importable.split(".")
63 infileComponents: List[str] = []
64 previousError = None
66 while moduleComponents:
67 try:
68 pytype = tryImport(".".join(moduleComponents), infileComponents, previousError)
69 if not infileComponents and hasattr(pytype, moduleComponents[-1]):
70 # This module has an attribute with the same name as the
71 # module itself (like doImport.doImport, actually!).
72 # If that attribute was lifted to the package, we should
73 # return the attribute, not the module.
74 try:
75 return tryImport(".".join(moduleComponents[:-1]), moduleComponents[-1:], previousError)
76 except ModuleNotFoundError:
77 pass
78 return pytype
79 except ModuleNotFoundError as e:
80 previousError = str(e)
81 # Move element from module to file and try again
82 infileComponents.insert(0, moduleComponents.pop())
84 raise ModuleNotFoundError(f"Unable to import {importable}")
87def doImportType(importable: str) -> Type:
88 """Import a python type given an importable string and return it.
90 Parameters
91 ----------
92 importable : `str`
93 String containing dot-separated path of a Python class,
94 or member function.
96 Returns
97 -------
98 type : `type`
99 Type object. Can not return a module.
101 Raises
102 ------
103 TypeError
104 ``importable`` is not a `str` or the imported type is a module.
105 ModuleNotFoundError
106 No module in the supplied import string could be found.
107 ImportError
108 ``importable`` is found but can not be imported or the requested
109 item could not be retrieved from the imported module.
110 """
111 imported = doImport(importable)
112 if isinstance(imported, types.ModuleType):
113 raise TypeError(f"Import of {importable} returned a module and not a type.")
114 return imported