Skip to content

Commit 8ec96f4

Browse files
kiukchungfacebook-github-bot
authored andcommitted
Fix type bug in build_trackers -- skip non-callable modules
Summary: Fix ``build_trackers()`` in ``tracker/api.py`` to skip non-callable modules returned by ``load_module()``. Previously, ``ModuleType`` objects were being passed where callables were expected, causing ``TypeError: 'module' object is not callable`` at runtime. **Root cause:** ``load_module()`` returns ``types.ModuleType`` for packages (directories with ``__init__.py``) but ``build_trackers()`` assumed all return values were factory callables. **Fix:** Added ``callable()`` guard before invoking the loaded object. Non-callable modules are silently skipped with a debug log message. **Cleanup:** Removed unused ``source_data`` variable in test MockTracker. Differential Revision: D95932184
1 parent b3b5388 commit 8ec96f4

2 files changed

Lines changed: 37 additions & 7 deletions

File tree

torchx/tracker/api.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,16 +155,26 @@ def build_trackers(
155155
logger.warning("No 'torchx.tracker' entry_points are defined.")
156156

157157
for factory_name, config in factory_and_config.items():
158-
factory = entrypoint_factories.get(factory_name) or load_module(factory_name)
159-
if not factory:
158+
resolved = entrypoint_factories.get(factory_name) or load_module(factory_name)
159+
if not resolved:
160160
logger.warning(
161-
f"No tracker factory `{factory_name}` found in entry_points or modules. See https://meta-pytorch.org/torchx/main/tracker.html#module-torchx.tracker"
161+
"no tracker factory `%s` found in entry_points or modules."
162+
" See https://meta-pytorch.org/torchx/main/tracker.html#module-torchx.tracker",
163+
factory_name,
162164
)
163165
continue
166+
if not callable(resolved):
167+
logger.warning(
168+
"tracker factory `%s` resolved to a module, not a callable."
169+
" Use `module.path:factory_function` syntax to specify the factory",
170+
factory_name,
171+
)
172+
continue
173+
factory = resolved
164174
if config:
165-
logger.info(f"Tracker config found for `{factory_name}` as `{config}`")
175+
logger.info("tracker config found for `%s` as `%s`", factory_name, config)
166176
else:
167-
logger.info(f"No tracker config specified for `{factory_name}`")
177+
logger.info("no tracker config specified for `%s`", factory_name)
168178
tracker = factory(config)
169179
trackers.append(tracker)
170180
return trackers

torchx/tracker/test/api_test.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,6 @@ def sources(
8383
run_id: str,
8484
artifact_name: str | None = None,
8585
) -> Iterable[TrackerSource]:
86-
source_data = self._sources[run_id]
87-
8886
sources = []
8987
for artifact_name, source_id in self._sources[run_id].items():
9088
if artifact_name == DEFAULT_SOURCE:
@@ -289,6 +287,28 @@ def test_build_trackers_with_module(self) -> None:
289287
self.assertIsInstance(tracker, MLflowTracker)
290288
module.assert_called_once_with(config)
291289

290+
def test_build_trackers_with_non_callable_module(self) -> None:
291+
"""load_module returns a ModuleType when no :attr is specified.
292+
Modules aren't callable -- build_trackers should skip them with a warning.
293+
"""
294+
from types import ModuleType
295+
296+
module = ModuleType("fake_tracker_module")
297+
with (
298+
patch("torchx.tracker.api.load_group", return_value=None),
299+
patch(
300+
"torchx.tracker.api.load_module",
301+
return_value=module,
302+
),
303+
):
304+
tracker_names = {"fake_tracker_module": "myconfig.txt"}
305+
trackers = build_trackers(tracker_names)
306+
self.assertEqual(
307+
0,
308+
len(list(trackers)),
309+
"non-callable module should be skipped, not called as a factory",
310+
)
311+
292312
def test_build_trackers(self) -> None:
293313
with patch(
294314
"torchx.tracker.api.load_group",

0 commit comments

Comments
 (0)