/
cli.py
261 lines (225 loc) · 7.82 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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
from pathlib import Path
import os
import sys
import click
from ploomber.spec import DAGSpec
from ploomber import __version__
from ploomber import cli as cli_module
from ploomber import scaffold as _scaffold
from ploomber_scaffold import scaffold as scaffold_project
from ploomber.telemetry import telemetry
@click.group()
@click.version_option(version=__version__)
def cli():
"""Ploomber command line interface.
"""
pass # pragma: no cover
@cli.command()
@click.option(
'--conda',
is_flag=True,
help='Use conda (environemnt.yml)',
)
@click.option(
'--package',
is_flag=True,
help='Use package template (setup.py)',
)
@click.option(
'--empty',
is_flag=True,
help='Create a sample pipeline.yaml with no tasks',
)
@click.option(
'--entry-point',
'-e',
default=None,
help='Entry point to add tasks. Invalid if other flags present',
)
def scaffold(conda, package, entry_point, empty):
"""Create new projects (if no pipeline.yaml exists) or add missings tasks
"""
template = '-e/--entry-point is not compatible with the {flag} flag'
if entry_point and conda:
err = template.format(flag='--conda')
telemetry.log_api("scaffold_error",
metadata={
'type': 'entry_and_conda_flag',
'exception': err,
'argv': sys.argv
})
raise click.ClickException(err)
if entry_point and package:
err = template.format(flag='--package')
telemetry.log_api("scaffold_error",
metadata={
'type': 'entry_and_package_flag',
'exception': err,
'argv': sys.argv
})
raise click.ClickException(err)
if entry_point and empty:
err = template.format(flag='--empty')
telemetry.log_api("scaffold_error",
metadata={
'type': 'entry_and_empty_flag',
'exception': err,
'argv': sys.argv
})
raise click.ClickException(err)
# try to load a dag by looking in default places
if entry_point is None:
loaded = _scaffold.load_dag()
else:
try:
loaded = (
DAGSpec(entry_point, lazy_import='skip'),
Path(entry_point).parent,
Path(entry_point),
)
except Exception as e:
telemetry.log_api("scaffold_error",
metadata={
'type': 'dag_load_failed',
'exception': e,
'argv': sys.argv
})
raise click.ClickException(e) from e
if loaded:
# existing pipeline, add tasks
spec, _, path_to_spec = loaded
_scaffold.add(spec, path_to_spec)
telemetry.log_api("ploomber_scaffold",
dag=loaded,
metadata={
'type': 'add_task',
'argv': sys.argv
})
else:
# no pipeline, create base project
telemetry.log_api("ploomber_scaffold",
metadata={
'type': 'base_project',
'argv': sys.argv
})
scaffold_project.cli(project_path=None,
conda=conda,
package=package,
empty=empty)
@cli.command()
@click.option('-l',
'--use-lock',
help='Use lock files',
default=False,
is_flag=True)
def install(use_lock):
"""Install dependencies and package
"""
cli_module.install.main(use_lock=use_lock)
@cli.command()
@click.option('-n', '--name', help='Example to download', default=None)
@click.option('-f', '--force', help='Force examples download', is_flag=True)
@click.option('-o', '--output', help='Target directory', default=None)
@click.option('-b', '--branch', help='Git branch to use.', default=None)
def examples(name, force, branch, output):
"""Get sample projects. Run "ploomber examples" to list them
"""
try:
cli_module.examples.main(name=name,
force=force,
branch=branch,
output=output)
except click.ClickException:
raise
except Exception as e:
telemetry.log_api("examples_error",
metadata={
'type': 'runtime_error',
'exception': e,
'argv': sys.argv
})
raise RuntimeError(
'An error happened when executing the examples command. Check out '
'the full error message for details. Downloading the examples '
'again or upgrading Ploomber may fix the '
'issue.\nDownload: ploomber examples -f\n'
'Update: pip install ploomber -U\n'
'Update [conda]: conda update ploomber -c conda-forge') from e
def _exit_with_error_message(msg):
click.echo(msg, err=True)
sys.exit(2)
def cmd_router():
cmd_name = None if len(sys.argv) < 2 else sys.argv[1]
custom = {
'build': cli_module.build.main,
'plot': cli_module.plot.main,
'task': cli_module.task.main,
'report': cli_module.report.main,
'interact': cli_module.interact.main,
'status': cli_module.status.main,
'nb': cli_module.nb.main,
}
# users may attempt to run execute/run, suggest to use build instead
alias = {'execute': 'build', 'run': 'build'}
if cmd_name in custom:
# NOTE: we don't use the argument here, it is parsed by _main
# pop the second element ('entry') to make the CLI behave as expected
sys.argv.pop(1)
# Add the current working directory, this is done automatically when
# calling "python -m ploomber.build" but not here ("ploomber build")
sys.path.insert(0, os.path.abspath('.'))
fn = custom[cmd_name]
fn()
elif cmd_name in alias:
suggestion = alias[cmd_name]
telemetry.log_api("unsupported_build_cmd",
metadata={
'cmd_name': cmd_name,
'suggestion': suggestion,
'argv': sys.argv
})
_exit_with_error_message("Try 'ploomber --help' for help.\n\n"
f"Error: {cmd_name!r} is not a valid command."
f" Did you mean {suggestion!r}?")
else:
if cmd_name not in ['examples', 'scaffold']:
telemetry.log_api("unsupported-api-call",
metadata={'argv': sys.argv})
cli()
# the commands below are handled by the router,
# those are a place holder to show up in ploomber --help
@cli.command()
def build():
"""Build pipeline
"""
pass # pragma: no cover
@cli.command()
def status():
"""Show pipeline status
"""
pass # pragma: no cover
@cli.command()
def plot():
"""Plot pipeline
"""
pass # pragma: no cover
@cli.command()
def task():
"""Interact with specific tasks
"""
pass # pragma: no cover
@cli.command()
def report():
"""Make a pipeline report
"""
pass # pragma: no cover
@cli.command()
def interact():
"""Start an interactive session (use the "dag" variable)
"""
pass # pragma: no cover
@cli.command()
def nb():
"""Manage scripts and notebooks
"""
pass # pragma: no cover