threedi-cmd 0.3.0__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.
- threedi_cmd/__init__.py +0 -0
- threedi_cmd/commands/__init__.py +0 -0
- threedi_cmd/commands/active_simulations.py +44 -0
- threedi_cmd/commands/api.py +384 -0
- threedi_cmd/commands/app_definitions.py +34 -0
- threedi_cmd/commands/callbacks.py +20 -0
- threedi_cmd/commands/main.py +23 -0
- threedi_cmd/commands/models.py +7 -0
- threedi_cmd/commands/scenarios.py +55 -0
- threedi_cmd/commands/settings.py +293 -0
- threedi_cmd/commands/slack_notify.py +16 -0
- threedi_cmd/commands/suite.py +372 -0
- threedi_cmd/commands/utils.py +77 -0
- threedi_cmd/console.py +13 -0
- threedi_cmd/errors.py +23 -0
- threedi_cmd/example.py +39 -0
- threedi_cmd/jinja2_time.py +64 -0
- threedi_cmd/logger.py +57 -0
- threedi_cmd/models/__init__.py +50 -0
- threedi_cmd/models/actions.py +109 -0
- threedi_cmd/models/base.py +443 -0
- threedi_cmd/models/boundary_conditions.py +50 -0
- threedi_cmd/models/breach.py +12 -0
- threedi_cmd/models/errors.py +2 -0
- threedi_cmd/models/initial_waterlevels.py +119 -0
- threedi_cmd/models/lateral.py +104 -0
- threedi_cmd/models/leakage.py +103 -0
- threedi_cmd/models/monitor.py +268 -0
- threedi_cmd/models/postprocessing.py +32 -0
- threedi_cmd/models/rain.py +157 -0
- threedi_cmd/models/rasteredit.py +43 -0
- threedi_cmd/models/revision.py +126 -0
- threedi_cmd/models/savedstate.py +18 -0
- threedi_cmd/models/scenario.py +374 -0
- threedi_cmd/models/settings.py +40 -0
- threedi_cmd/models/simulation.py +9 -0
- threedi_cmd/models/sources_sinks.py +121 -0
- threedi_cmd/models/structure_control.py +135 -0
- threedi_cmd/models/substance.py +45 -0
- threedi_cmd/models/waitfor.py +228 -0
- threedi_cmd/models/wind.py +32 -0
- threedi_cmd/parser.py +113 -0
- threedi_cmd/plugins/__init__.py +0 -0
- threedi_cmd/plugins/models.py +17 -0
- threedi_cmd/plugins/tools.py +20 -0
- threedi_cmd/schematisation_uploader/__init__.py +0 -0
- threedi_cmd/schematisation_uploader/db.py +112 -0
- threedi_cmd/schematisation_uploader/example.py +16 -0
- threedi_cmd/schematisation_uploader/main.py +203 -0
- threedi_cmd/schematisation_uploader/yaml_converter.py +157 -0
- threedi_cmd/test.py +45 -0
- threedi_cmd/version.py +1 -0
- threedi_cmd/websockets/__init__.py +0 -0
- threedi_cmd/websockets/clients.py +54 -0
- threedi_cmd/websockets/settings.py +17 -0
- threedi_cmd-0.3.0.dist-info/AUTHORS.rst +15 -0
- threedi_cmd-0.3.0.dist-info/LICENSE +22 -0
- threedi_cmd-0.3.0.dist-info/METADATA +245 -0
- threedi_cmd-0.3.0.dist-info/RECORD +62 -0
- threedi_cmd-0.3.0.dist-info/WHEEL +5 -0
- threedi_cmd-0.3.0.dist-info/entry_points.txt +6 -0
- threedi_cmd-0.3.0.dist-info/top_level.txt +1 -0
threedi_cmd/__init__.py
ADDED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from threedi_cmd.commands.callbacks import default_callback
|
|
7
|
+
from threedi_cmd.console import console
|
|
8
|
+
from threedi_cmd.models.monitor import ActiveSimulations
|
|
9
|
+
|
|
10
|
+
active_sims_app = typer.Typer(callback=default_callback)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def monitor_active_simulations(endpoint: str) -> None:
|
|
14
|
+
"""
|
|
15
|
+
executes ActiveSimlations "run_monitor" task in the background
|
|
16
|
+
"""
|
|
17
|
+
active_simulations = ActiveSimulations(endpoint)
|
|
18
|
+
result = await asyncio.gather(
|
|
19
|
+
active_simulations.run_monitor(), return_exceptions=True
|
|
20
|
+
)
|
|
21
|
+
if result:
|
|
22
|
+
console.print(result, style="error")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@active_sims_app.command()
|
|
26
|
+
def simulations(ctx: typer.Context):
|
|
27
|
+
"""
|
|
28
|
+
Show currently running simulations
|
|
29
|
+
"""
|
|
30
|
+
start_time = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
31
|
+
try:
|
|
32
|
+
console.print(f"[{start_time}] Starting active simulations worker")
|
|
33
|
+
asyncio.run(monitor_active_simulations(ctx.obj._endpoint.name))
|
|
34
|
+
except KeyboardInterrupt:
|
|
35
|
+
pass
|
|
36
|
+
finally:
|
|
37
|
+
console.print(
|
|
38
|
+
":sparkles: Bye bye, hope to see you soon! :sparkles:",
|
|
39
|
+
style="success",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if __name__ == "__main__":
|
|
44
|
+
active_sims_app()
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import typer
|
|
8
|
+
import websockets
|
|
9
|
+
from rich import box
|
|
10
|
+
from rich.padding import Padding
|
|
11
|
+
from rich.panel import Panel
|
|
12
|
+
from rich.prompt import Confirm, IntPrompt, Prompt
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from threedi_api_client.api import Configuration
|
|
15
|
+
from threedi_api_client.openapi.api.v3_api import V3Api
|
|
16
|
+
from threedi_api_client.openapi.exceptions import ApiException
|
|
17
|
+
|
|
18
|
+
from threedi_cmd.commands.callbacks import default_callback
|
|
19
|
+
from threedi_cmd.commands.scenarios import scenarios
|
|
20
|
+
from threedi_cmd.commands.utils import PathPrompt, download_files
|
|
21
|
+
from threedi_cmd.console import console
|
|
22
|
+
from threedi_cmd.errors import ExitCodes, LoadScenarioError
|
|
23
|
+
from threedi_cmd.logger import get_logger
|
|
24
|
+
from threedi_cmd.models.errors import ApiModelError
|
|
25
|
+
from threedi_cmd.models.scenario import FailedStep, ResolveError
|
|
26
|
+
from threedi_cmd.parser import ScenarioParser
|
|
27
|
+
|
|
28
|
+
logger = get_logger("INFO")
|
|
29
|
+
|
|
30
|
+
DEFAULT_PAGER_SIZE = 20
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
app = typer.Typer()
|
|
34
|
+
api_app = typer.Typer(callback=default_callback)
|
|
35
|
+
app.add_typer(api_app, name="api")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@api_app.command()
|
|
39
|
+
def models(ctx: typer.Context):
|
|
40
|
+
"""List available threedimodels"""
|
|
41
|
+
threedi_models_api: V3Api = ctx.obj.api_client
|
|
42
|
+
threedi_models = threedi_models_api.threedimodels_list()
|
|
43
|
+
|
|
44
|
+
table = Table(
|
|
45
|
+
show_header=True,
|
|
46
|
+
box=box.HORIZONTALS,
|
|
47
|
+
show_lines=False,
|
|
48
|
+
width=(console.width * 80) / 100,
|
|
49
|
+
)
|
|
50
|
+
table.add_column("Id", width=5)
|
|
51
|
+
table.add_column(
|
|
52
|
+
"Name",
|
|
53
|
+
width=20,
|
|
54
|
+
justify="left",
|
|
55
|
+
style="bold cyan",
|
|
56
|
+
header_style="bold cyan",
|
|
57
|
+
)
|
|
58
|
+
table.add_column("Revision", justify="left")
|
|
59
|
+
table.add_column("Inp success", justify="left")
|
|
60
|
+
|
|
61
|
+
remaining = threedi_models.count
|
|
62
|
+
limit = min(DEFAULT_PAGER_SIZE, remaining)
|
|
63
|
+
|
|
64
|
+
while remaining == threedi_models.count or (
|
|
65
|
+
remaining > 0 and Confirm.ask("Show more?")
|
|
66
|
+
):
|
|
67
|
+
threedi_models = threedi_models_api.threedimodels_list(
|
|
68
|
+
limit=limit, offset=remaining - limit
|
|
69
|
+
)
|
|
70
|
+
for i, model in enumerate(threedi_models.results):
|
|
71
|
+
if model.inp_success is True:
|
|
72
|
+
txt = f":heavy_check_mark: [bold green]{model.inp_success}[/bold green] "
|
|
73
|
+
else:
|
|
74
|
+
txt = f"[bold red]{model.inp_success}[/bold red]"
|
|
75
|
+
table.add_row(f"{model.id}", f"{model.name}", f"{model.revision_hash}", txt)
|
|
76
|
+
remaining -= limit
|
|
77
|
+
limit = min(remaining, DEFAULT_PAGER_SIZE)
|
|
78
|
+
console.print(table)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@api_app.command()
|
|
82
|
+
def organisations(ctx: typer.Context):
|
|
83
|
+
"""List available organisations"""
|
|
84
|
+
organisations_api: V3Api = ctx.obj.api_client
|
|
85
|
+
organisations = organisations_api.organisations_list()
|
|
86
|
+
table = Table(
|
|
87
|
+
show_header=True,
|
|
88
|
+
box=box.HORIZONTALS,
|
|
89
|
+
show_lines=False,
|
|
90
|
+
width=(console.width * 80) / 100,
|
|
91
|
+
)
|
|
92
|
+
table.add_column("Unique Id", width=15)
|
|
93
|
+
table.add_column(
|
|
94
|
+
"Name",
|
|
95
|
+
width=20,
|
|
96
|
+
justify="left",
|
|
97
|
+
style="bold cyan",
|
|
98
|
+
header_style="bold cyan",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
remaining = organisations.count
|
|
102
|
+
limit = min(DEFAULT_PAGER_SIZE, remaining)
|
|
103
|
+
|
|
104
|
+
while remaining == organisations.count or (
|
|
105
|
+
remaining > 0 and Confirm.ask("Show more?")
|
|
106
|
+
):
|
|
107
|
+
organisations = organisations_api.organisations_list(
|
|
108
|
+
limit=limit, offset=remaining - limit
|
|
109
|
+
)
|
|
110
|
+
for i, org in enumerate(organisations.results):
|
|
111
|
+
table.add_row(f"{org.unique_id}", f"{org.name}")
|
|
112
|
+
remaining -= limit
|
|
113
|
+
limit = min(remaining, DEFAULT_PAGER_SIZE)
|
|
114
|
+
console.print(table)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@api_app.command()
|
|
118
|
+
def simulations(
|
|
119
|
+
ctx: typer.Context,
|
|
120
|
+
queued: bool = typer.Option(
|
|
121
|
+
False,
|
|
122
|
+
"--queued",
|
|
123
|
+
help="Show the current queue",
|
|
124
|
+
),
|
|
125
|
+
):
|
|
126
|
+
"""List simulations"""
|
|
127
|
+
simulations_api: V3Api = ctx.obj.api_client
|
|
128
|
+
simulations = simulations_api.simulations_list()
|
|
129
|
+
|
|
130
|
+
table = Table(
|
|
131
|
+
show_header=True,
|
|
132
|
+
box=box.HORIZONTALS,
|
|
133
|
+
show_lines=False,
|
|
134
|
+
width=(console.width * 80) / 100,
|
|
135
|
+
)
|
|
136
|
+
table.add_column(
|
|
137
|
+
"Id", width=5
|
|
138
|
+
) # , style="dark_green", header_style="bold dark_green")
|
|
139
|
+
table.add_column("Name", width=20, justify="left")
|
|
140
|
+
table.add_column("Status", justify="left")
|
|
141
|
+
|
|
142
|
+
remaining = simulations.count
|
|
143
|
+
limit = DEFAULT_PAGER_SIZE
|
|
144
|
+
offset = 0
|
|
145
|
+
while remaining == simulations.count or (remaining > 0 and Confirm.ask("Show more?")):
|
|
146
|
+
simulations = simulations_api.simulations_list(limit=limit, offset=offset)
|
|
147
|
+
for i, simulation in enumerate(simulations.results):
|
|
148
|
+
status = simulations_api.simulations_status_list(simulation.id)
|
|
149
|
+
if status.name == "finished":
|
|
150
|
+
txt = f":heavy_check_mark: [bold green]{status.name}[/bold green] "
|
|
151
|
+
elif status.name == "created":
|
|
152
|
+
txt = f"[dim grey27]{status.name}[/dim grey27] "
|
|
153
|
+
elif status.name == "initialized":
|
|
154
|
+
txt = f"[bold dark_violet]{status.name}[/bold dark_violet] "
|
|
155
|
+
else:
|
|
156
|
+
txt = f"[bold red]{status.name}[/bold red]"
|
|
157
|
+
table.add_row(f"{simulation.id}", f"{simulation.name}", txt)
|
|
158
|
+
console.print(table)
|
|
159
|
+
remaining -= limit
|
|
160
|
+
offset += DEFAULT_PAGER_SIZE
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@api_app.command()
|
|
164
|
+
def settings(
|
|
165
|
+
ctx: typer.Context,
|
|
166
|
+
organisation: str = typer.Option(
|
|
167
|
+
None, help="Unique-id of the organisation to set as default"
|
|
168
|
+
),
|
|
169
|
+
scenario_folder: Path = typer.Option(
|
|
170
|
+
None,
|
|
171
|
+
dir_okay=True,
|
|
172
|
+
writable=True,
|
|
173
|
+
resolve_path=True,
|
|
174
|
+
help="Specify a the folder for your scenario's.",
|
|
175
|
+
),
|
|
176
|
+
result_folder: Path = typer.Option(
|
|
177
|
+
None,
|
|
178
|
+
dir_okay=True,
|
|
179
|
+
writable=True,
|
|
180
|
+
resolve_path=True,
|
|
181
|
+
help="Specify a results folder.",
|
|
182
|
+
),
|
|
183
|
+
):
|
|
184
|
+
"""Set default settings"""
|
|
185
|
+
console.rule(":wrench: Configuring defaults for the 3Di cli", style="bold blue")
|
|
186
|
+
console.print(Padding("", (1, 0)))
|
|
187
|
+
if organisation is None:
|
|
188
|
+
console.rule("Please choose an organisation", style="bold blue")
|
|
189
|
+
if (
|
|
190
|
+
ctx.obj.organisation_uuid
|
|
191
|
+
and Confirm.ask(f"Change current organisation {ctx.obj.organisation_uuid}?")
|
|
192
|
+
or not ctx.obj.organisation_uuid
|
|
193
|
+
):
|
|
194
|
+
organisations(ctx)
|
|
195
|
+
console.rule(":pencil2:", style="bold blue")
|
|
196
|
+
organisation = Prompt.ask(
|
|
197
|
+
"Set default organisation. UNIQUE_ID",
|
|
198
|
+
default=ctx.obj.organisation_uuid,
|
|
199
|
+
)
|
|
200
|
+
else:
|
|
201
|
+
organisation = ctx.obj.organisation_uuid
|
|
202
|
+
if not scenario_folder:
|
|
203
|
+
default_scenario_path = ctx.obj.scenario_folder or Path("./scenarios").resolve()
|
|
204
|
+
print(default_scenario_path)
|
|
205
|
+
scenario_folder = PathPrompt.ask(
|
|
206
|
+
"Where do you keep your scenario files?", default=default_scenario_path
|
|
207
|
+
)
|
|
208
|
+
if not result_folder:
|
|
209
|
+
results_folder_default = ctx.obj.result_folder or Path("./results").resolve()
|
|
210
|
+
result_folder = PathPrompt.ask(
|
|
211
|
+
"Where do you want to store your result files?",
|
|
212
|
+
default=results_folder_default,
|
|
213
|
+
)
|
|
214
|
+
ctx.obj.organisation_uuid = organisation
|
|
215
|
+
ctx.obj.result_folder = result_folder
|
|
216
|
+
ctx.obj.scenario_folder = scenario_folder
|
|
217
|
+
console.print(
|
|
218
|
+
f":heavy_check_mark: Default settings are saved in {ctx.obj.config_file}"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@api_app.command()
|
|
223
|
+
def run_scenario(
|
|
224
|
+
ctx: typer.Context,
|
|
225
|
+
scenario: int = typer.Option(
|
|
226
|
+
None,
|
|
227
|
+
help="The ID of the scenario, as returned by the 'scenarios' command. If not provided the scenarios command will be invoked so you can choose an ID",
|
|
228
|
+
),
|
|
229
|
+
model_id: int = typer.Option(
|
|
230
|
+
None,
|
|
231
|
+
help="The ID of the model you want to use, as returned by the 'models' command. If not provided the models command will be invoked so you can choose an ID",
|
|
232
|
+
),
|
|
233
|
+
organisation: str = typer.Option(
|
|
234
|
+
None, help="Unique-id of the organisation to set as default"
|
|
235
|
+
),
|
|
236
|
+
):
|
|
237
|
+
"""Run a scenario"""
|
|
238
|
+
if scenario is None:
|
|
239
|
+
scenarios(ctx)
|
|
240
|
+
scenario = IntPrompt.ask("Which scenario do you want to run? ID")
|
|
241
|
+
scenario_to_run = ctx.obj.scenarios[scenario]["file"]
|
|
242
|
+
parser = ScenarioParser(scenario_to_run)
|
|
243
|
+
|
|
244
|
+
if parser.is_simulation_scenario:
|
|
245
|
+
if model_id is None:
|
|
246
|
+
models(ctx)
|
|
247
|
+
model_id = IntPrompt.ask("Which model do you want to run? ID")
|
|
248
|
+
|
|
249
|
+
if not organisation:
|
|
250
|
+
organisations(ctx)
|
|
251
|
+
organisation = Prompt.ask("Which organisation do you want to use? UUID")
|
|
252
|
+
|
|
253
|
+
name = ctx.obj.scenarios[scenario]["name"]
|
|
254
|
+
|
|
255
|
+
model = ctx.obj.api_client.threedimodels_read(model_id)
|
|
256
|
+
context = {
|
|
257
|
+
"threedimodel_id": model.id,
|
|
258
|
+
"threedimodel": model,
|
|
259
|
+
"organisation_uuid": organisation,
|
|
260
|
+
"simulation_name": name,
|
|
261
|
+
"schematisation_name": name,
|
|
262
|
+
"datetime_now": datetime.utcnow().isoformat(),
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
scenario = parser.parse(
|
|
267
|
+
ctx.obj.api_client, ctx.obj.websocket_settings, context=context
|
|
268
|
+
)
|
|
269
|
+
except (ResolveError, ApiModelError, LoadScenarioError) as err:
|
|
270
|
+
console.print(f":collision: {err}", style="error")
|
|
271
|
+
sys.exit(ExitCodes.SCENARIO_CONFIG_ERROR.value)
|
|
272
|
+
|
|
273
|
+
console.rule(f"Loading scenario {name}", style="bold blue")
|
|
274
|
+
scenario.base_object.save()
|
|
275
|
+
console.print(f":link: URL: {scenario.base_object.instance.url}")
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
console.rule("Starting scenario run...", style="bold blue")
|
|
279
|
+
asyncio.run(scenario.execute())
|
|
280
|
+
except KeyboardInterrupt:
|
|
281
|
+
pass
|
|
282
|
+
except FailedStep as err:
|
|
283
|
+
console.print(f"{err}", style="error")
|
|
284
|
+
sys.exit(ExitCodes.RUN_SCENARIO_ERROR.value)
|
|
285
|
+
except websockets.exceptions.InvalidStatusCode as err:
|
|
286
|
+
console.print(f"{err}", style="error")
|
|
287
|
+
sys.exit(ExitCodes.CONNECTION_ERROR.value)
|
|
288
|
+
else:
|
|
289
|
+
success_panel = Panel(
|
|
290
|
+
f"Run for scenario {name} successful",
|
|
291
|
+
expand=True,
|
|
292
|
+
box=box.DOUBLE,
|
|
293
|
+
border_style="bold spring_green4",
|
|
294
|
+
title=":sparkles: Finished :sparkles:",
|
|
295
|
+
)
|
|
296
|
+
console.print(success_panel, justify="center")
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@api_app.command()
|
|
300
|
+
def results(
|
|
301
|
+
ctx: typer.Context,
|
|
302
|
+
simulation: int = typer.Option(None, help="ID of the simulation"),
|
|
303
|
+
folder: Path = typer.Option(
|
|
304
|
+
None,
|
|
305
|
+
dir_okay=True,
|
|
306
|
+
writable=True,
|
|
307
|
+
resolve_path=True,
|
|
308
|
+
help="Absolute path to where the files will be stored.",
|
|
309
|
+
),
|
|
310
|
+
):
|
|
311
|
+
"""Download results of a simulation"""
|
|
312
|
+
console.rule(":arrow_heading_down: Download simulation results", style="bold blue")
|
|
313
|
+
console.print(Padding("", (1, 0)))
|
|
314
|
+
|
|
315
|
+
if simulation is None:
|
|
316
|
+
console.rule("Please choose a simulation", style="bold blue")
|
|
317
|
+
simulations(ctx)
|
|
318
|
+
simulation = IntPrompt.ask("Which simulation results do you want download? ID")
|
|
319
|
+
if not folder:
|
|
320
|
+
result_folder = ctx.obj.result_folder
|
|
321
|
+
if not result_folder:
|
|
322
|
+
result_folder = Path.cwd()
|
|
323
|
+
folder = PathPrompt.ask(
|
|
324
|
+
"Where do you want to store the results files?",
|
|
325
|
+
default=Path(f"{result_folder}/simulation-{simulation}"),
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
try:
|
|
329
|
+
folder.resolve().mkdir(parents=True)
|
|
330
|
+
except FileExistsError:
|
|
331
|
+
click.confirm(
|
|
332
|
+
"Output folder already exists, we might override files in the folder. "
|
|
333
|
+
"Do you want to continue?",
|
|
334
|
+
abort=True,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
try:
|
|
338
|
+
simulations_api: V3Api = ctx.obj.api_client
|
|
339
|
+
threedimodels_api = simulations_api
|
|
340
|
+
simulation = simulations_api.simulations_read(id=simulation)
|
|
341
|
+
except ApiException as err:
|
|
342
|
+
console.print(f"{err}", style="error")
|
|
343
|
+
sys.exit(ExitCodes.RETRIEVE_ERROR.value)
|
|
344
|
+
threedi_model_id = simulation.threedimodel_id
|
|
345
|
+
|
|
346
|
+
gridadmin_download = threedimodels_api.threedimodels_gridadmin_download(
|
|
347
|
+
threedi_model_id
|
|
348
|
+
)
|
|
349
|
+
f = [gridadmin_download]
|
|
350
|
+
|
|
351
|
+
result_files = simulations_api.simulations_results_files_list(simulation.id)
|
|
352
|
+
for result in result_files.results:
|
|
353
|
+
if result.file.state in ["error", "removed"]:
|
|
354
|
+
console.print(
|
|
355
|
+
f"{result.filename} is in state {result.file.state} and will be skipped.",
|
|
356
|
+
style="warning",
|
|
357
|
+
)
|
|
358
|
+
continue
|
|
359
|
+
|
|
360
|
+
result_download = simulations_api.simulations_results_files_download(
|
|
361
|
+
id=result.id, simulation_pk=simulation.id
|
|
362
|
+
)
|
|
363
|
+
f.append(result_download)
|
|
364
|
+
success = download_files(f, folder)
|
|
365
|
+
if success:
|
|
366
|
+
console.print(":heavy_check_mark: Finished downloading results", style="success")
|
|
367
|
+
else:
|
|
368
|
+
console.print(":collision: Not all files could be downloaded", style="error")
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
@api_app.command()
|
|
372
|
+
def login(ctx: typer.Context):
|
|
373
|
+
"""Login to the specified endpoint."""
|
|
374
|
+
try:
|
|
375
|
+
configuration = Configuration(ctx.obj.endpoint)
|
|
376
|
+
ctx.obj.credentials_prompt(configuration)
|
|
377
|
+
console.print(f":unlock: Authenticated as {ctx.obj.username}", style="success")
|
|
378
|
+
except ApiException as e:
|
|
379
|
+
console.print(f":lock: Failed to authenticate: {e.reason}", style="warning")
|
|
380
|
+
sys.exit(ExitCodes.AUTHENTICATION_FAILED.value)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
if __name__ == "__main__":
|
|
384
|
+
app()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from threedi_cmd.commands.active_simulations import active_sims_app
|
|
4
|
+
from threedi_cmd.commands.api import api_app
|
|
5
|
+
from threedi_cmd.commands.scenarios import scenario_app
|
|
6
|
+
from threedi_cmd.commands.suite import suite_app
|
|
7
|
+
from threedi_cmd.plugins.models import AppMeta, AppRegistry
|
|
8
|
+
|
|
9
|
+
core = typer.Typer()
|
|
10
|
+
|
|
11
|
+
# main app definition
|
|
12
|
+
core = AppMeta(app=core, name="core", help="Interact with various parts of 3Di")
|
|
13
|
+
|
|
14
|
+
live = AppMeta(
|
|
15
|
+
app=active_sims_app,
|
|
16
|
+
name="live",
|
|
17
|
+
help="Get real time updates of running simulations",
|
|
18
|
+
add_to="core",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
scenarios = AppMeta(
|
|
22
|
+
app=scenario_app,
|
|
23
|
+
name="scenarios",
|
|
24
|
+
help="Manage your local scenarios",
|
|
25
|
+
add_to="core",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
api = AppMeta(
|
|
29
|
+
app=api_app, name="api", help="Interact with with the 3Di API", add_to="core"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
suite = AppMeta(app=suite_app, name="suite", help="Run a scenario suite", add_to="core")
|
|
33
|
+
|
|
34
|
+
registry = AppRegistry(apps={inst.name: inst for inst in [core, live, scenarios, api]})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from threedi_cmd.commands.models import EndpointChoices
|
|
4
|
+
from threedi_cmd.commands.settings import (
|
|
5
|
+
EndpointOption,
|
|
6
|
+
Settings,
|
|
7
|
+
get_settings,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def default_callback(
|
|
12
|
+
ctx: typer.Context,
|
|
13
|
+
endpoint: EndpointChoices = typer.Option(
|
|
14
|
+
EndpointChoices.production, case_sensitive=False
|
|
15
|
+
),
|
|
16
|
+
):
|
|
17
|
+
endpoint_name = EndpointOption[endpoint.value].name
|
|
18
|
+
settings = get_settings(endpoint_name)
|
|
19
|
+
ctx.obj = settings
|
|
20
|
+
ctx.call_on_close(Settings.save_settings)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from functools import lru_cache
|
|
2
|
+
|
|
3
|
+
from threedi_cmd.commands.app_definitions import registry
|
|
4
|
+
from threedi_cmd.plugins.tools import discover
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@lru_cache()
|
|
8
|
+
def get_app():
|
|
9
|
+
plugin_registry = discover()
|
|
10
|
+
registry.apps.update(plugin_registry.apps)
|
|
11
|
+
for app_name, app_meta in registry.apps.items():
|
|
12
|
+
if not app_meta.add_to:
|
|
13
|
+
continue
|
|
14
|
+
add_to_meta = registry.apps[app_meta.add_to]
|
|
15
|
+
add_to_meta.app.add_typer(app_meta.app, name=app_meta.name, help=app_meta.help)
|
|
16
|
+
return registry.apps["core"].app
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
app = get_app()
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
app = get_app()
|
|
23
|
+
app()
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
from rich import box
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
from threedi_cmd.commands.callbacks import default_callback
|
|
6
|
+
from threedi_cmd.commands.settings import (
|
|
7
|
+
ScenariosMeta,
|
|
8
|
+
)
|
|
9
|
+
from threedi_cmd.console import console
|
|
10
|
+
from threedi_cmd.logger import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger("INFO")
|
|
13
|
+
|
|
14
|
+
DEFAULT_PAGER_SIZE = 20
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
scenario_app = typer.Typer(callback=default_callback)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@scenario_app.command()
|
|
21
|
+
def scenarios(ctx: typer.Context):
|
|
22
|
+
"""List local scenarios"""
|
|
23
|
+
table = Table(show_header=True, box=box.HORIZONTALS, show_lines=True)
|
|
24
|
+
table.add_column("Id", width=5)
|
|
25
|
+
table.add_column(
|
|
26
|
+
"Name",
|
|
27
|
+
width=20,
|
|
28
|
+
justify="left",
|
|
29
|
+
style="bold cyan",
|
|
30
|
+
header_style="bold cyan",
|
|
31
|
+
)
|
|
32
|
+
table.add_column("Description", justify="left", no_wrap=False)
|
|
33
|
+
table.add_column(
|
|
34
|
+
"Caution", justify="left", style="orange3", header_style="bold orange3"
|
|
35
|
+
)
|
|
36
|
+
table.add_column("yaml", justify="left", style="dim blue", header_style="bold blue")
|
|
37
|
+
s = ScenariosMeta(ctx.obj.scenario_folder)
|
|
38
|
+
for i, scenario in enumerate(s.scenarios, start=0):
|
|
39
|
+
if isinstance(scenario, dict):
|
|
40
|
+
s = ""
|
|
41
|
+
if scenario.get("known_constraints"):
|
|
42
|
+
for k, v in scenario["known_constraints"].items():
|
|
43
|
+
s += f"{k}: {v} \n"
|
|
44
|
+
table.add_row(
|
|
45
|
+
f"{i}",
|
|
46
|
+
f"{scenario['name']}",
|
|
47
|
+
f"{scenario['description']}",
|
|
48
|
+
f"{s}",
|
|
49
|
+
f"{scenario['file'].stem}",
|
|
50
|
+
)
|
|
51
|
+
console.print(table)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
scenario_app()
|