Source code for flask_unchained.hooks.register_commands_hook
import click
import itertools
from typing import *
from warnings import warn
from ..app_factory_hook import AppFactoryHook
from ..bundles import Bundle
from ..flask_unchained import FlaskUnchained
[docs]class RegisterCommandsHook(AppFactoryHook):
"""
Registers commands and command groups from bundles.
"""
name = 'commands'
"""
The name of this hook.
"""
bundle_module_names = ['commands']
"""
The default module this hook loads from.
Override by setting the ``commands_module_names`` attribute on your
bundle class.
"""
run_after = ['services']
limit_discovery_to_local_declarations = False
[docs] def run_hook(self,
app: FlaskUnchained,
bundles: List[Bundle],
unchained_config: Optional[Dict[str, Any]] = None,
) -> Dict[str, Union[click.Command, click.Group]]:
"""
Discover CLI commands and command groups from bundles and register them
with the app.
"""
commands = {}
for bundle in bundles:
command_groups = self.get_bundle_command_groups(bundle)
commands.update(inherit_docstrings(command_groups, commands))
commands.update(self.get_bundle_commands(bundle, command_groups))
for name, command in commands.items():
if name in app.cli.commands:
warn(f'Command name conflict: "{name}" is taken.')
continue
app.cli.add_command(command, name)
return commands
def get_bundle_commands(self,
bundle: Bundle,
command_groups: Dict[str, click.Group],
) -> Dict[str, click.Command]:
# when a command belongs to a group, we don't also want to register the command.
# therefore we collect all the command names belonging to groups, and use that
# in our is_click_command type-checking fn below
group_command_names = set(itertools.chain.from_iterable(
group.commands.keys() for group in command_groups.values()))
def is_click_command(obj: Any) -> bool:
return self.is_click_command(obj) and obj.name not in group_command_names
commands = {}
for b in bundle._iter_class_hierarchy():
for module in self.import_bundle_modules(b):
new = self._collect_from_package(module, is_click_command)
commands.update(inherit_docstrings(new, commands))
return commands
def get_bundle_command_groups(self, bundle: Bundle) -> Dict[str, click.Group]:
command_groups = {}
module_found = False
for b in bundle._iter_class_hierarchy():
for module in self.import_bundle_modules(b):
module_found = True
command_groups.update(
self._collect_from_package(module, self.is_click_group)
)
groups = {}
for name in getattr(bundle, 'command_group_names', [bundle.name]):
try:
groups[name] = command_groups[name]
except KeyError as e:
if module_found and not bundle.is_single_module:
warn(f'WARNING: Found a commands module for the {bundle.name} '
f'bundle, but there was no command group named {e.args[0]}'
f' in it. Either create one, or customize the bundle\'s '
f'`command_group_names` class attribute.')
continue
return groups
def is_click_command(self, obj: Any) -> bool:
return isinstance(obj, click.Command) and not self.is_click_group(obj)
def is_click_group(self, obj: Any) -> bool:
return isinstance(obj, click.Group)
def inherit_docstrings(new, preexisting):
preexisting_names = set(preexisting.keys()) & set(new.keys())
for name in preexisting_names:
if not new[name].__doc__:
new[name].__doc__ = preexisting[name].__doc__
if isinstance(new[name], click.Group):
new[name].commands = inherit_docstrings(new[name].commands,
preexisting[name].commands)
return new