11# (c) 2014-2023 The mqttwarn developers
22import functools
3- import hashlib
4- import imp
3+ import importlib . machinery
4+ import importlib . util
55import json
66import logging
77import os
1515import pkg_resources
1616from six import string_types
1717
18+ if t .TYPE_CHECKING :
19+ from importlib ._bootstrap_external import FileLoader
20+
1821logger = logging .getLogger (__name__ )
1922
2023
@@ -132,15 +135,20 @@ def load_module_from_file(path: str) -> types.ModuleType:
132135 :param path:
133136 :return:
134137 """
135- try :
136- fp = open (path ,"rb" )
137- digest = hashlib .md5 (path .encode ("utf-8" )).hexdigest ()
138- return imp .load_source (digest ,path ,fp )# type: ignore[arg-type]
139- finally :
140- try :
141- fp .close ()
142- except :
143- pass
138+ name = Path (path ).stem
139+ loader :"FileLoader"
140+ if path .endswith (".py" ):
141+ loader = importlib .machinery .SourceFileLoader (fullname = name ,path = path )
142+ elif path .endswith (".pyc" ):
143+ loader = importlib .machinery .SourcelessFileLoader (fullname = name ,path = path )
144+ else :
145+ raise ImportError (f"Loading file failed (only .py and .pyc):{ path } " )
146+ spec = importlib .util .spec_from_loader (loader .name ,loader )
147+ if spec is None :
148+ raise ModuleNotFoundError (f"Failed loading module from file:{ path } " )
149+ mod = importlib .util .module_from_spec (spec )
150+ loader .exec_module (mod )
151+ return mod
144152
145153
146154def load_module_by_name (name :str )-> types .ModuleType :
@@ -150,11 +158,11 @@ def load_module_by_name(name: str) -> types.ModuleType:
150158 :param name:
151159 :return:
152160 """
153- module = import_module (name )
161+ module = import_symbol (name )
154162return module
155163
156164
157- def import_module (name :str ,path :t .Optional [t . List [ str ] ]= None )-> types .ModuleType :
165+ def import_symbol (name :str ,parent :t .Optional [types . ModuleType ]= None )-> types .ModuleType :
158166"""
159167 Derived from `import_from_dotted_path`:
160168 https://chase-seibert.github.io/blog/2014/04/23/python-imp-examples.html
@@ -168,16 +176,38 @@ def import_module(name: str, path: t.Optional[t.List[str]] = None) -> types.Modu
168176next_module = name
169177remaining_names = None
170178
171- fp ,pathname ,description = imp .find_module (next_module ,path )
172- module = imp .load_module (next_module ,fp ,pathname ,description )# type: ignore[arg-type]
179+ parent_name = None
180+ next_module_real = next_module
181+
182+ if parent is not None :
183+ next_module_real = "." + next_module
184+ parent_name = parent .__name__
185+ try :
186+ spec = importlib .util .find_spec (next_module_real ,parent_name )
187+ except (AttributeError ,ModuleNotFoundError ):
188+ module = parent
189+ if module is None or module .__loader__ is None :
190+ raise ImportError (f"Symbol not found:{ name } " )
191+ if hasattr (module ,next_module ):
192+ return getattr (module ,next_module )
193+ else :
194+ raise ImportError (f"Symbol not found:{ name } , module={ module } " )
195+
196+ if spec is None :
197+ msg = f"Symbol not found:{ name } "
198+ if parent is not None :
199+ msg += f", module={ parent .__name__ } "
200+ raise ImportError (msg )
201+ module = importlib .util .module_from_spec (spec )
202+
203+ # Actually load the module.
204+ loader :FileLoader = module .__loader__
205+ loader .exec_module (module )
173206
174207if remaining_names is None :
175208return module
176209
177- if hasattr (module ,remaining_names ):
178- return getattr (module ,remaining_names )
179- else :
180- return import_module (remaining_names ,path = list (module .__path__ ))
210+ return import_symbol (remaining_names ,parent = module )
181211
182212
183213def load_functions (filepath :t .Optional [str ]= None )-> t .Optional [types .ModuleType ]:
@@ -188,17 +218,7 @@ def load_functions(filepath: t.Optional[str] = None) -> t.Optional[types.ModuleT
188218if not os .path .isfile (filepath ):
189219raise IOError ("'{}' not found" .format (filepath ))
190220
191- mod_name ,file_ext = os .path .splitext (os .path .split (filepath )[- 1 ])
192-
193- if file_ext .lower ()== ".py" :
194- py_mod = imp .load_source (mod_name ,filepath )
195-
196- elif file_ext .lower ()== ".pyc" :
197- py_mod = imp .load_compiled (mod_name ,filepath )
198-
199- else :
200- raise ValueError ("'{}' does not have the .py or .pyc extension" .format (filepath ))
201-
221+ py_mod = load_module_from_file (filepath )
202222return py_mod
203223
204224