-
Notifications
You must be signed in to change notification settings - Fork 916
/
Copy pathcli.py
211 lines (181 loc) · 7.14 KB
/
cli.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
"""kedro is a CLI for managing Kedro projects.
This module implements commands available from the kedro CLI.
"""
import importlib
import sys
import webbrowser
from collections import defaultdict
from pathlib import Path
from typing import Sequence
import click
from kedro import __version__ as version
from kedro.framework.cli.catalog import catalog_cli
from kedro.framework.cli.hooks import get_cli_hook_manager
from kedro.framework.cli.jupyter import jupyter_cli
from kedro.framework.cli.micropkg import micropkg_cli
from kedro.framework.cli.pipeline import pipeline_cli
from kedro.framework.cli.project import project_group
from kedro.framework.cli.registry import registry_cli
from kedro.framework.cli.starters import create_cli
from kedro.framework.cli.utils import (
CONTEXT_SETTINGS,
ENTRY_POINT_GROUPS,
CommandCollection,
KedroCliError,
_get_entry_points,
load_entry_points,
)
from kedro.framework.project import LOGGING # noqa # pylint:disable=unused-import
from kedro.framework.startup import _is_project, bootstrap_project
LOGO = rf"""
_ _
| | _____ __| |_ __ ___
| |/ / _ \/ _` | '__/ _ \
| < __/ (_| | | | (_) |
|_|\_\___|\__,_|_| \___/
v{version}
"""
@click.group(context_settings=CONTEXT_SETTINGS, name="Kedro")
@click.version_option(version, "--version", "-V", help="Show version and exit")
def cli(): # pragma: no cover
"""Kedro is a CLI for creating and using Kedro projects. For more
information, type ``kedro info``.
"""
pass
@cli.command()
def info():
"""Get more information about kedro."""
click.secho(LOGO, fg="green")
click.echo(
"Kedro is a Python framework for\n"
"creating reproducible, maintainable\n"
"and modular data science code."
)
plugin_versions = {}
plugin_entry_points = defaultdict(set)
for plugin_entry_point in ENTRY_POINT_GROUPS:
for entry_point in _get_entry_points(plugin_entry_point):
module_name = entry_point.module.split(".")[0]
plugin_versions[module_name] = entry_point.dist.version
plugin_entry_points[module_name].add(plugin_entry_point)
click.echo()
if plugin_versions:
click.echo("Installed plugins:")
for plugin_name, plugin_version in sorted(plugin_versions.items()):
entrypoints_str = ",".join(sorted(plugin_entry_points[plugin_name]))
click.echo(
f"{plugin_name}: {plugin_version} (entry points:{entrypoints_str})"
)
else:
click.echo("No plugins installed")
@cli.command(short_help="See the kedro API docs and introductory tutorial.")
def docs():
"""Display the online API docs and introductory tutorial in the browser. (DEPRECATED)"""
deprecation_message = (
"DeprecationWarning: Command 'kedro docs' is deprecated and "
"will not be available from Kedro 0.19.0."
)
click.secho(deprecation_message, fg="red")
index_path = f"https://kedro.readthedocs.io/en/{version}"
click.echo(f"Opening {index_path}")
webbrowser.open(index_path)
def _init_plugins() -> None:
init_hooks = load_entry_points("init")
for init_hook in init_hooks:
init_hook()
class KedroCLI(CommandCollection):
"""A CommandCollection class to encapsulate the KedroCLI command
loading.
"""
def __init__(self, project_path: Path):
self._metadata = None # running in package mode
if _is_project(project_path):
self._metadata = bootstrap_project(project_path)
self._cli_hook_manager = get_cli_hook_manager()
super().__init__(
("Global commands", self.global_groups),
("Project specific commands", self.project_groups),
)
def main(
self,
args=None,
prog_name=None,
complete_var=None,
standalone_mode=True,
**extra,
):
if self._metadata:
extra.update(obj=self._metadata)
# This is how click's internals parse sys.argv, which include the command,
# subcommand, arguments and options. click doesn't store this information anywhere
# so we have to re-do it.
args = sys.argv[1:] if args is None else list(args)
self._cli_hook_manager.hook.before_command_run( # pylint: disable=no-member
project_metadata=self._metadata, command_args=args
)
try:
super().main(
args=args,
prog_name=prog_name,
complete_var=complete_var,
standalone_mode=standalone_mode,
**extra,
)
# click.core.main() method exits by default, we capture this and then
# exit as originally intended
except SystemExit as exc:
self._cli_hook_manager.hook.after_command_run( # pylint: disable=no-member
project_metadata=self._metadata, command_args=args, exit_code=exc.code
)
sys.exit(exc.code)
@property
def global_groups(self) -> Sequence[click.MultiCommand]:
"""Property which loads all global command groups from plugins and
combines them with the built-in ones (eventually overriding the
built-in ones if they are redefined by plugins).
"""
return [cli, create_cli, *load_entry_points("global")]
@property
def project_groups(self) -> Sequence[click.MultiCommand]:
# pylint: disable=line-too-long
"""Property which loads all project command groups from the
project and the plugins, then combines them with the built-in ones.
Built-in commands can be overridden by plugins, which can be
overridden by a custom project cli.py.
See https://kedro.readthedocs.io/en/stable/extend_kedro/common_use_cases.html#use-case-3-how-to-add-or-modify-cli-commands
on how to add this.
"""
if not self._metadata:
return []
built_in = [
catalog_cli,
jupyter_cli,
pipeline_cli,
micropkg_cli,
project_group,
registry_cli,
]
plugins = load_entry_points("project")
try:
project_cli = importlib.import_module(f"{self._metadata.package_name}.cli")
# fail gracefully if cli.py does not exist
except ModuleNotFoundError:
# return only built-in commands and commands from plugins
# (plugins can override built-in commands)
return [*built_in, *plugins]
# fail badly if cli.py exists, but has no `cli` in it
if not hasattr(project_cli, "cli"):
raise KedroCliError(
f"Cannot load commands from {self._metadata.package_name}.cli"
)
user_defined = project_cli.cli # type: ignore
# return built-in commands, plugin commands and user defined commands
# (overriding happens as follows built-in < plugins < cli.py)
return [*built_in, *plugins, user_defined]
def main(): # pragma: no cover
"""Main entry point. Look for a ``cli.py``, and, if found, add its
commands to `kedro`'s before invoking the CLI.
"""
_init_plugins()
cli_collection = KedroCLI(project_path=Path.cwd())
cli_collection()