Coverage for python/lsst/utils/doImport.py: 12%
38 statements
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 15:14 -0700
« prev ^ index » next coverage.py v7.5.0, created at 2024-05-01 15:14 -0700
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.
12from __future__ import annotations
14__all__ = ("doImport", "doImportType")
16import importlib
17import types
20def doImport(importable: str) -> types.ModuleType | type:
21 """Import a python object given an importable string and return it.
23 Parameters
24 ----------
25 importable : `str`
26 String containing dot-separated path of a Python class, module,
27 or member function.
29 Returns
30 -------
31 type : `type`
32 Type object. Either a module or class or a function.
34 Raises
35 ------
36 TypeError
37 ``importable`` is not a `str`.
38 ModuleNotFoundError
39 No module in the supplied import string could be found.
40 ImportError
41 ``importable`` is found but can not be imported or the requested
42 item could not be retrieved from the imported module.
43 """
44 if not isinstance(importable, str):
45 raise TypeError(f"Unhandled type of importable, val: {importable}")
47 def tryImport(module: str, fromlist: list[str], previousError: str | None) -> 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 as e:
54 extra = f"({previousError})" if previousError is not None else ""
55 raise ImportError(
56 f"Could not get attribute '{f}' from '{module}' when importing '{importable}' {extra}"
57 ) from e
58 return pytype
60 # Go through the import path attempting to load the module
61 # and retrieve the class or function as an attribute. Shift components
62 # from the module list to the attribute list until something works.
63 moduleComponents = importable.split(".")
64 infileComponents: list[str] = []
65 previousError = None
67 while moduleComponents:
68 try:
69 pytype = tryImport(".".join(moduleComponents), infileComponents, previousError)
70 if not infileComponents and hasattr(pytype, moduleComponents[-1]):
71 # This module has an attribute with the same name as the
72 # module itself (like doImport.doImport, actually!).
73 # If that attribute was lifted to the package, we should
74 # return the attribute, not the module.
75 try:
76 return tryImport(".".join(moduleComponents[:-1]), moduleComponents[-1:], previousError)
77 except ModuleNotFoundError:
78 pass
79 return pytype
80 except ModuleNotFoundError as e:
81 previousError = str(e)
82 # Move element from module to file and try again
83 infileComponents.insert(0, moduleComponents.pop())
85 # Fell through without success.
86 extra = f"({previousError})" if previousError is not None else ""
87 raise ModuleNotFoundError(f"Unable to import {importable!r} {extra}")
90def doImportType(importable: str) -> type:
91 """Import a python type given an importable string and return it.
93 Parameters
94 ----------
95 importable : `str`
96 String containing dot-separated path of a Python class,
97 or member function.
99 Returns
100 -------
101 type : `type`
102 Type object. Can not return a module.
104 Raises
105 ------
106 TypeError
107 ``importable`` is not a `str` or the imported type is a module.
108 ModuleNotFoundError
109 No module in the supplied import string could be found.
110 ImportError
111 ``importable`` is found but can not be imported or the requested
112 item could not be retrieved from the imported module.
113 """
114 imported = doImport(importable)
115 if isinstance(imported, types.ModuleType):
116 raise TypeError(f"Import of {importable} returned a module and not a type.")
117 return imported