Skip to content

Commit 0703e08

Browse files
ppwwyyxxfacebook-github-bot
authored andcommitted
Better error message in lazyconfig relative import
Summary: Make the error messages in #4214 better And add a few more tests for lazyconfig Pull Request resolved: #4215 Reviewed By: tglik Differential Revision: D39402746 fbshipit-source-id: 5d9cbfdbfd34b829e25ed1b27b6f2bf96e6ab1e9
1 parent 9725c7f commit 0703e08

File tree

6 files changed

+48
-11
lines changed

6 files changed

+48
-11
lines changed

INSTALL.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,8 @@ whose version is closer to what's used by PyTorch (available in `torch.__config_
247247
"library not found for -lstdc++" on older version of MacOS
248248
</summary>
249249
<br/>
250-
See
251-
[this stackoverflow answer](https://stackoverflow.com/questions/56083725/macos-build-issues-lstdc-not-found-while-building-python-package).
250+
251+
See [this stackoverflow answer](https://stackoverflow.com/questions/56083725/macos-build-issues-lstdc-not-found-while-building-python-package).
252252

253253
</details>
254254

detectron2/config/lazy.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,28 +106,41 @@ def _patch_import():
106106
1. locate files purely based on relative location, regardless of packages.
107107
e.g. you can import file without having __init__
108108
2. do not cache modules globally; modifications of module states has no side effect
109-
3. support other storage system through PathManager
109+
3. support other storage system through PathManager, so config files can be in the cloud
110110
4. imported dict are turned into omegaconf.DictConfig automatically
111111
"""
112112
old_import = builtins.__import__
113113

114114
def find_relative_file(original_file, relative_import_path, level):
115+
# NOTE: "from . import x" is not handled. Because then it's unclear
116+
# if such import should produce `x` as a python module or DictConfig.
117+
# This can be discussed further if needed.
118+
relative_import_err = """
119+
Relative import of directories is not allowed within config files.
120+
Within a config file, relative import can only import other config files.
121+
""".replace(
122+
"\n", " "
123+
)
124+
if not len(relative_import_path):
125+
raise ImportError(relative_import_err)
126+
115127
cur_file = os.path.dirname(original_file)
116128
for _ in range(level - 1):
117129
cur_file = os.path.dirname(cur_file)
118130
cur_name = relative_import_path.lstrip(".")
119131
for part in cur_name.split("."):
120132
cur_file = os.path.join(cur_file, part)
121-
# NOTE: directory import is not handled. Because then it's unclear
122-
# if such import should produce python module or DictConfig. This can
123-
# be discussed further if needed.
124133
if not cur_file.endswith(".py"):
125134
cur_file += ".py"
126135
if not PathManager.isfile(cur_file):
127-
raise ImportError(
128-
f"Cannot import name {relative_import_path} from "
129-
f"{original_file}: {cur_file} has to exist."
130-
)
136+
cur_file_no_suffix = cur_file[: -len(".py")]
137+
if PathManager.isdir(cur_file_no_suffix):
138+
raise ImportError(f"Cannot import from {cur_file_no_suffix}." + relative_import_err)
139+
else:
140+
raise ImportError(
141+
f"Cannot import name {relative_import_path} from "
142+
f"{original_file}: {cur_file} does not exist."
143+
)
131144
return cur_file
132145

133146
def new_import(name, globals=None, locals=None, fromlist=(), level=0):

tests/config/dir1/bad_import.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# import from directory is not allowed
2+
from . import dir1a

tests/config/dir1/bad_import2.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .does_not_exist import x

tests/config/dir1/load_rel.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# test that load_rel can work
2+
from detectron2.config import LazyConfig
3+
4+
x = LazyConfig.load_rel("dir1_a.py", "dir1a_dict")
5+
assert x["a"] == 1

tests/config/test_lazy_config.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
class TestLazyPythonConfig(unittest.TestCase):
1212
def setUp(self):
13-
self.root_filename = os.path.join(os.path.dirname(__file__), "root_cfg.py")
13+
self.curr_dir = os.path.dirname(__file__)
14+
self.root_filename = os.path.join(self.curr_dir, "root_cfg.py")
1415

1516
def test_load(self):
1617
cfg = LazyConfig.load(self.root_filename)
@@ -77,3 +78,18 @@ def test_to_py(self):
7778
cfg.list = ["a", 1, "b", 3.2]
7879
"""
7980
self.assertEqual(py_str, expected)
81+
82+
def test_bad_import(self):
83+
file = os.path.join(self.curr_dir, "dir1", "bad_import.py")
84+
with self.assertRaisesRegex(ImportError, "relative import"):
85+
LazyConfig.load(file)
86+
87+
def test_bad_import2(self):
88+
file = os.path.join(self.curr_dir, "dir1", "bad_import2.py")
89+
with self.assertRaisesRegex(ImportError, "not exist"):
90+
LazyConfig.load(file)
91+
92+
def test_load_rel(self):
93+
file = os.path.join(self.curr_dir, "dir1", "load_rel.py")
94+
cfg = LazyConfig.load(file)
95+
self.assertIn("x", cfg)

0 commit comments

Comments
 (0)