From 85f56dc8ebfb956e9b23ff623b8a0ade065373c6 Mon Sep 17 00:00:00 2001 From: Mercury Beach Date: Wed, 17 Dec 2025 14:27:21 +0100 Subject: [PATCH 1/6] Add sys.path lookup when searching for tasks --- invoke/loader.py | 63 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 21 deletions(-) diff --git a/invoke/loader.py b/invoke/loader.py index 801d16333..fec7df8e2 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -121,34 +121,55 @@ def start(self) -> str: return self._start or os.getcwd() def find(self, name: str) -> Optional[ModuleSpec]: - debug("FilesystemLoader find starting at {!r}".format(self.start)) spec = None - module = "{}.py".format(name) - paths = self.start.split(os.sep) + try: # walk the path upwards to check for dynamic import + debug("FilesystemLoader find starting at {!r}".format(self.start)) + paths = self.start.split(os.sep) for x in reversed(range(len(paths) + 1)): path = os.sep.join(paths[0:x]) - if module in os.listdir(path): - spec = spec_from_file_location( - name, os.path.join(path, module) - ) - break - elif name in os.listdir(path) and os.path.exists( - os.path.join(path, name, "__init__.py") - ): - basepath = os.path.join(path, name) - spec = spec_from_file_location( - name, - os.path.join(basepath, "__init__.py"), - submodule_search_locations=[basepath], - ) - break - if spec: - debug("Found module: {!r}".format(spec)) - return spec + if path == "" : continue + if spec := self._try_get_module_or_package_spec_from_path(path, name): + debug("Found module: {!r}".format(spec)) + return spec + + # if not found, check sys.path + for path in sys.path: + if path == self.start: + continue + debug("FilesystemLoader checking sys.path entry {!r}".format(path)) + if spec := self._try_get_module_or_package_spec_from_path(path, name): + debug("Found module: {!r}".format(spec)) + return spec + except (FileNotFoundError, ModuleNotFoundError): msg = "ImportError loading {!r}, raising CollectionNotFound" debug(msg.format(name)) raise CollectionNotFound(name=name, start=self.start) return None + + def _try_get_module_or_package_spec_from_path(self, dir_path: str, package_name: str) -> Optional[ModuleSpec]: + spec = None + module = "{}.py".format(package_name) + + # If dir_path is a file, not a directory, return None + if os.path.isfile(dir_path): + return None + + # Try get spec from module "package_name.py" + if module in os.listdir(dir_path): + spec = spec_from_file_location( + package_name, os.path.join(dir_path, module) + ) + # Try get spec from package "package_name/__init__.py" + elif package_name in os.listdir(dir_path) and os.path.exists( + os.path.join(dir_path, package_name, "__init__.py") + ): + basepath = os.path.join(dir_path, package_name) + spec = spec_from_file_location( + package_name, + os.path.join(basepath, "__init__.py"), + submodule_search_locations=[basepath], + ) + return spec From 35434acd0d4dc5e776815fcaedc9ad8bc0c17fbc Mon Sep 17 00:00:00 2001 From: Mercury Beach Date: Wed, 17 Dec 2025 16:02:24 +0100 Subject: [PATCH 2/6] pep8 updates --- invoke/loader.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/invoke/loader.py b/invoke/loader.py index fec7df8e2..b4467bf4c 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -122,15 +122,16 @@ def start(self) -> str: def find(self, name: str) -> Optional[ModuleSpec]: spec = None - try: # walk the path upwards to check for dynamic import debug("FilesystemLoader find starting at {!r}".format(self.start)) paths = self.start.split(os.sep) for x in reversed(range(len(paths) + 1)): path = os.sep.join(paths[0:x]) - if path == "" : continue - if spec := self._try_get_module_or_package_spec_from_path(path, name): + if path == "": + continue + spec = self._get_module_or_package_spec_from_path(path, name) + if spec: debug("Found module: {!r}".format(spec)) return spec @@ -138,21 +139,24 @@ def find(self, name: str) -> Optional[ModuleSpec]: for path in sys.path: if path == self.start: continue - debug("FilesystemLoader checking sys.path entry {!r}".format(path)) - if spec := self._try_get_module_or_package_spec_from_path(path, name): + debug("FilesystemLoader checking sys.path {!r}".format(path)) + spec = self._get_module_or_package_spec_from_path(path, name) + if spec: debug("Found module: {!r}".format(spec)) - return spec + return spec except (FileNotFoundError, ModuleNotFoundError): msg = "ImportError loading {!r}, raising CollectionNotFound" debug(msg.format(name)) raise CollectionNotFound(name=name, start=self.start) return None - - def _try_get_module_or_package_spec_from_path(self, dir_path: str, package_name: str) -> Optional[ModuleSpec]: + + def _get_module_or_package_spec_from_path(self, + dir_path: str, + package_name: str) -> Optional[ModuleSpec]: spec = None module = "{}.py".format(package_name) - + # If dir_path is a file, not a directory, return None if os.path.isfile(dir_path): return None From 2d62cd6dffbc2e9ff6074932099375d9a1136022 Mon Sep 17 00:00:00 2001 From: Mercury Beach Date: Wed, 17 Dec 2025 17:03:54 +0100 Subject: [PATCH 3/6] added a check so no sys path lookup happens with a root path specified --- invoke/loader.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/invoke/loader.py b/invoke/loader.py index b4467bf4c..4e5c12141 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -65,9 +65,11 @@ def load(self, name: Optional[str] = None) -> Tuple[ModuleType, str]: .. versionadded:: 1.0 """ + should_look_into_sys_path = False if name is None: name = self.config.tasks.collection_name - spec = self.find(name) + should_look_into_sys_path = True + spec = self.find(name, should_look_into_sys_path) if spec and spec.loader and spec.origin: # Typically either tasks.py or tasks/__init__.py source_file = Path(spec.origin) From eefb0ae2613291bcbd2b9add1e91cbd9909b1da5 Mon Sep 17 00:00:00 2001 From: Mercury Beach Date: Wed, 17 Dec 2025 17:04:08 +0100 Subject: [PATCH 4/6] pep8 fixes --- invoke/loader.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/invoke/loader.py b/invoke/loader.py index 4e5c12141..9843502b3 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -122,7 +122,9 @@ def start(self) -> str: # Lazily determine default CWD if configured value is falsey return self._start or os.getcwd() - def find(self, name: str) -> Optional[ModuleSpec]: + def find(self, + name: str, + look_into_sys_path: bool = False) -> Optional[ModuleSpec]: spec = None try: # walk the path upwards to check for dynamic import @@ -132,20 +134,21 @@ def find(self, name: str) -> Optional[ModuleSpec]: path = os.sep.join(paths[0:x]) if path == "": continue - spec = self._get_module_or_package_spec_from_path(path, name) + spec = self._get_module_or_pkg_spec_from_path(path, name) if spec: debug("Found module: {!r}".format(spec)) return spec # if not found, check sys.path - for path in sys.path: - if path == self.start: - continue - debug("FilesystemLoader checking sys.path {!r}".format(path)) - spec = self._get_module_or_package_spec_from_path(path, name) - if spec: - debug("Found module: {!r}".format(spec)) - return spec + if look_into_sys_path: + for path in sys.path: + if path == self.start: + continue + debug("FilesystemLoader checks sys.path {!r}".format(path)) + spec = self._get_module_or_pkg_spec_from_path(path, name) + if spec: + debug("Found module: {!r}".format(spec)) + return spec except (FileNotFoundError, ModuleNotFoundError): msg = "ImportError loading {!r}, raising CollectionNotFound" @@ -153,7 +156,7 @@ def find(self, name: str) -> Optional[ModuleSpec]: raise CollectionNotFound(name=name, start=self.start) return None - def _get_module_or_package_spec_from_path(self, + def _get_module_or_pkg_spec_from_path(self, dir_path: str, package_name: str) -> Optional[ModuleSpec]: spec = None From 70238048d6b33ffded29fceaccf959a1ce86053f Mon Sep 17 00:00:00 2001 From: Mercury Beach Date: Wed, 17 Dec 2025 18:18:56 +0100 Subject: [PATCH 5/6] Removed previous changes as it was causing too many problems --- invoke/loader.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/invoke/loader.py b/invoke/loader.py index 9843502b3..d4f643f28 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -65,11 +65,9 @@ def load(self, name: Optional[str] = None) -> Tuple[ModuleType, str]: .. versionadded:: 1.0 """ - should_look_into_sys_path = False if name is None: name = self.config.tasks.collection_name - should_look_into_sys_path = True - spec = self.find(name, should_look_into_sys_path) + spec = self.find(name) if spec and spec.loader and spec.origin: # Typically either tasks.py or tasks/__init__.py source_file = Path(spec.origin) @@ -122,9 +120,7 @@ def start(self) -> str: # Lazily determine default CWD if configured value is falsey return self._start or os.getcwd() - def find(self, - name: str, - look_into_sys_path: bool = False) -> Optional[ModuleSpec]: + def find(self,name: str) -> Optional[ModuleSpec]: spec = None try: # walk the path upwards to check for dynamic import @@ -140,15 +136,14 @@ def find(self, return spec # if not found, check sys.path - if look_into_sys_path: - for path in sys.path: - if path == self.start: - continue - debug("FilesystemLoader checks sys.path {!r}".format(path)) - spec = self._get_module_or_pkg_spec_from_path(path, name) - if spec: - debug("Found module: {!r}".format(spec)) - return spec + for path in sys.path: + if path == self.start: + continue + debug("FilesystemLoader checks sys.path {!r}".format(path)) + spec = self._get_module_or_pkg_spec_from_path(path, name) + if spec: + debug("Found module: {!r}".format(spec)) + return spec except (FileNotFoundError, ModuleNotFoundError): msg = "ImportError loading {!r}, raising CollectionNotFound" From 86896821734cb4eceef2a6ba98afe21e0d981fee Mon Sep 17 00:00:00 2001 From: Mercury Beach Date: Wed, 17 Dec 2025 18:27:43 +0100 Subject: [PATCH 6/6] pep8 fix --- invoke/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoke/loader.py b/invoke/loader.py index d4f643f28..9dd7ef8dc 100644 --- a/invoke/loader.py +++ b/invoke/loader.py @@ -120,7 +120,7 @@ def start(self) -> str: # Lazily determine default CWD if configured value is falsey return self._start or os.getcwd() - def find(self,name: str) -> Optional[ModuleSpec]: + def find(self, name: str) -> Optional[ModuleSpec]: spec = None try: # walk the path upwards to check for dynamic import