Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitced67d2

Browse files
committed
Util: For loading code at runtime, useimportlib instead ofimp
The `imp` module is deprecated and got removed from Python 3.12.
1 parent40d4496 commitced67d2

File tree

2 files changed

+86
-40
lines changed

2 files changed

+86
-40
lines changed

‎mqttwarn/util.py‎

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# (c) 2014-2023 The mqttwarn developers
22
importfunctools
3-
importhashlib
4-
importimp
3+
importimportlib.machinery
4+
importimportlib.util
55
importjson
66
importlogging
77
importos
@@ -15,6 +15,9 @@
1515
importpkg_resources
1616
fromsiximportstring_types
1717

18+
ift.TYPE_CHECKING:
19+
fromimportlib._bootstrap_externalimportFileLoader
20+
1821
logger=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-
returnimp.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+
ifpath.endswith(".py"):
141+
loader=importlib.machinery.SourceFileLoader(fullname=name,path=path)
142+
elifpath.endswith(".pyc"):
143+
loader=importlib.machinery.SourcelessFileLoader(fullname=name,path=path)
144+
else:
145+
raiseImportError(f"Loading file failed (only .py and .pyc):{path}")
146+
spec=importlib.util.spec_from_loader(loader.name,loader)
147+
ifspecisNone:
148+
raiseModuleNotFoundError(f"Failed loading module from file:{path}")
149+
mod=importlib.util.module_from_spec(spec)
150+
loader.exec_module(mod)
151+
returnmod
144152

145153

146154
defload_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)
154162
returnmodule
155163

156164

157-
defimport_module(name:str,path:t.Optional[t.List[str]]=None)->types.ModuleType:
165+
defimport_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
168176
next_module=name
169177
remaining_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+
ifparentisnotNone:
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+
ifmoduleisNoneormodule.__loader__isNone:
190+
raiseImportError(f"Symbol not found:{name}")
191+
ifhasattr(module,next_module):
192+
returngetattr(module,next_module)
193+
else:
194+
raiseImportError(f"Symbol not found:{name}, module={module}")
195+
196+
ifspecisNone:
197+
msg=f"Symbol not found:{name}"
198+
ifparentisnotNone:
199+
msg+=f", module={parent.__name__}"
200+
raiseImportError(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

174207
ifremaining_namesisNone:
175208
returnmodule
176209

177-
ifhasattr(module,remaining_names):
178-
returngetattr(module,remaining_names)
179-
else:
180-
returnimport_module(remaining_names,path=list(module.__path__))
210+
returnimport_symbol(remaining_names,parent=module)
181211

182212

183213
defload_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
188218
ifnotos.path.isfile(filepath):
189219
raiseIOError("'{}' not found".format(filepath))
190220

191-
mod_name,file_ext=os.path.splitext(os.path.split(filepath)[-1])
192-
193-
iffile_ext.lower()==".py":
194-
py_mod=imp.load_source(mod_name,filepath)
195-
196-
eliffile_ext.lower()==".pyc":
197-
py_mod=imp.load_compiled(mod_name,filepath)
198-
199-
else:
200-
raiseValueError("'{}' does not have the .py or .pyc extension".format(filepath))
201-
221+
py_mod=load_module_from_file(filepath)
202222
returnpy_mod
203223

204224

‎tests/test_util.py‎

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# -*- coding: utf-8 -*-
2-
# (c) 2018-2022 The mqttwarn developers
2+
# (c) 2018-2023 The mqttwarn developers
33
from __future__importdivision
44

55
importpy_compile
66
importre
77
importtime
8+
importtypes
89
frombuiltinsimportstr
910

1011
importpytest
@@ -13,7 +14,7 @@
1314
Formatter,
1415
asbool,
1516
get_resource_content,
16-
import_module,
17+
import_symbol,
1718
load_file,
1819
load_function,
1920
load_functions,
@@ -135,9 +136,9 @@ def test_load_functions():
135136
assertstr(excinfo.value)=="'{}' not found".format("unknown.txt")
136137

137138
# Load functions file that is not a python file
138-
withpytest.raises(ValueError)asexcinfo:
139+
withpytest.raises(ImportError)asexcinfo:
139140
load_functions(filepath=configfile_full)
140-
assertstr(excinfo.value)=="'{}' does not have the.pyor .pyc extension".format(configfile_full)
141+
assertre.match(r"Loading file failed \(only.pyand .pyc\): .+full.ini",str(excinfo.value))
141142

142143
# Load bad functions file
143144
withpytest.raises(Exception):
@@ -172,7 +173,7 @@ def test_load_function():
172173
withpytest.raises(AttributeError)asexcinfo:
173174
load_function(name="unknown",py_mod=py_mod)
174175
assertre.match(
175-
"Function 'unknown' does not exist in '.*{}c?'".format(funcfile_good),
176+
"Function 'unknown' does not exist in '.+functions_good.py'",
176177
str(excinfo.value),
177178
)
178179

@@ -182,15 +183,40 @@ def test_get_resource_content():
182183
assert"[defaults]"inpayload
183184

184185

185-
deftest_import_module():
186+
deftest_import_symbol_module_success():
186187
"""
187-
Proof that the `import_module` function works as intended.
188+
Proof that the `import_symbol` function works as intended.
188189
"""
189-
symbol=import_module("mqttwarn.services.log")
190-
assertsymbol.__name__=="log"
190+
symbol=import_symbol("mqttwarn.services.log")
191+
assertsymbol.__name__=="mqttwarn.services.log"
192+
assertisinstance(symbol,types.ModuleType)
193+
194+
195+
deftest_import_symbol_module_fail():
196+
"""
197+
Proof that the `import_symbol` function works as intended.
198+
"""
199+
withpytest.raises(ImportError)asex:
200+
import_symbol("foo.bar.baz")
201+
assertex.match("Symbol not found: foo.bar.baz")
202+
191203

192-
symbol=import_module("mqttwarn.services.log.plugin")
204+
deftest_import_symbol_function_success():
205+
"""
206+
Proof that the `import_symbol` function works as intended.
207+
"""
208+
symbol=import_symbol("mqttwarn.services.log.plugin")
193209
assertsymbol.__name__=="plugin"
210+
assertisinstance(symbol,types.FunctionType)
211+
212+
213+
deftest_import_symbol_function_fail():
214+
"""
215+
Proof that the `import_symbol` function works as intended.
216+
"""
217+
withpytest.raises(ImportError)asex:
218+
import_symbol("mqttwarn.services.log.foo.bar.baz")
219+
assertex.match("Symbol not found: foo.bar.baz, module=<module 'mqttwarn.services.log' from")
194220

195221

196222
deftest_load_file_success(tmp_path):

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp