esgpull 0.6.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- esgpull/__init__.py +12 -0
- esgpull/auth.py +181 -0
- esgpull/cli/__init__.py +73 -0
- esgpull/cli/add.py +103 -0
- esgpull/cli/autoremove.py +38 -0
- esgpull/cli/config.py +116 -0
- esgpull/cli/convert.py +285 -0
- esgpull/cli/decorators.py +342 -0
- esgpull/cli/download.py +74 -0
- esgpull/cli/facet.py +23 -0
- esgpull/cli/get.py +28 -0
- esgpull/cli/install.py +85 -0
- esgpull/cli/link.py +105 -0
- esgpull/cli/login.py +56 -0
- esgpull/cli/remove.py +73 -0
- esgpull/cli/retry.py +43 -0
- esgpull/cli/search.py +201 -0
- esgpull/cli/self.py +238 -0
- esgpull/cli/show.py +66 -0
- esgpull/cli/status.py +67 -0
- esgpull/cli/track.py +87 -0
- esgpull/cli/update.py +184 -0
- esgpull/cli/utils.py +247 -0
- esgpull/config.py +410 -0
- esgpull/constants.py +56 -0
- esgpull/context.py +724 -0
- esgpull/database.py +161 -0
- esgpull/download.py +162 -0
- esgpull/esgpull.py +447 -0
- esgpull/exceptions.py +167 -0
- esgpull/fs.py +253 -0
- esgpull/graph.py +460 -0
- esgpull/install_config.py +185 -0
- esgpull/migrations/README +1 -0
- esgpull/migrations/env.py +82 -0
- esgpull/migrations/script.py.mako +24 -0
- esgpull/migrations/versions/0.3.0_update_tables.py +170 -0
- esgpull/migrations/versions/0.3.1_update_tables.py +25 -0
- esgpull/migrations/versions/0.3.2_update_tables.py +26 -0
- esgpull/migrations/versions/0.3.3_update_tables.py +25 -0
- esgpull/migrations/versions/0.3.4_update_tables.py +25 -0
- esgpull/migrations/versions/0.3.5_update_tables.py +25 -0
- esgpull/migrations/versions/0.3.6_update_tables.py +26 -0
- esgpull/migrations/versions/0.3.7_update_tables.py +26 -0
- esgpull/migrations/versions/0.3.8_update_tables.py +26 -0
- esgpull/migrations/versions/0.4.0_update_tables.py +25 -0
- esgpull/migrations/versions/0.5.0_update_tables.py +26 -0
- esgpull/migrations/versions/0.5.1_update_tables.py +26 -0
- esgpull/migrations/versions/0.5.2_update_tables.py +25 -0
- esgpull/migrations/versions/0.5.3_update_tables.py +26 -0
- esgpull/migrations/versions/0.5.4_update_tables.py +25 -0
- esgpull/migrations/versions/0.5.5_update_tables.py +25 -0
- esgpull/migrations/versions/0.6.0_update_tables.py +25 -0
- esgpull/migrations/versions/0.6.1_update_tables.py +25 -0
- esgpull/migrations/versions/0.6.2_update_tables.py +25 -0
- esgpull/migrations/versions/0.6.3_update_tables.py +25 -0
- esgpull/models/__init__.py +31 -0
- esgpull/models/base.py +50 -0
- esgpull/models/dataset.py +34 -0
- esgpull/models/facet.py +18 -0
- esgpull/models/file.py +65 -0
- esgpull/models/options.py +164 -0
- esgpull/models/query.py +481 -0
- esgpull/models/selection.py +201 -0
- esgpull/models/sql.py +258 -0
- esgpull/models/synda_file.py +85 -0
- esgpull/models/tag.py +19 -0
- esgpull/models/utils.py +54 -0
- esgpull/presets.py +13 -0
- esgpull/processor.py +172 -0
- esgpull/py.typed +0 -0
- esgpull/result.py +53 -0
- esgpull/tui.py +346 -0
- esgpull/utils.py +54 -0
- esgpull/version.py +1 -0
- esgpull-0.6.3.dist-info/METADATA +110 -0
- esgpull-0.6.3.dist-info/RECORD +80 -0
- esgpull-0.6.3.dist-info/WHEEL +4 -0
- esgpull-0.6.3.dist-info/entry_points.txt +3 -0
- esgpull-0.6.3.dist-info/licenses/LICENSE +28 -0
esgpull/cli/convert.py
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
from collections import Counter
|
|
2
|
+
from collections.abc import MutableMapping
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import pyparsing as pp
|
|
7
|
+
import yaml
|
|
8
|
+
from click.exceptions import Abort, Exit
|
|
9
|
+
from rich.box import MINIMAL_DOUBLE_HEAD
|
|
10
|
+
from rich.prompt import Confirm
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
from rich.text import Text
|
|
13
|
+
|
|
14
|
+
from esgpull.cli.decorators import opts
|
|
15
|
+
from esgpull.cli.utils import init_esgpull
|
|
16
|
+
from esgpull.graph import Graph
|
|
17
|
+
from esgpull.models import Options, Query, Tag
|
|
18
|
+
from esgpull.models.selection import FacetValues, Selection
|
|
19
|
+
from esgpull.tui import Verbosity, logger
|
|
20
|
+
|
|
21
|
+
SKIP = {"priority", "protocol"}
|
|
22
|
+
options_names = Options()._names
|
|
23
|
+
pp.ParserElement.set_default_whitespace_chars(" \t,") # remove newline
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def line(expr: pp.ParserElement) -> pp.ParserElement:
|
|
27
|
+
return pp.LineStart().suppress() + expr + pp.LineEnd().suppress()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def sqbr(expr: pp.ParserElement) -> pp.ParserElement:
|
|
31
|
+
return pp.Suppress("[") + expr + pp.Suppress("]")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
anything = pp.Word(pp.printables)
|
|
35
|
+
word = pp.Word(pp.alphanums + "-_")
|
|
36
|
+
eq = pp.Suppress("=")
|
|
37
|
+
|
|
38
|
+
comment_start = pp.Char("#")[1, ...].suppress()
|
|
39
|
+
name_comment = pp.Group(line(comment_start + anything[1])[0, 1])
|
|
40
|
+
comment = line(comment_start + anything[...]).suppress()
|
|
41
|
+
facet_name = word("name")
|
|
42
|
+
facet_value = word
|
|
43
|
+
facet_values = pp.Group(word[1, ...])
|
|
44
|
+
facet = pp.Group(line(facet_name + eq + facet_values("vals")))("facet")
|
|
45
|
+
variable_cmip5 = pp.Group(
|
|
46
|
+
line(
|
|
47
|
+
"variable"
|
|
48
|
+
+ sqbr(facet_value)("realm")
|
|
49
|
+
+ sqbr(facet_value)("time_frequency")
|
|
50
|
+
+ eq
|
|
51
|
+
+ facet_values("variable")
|
|
52
|
+
)
|
|
53
|
+
)("variable_cmip5")
|
|
54
|
+
variable_cmip6 = pp.Group(
|
|
55
|
+
line(
|
|
56
|
+
"variable"
|
|
57
|
+
+ sqbr(pp.Opt("table_id=") + facet_value("table_id"))
|
|
58
|
+
+ eq
|
|
59
|
+
+ facet_values("variable_id")
|
|
60
|
+
)
|
|
61
|
+
)("variable_cmip6")
|
|
62
|
+
variable_no_sqbr = pp.Group(line("variable" + eq + facet_values("variable")))(
|
|
63
|
+
"variable_no_sqbr"
|
|
64
|
+
)
|
|
65
|
+
otherwise = line(anything[...])("otherwise")
|
|
66
|
+
rest = pp.Group(
|
|
67
|
+
comment
|
|
68
|
+
| facet
|
|
69
|
+
| variable_no_sqbr
|
|
70
|
+
| variable_cmip5
|
|
71
|
+
| variable_cmip6
|
|
72
|
+
| otherwise
|
|
73
|
+
)[...]
|
|
74
|
+
selection_file = name_comment("name") + rest("rest")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def remove_duplicates(
|
|
78
|
+
selection: MutableMapping[str, FacetValues],
|
|
79
|
+
) -> MutableMapping[str, FacetValues]:
|
|
80
|
+
result: dict[str, FacetValues] = {}
|
|
81
|
+
duplicates: dict[str, list[str]] = {}
|
|
82
|
+
for name, values in selection.items():
|
|
83
|
+
if isinstance(values, str):
|
|
84
|
+
result[name] = values
|
|
85
|
+
continue
|
|
86
|
+
counter = Counter(values)
|
|
87
|
+
nb_dup = sum(c > 1 for c in counter.values())
|
|
88
|
+
if nb_dup == 0:
|
|
89
|
+
result[name] = values
|
|
90
|
+
continue
|
|
91
|
+
duplicates[name] = list(dict(counter.most_common(nb_dup)))
|
|
92
|
+
if duplicates:
|
|
93
|
+
logger.warning(f"Duplicate values {duplicates}'")
|
|
94
|
+
return result
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def is_CMIP6(q: Query) -> bool:
|
|
98
|
+
project = set(q.selection["project"] + q.selection["mip_era"])
|
|
99
|
+
return "CMIP6" in project
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def isnot_CMIP6(q: Query) -> bool:
|
|
103
|
+
project = set(q.selection["project"] + q.selection["mip_era"])
|
|
104
|
+
return any({"CMIP5", "CORDEX"} & project)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def fix_CMIP5(q: Query) -> Query:
|
|
108
|
+
qd = q.asdict()
|
|
109
|
+
if q.selection["frequency"]:
|
|
110
|
+
qd["selection"]["time_frequency"] = qd["selection"].pop("frequency")
|
|
111
|
+
return Query(**qd)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def convert_file(path: Path) -> Graph:
|
|
115
|
+
logger.info(path)
|
|
116
|
+
query = Query()
|
|
117
|
+
kids: list[Query] = []
|
|
118
|
+
result = selection_file.parse_file(path)
|
|
119
|
+
if result.name:
|
|
120
|
+
# name = result.name[0].split("@", 1)[0]
|
|
121
|
+
query.tags.append(Tag(name=result.name[0]))
|
|
122
|
+
for line in result.rest:
|
|
123
|
+
if line.facet:
|
|
124
|
+
name, values = line.facet.as_list()
|
|
125
|
+
if name in SKIP:
|
|
126
|
+
continue
|
|
127
|
+
elif name in options_names:
|
|
128
|
+
if len(values) > 1:
|
|
129
|
+
raise ValueError({name: values})
|
|
130
|
+
d = {name: values[0]}
|
|
131
|
+
logger.debug(f"OPTION {d}")
|
|
132
|
+
setattr(query.options, name, values[0])
|
|
133
|
+
elif name in Selection._facet_names:
|
|
134
|
+
d = {name: values}
|
|
135
|
+
logger.debug(f"FACET {d}")
|
|
136
|
+
query.selection[name] = list(set(values))
|
|
137
|
+
else:
|
|
138
|
+
raise ValueError(f"{name!r} undefined\n{path.read_text()}")
|
|
139
|
+
elif line.variable_cmip5:
|
|
140
|
+
selection = line.variable_cmip5.as_dict()
|
|
141
|
+
logger.debug(f"SUBQUERY {selection}")
|
|
142
|
+
kid = Query(selection=remove_duplicates(selection), tracked=True)
|
|
143
|
+
kids.append(kid)
|
|
144
|
+
elif line.variable_cmip6:
|
|
145
|
+
selection = line.variable_cmip6.as_dict()
|
|
146
|
+
project = query.selection["project"]
|
|
147
|
+
if project and project[0] in ["CMIP5", "CORDEX"]:
|
|
148
|
+
if "variable_id" in selection:
|
|
149
|
+
selection["variable"] = selection.pop("variable_id")
|
|
150
|
+
if "table_id" in selection:
|
|
151
|
+
selection["time_frequency"] = selection.pop("table_id")
|
|
152
|
+
logger.debug(f"SUBQUERY {selection}")
|
|
153
|
+
kid = Query(selection=remove_duplicates(selection))
|
|
154
|
+
kids.append(kid)
|
|
155
|
+
elif line.variable_no_sqbr:
|
|
156
|
+
logger.error(line.as_dict())
|
|
157
|
+
elif line.as_list():
|
|
158
|
+
logger.error(line.as_list())
|
|
159
|
+
if len(kids) > 1:
|
|
160
|
+
query.untrack()
|
|
161
|
+
elif len(kids) == 1:
|
|
162
|
+
query = query << kids.pop()
|
|
163
|
+
else:
|
|
164
|
+
query.track(query.options)
|
|
165
|
+
if isnot_CMIP6(query):
|
|
166
|
+
query = fix_CMIP5(query)
|
|
167
|
+
query.compute_sha()
|
|
168
|
+
graph = Graph(None)
|
|
169
|
+
graph.add(query)
|
|
170
|
+
for kid in kids:
|
|
171
|
+
if isnot_CMIP6(query) or isnot_CMIP6(kid):
|
|
172
|
+
kid = fix_CMIP5(kid)
|
|
173
|
+
kid.require = query.sha
|
|
174
|
+
kid.compute_sha()
|
|
175
|
+
expanded = query << kid
|
|
176
|
+
kid.track(expanded.options)
|
|
177
|
+
graph.add(kid)
|
|
178
|
+
return graph
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@click.command(context_settings=CONTEXT_SETTINGS)
|
|
185
|
+
@click.argument(
|
|
186
|
+
"paths",
|
|
187
|
+
required=True,
|
|
188
|
+
type=click.Path(exists=True, path_type=Path),
|
|
189
|
+
nargs=-1,
|
|
190
|
+
)
|
|
191
|
+
@click.option(
|
|
192
|
+
"--out",
|
|
193
|
+
"-o",
|
|
194
|
+
type=click.Path(exists=False, path_type=Path),
|
|
195
|
+
default=None,
|
|
196
|
+
)
|
|
197
|
+
@click.option("print_table", "--table", is_flag=True, default=False)
|
|
198
|
+
@click.option("print_graph", "--graph", is_flag=True, default=False)
|
|
199
|
+
@opts.record
|
|
200
|
+
@opts.verbosity
|
|
201
|
+
def convert(
|
|
202
|
+
paths: list[Path],
|
|
203
|
+
out: Path | None,
|
|
204
|
+
print_table: bool,
|
|
205
|
+
print_graph: bool,
|
|
206
|
+
record: bool,
|
|
207
|
+
verbosity: Verbosity,
|
|
208
|
+
):
|
|
209
|
+
"""
|
|
210
|
+
Convert synda selection files to esgpull queries
|
|
211
|
+
|
|
212
|
+
The flags `--table/--graph` are used to select the console output design.
|
|
213
|
+
|
|
214
|
+
Use `--out <path/to/output.yaml>` to generate a query file containing the converted synda
|
|
215
|
+
selection files. This file can be used as an input to the `add --query-file` command.
|
|
216
|
+
|
|
217
|
+
Note that `convert` takes any number of input paths, and produces a single output.
|
|
218
|
+
Using `**/*` as the input path is a good way to convert a whole tree of selection files.
|
|
219
|
+
|
|
220
|
+
As a an arbitrary convention, the generated queries will be tagged with the content of the first line of a synda selection file, if that line starts with `#` and has a single word (+symbols) without whitespaces in it.
|
|
221
|
+
"""
|
|
222
|
+
esg = init_esgpull(verbosity, safe=False, record=record)
|
|
223
|
+
with esg.ui.logging("convert", onraise=Abort):
|
|
224
|
+
if len(paths) == 0:
|
|
225
|
+
esg.ui.print("No file provided")
|
|
226
|
+
raise click.exceptions.Exit(0)
|
|
227
|
+
if out is not None:
|
|
228
|
+
out_str = f"[yellow]{out.resolve()}[/]"
|
|
229
|
+
if out.is_file():
|
|
230
|
+
esg.ui.print(f"Overwriting existing file {out_str}")
|
|
231
|
+
allow = Confirm.ask("Continue?", default=False)
|
|
232
|
+
if not allow:
|
|
233
|
+
raise click.exceptions.Exit(0)
|
|
234
|
+
if out is None and not print_table and not print_graph:
|
|
235
|
+
esg.ui.print(
|
|
236
|
+
"[red]Error[/]: At least one of "
|
|
237
|
+
"[yellow]--out/--table/--graph[/] is required."
|
|
238
|
+
)
|
|
239
|
+
raise click.exceptions.Exit(1)
|
|
240
|
+
table = Table(
|
|
241
|
+
box=MINIMAL_DOUBLE_HEAD,
|
|
242
|
+
show_edge=False,
|
|
243
|
+
show_lines=True,
|
|
244
|
+
)
|
|
245
|
+
table.add_column(Text("path", justify="left"))
|
|
246
|
+
table.add_column(Text("file", justify="center"))
|
|
247
|
+
table.add_column(Text("query", justify="center"))
|
|
248
|
+
full_graph = Graph(None)
|
|
249
|
+
empty_query = Query()
|
|
250
|
+
empty_query.options.apply_defaults(empty_query.options)
|
|
251
|
+
empty_query.compute_sha()
|
|
252
|
+
for path in paths:
|
|
253
|
+
if path.is_file():
|
|
254
|
+
try:
|
|
255
|
+
graph = convert_file(path)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
msg = f"Cannot convert {path.resolve()}"
|
|
258
|
+
logger.exception(e)
|
|
259
|
+
logger.error(msg)
|
|
260
|
+
esg.ui.print(f"[red]Error[/]: {msg}")
|
|
261
|
+
continue
|
|
262
|
+
queries = list(graph.queries.values())
|
|
263
|
+
if len(queries) == 1 and queries[0].sha == empty_query.sha:
|
|
264
|
+
esg.ui.print(
|
|
265
|
+
f"Skipping empty query produced from {path.resolve()}"
|
|
266
|
+
)
|
|
267
|
+
continue
|
|
268
|
+
path_text = Text(str(path).replace("/", "/\n"), style="yellow")
|
|
269
|
+
file_text = Text(path.read_text())
|
|
270
|
+
table.add_row(path_text, file_text, graph)
|
|
271
|
+
full_graph.add(*queries, force=True)
|
|
272
|
+
if print_table:
|
|
273
|
+
esg.ui.print(table)
|
|
274
|
+
if print_graph:
|
|
275
|
+
esg.ui.print(full_graph)
|
|
276
|
+
if out is not None:
|
|
277
|
+
graph_as_yaml = yaml.dump(full_graph.dump())
|
|
278
|
+
try:
|
|
279
|
+
with out.open("w") as f:
|
|
280
|
+
f.write(graph_as_yaml)
|
|
281
|
+
esg.ui.print(f":+1: [green]Graph was written to[/] {out_str}")
|
|
282
|
+
except Exception:
|
|
283
|
+
out.unlink()
|
|
284
|
+
raise
|
|
285
|
+
esg.ui.raise_maybe_record(Exit(0))
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any, TypeAlias, TypeVar
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
from click_params import StringListParamType
|
|
7
|
+
|
|
8
|
+
from esgpull.cli.utils import EnumParam
|
|
9
|
+
from esgpull.models import FileStatus, Option
|
|
10
|
+
from esgpull.tui import Verbosity
|
|
11
|
+
|
|
12
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
13
|
+
Dec: TypeAlias = Callable[[F], F]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def compose(*decs: Dec) -> Dec:
|
|
17
|
+
def deco(fn: Callable):
|
|
18
|
+
for dec in reversed(decs):
|
|
19
|
+
fn = dec(fn)
|
|
20
|
+
return fn
|
|
21
|
+
|
|
22
|
+
return deco
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class args:
|
|
26
|
+
facets: Dec = click.argument(
|
|
27
|
+
"facets",
|
|
28
|
+
type=str,
|
|
29
|
+
nargs=-1,
|
|
30
|
+
)
|
|
31
|
+
key: Dec = click.argument(
|
|
32
|
+
"key",
|
|
33
|
+
type=str,
|
|
34
|
+
nargs=1,
|
|
35
|
+
required=False,
|
|
36
|
+
default=None,
|
|
37
|
+
)
|
|
38
|
+
value: Dec = click.argument(
|
|
39
|
+
"value",
|
|
40
|
+
type=str,
|
|
41
|
+
nargs=1,
|
|
42
|
+
required=False,
|
|
43
|
+
default=None,
|
|
44
|
+
)
|
|
45
|
+
path: Dec = click.argument(
|
|
46
|
+
"path",
|
|
47
|
+
type=click.Path(exists=False, path_type=Path),
|
|
48
|
+
required=False,
|
|
49
|
+
default=None,
|
|
50
|
+
)
|
|
51
|
+
status: Dec = click.argument(
|
|
52
|
+
"status",
|
|
53
|
+
type=EnumParam(FileStatus),
|
|
54
|
+
nargs=-1,
|
|
55
|
+
)
|
|
56
|
+
query_id: Dec = click.argument(
|
|
57
|
+
"query_id",
|
|
58
|
+
type=str,
|
|
59
|
+
nargs=1,
|
|
60
|
+
required=False,
|
|
61
|
+
)
|
|
62
|
+
query_ids: Dec = click.argument(
|
|
63
|
+
"query_ids",
|
|
64
|
+
type=str,
|
|
65
|
+
nargs=-1,
|
|
66
|
+
required=True,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class opts:
|
|
71
|
+
all: Dec = click.option(
|
|
72
|
+
"all_",
|
|
73
|
+
"--all",
|
|
74
|
+
"-a",
|
|
75
|
+
is_flag=True,
|
|
76
|
+
default=False,
|
|
77
|
+
)
|
|
78
|
+
children: Dec = click.option(
|
|
79
|
+
"--children",
|
|
80
|
+
"-c",
|
|
81
|
+
is_flag=True,
|
|
82
|
+
default=False,
|
|
83
|
+
)
|
|
84
|
+
default: Dec = click.option(
|
|
85
|
+
"--default",
|
|
86
|
+
"-d",
|
|
87
|
+
is_flag=True,
|
|
88
|
+
default=False,
|
|
89
|
+
)
|
|
90
|
+
detail: Dec = click.option(
|
|
91
|
+
"--detail",
|
|
92
|
+
type=int,
|
|
93
|
+
default=None,
|
|
94
|
+
)
|
|
95
|
+
disable_ssl: Dec = click.option(
|
|
96
|
+
"--disable-ssl",
|
|
97
|
+
is_flag=True,
|
|
98
|
+
default=False,
|
|
99
|
+
)
|
|
100
|
+
dry_run: Dec = click.option(
|
|
101
|
+
"--dry-run",
|
|
102
|
+
"-z",
|
|
103
|
+
is_flag=True,
|
|
104
|
+
default=False,
|
|
105
|
+
)
|
|
106
|
+
facets_hints: Dec = click.option(
|
|
107
|
+
"facets_hints",
|
|
108
|
+
"--facets",
|
|
109
|
+
"-F",
|
|
110
|
+
is_flag=True,
|
|
111
|
+
default=False,
|
|
112
|
+
)
|
|
113
|
+
file: Dec = click.option(
|
|
114
|
+
"--file",
|
|
115
|
+
"-f",
|
|
116
|
+
is_flag=True,
|
|
117
|
+
default=False,
|
|
118
|
+
)
|
|
119
|
+
files: Dec = click.option(
|
|
120
|
+
"--files",
|
|
121
|
+
is_flag=True,
|
|
122
|
+
default=False,
|
|
123
|
+
)
|
|
124
|
+
force: Dec = click.option(
|
|
125
|
+
"--force",
|
|
126
|
+
is_flag=True,
|
|
127
|
+
default=False,
|
|
128
|
+
)
|
|
129
|
+
generate: Dec = click.option(
|
|
130
|
+
"--generate",
|
|
131
|
+
is_flag=True,
|
|
132
|
+
default=False,
|
|
133
|
+
)
|
|
134
|
+
hints: Dec = click.option(
|
|
135
|
+
"--hints",
|
|
136
|
+
"-H",
|
|
137
|
+
type=StringListParamType(","),
|
|
138
|
+
default=None,
|
|
139
|
+
)
|
|
140
|
+
name: Dec = click.option(
|
|
141
|
+
"--name",
|
|
142
|
+
"-n",
|
|
143
|
+
type=str,
|
|
144
|
+
default=None,
|
|
145
|
+
)
|
|
146
|
+
query_file: Dec = click.option(
|
|
147
|
+
"--query-file",
|
|
148
|
+
"-q",
|
|
149
|
+
type=click.Path(exists=True, dir_okay=False, path_type=Path),
|
|
150
|
+
default=None,
|
|
151
|
+
)
|
|
152
|
+
quiet: Dec = click.option(
|
|
153
|
+
"--quiet",
|
|
154
|
+
is_flag=True,
|
|
155
|
+
default=False,
|
|
156
|
+
)
|
|
157
|
+
record: Dec = click.option(
|
|
158
|
+
"--record",
|
|
159
|
+
is_flag=True,
|
|
160
|
+
default=False,
|
|
161
|
+
)
|
|
162
|
+
reset: Dec = click.option(
|
|
163
|
+
"--reset",
|
|
164
|
+
is_flag=True,
|
|
165
|
+
default=False,
|
|
166
|
+
)
|
|
167
|
+
shas: Dec = click.option(
|
|
168
|
+
"--shas",
|
|
169
|
+
"-s",
|
|
170
|
+
is_flag=True,
|
|
171
|
+
default=False,
|
|
172
|
+
)
|
|
173
|
+
show: Dec = click.option(
|
|
174
|
+
"--show",
|
|
175
|
+
is_flag=True,
|
|
176
|
+
default=False,
|
|
177
|
+
)
|
|
178
|
+
simple: Dec = click.option(
|
|
179
|
+
"--simple",
|
|
180
|
+
is_flag=True,
|
|
181
|
+
default=False,
|
|
182
|
+
)
|
|
183
|
+
since: Dec = click.option(
|
|
184
|
+
"--since",
|
|
185
|
+
type=str,
|
|
186
|
+
default=None,
|
|
187
|
+
)
|
|
188
|
+
status: Dec = click.option(
|
|
189
|
+
"--status",
|
|
190
|
+
type=EnumParam(FileStatus),
|
|
191
|
+
default=None,
|
|
192
|
+
multiple=True,
|
|
193
|
+
)
|
|
194
|
+
tag: Dec = click.option(
|
|
195
|
+
"--tag",
|
|
196
|
+
"-t",
|
|
197
|
+
type=str,
|
|
198
|
+
default=None,
|
|
199
|
+
)
|
|
200
|
+
track: Dec = click.option(
|
|
201
|
+
"--track",
|
|
202
|
+
is_flag=True,
|
|
203
|
+
default=False,
|
|
204
|
+
)
|
|
205
|
+
yes: Dec = click.option(
|
|
206
|
+
"--yes",
|
|
207
|
+
"-y",
|
|
208
|
+
is_flag=True,
|
|
209
|
+
default=False,
|
|
210
|
+
)
|
|
211
|
+
verbosity: Dec = click.option(
|
|
212
|
+
"verbosity",
|
|
213
|
+
"-v",
|
|
214
|
+
count=True,
|
|
215
|
+
type=EnumParam(Verbosity),
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Display group
|
|
220
|
+
_page: Dec = click.option(
|
|
221
|
+
"--page",
|
|
222
|
+
"-p",
|
|
223
|
+
type=int,
|
|
224
|
+
default=0, # 1?
|
|
225
|
+
)
|
|
226
|
+
_zero: Dec = click.option(
|
|
227
|
+
"--zero",
|
|
228
|
+
"-0",
|
|
229
|
+
is_flag=True,
|
|
230
|
+
default=False,
|
|
231
|
+
)
|
|
232
|
+
_all: Dec = click.option(
|
|
233
|
+
"_all",
|
|
234
|
+
"--all",
|
|
235
|
+
"-a",
|
|
236
|
+
is_flag=True,
|
|
237
|
+
default=False,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# Json/Yaml exclusive group
|
|
241
|
+
_json: Dec = click.option(
|
|
242
|
+
"--json",
|
|
243
|
+
is_flag=True,
|
|
244
|
+
default=False,
|
|
245
|
+
)
|
|
246
|
+
_yaml: Dec = click.option(
|
|
247
|
+
"--yaml",
|
|
248
|
+
is_flag=True,
|
|
249
|
+
default=False,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# Query definition group
|
|
253
|
+
OptionChoice = click.Choice([opt.name for opt in list(Option)[:-1]])
|
|
254
|
+
_tag: Dec = click.option("tags", "--tag", "-t", multiple=True)
|
|
255
|
+
_require: Dec = click.option(
|
|
256
|
+
"--require",
|
|
257
|
+
"-r",
|
|
258
|
+
type=str,
|
|
259
|
+
nargs=1,
|
|
260
|
+
required=False,
|
|
261
|
+
default=None,
|
|
262
|
+
)
|
|
263
|
+
_distrib: Dec = click.option(
|
|
264
|
+
"--distrib",
|
|
265
|
+
"-d",
|
|
266
|
+
type=OptionChoice,
|
|
267
|
+
)
|
|
268
|
+
_latest: Dec = click.option(
|
|
269
|
+
"--latest",
|
|
270
|
+
type=OptionChoice,
|
|
271
|
+
)
|
|
272
|
+
_replica: Dec = click.option(
|
|
273
|
+
"--replica",
|
|
274
|
+
type=OptionChoice,
|
|
275
|
+
)
|
|
276
|
+
_retracted: Dec = click.option(
|
|
277
|
+
"--retracted",
|
|
278
|
+
type=OptionChoice,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# Query dates group
|
|
282
|
+
datetime_type = click.DateTime(["%Y-%m-%d"])
|
|
283
|
+
_from: Dec = click.option(
|
|
284
|
+
"date_from",
|
|
285
|
+
"--from",
|
|
286
|
+
type=datetime_type,
|
|
287
|
+
default=None,
|
|
288
|
+
)
|
|
289
|
+
_to: Dec = click.option(
|
|
290
|
+
"date_to",
|
|
291
|
+
"--to",
|
|
292
|
+
type=datetime_type,
|
|
293
|
+
default=None,
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
_children: Dec = click.option(
|
|
297
|
+
"--children",
|
|
298
|
+
"-c",
|
|
299
|
+
is_flag=True,
|
|
300
|
+
default=False,
|
|
301
|
+
)
|
|
302
|
+
_parents: Dec = click.option(
|
|
303
|
+
"--parents",
|
|
304
|
+
"-p",
|
|
305
|
+
is_flag=True,
|
|
306
|
+
default=False,
|
|
307
|
+
)
|
|
308
|
+
_expand: Dec = click.option(
|
|
309
|
+
"--expand",
|
|
310
|
+
"-e",
|
|
311
|
+
is_flag=True,
|
|
312
|
+
default=False,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class groups:
|
|
317
|
+
display: Dec = compose(
|
|
318
|
+
_page,
|
|
319
|
+
_zero,
|
|
320
|
+
_all,
|
|
321
|
+
)
|
|
322
|
+
json_yaml: Dec = compose(
|
|
323
|
+
_json,
|
|
324
|
+
_yaml,
|
|
325
|
+
)
|
|
326
|
+
query_def: Dec = compose(
|
|
327
|
+
_tag,
|
|
328
|
+
_require,
|
|
329
|
+
_distrib,
|
|
330
|
+
_latest,
|
|
331
|
+
_replica,
|
|
332
|
+
_retracted,
|
|
333
|
+
)
|
|
334
|
+
query_date: Dec = compose(
|
|
335
|
+
_from,
|
|
336
|
+
_to,
|
|
337
|
+
)
|
|
338
|
+
show: Dec = compose(
|
|
339
|
+
_children,
|
|
340
|
+
_parents,
|
|
341
|
+
_expand,
|
|
342
|
+
)
|
esgpull/cli/download.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import rich
|
|
6
|
+
from click.exceptions import Abort, Exit
|
|
7
|
+
|
|
8
|
+
if sys.version_info < (3, 11):
|
|
9
|
+
from exceptiongroup import BaseExceptionGroup
|
|
10
|
+
|
|
11
|
+
from esgpull.cli.decorators import args, opts
|
|
12
|
+
from esgpull.cli.utils import get_queries, init_esgpull, valid_name_tag
|
|
13
|
+
from esgpull.models import File, FileStatus
|
|
14
|
+
from esgpull.tui import Verbosity, logger
|
|
15
|
+
from esgpull.utils import format_size
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command()
|
|
19
|
+
@args.query_id
|
|
20
|
+
@opts.tag
|
|
21
|
+
@opts.disable_ssl
|
|
22
|
+
@opts.quiet
|
|
23
|
+
@opts.record
|
|
24
|
+
@opts.verbosity
|
|
25
|
+
def download(
|
|
26
|
+
query_id: str | None,
|
|
27
|
+
tag: str | None,
|
|
28
|
+
disable_ssl: bool,
|
|
29
|
+
quiet: bool,
|
|
30
|
+
record: bool,
|
|
31
|
+
verbosity: Verbosity,
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Asynchronously download files linked to queries
|
|
35
|
+
"""
|
|
36
|
+
esg = init_esgpull(verbosity, record=record)
|
|
37
|
+
if disable_ssl:
|
|
38
|
+
esg.config.download.disable_ssl = True
|
|
39
|
+
with esg.ui.logging("download", onraise=Abort):
|
|
40
|
+
if not valid_name_tag(esg.graph, esg.ui, query_id, tag):
|
|
41
|
+
esg.ui.raise_maybe_record(Exit(1))
|
|
42
|
+
if query_id is None and tag is None:
|
|
43
|
+
esg.graph.load_db()
|
|
44
|
+
graph = esg.graph
|
|
45
|
+
else:
|
|
46
|
+
queries = get_queries(esg.graph, query_id, tag)
|
|
47
|
+
graph = esg.graph.subgraph(
|
|
48
|
+
*queries,
|
|
49
|
+
children=True,
|
|
50
|
+
parents=True,
|
|
51
|
+
)
|
|
52
|
+
esg.ui.print(graph)
|
|
53
|
+
shas: set[str] = set()
|
|
54
|
+
queue: list[File] = []
|
|
55
|
+
for query in graph.queries.values():
|
|
56
|
+
for file in query.files:
|
|
57
|
+
if file.status == FileStatus.Queued and file.sha not in shas:
|
|
58
|
+
shas.add(file.sha)
|
|
59
|
+
queue.append(file)
|
|
60
|
+
if not queue:
|
|
61
|
+
rich.print("Download queue is empty.")
|
|
62
|
+
esg.ui.raise_maybe_record(Exit(0))
|
|
63
|
+
coro = esg.download(queue, show_progress=not quiet)
|
|
64
|
+
files, errors = asyncio.run(coro)
|
|
65
|
+
if files:
|
|
66
|
+
size = format_size(sum(file.size for file in files))
|
|
67
|
+
esg.ui.print(
|
|
68
|
+
f"Downloaded {len(files)} new files for a total size of {size}"
|
|
69
|
+
)
|
|
70
|
+
if errors:
|
|
71
|
+
logger.error(f"{len(errors)} files could not be installed.")
|
|
72
|
+
exc_group = BaseExceptionGroup("Download", [e.err for e in errors])
|
|
73
|
+
esg.ui.raise_maybe_record(exc_group)
|
|
74
|
+
esg.ui.raise_maybe_record(Exit(0))
|