esgpull 0.8.0__py3-none-any.whl → 0.9.1__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/cli/__init__.py +2 -2
- esgpull/cli/add.py +7 -1
- esgpull/cli/config.py +5 -21
- esgpull/cli/plugins.py +398 -0
- esgpull/cli/remove.py +9 -3
- esgpull/cli/self.py +1 -1
- esgpull/cli/update.py +78 -35
- esgpull/cli/utils.py +16 -1
- esgpull/config.py +83 -26
- esgpull/constants.py +3 -0
- esgpull/context.py +9 -9
- esgpull/database.py +21 -7
- esgpull/download.py +3 -0
- esgpull/esgpull.py +49 -5
- esgpull/fs.py +9 -20
- esgpull/graph.py +1 -1
- esgpull/migrations/versions/0.9.0_update_tables.py +28 -0
- esgpull/migrations/versions/0.9.1_update_tables.py +28 -0
- esgpull/migrations/versions/d14f179e553c_file_add_composite_index_dataset_id_.py +32 -0
- esgpull/migrations/versions/e7edab5d4e4b_add_dataset_tracking.py +39 -0
- esgpull/models/__init__.py +2 -1
- esgpull/models/base.py +31 -14
- esgpull/models/dataset.py +48 -5
- esgpull/models/query.py +58 -14
- esgpull/models/sql.py +48 -9
- esgpull/plugin.py +574 -0
- esgpull/processor.py +3 -3
- esgpull/tui.py +23 -1
- esgpull/utils.py +5 -1
- {esgpull-0.8.0.dist-info → esgpull-0.9.1.dist-info}/METADATA +19 -3
- {esgpull-0.8.0.dist-info → esgpull-0.9.1.dist-info}/RECORD +34 -29
- esgpull/cli/datasets.py +0 -78
- {esgpull-0.8.0.dist-info → esgpull-0.9.1.dist-info}/WHEEL +0 -0
- {esgpull-0.8.0.dist-info → esgpull-0.9.1.dist-info}/entry_points.txt +0 -0
- {esgpull-0.8.0.dist-info → esgpull-0.9.1.dist-info}/licenses/LICENSE +0 -0
esgpull/cli/__init__.py
CHANGED
|
@@ -7,9 +7,9 @@ from esgpull import __version__
|
|
|
7
7
|
from esgpull.cli.add import add
|
|
8
8
|
from esgpull.cli.config import config
|
|
9
9
|
from esgpull.cli.convert import convert
|
|
10
|
-
from esgpull.cli.datasets import datasets
|
|
11
10
|
from esgpull.cli.download import download
|
|
12
11
|
from esgpull.cli.login import login
|
|
12
|
+
from esgpull.cli.plugins import plugins
|
|
13
13
|
from esgpull.cli.remove import remove
|
|
14
14
|
from esgpull.cli.retry import retry
|
|
15
15
|
from esgpull.cli.search import search
|
|
@@ -35,13 +35,13 @@ SUBCOMMANDS: list[click.Command] = [
|
|
|
35
35
|
# autoremove,
|
|
36
36
|
config,
|
|
37
37
|
convert,
|
|
38
|
-
datasets,
|
|
39
38
|
download,
|
|
40
39
|
# facet,
|
|
41
40
|
# get,
|
|
42
41
|
self,
|
|
43
42
|
# install,
|
|
44
43
|
login,
|
|
44
|
+
plugins,
|
|
45
45
|
remove,
|
|
46
46
|
retry,
|
|
47
47
|
search,
|
esgpull/cli/add.py
CHANGED
|
@@ -89,6 +89,7 @@ def add(
|
|
|
89
89
|
esg.ui.print(subgraph)
|
|
90
90
|
empty = Query()
|
|
91
91
|
empty.compute_sha()
|
|
92
|
+
next_steps_queries = []
|
|
92
93
|
for query in queries:
|
|
93
94
|
query.compute_sha()
|
|
94
95
|
esg.graph.resolve_require(query)
|
|
@@ -99,7 +100,8 @@ def add(
|
|
|
99
100
|
esg.ui.print(f"Skipping existing query: {query.rich_name}")
|
|
100
101
|
else:
|
|
101
102
|
esg.graph.add(query)
|
|
102
|
-
|
|
103
|
+
if query.tracked:
|
|
104
|
+
next_steps_queries.append(query)
|
|
103
105
|
new_queries = esg.graph.merge()
|
|
104
106
|
nb = len(new_queries)
|
|
105
107
|
ies = "ies" if nb > 1 else "y"
|
|
@@ -107,4 +109,8 @@ def add(
|
|
|
107
109
|
esg.ui.print(f":+1: {nb} new quer{ies} added.")
|
|
108
110
|
else:
|
|
109
111
|
esg.ui.print(":stop_sign: No new query was added.")
|
|
112
|
+
if next_steps_queries:
|
|
113
|
+
esg.ui.print("\nNext steps:\n")
|
|
114
|
+
for query in next_steps_queries:
|
|
115
|
+
esg.ui.print(f"\tesgpull update {query.sha[:6]}")
|
|
110
116
|
esg.ui.raise_maybe_record(Exit(0))
|
esgpull/cli/config.py
CHANGED
|
@@ -4,26 +4,11 @@ import click
|
|
|
4
4
|
from click.exceptions import Abort, BadOptionUsage, Exit
|
|
5
5
|
|
|
6
6
|
from esgpull.cli.decorators import args, opts
|
|
7
|
-
from esgpull.cli.utils import init_esgpull
|
|
7
|
+
from esgpull.cli.utils import extract_subdict, init_esgpull
|
|
8
8
|
from esgpull.config import ConfigKind
|
|
9
9
|
from esgpull.tui import Verbosity
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def extract_command(doc: dict, key: str | None) -> dict:
|
|
13
|
-
if key is None:
|
|
14
|
-
return doc
|
|
15
|
-
for part in key.split("."):
|
|
16
|
-
if not part:
|
|
17
|
-
raise KeyError(key)
|
|
18
|
-
elif part in doc:
|
|
19
|
-
doc = doc[part]
|
|
20
|
-
else:
|
|
21
|
-
raise KeyError(part)
|
|
22
|
-
for part in key.split(".")[::-1]:
|
|
23
|
-
doc = {part: doc}
|
|
24
|
-
return doc
|
|
25
|
-
|
|
26
|
-
|
|
27
12
|
@click.command()
|
|
28
13
|
@args.key
|
|
29
14
|
@args.value
|
|
@@ -73,7 +58,7 @@ def config(
|
|
|
73
58
|
)
|
|
74
59
|
kind = esg.config.kind
|
|
75
60
|
old_value = esg.config.update_item(key, value, empty_ok=True)
|
|
76
|
-
info =
|
|
61
|
+
info = extract_subdict(esg.config.dump(), key)
|
|
77
62
|
esg.config.write()
|
|
78
63
|
esg.ui.print(info, toml=True)
|
|
79
64
|
if kind == ConfigKind.NoFile:
|
|
@@ -86,12 +71,12 @@ def config(
|
|
|
86
71
|
elif key is not None:
|
|
87
72
|
if default:
|
|
88
73
|
old_value = esg.config.set_default(key)
|
|
89
|
-
info =
|
|
74
|
+
info = extract_subdict(esg.config.dump(), key)
|
|
90
75
|
esg.config.write()
|
|
91
76
|
esg.ui.print(info, toml=True)
|
|
92
77
|
esg.ui.print(f"Previous value: {old_value}")
|
|
93
78
|
else:
|
|
94
|
-
info =
|
|
79
|
+
info = extract_subdict(esg.config.dump(), key)
|
|
95
80
|
esg.ui.print(info, toml=True)
|
|
96
81
|
elif generate:
|
|
97
82
|
overwrite = False
|
|
@@ -102,8 +87,7 @@ def config(
|
|
|
102
87
|
)
|
|
103
88
|
esg.ui.raise_maybe_record(Exit(0))
|
|
104
89
|
elif esg.config.kind == ConfigKind.Partial and esg.ui.ask(
|
|
105
|
-
"A config file already exists,"
|
|
106
|
-
" fill it with missing defaults?",
|
|
90
|
+
"A config file already exists, fill it with missing defaults?",
|
|
107
91
|
default=False,
|
|
108
92
|
):
|
|
109
93
|
overwrite = True
|
esgpull/cli/plugins.py
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
from collections import OrderedDict
|
|
2
|
+
from textwrap import dedent
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
from click.exceptions import Abort, BadOptionUsage
|
|
6
|
+
|
|
7
|
+
from esgpull import Esgpull
|
|
8
|
+
from esgpull.cli.decorators import args, opts
|
|
9
|
+
from esgpull.cli.utils import extract_subdict, init_esgpull, totable
|
|
10
|
+
from esgpull.models import Dataset, File, FileStatus
|
|
11
|
+
from esgpull.plugin import Event, emit
|
|
12
|
+
from esgpull.tui import Verbosity
|
|
13
|
+
from esgpull.version import __version__
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@click.group()
|
|
17
|
+
def plugins():
|
|
18
|
+
"""Manage plugins."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_enabled(esg: Esgpull):
|
|
23
|
+
if not esg.plugin_manager.enabled:
|
|
24
|
+
esg.ui.print("Plugin system is [red]disabled[/]")
|
|
25
|
+
esg.ui.print("To enable it, run:")
|
|
26
|
+
esg.ui.print(" esgpull config plugins.enabled true")
|
|
27
|
+
raise Abort
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@plugins.command("ls")
|
|
31
|
+
@click.option(
|
|
32
|
+
"--json", "json_output", is_flag=True, help="Output in JSON format"
|
|
33
|
+
)
|
|
34
|
+
@opts.verbosity
|
|
35
|
+
def list_plugins(verbosity: Verbosity, json_output: bool = False):
|
|
36
|
+
"""List all available plugins and their status."""
|
|
37
|
+
esg = init_esgpull(verbosity=verbosity)
|
|
38
|
+
|
|
39
|
+
with esg.ui.logging("plugins", onraise=Abort):
|
|
40
|
+
check_enabled(esg)
|
|
41
|
+
esg.plugin_manager.discover_plugins(
|
|
42
|
+
esg.config.paths.plugins, load_all=True
|
|
43
|
+
)
|
|
44
|
+
if json_output:
|
|
45
|
+
# Format for JSON output
|
|
46
|
+
result = {}
|
|
47
|
+
for name, p in esg.plugin_manager.plugins.items():
|
|
48
|
+
# Get handlers by event type
|
|
49
|
+
handlers = {}
|
|
50
|
+
for h in p.handlers:
|
|
51
|
+
event_type = h.event.value
|
|
52
|
+
if event_type not in handlers:
|
|
53
|
+
handlers[event_type] = []
|
|
54
|
+
handlers[event_type].append(
|
|
55
|
+
{
|
|
56
|
+
"function": h.func.__name__,
|
|
57
|
+
"priority": h.priority,
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
result[name] = {
|
|
62
|
+
"enabled": esg.plugin_manager.is_plugin_enabled(name),
|
|
63
|
+
"handlers": handlers,
|
|
64
|
+
"min_version": p.min_version,
|
|
65
|
+
"max_version": p.max_version,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
esg.ui.print(result, json=True)
|
|
69
|
+
else:
|
|
70
|
+
# Format for human-readable output
|
|
71
|
+
if not esg.plugin_manager.plugins:
|
|
72
|
+
esg.ui.print("No plugins found.")
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
# Prepare table data
|
|
76
|
+
table_data = []
|
|
77
|
+
for name, p in esg.plugin_manager.plugins.items():
|
|
78
|
+
enabled = esg.plugin_manager.is_plugin_enabled(name)
|
|
79
|
+
status_circle = "🟢" if enabled else "🔴"
|
|
80
|
+
plugin_name = f"{status_circle} {name}"
|
|
81
|
+
|
|
82
|
+
# Collect event handlers with their details
|
|
83
|
+
handler_rows = []
|
|
84
|
+
events_by_type = {}
|
|
85
|
+
for h in p.handlers:
|
|
86
|
+
event_type = h.event.value
|
|
87
|
+
if event_type not in events_by_type:
|
|
88
|
+
events_by_type[event_type] = []
|
|
89
|
+
events_by_type[event_type].append(h.func.__name__)
|
|
90
|
+
|
|
91
|
+
# Create handler rows with event type and function names
|
|
92
|
+
for event_type, handler_names in events_by_type.items():
|
|
93
|
+
for handler_name in handler_names:
|
|
94
|
+
handler_rows.append(
|
|
95
|
+
{"event": event_type, "function": handler_name}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# First row with plugin info and first handler
|
|
99
|
+
if handler_rows:
|
|
100
|
+
first_handler = handler_rows[0]
|
|
101
|
+
first_row = OrderedDict(
|
|
102
|
+
[
|
|
103
|
+
("plugin", plugin_name),
|
|
104
|
+
("event", first_handler["event"]),
|
|
105
|
+
("function", first_handler["function"]),
|
|
106
|
+
]
|
|
107
|
+
)
|
|
108
|
+
table_data.append(first_row)
|
|
109
|
+
|
|
110
|
+
# Additional rows for remaining handlers
|
|
111
|
+
for handler in handler_rows[1:]:
|
|
112
|
+
additional_row = OrderedDict(
|
|
113
|
+
[
|
|
114
|
+
("plugin", ""),
|
|
115
|
+
("event", handler["event"]),
|
|
116
|
+
("function", handler["function"]),
|
|
117
|
+
]
|
|
118
|
+
)
|
|
119
|
+
table_data.append(additional_row)
|
|
120
|
+
else:
|
|
121
|
+
# Plugin with no handlers
|
|
122
|
+
row = OrderedDict(
|
|
123
|
+
[
|
|
124
|
+
("plugin", plugin_name),
|
|
125
|
+
("event", ""),
|
|
126
|
+
("function", ""),
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
table_data.append(row)
|
|
130
|
+
|
|
131
|
+
# Create and print table
|
|
132
|
+
table = totable(table_data)
|
|
133
|
+
esg.ui.print(table)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@plugins.command("enable")
|
|
137
|
+
@click.argument("plugin_name")
|
|
138
|
+
@opts.verbosity
|
|
139
|
+
def enable_plugin_cmd(plugin_name: str, verbosity: Verbosity):
|
|
140
|
+
"""Enable a plugin."""
|
|
141
|
+
esg = init_esgpull(verbosity=verbosity)
|
|
142
|
+
plugin_path = esg.config.paths.plugins / f"{plugin_name}.py"
|
|
143
|
+
if not plugin_path.exists():
|
|
144
|
+
esg.ui.print(f"Plugin file '{plugin_name}.py' not found.")
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
result = esg.plugin_manager.enable_plugin(plugin_name)
|
|
148
|
+
if result:
|
|
149
|
+
esg.ui.print(f"Plugin '{plugin_name}' enabled.")
|
|
150
|
+
else:
|
|
151
|
+
esg.ui.print(f"Failed to enable plugin '{plugin_name}'.")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@plugins.command("disable")
|
|
155
|
+
@click.argument("plugin_name")
|
|
156
|
+
@opts.verbosity
|
|
157
|
+
def disable_plugin_cmd(plugin_name: str, verbosity: Verbosity):
|
|
158
|
+
"""Disable a plugin."""
|
|
159
|
+
esg = init_esgpull(verbosity=verbosity)
|
|
160
|
+
|
|
161
|
+
# Check if the plugin file exists (even if not loaded)
|
|
162
|
+
plugin_path = esg.config.paths.plugins / f"{plugin_name}.py"
|
|
163
|
+
if not plugin_path.exists():
|
|
164
|
+
esg.ui.print(f"Plugin file '{plugin_name}.py' not found.")
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
result = esg.plugin_manager.disable_plugin(plugin_name)
|
|
168
|
+
if result:
|
|
169
|
+
esg.ui.print(f"Plugin '{plugin_name}' disabled.")
|
|
170
|
+
else:
|
|
171
|
+
esg.ui.print(f"Failed to disable plugin '{plugin_name}'.")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@plugins.command("config")
|
|
175
|
+
@args.key
|
|
176
|
+
@args.value
|
|
177
|
+
@opts.default
|
|
178
|
+
@opts.generate
|
|
179
|
+
@opts.verbosity
|
|
180
|
+
def config_plugin(
|
|
181
|
+
verbosity: Verbosity,
|
|
182
|
+
key: str | None,
|
|
183
|
+
value: str | None,
|
|
184
|
+
default: bool,
|
|
185
|
+
generate: bool,
|
|
186
|
+
# config_set: str | None = None,
|
|
187
|
+
# config_unset: str | None = None,
|
|
188
|
+
):
|
|
189
|
+
"""View or modify plugin configuration."""
|
|
190
|
+
esg = init_esgpull(verbosity=verbosity)
|
|
191
|
+
|
|
192
|
+
with esg.ui.logging("plugins", onraise=Abort):
|
|
193
|
+
check_enabled(esg)
|
|
194
|
+
esg.plugin_manager.discover_plugins(
|
|
195
|
+
esg.config.paths.plugins, load_all=True
|
|
196
|
+
)
|
|
197
|
+
if key is not None:
|
|
198
|
+
plugin_name = key.split(".", 1)[0]
|
|
199
|
+
plugin_path = esg.config.paths.plugins / f"{plugin_name}.py"
|
|
200
|
+
if not plugin_path.exists():
|
|
201
|
+
esg.ui.print(f"Plugin file '{plugin_name}.py' not found.")
|
|
202
|
+
raise Abort
|
|
203
|
+
if key is not None and value is not None:
|
|
204
|
+
"""update config"""
|
|
205
|
+
if default:
|
|
206
|
+
raise BadOptionUsage(
|
|
207
|
+
"default",
|
|
208
|
+
dedent(
|
|
209
|
+
f"""
|
|
210
|
+
--default/-d is invalid with a value.
|
|
211
|
+
Instead use:
|
|
212
|
+
|
|
213
|
+
$ esgpull plugins config {key} -d
|
|
214
|
+
"""
|
|
215
|
+
),
|
|
216
|
+
)
|
|
217
|
+
# Split key into plugin_name and remaining_path
|
|
218
|
+
plugin_name, *rest = key.split(".", 1)
|
|
219
|
+
remaining_path = rest[0] if rest else None
|
|
220
|
+
|
|
221
|
+
# Set the configuration value
|
|
222
|
+
if remaining_path:
|
|
223
|
+
old_value = esg.plugin_manager.set_plugin_config(
|
|
224
|
+
plugin_name, remaining_path, value
|
|
225
|
+
)
|
|
226
|
+
info = extract_subdict(esg.plugin_manager.config.plugins, key)
|
|
227
|
+
esg.ui.print(info, toml=True)
|
|
228
|
+
esg.ui.print(f"Previous value: {old_value}")
|
|
229
|
+
else:
|
|
230
|
+
esg.ui.print(f"Cannot set plugin name directly: {plugin_name}")
|
|
231
|
+
raise Abort
|
|
232
|
+
elif key is not None:
|
|
233
|
+
if default:
|
|
234
|
+
# Split key into plugin_name and remaining_path
|
|
235
|
+
plugin_name, *rest = key.split(".", 1)
|
|
236
|
+
remaining_path = rest[0] if rest else None
|
|
237
|
+
|
|
238
|
+
if remaining_path:
|
|
239
|
+
esg.plugin_manager.unset_plugin_config(
|
|
240
|
+
plugin_name, remaining_path
|
|
241
|
+
)
|
|
242
|
+
msg = f":+1: Config reset to default for {key}"
|
|
243
|
+
esg.ui.print(msg)
|
|
244
|
+
else:
|
|
245
|
+
esg.ui.print(
|
|
246
|
+
f"Cannot reset plugin directly: {plugin_name}"
|
|
247
|
+
)
|
|
248
|
+
raise Abort
|
|
249
|
+
else:
|
|
250
|
+
config = esg.plugin_manager.config.plugins
|
|
251
|
+
config = extract_subdict(config, key)
|
|
252
|
+
esg.ui.print(config, toml=True)
|
|
253
|
+
elif generate:
|
|
254
|
+
# Write the config file with all current settings
|
|
255
|
+
esg.plugin_manager.write_config(generate_full_config=True)
|
|
256
|
+
msg = f":+1: Plugin config generated at {esg.plugin_manager.config_path}"
|
|
257
|
+
esg.ui.print(msg)
|
|
258
|
+
|
|
259
|
+
else:
|
|
260
|
+
esg.ui.rule(str(esg.plugin_manager.config_path))
|
|
261
|
+
esg.ui.print(esg.plugin_manager.config.plugins, toml=True)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@plugins.command("test")
|
|
265
|
+
@click.argument(
|
|
266
|
+
"event_type",
|
|
267
|
+
type=click.Choice([e.value for e in Event]),
|
|
268
|
+
)
|
|
269
|
+
@opts.verbosity
|
|
270
|
+
def test_plugin(
|
|
271
|
+
event_type: str,
|
|
272
|
+
verbosity: Verbosity,
|
|
273
|
+
):
|
|
274
|
+
"""Test plugin events."""
|
|
275
|
+
esg = init_esgpull(verbosity=verbosity)
|
|
276
|
+
with esg.ui.logging("plugins", onraise=Abort):
|
|
277
|
+
check_enabled(esg)
|
|
278
|
+
event = Event(event_type)
|
|
279
|
+
# Create sample test data
|
|
280
|
+
file1 = File(
|
|
281
|
+
file_id="file1",
|
|
282
|
+
dataset_id="dataset",
|
|
283
|
+
master_id="master",
|
|
284
|
+
url="file",
|
|
285
|
+
version="v0",
|
|
286
|
+
filename="file.nc",
|
|
287
|
+
local_path="project/folder",
|
|
288
|
+
data_node="data_node",
|
|
289
|
+
checksum="0",
|
|
290
|
+
checksum_type="0",
|
|
291
|
+
size=2**42,
|
|
292
|
+
status=FileStatus.Queued,
|
|
293
|
+
)
|
|
294
|
+
file2 = file1.clone()
|
|
295
|
+
file2.file_id = "file2"
|
|
296
|
+
file2.status = FileStatus.Done
|
|
297
|
+
error = ValueError("Placeholder example error")
|
|
298
|
+
dataset = Dataset(dataset_id="dataset", total_files=2)
|
|
299
|
+
dataset.files = [file1, file2]
|
|
300
|
+
emit(
|
|
301
|
+
event,
|
|
302
|
+
file=file1,
|
|
303
|
+
dataset=dataset,
|
|
304
|
+
error=error,
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@plugins.command("create")
|
|
309
|
+
@click.argument(
|
|
310
|
+
"events",
|
|
311
|
+
nargs=-1,
|
|
312
|
+
type=click.Choice([e.value for e in Event]),
|
|
313
|
+
)
|
|
314
|
+
@click.option("-n", "--name", required=True, type=str)
|
|
315
|
+
@opts.verbosity
|
|
316
|
+
def create_plugin(
|
|
317
|
+
name: str, verbosity: Verbosity, events: list[str] | None = None
|
|
318
|
+
):
|
|
319
|
+
"""Create a new plugin template."""
|
|
320
|
+
esg = init_esgpull(verbosity=verbosity)
|
|
321
|
+
|
|
322
|
+
# Create plugin file path
|
|
323
|
+
plugin_path = esg.config.paths.plugins / f"{name}.py"
|
|
324
|
+
if plugin_path.exists():
|
|
325
|
+
esg.ui.print(f"Plugin file already exists: {plugin_path}")
|
|
326
|
+
return
|
|
327
|
+
|
|
328
|
+
# If no events specified, include them all
|
|
329
|
+
if not events:
|
|
330
|
+
events = [e.value for e in Event]
|
|
331
|
+
|
|
332
|
+
# Generate template
|
|
333
|
+
template = f"""# {name} plugin for ESGPull
|
|
334
|
+
#
|
|
335
|
+
# This plugin was auto-generated. Edit as needed.
|
|
336
|
+
from datetime import datetime
|
|
337
|
+
from pathlib import Path
|
|
338
|
+
from logging import Logger
|
|
339
|
+
|
|
340
|
+
from esgpull.models import File, Query
|
|
341
|
+
from esgpull.plugin import Event, on
|
|
342
|
+
|
|
343
|
+
# Specify version compatibility (optional)
|
|
344
|
+
MIN_ESGPULL_VERSION = "{__version__}"
|
|
345
|
+
MAX_ESGPULL_VERSION = None
|
|
346
|
+
|
|
347
|
+
# Configuration class for the plugin (optional)
|
|
348
|
+
class Config:
|
|
349
|
+
\"""Configuration for {name} plugin\"""
|
|
350
|
+
# Add your configuration options here
|
|
351
|
+
timeout = 30 # Example config option
|
|
352
|
+
log_level = "INFO" # Example config option
|
|
353
|
+
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
# Add handlers for requested events
|
|
357
|
+
handlers = {
|
|
358
|
+
"file_complete": """
|
|
359
|
+
# File complete event handler
|
|
360
|
+
@on(Event.file_complete, priority="normal")
|
|
361
|
+
def handle_file_complete(
|
|
362
|
+
file: File,
|
|
363
|
+
destination: Path,
|
|
364
|
+
start_time: datetime,
|
|
365
|
+
end_time: datetime,
|
|
366
|
+
logger: Logger
|
|
367
|
+
):
|
|
368
|
+
\"""Handle file complete event\"""
|
|
369
|
+
logger.info(f"File downloaded: {file.filename}")
|
|
370
|
+
# Add your custom logic here
|
|
371
|
+
""",
|
|
372
|
+
"file_error": """
|
|
373
|
+
# Download error event handler
|
|
374
|
+
@on(Event.file_error, priority="normal")
|
|
375
|
+
def handle_file_error(file: File, exception: Exception, logger: Logger):
|
|
376
|
+
\"""Handle file error event\"""
|
|
377
|
+
logger.error(f"Download failed for {file.filename}: {exception}")
|
|
378
|
+
# Add your custom logic here
|
|
379
|
+
""",
|
|
380
|
+
"dataset_complete": """
|
|
381
|
+
# Dataset complete event handler
|
|
382
|
+
@on(Event.dataset_complete, priority="normal")
|
|
383
|
+
def handle_dataset_complete(dataset: Dataset, logger: Logger):
|
|
384
|
+
\"""Handle dataset complete event\"""
|
|
385
|
+
logger.error(f"Dataset downloaded: {dataset.dataset_id}")
|
|
386
|
+
# Add your custom logic here
|
|
387
|
+
""",
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
for event in events:
|
|
391
|
+
template += handlers[event]
|
|
392
|
+
|
|
393
|
+
# Write the plugin file
|
|
394
|
+
with open(plugin_path, "w") as f:
|
|
395
|
+
f.write(template)
|
|
396
|
+
|
|
397
|
+
esg.ui.print(f"Plugin template created at: {plugin_path}")
|
|
398
|
+
esg.ui.print("Edit the file to implement your custom plugin logic.")
|
esgpull/cli/remove.py
CHANGED
|
@@ -15,11 +15,13 @@ from esgpull.utils import format_size
|
|
|
15
15
|
@args.query_id
|
|
16
16
|
@opts.tag
|
|
17
17
|
@opts.children
|
|
18
|
+
@opts.yes
|
|
18
19
|
@opts.verbosity
|
|
19
20
|
def remove(
|
|
20
21
|
query_id: str | None,
|
|
21
22
|
tag: str | None,
|
|
22
23
|
children: bool,
|
|
24
|
+
yes: bool,
|
|
23
25
|
verbosity: Verbosity,
|
|
24
26
|
) -> None:
|
|
25
27
|
"""
|
|
@@ -49,7 +51,7 @@ def remove(
|
|
|
49
51
|
graph.add(*queries)
|
|
50
52
|
esg.ui.print(graph)
|
|
51
53
|
msg = f"Remove {nb} quer{ies}?"
|
|
52
|
-
if not esg.ui.ask(msg, default=True):
|
|
54
|
+
if not yes and not esg.ui.ask(msg, default=True):
|
|
53
55
|
raise Abort
|
|
54
56
|
for query in queries:
|
|
55
57
|
if query.has_files:
|
|
@@ -59,14 +61,18 @@ def remove(
|
|
|
59
61
|
f":stop_sign: {query.rich_name} is linked"
|
|
60
62
|
f" to {nb} downloaded files ({format_size(size)})."
|
|
61
63
|
)
|
|
62
|
-
if not esg.ui.ask(
|
|
64
|
+
if not yes and not esg.ui.ask(
|
|
65
|
+
"Delete anyway?", default=False
|
|
66
|
+
):
|
|
63
67
|
raise Abort
|
|
64
68
|
if not children and esg.graph.get_children(query.sha):
|
|
65
69
|
esg.ui.print(
|
|
66
70
|
":stop_sign: Some queries block"
|
|
67
71
|
f" removal of {query.rich_name}."
|
|
68
72
|
)
|
|
69
|
-
if esg.ui.ask(
|
|
73
|
+
if not yes and esg.ui.ask(
|
|
74
|
+
"Show blocking queries?", default=False
|
|
75
|
+
):
|
|
70
76
|
esg.ui.print(esg.graph.subgraph(query, children=True))
|
|
71
77
|
raise Exit(1)
|
|
72
78
|
esg.db.delete(*queries)
|
esgpull/cli/self.py
CHANGED
|
@@ -199,7 +199,7 @@ def delete():
|
|
|
199
199
|
TempUI.print(f"Deleting {path} from config...")
|
|
200
200
|
TempUI.print("To remove all files from this install, run:\n")
|
|
201
201
|
config = Config.load(path=path)
|
|
202
|
-
for p in config.paths:
|
|
202
|
+
for p in config.paths.values():
|
|
203
203
|
if not p.is_relative_to(path):
|
|
204
204
|
TempUI.print(f"$ rm -rf {p}")
|
|
205
205
|
TempUI.print(f"$ rm -rf {path}")
|