Skip to content

Commit 3d28c13

Browse files
committed
Improve the user experience when an ImportError occurs while instantiating tools
Fixes #20 Signed-off-by: Pedro Algarvio <[email protected]>
1 parent 902ce1f commit 3d28c13

File tree

4 files changed

+60
-3
lines changed

4 files changed

+60
-3
lines changed

changelog/20.feature.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Improve the user experience when an `ImportError` occurs while instantiating tools.
2+
Instead of relying on direct imports, users can now call, `pyscripts.register_tools_module('tools.<whatever>')`.
3+
Python tools scripts will then import them one by one, catching and reporting any `ImportErrors` occurring.
4+
Due to these errors, some of the commands might be unavailable, but most likely not all, while providing a clue as to why that is.

src/ptscripts/__init__.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66

77
import ptscripts.logs
88
from ptscripts.parser import command_group
9-
from ptscripts.parser import Context
9+
from ptscripts.parser import Context, RegisteredImports
1010

11-
__all__ = ["command_group", "Context", "CWD"]
11+
__all__ = ["command_group", "register_tools_module", "Context", "CWD"]
12+
13+
14+
def register_tools_module(import_module: str) -> None:
15+
"""
16+
Register a module to be imported when instantiating the tools parser.
17+
"""
18+
RegisteredImports.register_import(import_module)

src/ptscripts/__main__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ def main():
2323
except ImportError:
2424
# No tools/ directory in the current CWD
2525
pass
26-
2726
parser.parse_args()
2827

2928

src/ptscripts/parser.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
import argparse
7+
import importlib
78
import inspect
89
import logging
910
import os
@@ -241,6 +242,40 @@ def web(self) -> requests.Session:
241242
return requests.Session()
242243

243244

245+
class RegisteredImports:
246+
"""
247+
Simple class to hold registered imports.
248+
"""
249+
250+
_instance: RegisteredImports | None = None
251+
_registered_imports: list[str]
252+
253+
def __new__(cls):
254+
"""
255+
Method that instantiates a singleton class and returns it.
256+
"""
257+
if cls._instance is None:
258+
instance = super().__new__(cls)
259+
instance._registered_imports = []
260+
cls._instance = instance
261+
return cls._instance
262+
263+
@classmethod
264+
def register_import(cls, import_module: str) -> None:
265+
"""
266+
Register an import.
267+
"""
268+
instance = cls()
269+
if import_module not in instance._registered_imports:
270+
instance._registered_imports.append(import_module)
271+
272+
def __iter__(self):
273+
"""
274+
Return an iterator of all registered imports.
275+
"""
276+
return iter(self._registered_imports)
277+
278+
244279
class Parser:
245280
"""
246281
Singleton parser class that wraps argparse.
@@ -328,10 +363,22 @@ def __new__(cls):
328363
cls._instance = instance
329364
return cls._instance
330365

366+
def _process_registered_tool_modules(self):
367+
for module_name in RegisteredImports():
368+
try:
369+
importlib.import_module(module_name)
370+
except ImportError as exc:
371+
if os.environ.get("TOOLS_IGNORE_IMPORT_ERRORS", "0") == "0":
372+
self.context.warn(
373+
f"Could not import the registered tools module {module_name!r}: {exc}"
374+
)
375+
331376
def parse_args(self):
332377
"""
333378
Parse CLI.
334379
"""
380+
# Process registered imports to allow other modules to register commands
381+
self._process_registered_tool_modules()
335382
options = self.parser.parse_args()
336383
if options.quiet:
337384
logging.root.setLevel(logging.CRITICAL + 1)

0 commit comments

Comments
 (0)