Skip to content

Commit db2234b

Browse files
committed
feat(webdriver-backend): add dynamic import scripts from module and file
1 parent 2f4fd45 commit db2234b

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""high-level module for dynamic importing of python modules at runtime
2+
3+
source code inspired by https://gist.github.com/DiTo97/46f4b733396b8d7a8f1d4d22db902cfc
4+
"""
5+
6+
import sys
7+
import typing
8+
9+
10+
if typing.TYPE_CHECKING:
11+
import types
12+
13+
14+
def srcfile_import(modpath: str, modname: str) -> "types.ModuleType":
15+
"""imports a python module from its srcfile
16+
17+
Args:
18+
modpath: The srcfile absolute path
19+
modname: The module name in the scope
20+
21+
Returns:
22+
The imported module
23+
24+
Raises:
25+
ImportError: If the module cannot be imported from the srcfile
26+
"""
27+
import importlib.util # noqa: F401
28+
29+
#
30+
spec = importlib.util.spec_from_file_location(modname, modpath)
31+
32+
if spec is None:
33+
message = f"missing spec for module at {modpath}"
34+
raise ImportError(message)
35+
36+
if spec.loader is None:
37+
message = f"missing spec loader for module at {modpath}"
38+
raise ImportError(message)
39+
40+
module = importlib.util.module_from_spec(spec)
41+
42+
# adds the module to the global scope
43+
sys.modules[modname] = module
44+
45+
spec.loader.exec_module(module)
46+
47+
return module
48+
49+
50+
def dynamic_import(modname: str, message: str = "") -> None:
51+
"""imports a python module at runtime
52+
53+
Args:
54+
modname: The module name in the scope
55+
message: The display message in case of error
56+
57+
Raises:
58+
ImportError: If the module cannot be imported at runtime
59+
"""
60+
if modname not in sys.modules:
61+
try:
62+
import importlib # noqa: F401
63+
64+
module = importlib.import_module(modname)
65+
sys.modules[modname] = module
66+
except ImportError as x:
67+
raise ImportError(message) from x
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import os
2+
import sys
3+
4+
import pytest
5+
6+
from scrapegraphai.utils.sys_dynamic_import import dynamic_import, srcfile_import
7+
8+
9+
def _create_sample_file(filepath: str, content: str):
10+
"""creates a sample file at some path with some content"""
11+
with open(filepath, "w", encoding="utf-8") as f:
12+
f.write(content)
13+
14+
15+
def _delete_sample_file(filepath: str):
16+
"""deletes a sample file at some path"""
17+
if os.path.exists(filepath):
18+
os.remove(filepath)
19+
20+
21+
def test_srcfile_import_success():
22+
modpath = "example1.py"
23+
modname = "example1"
24+
25+
_create_sample_file(modpath, "def foo(): return 'bar'")
26+
27+
module = srcfile_import(modpath, modname)
28+
29+
assert hasattr(module, "foo")
30+
assert module.foo() == "bar"
31+
assert modname in sys.modules
32+
33+
_delete_sample_file(modpath)
34+
35+
36+
def test_srcfile_import_missing_spec():
37+
modpath = "nonexistent1.py"
38+
modname = "nonexistent1"
39+
40+
with pytest.raises(FileNotFoundError):
41+
srcfile_import(modpath, modname)
42+
43+
44+
def test_srcfile_import_missing_spec_loader(mocker):
45+
modpath = "example2.py"
46+
modname = "example2"
47+
48+
_create_sample_file(modpath, "")
49+
50+
mock_spec = mocker.Mock(loader=None)
51+
52+
mocker.patch("importlib.util.spec_from_file_location", return_value=mock_spec)
53+
54+
with pytest.raises(ImportError) as error_info:
55+
srcfile_import(modpath, modname)
56+
57+
assert "missing spec loader for module at" in str(error_info.value)
58+
59+
_delete_sample_file(modpath)
60+
61+
62+
def test_dynamic_import_success():
63+
print(sys.modules)
64+
modname = "playwright"
65+
assert modname not in sys.modules
66+
67+
dynamic_import(modname)
68+
69+
assert modname in sys.modules
70+
71+
import playwright # noqa: F401
72+
73+
74+
def test_dynamic_import_module_already_imported():
75+
modname = "json"
76+
77+
import json # noqa: F401
78+
79+
assert modname in sys.modules
80+
81+
dynamic_import(modname)
82+
83+
assert modname in sys.modules
84+
85+
86+
def test_dynamic_import_import_error_with_custom_message():
87+
modname = "nonexistent2"
88+
message = "could not import module"
89+
90+
with pytest.raises(ImportError) as error_info:
91+
dynamic_import(modname, message=message)
92+
93+
assert str(error_info.value) == message
94+
assert modname not in sys.modules

0 commit comments

Comments
 (0)