Skip to content

Commit 3ba9620

Browse files
authored
fix: allow ignoring shutdown and cleanup hooks (#33)
1 parent 9163fba commit 3ba9620

2 files changed

Lines changed: 60 additions & 35 deletions

File tree

aiocli/commander.py

Lines changed: 16 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -78,32 +78,32 @@ def __init__(
7878
def app(self) -> Application:
7979
return self._app
8080

81-
async def setup(self, all_hooks: bool = True) -> None:
81+
async def setup(self, all_hooks: bool = True, ignore_internal_hooks: bool = False) -> None:
8282
if self._handle_signals:
8383
try:
8484
self._loop.add_signal_handler(signal.SIGINT, _raise_graceful_exit)
8585
self._loop.add_signal_handler(signal.SIGTERM, _raise_graceful_exit)
8686
except NotImplementedError: # pragma: no cover
8787
# add_signal_handler is not implemented on Windows
8888
pass
89-
await self.startup(all_hooks=all_hooks)
89+
await self.startup(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
9090

91-
async def startup(self, all_hooks: bool) -> None:
92-
await self._app.startup(all_hooks=all_hooks)
91+
async def startup(self, all_hooks: bool, ignore_internal_hooks: bool = False) -> None:
92+
await self._app.startup(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
9393

94-
async def shutdown(self) -> None:
95-
await self._app.shutdown()
94+
async def shutdown(self, all_hooks: bool, ignore_internal_hooks: bool = False) -> None:
95+
await self._app.shutdown(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
9696

97-
async def cleanup(self) -> None:
98-
await self.shutdown()
97+
async def cleanup(self, all_hooks: bool = False, ignore_internal_hooks: bool = False) -> None:
98+
await self.shutdown(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
9999
if self._handle_signals:
100100
try:
101101
self._loop.remove_signal_handler(signal.SIGINT)
102102
self._loop.remove_signal_handler(signal.SIGTERM)
103103
except NotImplementedError: # pragma: no cover
104104
# remove_signal_handler is not implemented on Windows
105105
pass
106-
await self._app.cleanup()
106+
await self._app.cleanup(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
107107
if self._exit_code:
108108
self._app.exit()
109109

@@ -118,19 +118,13 @@ async def _run_app(
118118
) -> None:
119119
runner = AppRunner(app, loop=loop, handle_signals=handle_signals, exit_code=exit_code)
120120
args = argv or sys.argv[1:]
121-
if app.should_ignore_hooks(args):
122-
await runner.setup(all_hooks=False)
123-
try:
124-
await app(args)
125-
finally:
126-
if exit_code:
127-
app.exit()
128-
else:
129-
await runner.setup(all_hooks=True)
130-
try:
131-
await app(args)
132-
finally:
133-
await runner.cleanup()
121+
all_hooks = not app.should_ignore_hooks(args)
122+
ignore_internal_hooks = app.should_ignore_internal_hooks(args)
123+
await runner.setup(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
124+
try:
125+
await app(args)
126+
finally:
127+
await runner.cleanup(all_hooks=all_hooks, ignore_internal_hooks=ignore_internal_hooks)
134128

135129

136130
ApplicationParser = Callable[..., Optional[List[str]]]

aiocli/commander_app.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
'command',
2929
'CommandHandler',
3030
'Application',
31+
'InternalCommandHook',
3132
)
3233

3334
from .helpers import iscoroutinefunction, resolve_coroutine, resolve_function
@@ -86,6 +87,13 @@ class Command:
8687
description: Optional[str] = None
8788
usage: Optional[str] = None
8889
ignore_hooks: bool = False
90+
ignore_middleware: bool = False
91+
92+
def should_ignore_internal_hooks(self) -> bool:
93+
return self.ignore_hooks and self.name in ['-h', '--help', '-v', '--version']
94+
95+
def should_ignore_middleware(self) -> bool:
96+
return self.ignore_middleware and self.name in ['-h', '--help', '-v', '--version']
8997

9098

9199
def command(
@@ -269,7 +277,7 @@ async def self_handler() -> int:
269277
_ = self._parser.parse_args([name])
270278
return default_exit_code
271279

272-
return Command(name=name, handler=self_handler, deprecated=False, ignore_hooks=True)
280+
return Command(name=name, handler=self_handler, deprecated=False, ignore_hooks=True, ignore_middleware=True)
273281

274282
self._commands = {
275283
'-h': self_command('-h'),
@@ -303,7 +311,7 @@ async def __call__(self, args: List[str]) -> int:
303311
self._exit_code = self._exit_code if exit_code is None else exit_code
304312
return self._exit_code
305313

306-
async def _execute_command(self, name: str, args: List[str]) -> Optional[int]:
314+
def _ensure_command_exists(self, name: str) -> None:
307315
if name not in self._parsers:
308316
if name:
309317
self._log(msg='{0}Command got "{1}".'.format('[deprecated] ' if self._deprecated else '', name))
@@ -314,6 +322,9 @@ async def _execute_command(self, name: str, args: List[str]) -> Optional[int]:
314322
'[deprecated] ' if self._deprecated else '', self._commands[name].handler.__name__
315323
)
316324
)
325+
326+
async def _execute_command(self, name: str, args: List[str]) -> Optional[int]:
327+
self._ensure_command_exists(name=name)
317328
kwargs = await self._resolve_command_handler_args(name, args)
318329
kwargs = await self._resolve_command_handler_kwargs(self._commands[name].handler, kwargs)
319330
try:
@@ -386,14 +397,22 @@ def decorator(handler: CommandHandler) -> CommandHandler:
386397

387398
return decorator
388399

389-
def should_ignore_hooks(self, args: List[str]) -> bool:
400+
def _get_command_from_args(self, args: List[str]) -> Optional[Command]:
390401
cmd: Optional[Command] = None
391402
if len(args) > 0:
392403
cmd = self.get_command(name=args[0])
393404
if not cmd:
394405
cmd = self.get_command(name=self._default_command)
406+
return cmd
407+
408+
def should_ignore_hooks(self, args: List[str]) -> bool:
409+
cmd = self._get_command_from_args(args=args)
395410
return not cmd or cmd.ignore_hooks
396411

412+
def should_ignore_internal_hooks(self, args: List[str]) -> bool:
413+
cmd = self._get_command_from_args(args=args)
414+
return not cmd or cmd.should_ignore_internal_hooks() or args[-1] in ['-h', '--help', '-v', '--version']
415+
397416
def add_commands(self, commands: Sequence[Command]) -> None:
398417
for cmd in commands:
399418
self._add_command(cmd)
@@ -469,22 +488,22 @@ def exit(self) -> None:
469488
def on_startup(self) -> List[CommandHook]:
470489
return self._on_startup
471490

472-
async def startup(self, all_hooks: bool = True) -> None:
473-
await self._execute_command_hooks(self.on_startup, all_hooks)
491+
async def startup(self, all_hooks: bool = True, ignore_internal_hooks: bool = False) -> None:
492+
await self._execute_command_hooks(self.on_startup, all_hooks, ignore_internal_hooks)
474493

475494
@property
476495
def on_shutdown(self) -> List[CommandHook]:
477496
return self._on_shutdown
478497

479-
async def shutdown(self) -> None:
480-
await self._execute_command_hooks(self._on_shutdown)
498+
async def shutdown(self, all_hooks: bool = True, ignore_internal_hooks: bool = False) -> None:
499+
await self._execute_command_hooks(self._on_shutdown, all_hooks, ignore_internal_hooks)
481500

482501
@property
483502
def on_cleanup(self) -> List[CommandHook]:
484503
return self._on_cleanup
485504

486-
async def cleanup(self) -> None:
487-
await self._execute_command_hooks(self._on_cleanup)
505+
async def cleanup(self, all_hooks: bool = True, ignore_internal_hooks: bool = False) -> None:
506+
await self._execute_command_hooks(self._on_cleanup, all_hooks, ignore_internal_hooks)
488507

489508
def _add_command(self, cmd: Command) -> None:
490509
if cmd.deprecated is None:
@@ -523,6 +542,9 @@ async def _execute_command_middleware(
523542
cmd: Command,
524543
kwargs: Dict[str, Any],
525544
) -> None:
545+
if cmd.should_ignore_middleware():
546+
self._log(msg='Command middleware ignored')
547+
return
526548
for handler in command_middleware:
527549
self._log(
528550
msg='Executing middleware {0} with {1}({2})...'.format(
@@ -535,13 +557,22 @@ async def _execute_command_middleware(
535557
*([handler] if len(signature(handler).parameters) == 0 else [handler, cmd, kwargs]) # type: ignore
536558
)
537559

538-
async def _execute_command_hooks(self, command_hooks: List[CommandHook], all_hooks: bool = True) -> None:
560+
async def _execute_command_hooks(
561+
self,
562+
command_hooks: List[CommandHook],
563+
all_hooks: bool = True,
564+
ignore_internal_hooks: bool = False,
565+
) -> None:
539566
command_hooks_ = (
540-
command_hooks if all_hooks else [hook for hook in command_hooks if isinstance(hook, InternalCommandHook)]
567+
command_hooks
568+
if all_hooks
569+
else [
570+
hook.__call__
571+
for hook in command_hooks
572+
if isinstance(hook, InternalCommandHook) and not ignore_internal_hooks
573+
]
541574
)
542575
for hook in command_hooks_:
543-
if isinstance(hook, InternalCommandHook):
544-
hook = hook.__call__
545576
self._log(
546577
msg='Executing hook "{0}" ({1})'.format(
547578
hook.__name__ if hasattr(hook, '__name__') else 'unknown', id(hook)

0 commit comments

Comments
 (0)