From 7939f482b034d8d82a67edc21a353850239d3eb6 Mon Sep 17 00:00:00 2001 From: Ben Hearsum Date: Thu, 14 Aug 2025 11:18:59 -0400 Subject: [PATCH] fix: use importlib.import_module instead of `__import__` `import_module` is newer, and provides a couple of advantages over a bare `__import__`: > The import_module() function acts as a simplifying wrapper around importlib.__import__(). This means all semantics of the function are derived from importlib.__import__(). The most important difference between these two functions is that import_module() returns the specified package or module (e.g. pkg.mod), while __import__() returns the top-level package or module (e.g. pkg). It's unclear if this will actually fix any problems in the real world, but there's no recent to use `__import__` at this point AFAICT. --- src/taskgraph/config.py | 4 +++- src/taskgraph/decision.py | 6 +++--- src/taskgraph/generator.py | 10 ++++++---- src/taskgraph/util/python_path.py | 8 ++++---- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/taskgraph/config.py b/src/taskgraph/config.py index 6c55cb8ed..b011708ae 100644 --- a/src/taskgraph/config.py +++ b/src/taskgraph/config.py @@ -148,7 +148,9 @@ def register(self): sys.path.insert(0, self.root_dir) register_path = self["taskgraph"].get("register") if register_path: - find_object(register_path)(self) + register = find_object(register_path) + assert callable(register) + register(self) @property def vcs_root(self): diff --git a/src/taskgraph/decision.py b/src/taskgraph/decision.py index 361619221..e75bc3014 100644 --- a/src/taskgraph/decision.py +++ b/src/taskgraph/decision.py @@ -263,9 +263,9 @@ def get_decision_parameters(graph_config, options): parameters["optimize_target_tasks"] = options["optimize_target_tasks"] if "decision-parameters" in graph_config["taskgraph"]: - find_object(graph_config["taskgraph"]["decision-parameters"])( - graph_config, parameters - ) + decision_params = find_object(graph_config["taskgraph"]["decision-parameters"]) + assert callable(decision_params) + decision_params(graph_config, parameters) if options.get("try_task_config_file"): task_config_file = os.path.abspath(options.get("try_task_config_file")) diff --git a/src/taskgraph/generator.py b/src/taskgraph/generator.py index 9bc26a38c..a282c2e6c 100644 --- a/src/taskgraph/generator.py +++ b/src/taskgraph/generator.py @@ -37,12 +37,14 @@ class Kind: config: Dict graph_config: GraphConfig - def _get_loader(self): + def _get_loader(self) -> Callable: try: - loader = self.config["loader"] + loader_path = self.config["loader"] except KeyError: - loader = "taskgraph.loader.default:loader" - return find_object(loader) + loader_path = "taskgraph.loader.default:loader" + loader = find_object(loader_path) + assert callable(loader) + return loader def load_tasks(self, parameters, loaded_tasks, write_artifacts): loader = self._get_loader() diff --git a/src/taskgraph/util/python_path.py b/src/taskgraph/util/python_path.py index ff7e255e5..d8ad0a4fd 100644 --- a/src/taskgraph/util/python_path.py +++ b/src/taskgraph/util/python_path.py @@ -2,11 +2,12 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. +import importlib import inspect import os -def find_object(path): +def find_object(path: str): """ Find a Python object given a path of the form :. Conceptually equivalent to @@ -19,11 +20,10 @@ def find_object(modulepath, objectpath): raise ValueError(f'python path {path!r} does not have the form "module:object"') modulepath, objectpath = path.split(":") - obj = __import__(modulepath) - for a in modulepath.split(".")[1:]: - obj = getattr(obj, a) + obj = importlib.import_module(modulepath) for a in objectpath.split("."): obj = getattr(obj, a) + return obj