nextmv 0.31.0__py3-none-any.whl → 0.33.0.dev0__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.
- nextmv/__about__.py +1 -1
- nextmv/__init__.py +4 -9
- nextmv/cloud/__init__.py +7 -0
- nextmv/cloud/acceptance_test.py +60 -6
- nextmv/cloud/application.py +274 -49
- nextmv/cloud/batch_experiment.py +7 -1
- nextmv/cloud/ensemble.py +248 -0
- nextmv/default_app/README.md +17 -2
- nextmv/default_app/app.yaml +1 -2
- nextmv/default_app/input.json +5 -0
- nextmv/default_app/main.py +37 -0
- nextmv/input.py +2 -8
- nextmv/local/application.py +250 -224
- nextmv/local/executor.py +9 -13
- nextmv/local/local.py +97 -0
- nextmv/local/runner.py +3 -41
- nextmv/output.py +6 -26
- nextmv/run.py +476 -108
- {nextmv-0.31.0.dist-info → nextmv-0.33.0.dev0.dist-info}/METADATA +1 -1
- {nextmv-0.31.0.dist-info → nextmv-0.33.0.dev0.dist-info}/RECORD +22 -18
- {nextmv-0.31.0.dist-info → nextmv-0.33.0.dev0.dist-info}/WHEEL +0 -0
- {nextmv-0.31.0.dist-info → nextmv-0.33.0.dev0.dist-info}/licenses/LICENSE +0 -0
nextmv/local/application.py
CHANGED
|
@@ -6,19 +6,8 @@ including application management, running applications, and managing inputs.
|
|
|
6
6
|
|
|
7
7
|
Classes
|
|
8
8
|
-------
|
|
9
|
-
DownloadURL
|
|
10
|
-
Result of getting a download URL.
|
|
11
|
-
PollingOptions
|
|
12
|
-
Options for polling when waiting for run results.
|
|
13
|
-
UploadURL
|
|
14
|
-
Result of getting an upload URL.
|
|
15
9
|
Application
|
|
16
|
-
Class for interacting with
|
|
17
|
-
|
|
18
|
-
Functions
|
|
19
|
-
---------
|
|
20
|
-
poll
|
|
21
|
-
Function to poll for results with configurable options.
|
|
10
|
+
Class for interacting with local Nextmv Applications.
|
|
22
11
|
"""
|
|
23
12
|
|
|
24
13
|
import json
|
|
@@ -33,15 +22,22 @@ from typing import Any, Optional, Union
|
|
|
33
22
|
from nextmv import cloud
|
|
34
23
|
from nextmv._serialization import deflated_serialize_json
|
|
35
24
|
from nextmv.base_model import BaseModel
|
|
36
|
-
from nextmv.input import
|
|
37
|
-
from nextmv.local.
|
|
25
|
+
from nextmv.input import INPUTS_KEY, Input, InputFormat
|
|
26
|
+
from nextmv.local.local import (
|
|
27
|
+
DEFAULT_INPUT_JSON_FILE,
|
|
28
|
+
DEFAULT_OUTPUT_JSON_FILE,
|
|
29
|
+
LOGS_FILE,
|
|
30
|
+
LOGS_KEY,
|
|
31
|
+
NEXTMV_DIR,
|
|
32
|
+
RUNS_KEY,
|
|
33
|
+
)
|
|
38
34
|
from nextmv.local.runner import run
|
|
39
35
|
from nextmv.logger import log
|
|
40
36
|
from nextmv.manifest import Manifest
|
|
41
37
|
from nextmv.options import Options
|
|
42
|
-
from nextmv.output import
|
|
38
|
+
from nextmv.output import OUTPUTS_KEY, SOLUTIONS_KEY, OutputFormat
|
|
43
39
|
from nextmv.polling import DEFAULT_POLLING_OPTIONS, PollingOptions, poll
|
|
44
|
-
from nextmv.run import ErrorLog, Format, RunConfiguration, RunInformation, RunResult, TrackedRun, TrackedRunStatus
|
|
40
|
+
from nextmv.run import ErrorLog, Format, Run, RunConfiguration, RunInformation, RunResult, TrackedRun, TrackedRunStatus
|
|
45
41
|
from nextmv.safe import safe_id
|
|
46
42
|
from nextmv.status import StatusV2
|
|
47
43
|
|
|
@@ -152,224 +148,35 @@ class Application:
|
|
|
152
148
|
description=description,
|
|
153
149
|
)
|
|
154
150
|
|
|
155
|
-
def
|
|
151
|
+
def list_runs(self) -> list[Run]:
|
|
156
152
|
"""
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
This method is the local equivalent to
|
|
160
|
-
`cloud.Application.run_metadata`, which retrieves the metadata of a
|
|
161
|
-
remote run in Nextmv Cloud. This method is used to get the metadata of
|
|
162
|
-
a run that was executed locally using the `new_run` or
|
|
163
|
-
`new_run_with_result` method.
|
|
164
|
-
|
|
165
|
-
Retrieves information about a run without including the run output.
|
|
166
|
-
This is useful when you only need the run's status and metadata.
|
|
167
|
-
|
|
168
|
-
Parameters
|
|
169
|
-
----------
|
|
170
|
-
run_id : str
|
|
171
|
-
ID of the run to retrieve metadata for.
|
|
153
|
+
List all runs for the application.
|
|
172
154
|
|
|
173
155
|
Returns
|
|
174
156
|
-------
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
Raises
|
|
179
|
-
------
|
|
180
|
-
ValueError
|
|
181
|
-
If the `.nextmv/runs` directory does not exist at the application
|
|
182
|
-
source, or if the specified run ID does not exist.
|
|
183
|
-
|
|
184
|
-
Examples
|
|
185
|
-
--------
|
|
186
|
-
>>> metadata = app.run_metadata("run-789")
|
|
187
|
-
>>> print(metadata.metadata.status_v2)
|
|
188
|
-
StatusV2.succeeded
|
|
157
|
+
list[Run]
|
|
158
|
+
A list of all runs associated with the application.
|
|
189
159
|
"""
|
|
190
160
|
|
|
191
|
-
runs_dir = os.path.join(self.src,
|
|
161
|
+
runs_dir = os.path.join(self.src, NEXTMV_DIR, RUNS_KEY)
|
|
192
162
|
if not os.path.exists(runs_dir):
|
|
193
163
|
raise ValueError(f"`.nextmv/runs` dir does not exist at app source: {self.src}")
|
|
194
164
|
|
|
195
|
-
|
|
196
|
-
if not
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
info_file = os.path.join(run_dir, f"{run_id}.json")
|
|
200
|
-
if not os.path.exists(info_file):
|
|
201
|
-
raise ValueError(f"`{info_file}` file does not exist at: {run_dir}")
|
|
202
|
-
|
|
203
|
-
with open(info_file) as f:
|
|
204
|
-
info_dict = json.load(f)
|
|
205
|
-
|
|
206
|
-
info = RunInformation.from_dict(info_dict)
|
|
207
|
-
|
|
208
|
-
return info
|
|
209
|
-
|
|
210
|
-
def run_result(self, run_id: str, output_dir_path: Optional[str] = ".") -> RunResult:
|
|
211
|
-
"""
|
|
212
|
-
Get the local result of a run.
|
|
213
|
-
|
|
214
|
-
This method is the local equivalent to `cloud.Application.run_result`,
|
|
215
|
-
which retrieves the result of a remote run in Nextmv Cloud. This method
|
|
216
|
-
is used to get the result of a run that was executed locally using the
|
|
217
|
-
`new_run` or `new_run_with_result` method.
|
|
218
|
-
|
|
219
|
-
Retrieves the complete result of a run, including the run output.
|
|
165
|
+
dirs = os.listdir(runs_dir)
|
|
166
|
+
if not dirs:
|
|
167
|
+
return []
|
|
220
168
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
ID of the run to get results for.
|
|
225
|
-
output_dir_path : Optional[str], default="."
|
|
226
|
-
Path to a directory where non-JSON output files will be saved. This
|
|
227
|
-
is required if the output is non-JSON. If the directory does not
|
|
228
|
-
exist, it will be created. Uses the current directory by default.
|
|
169
|
+
run_ids = [d for d in dirs if os.path.isdir(os.path.join(runs_dir, d))]
|
|
170
|
+
if not run_ids:
|
|
171
|
+
return []
|
|
229
172
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Raises
|
|
236
|
-
------
|
|
237
|
-
ValueError
|
|
238
|
-
If the `.nextmv/runs` directory does not exist at the application
|
|
239
|
-
source, or if the specified run ID does not exist.
|
|
173
|
+
runs = []
|
|
174
|
+
for run_id in run_ids:
|
|
175
|
+
info = self.run_metadata(run_id=run_id)
|
|
176
|
+
run = info.to_run()
|
|
177
|
+
runs.append(run)
|
|
240
178
|
|
|
241
|
-
|
|
242
|
-
--------
|
|
243
|
-
>>> result = app.run_result("run-123")
|
|
244
|
-
>>> print(result.metadata.status_v2)
|
|
245
|
-
'succeeded'
|
|
246
|
-
"""
|
|
247
|
-
|
|
248
|
-
run_information = self.run_metadata(run_id=run_id)
|
|
249
|
-
|
|
250
|
-
return self.__run_result(
|
|
251
|
-
run_id=run_id,
|
|
252
|
-
run_information=run_information,
|
|
253
|
-
output_dir_path=output_dir_path,
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
def run_result_with_polling(
|
|
257
|
-
self,
|
|
258
|
-
run_id: str,
|
|
259
|
-
polling_options: PollingOptions = DEFAULT_POLLING_OPTIONS,
|
|
260
|
-
output_dir_path: Optional[str] = ".",
|
|
261
|
-
) -> RunResult:
|
|
262
|
-
"""
|
|
263
|
-
Get the result of a local run with polling.
|
|
264
|
-
|
|
265
|
-
This method is the local equivalent to
|
|
266
|
-
`cloud.Application.run_result_with_polling`, which retrieves the result
|
|
267
|
-
of a remote run in Nextmv Cloud. This method is used to get the result
|
|
268
|
-
of a run that was executed locally using the `new_run` or
|
|
269
|
-
`new_run_with_result` method.
|
|
270
|
-
|
|
271
|
-
Retrieves the result of a run including the run output. This method
|
|
272
|
-
polls for the result until the run finishes executing or the polling
|
|
273
|
-
strategy is exhausted.
|
|
274
|
-
|
|
275
|
-
Parameters
|
|
276
|
-
----------
|
|
277
|
-
run_id : str
|
|
278
|
-
ID of the run to retrieve the result for.
|
|
279
|
-
polling_options : PollingOptions, default=_DEFAULT_POLLING_OPTIONS
|
|
280
|
-
Options to use when polling for the run result.
|
|
281
|
-
output_dir_path : Optional[str], default="."
|
|
282
|
-
Path to a directory where non-JSON output files will be saved. This
|
|
283
|
-
is required if the output is non-JSON. If the directory does not
|
|
284
|
-
exist, it will be created. Uses the current directory by default.
|
|
285
|
-
|
|
286
|
-
Returns
|
|
287
|
-
-------
|
|
288
|
-
RunResult
|
|
289
|
-
Complete result of the run including output data.
|
|
290
|
-
|
|
291
|
-
Raises
|
|
292
|
-
------
|
|
293
|
-
requests.HTTPError
|
|
294
|
-
If the response status code is not 2xx.
|
|
295
|
-
TimeoutError
|
|
296
|
-
If the run does not complete after the polling strategy is
|
|
297
|
-
exhausted based on time duration.
|
|
298
|
-
RuntimeError
|
|
299
|
-
If the run does not complete after the polling strategy is
|
|
300
|
-
exhausted based on number of tries.
|
|
301
|
-
|
|
302
|
-
Examples
|
|
303
|
-
--------
|
|
304
|
-
>>> from nextmv.cloud import PollingOptions
|
|
305
|
-
>>> # Create custom polling options
|
|
306
|
-
>>> polling_opts = PollingOptions(max_tries=50, max_duration=600)
|
|
307
|
-
>>> # Get run result with polling
|
|
308
|
-
>>> result = app.run_result_with_polling("run-123", polling_opts)
|
|
309
|
-
>>> print(result.output)
|
|
310
|
-
{'solution': {...}}
|
|
311
|
-
"""
|
|
312
|
-
|
|
313
|
-
def polling_func() -> tuple[Any, bool]:
|
|
314
|
-
run_information = self.run_metadata(run_id=run_id)
|
|
315
|
-
if run_information.metadata.status_v2 in {
|
|
316
|
-
StatusV2.succeeded,
|
|
317
|
-
StatusV2.failed,
|
|
318
|
-
StatusV2.canceled,
|
|
319
|
-
}:
|
|
320
|
-
return run_information, True
|
|
321
|
-
|
|
322
|
-
return None, False
|
|
323
|
-
|
|
324
|
-
run_information = poll(polling_options=polling_options, polling_func=polling_func)
|
|
325
|
-
|
|
326
|
-
return self.__run_result(
|
|
327
|
-
run_id=run_id,
|
|
328
|
-
run_information=run_information,
|
|
329
|
-
output_dir_path=output_dir_path,
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
def run_visuals(self, run_id: str) -> None:
|
|
333
|
-
"""
|
|
334
|
-
Open the local run visuals in a web browser.
|
|
335
|
-
|
|
336
|
-
This method opens the visual representation of a locally executed run
|
|
337
|
-
in the default web browser. It assumes that the run was executed locally
|
|
338
|
-
using the `new_run` or `new_run_with_result` method and that
|
|
339
|
-
the necessary visualization files are present.
|
|
340
|
-
|
|
341
|
-
If the run was correctly configured to produce visual assets, then the
|
|
342
|
-
run will contain a `visuals` directory with one or more HTML files.
|
|
343
|
-
Each file is opened in a new tab in the default web browser.
|
|
344
|
-
|
|
345
|
-
Parameters
|
|
346
|
-
----------
|
|
347
|
-
run_id : str
|
|
348
|
-
ID of the local run to visualize.
|
|
349
|
-
|
|
350
|
-
Raises
|
|
351
|
-
------
|
|
352
|
-
ValueError
|
|
353
|
-
If the `.nextmv/runs` directory does not exist at the application
|
|
354
|
-
source, or if the specified run ID does not exist.
|
|
355
|
-
"""
|
|
356
|
-
|
|
357
|
-
runs_dir = os.path.join(self.src, ".nextmv", "runs")
|
|
358
|
-
if not os.path.exists(runs_dir):
|
|
359
|
-
raise ValueError(f"`.nextmv/runs` dir does not exist at app source: {self.src}")
|
|
360
|
-
|
|
361
|
-
run_dir = os.path.join(runs_dir, run_id)
|
|
362
|
-
if not os.path.exists(run_dir):
|
|
363
|
-
raise ValueError(f"`{run_id}` run dir does not exist at: {runs_dir}")
|
|
364
|
-
|
|
365
|
-
visuals_dir = os.path.join(run_dir, "visuals")
|
|
366
|
-
if not os.path.exists(visuals_dir):
|
|
367
|
-
raise ValueError(f"`visuals` dir does not exist at: {run_dir}")
|
|
368
|
-
|
|
369
|
-
for file in os.listdir(visuals_dir):
|
|
370
|
-
if file.endswith(".html"):
|
|
371
|
-
file_path = os.path.join(visuals_dir, file)
|
|
372
|
-
webbrowser.open_new_tab(f"file://{os.path.realpath(file_path)}")
|
|
179
|
+
return runs
|
|
373
180
|
|
|
374
181
|
def new_run(
|
|
375
182
|
self,
|
|
@@ -642,6 +449,225 @@ class Application:
|
|
|
642
449
|
output_dir_path=output_dir_path,
|
|
643
450
|
)
|
|
644
451
|
|
|
452
|
+
def run_metadata(self, run_id: str) -> RunInformation:
|
|
453
|
+
"""
|
|
454
|
+
Get the metadata of a local run.
|
|
455
|
+
|
|
456
|
+
This method is the local equivalent to
|
|
457
|
+
`cloud.Application.run_metadata`, which retrieves the metadata of a
|
|
458
|
+
remote run in Nextmv Cloud. This method is used to get the metadata of
|
|
459
|
+
a run that was executed locally using the `new_run` or
|
|
460
|
+
`new_run_with_result` method.
|
|
461
|
+
|
|
462
|
+
Retrieves information about a run without including the run output.
|
|
463
|
+
This is useful when you only need the run's status and metadata.
|
|
464
|
+
|
|
465
|
+
Parameters
|
|
466
|
+
----------
|
|
467
|
+
run_id : str
|
|
468
|
+
ID of the run to retrieve metadata for.
|
|
469
|
+
|
|
470
|
+
Returns
|
|
471
|
+
-------
|
|
472
|
+
RunInformation
|
|
473
|
+
Metadata of the run (run information without output).
|
|
474
|
+
|
|
475
|
+
Raises
|
|
476
|
+
------
|
|
477
|
+
ValueError
|
|
478
|
+
If the `.nextmv/runs` directory does not exist at the application
|
|
479
|
+
source, or if the specified run ID does not exist.
|
|
480
|
+
|
|
481
|
+
Examples
|
|
482
|
+
--------
|
|
483
|
+
>>> metadata = app.run_metadata("run-789")
|
|
484
|
+
>>> print(metadata.metadata.status_v2)
|
|
485
|
+
StatusV2.succeeded
|
|
486
|
+
"""
|
|
487
|
+
|
|
488
|
+
runs_dir = os.path.join(self.src, NEXTMV_DIR, RUNS_KEY)
|
|
489
|
+
if not os.path.exists(runs_dir):
|
|
490
|
+
raise ValueError(f"`.nextmv/runs` dir does not exist at app source: {self.src}")
|
|
491
|
+
|
|
492
|
+
run_dir = os.path.join(runs_dir, run_id)
|
|
493
|
+
if not os.path.exists(run_dir):
|
|
494
|
+
raise ValueError(f"`{run_id}` run dir does not exist at: {runs_dir}")
|
|
495
|
+
|
|
496
|
+
info_file = os.path.join(run_dir, f"{run_id}.json")
|
|
497
|
+
if not os.path.exists(info_file):
|
|
498
|
+
raise ValueError(f"`{info_file}` file does not exist at: {run_dir}")
|
|
499
|
+
|
|
500
|
+
with open(info_file) as f:
|
|
501
|
+
info_dict = json.load(f)
|
|
502
|
+
|
|
503
|
+
info = RunInformation.from_dict(info_dict)
|
|
504
|
+
|
|
505
|
+
return info
|
|
506
|
+
|
|
507
|
+
def run_result(self, run_id: str, output_dir_path: Optional[str] = ".") -> RunResult:
|
|
508
|
+
"""
|
|
509
|
+
Get the local result of a run.
|
|
510
|
+
|
|
511
|
+
This method is the local equivalent to `cloud.Application.run_result`,
|
|
512
|
+
which retrieves the result of a remote run in Nextmv Cloud. This method
|
|
513
|
+
is used to get the result of a run that was executed locally using the
|
|
514
|
+
`new_run` or `new_run_with_result` method.
|
|
515
|
+
|
|
516
|
+
Retrieves the complete result of a run, including the run output.
|
|
517
|
+
|
|
518
|
+
Parameters
|
|
519
|
+
----------
|
|
520
|
+
run_id : str
|
|
521
|
+
ID of the run to get results for.
|
|
522
|
+
output_dir_path : Optional[str], default="."
|
|
523
|
+
Path to a directory where non-JSON output files will be saved. This
|
|
524
|
+
is required if the output is non-JSON. If the directory does not
|
|
525
|
+
exist, it will be created. Uses the current directory by default.
|
|
526
|
+
|
|
527
|
+
Returns
|
|
528
|
+
-------
|
|
529
|
+
RunResult
|
|
530
|
+
Result of the run, including output.
|
|
531
|
+
|
|
532
|
+
Raises
|
|
533
|
+
------
|
|
534
|
+
ValueError
|
|
535
|
+
If the `.nextmv/runs` directory does not exist at the application
|
|
536
|
+
source, or if the specified run ID does not exist.
|
|
537
|
+
|
|
538
|
+
Examples
|
|
539
|
+
--------
|
|
540
|
+
>>> result = app.run_result("run-123")
|
|
541
|
+
>>> print(result.metadata.status_v2)
|
|
542
|
+
'succeeded'
|
|
543
|
+
"""
|
|
544
|
+
|
|
545
|
+
run_information = self.run_metadata(run_id=run_id)
|
|
546
|
+
|
|
547
|
+
return self.__run_result(
|
|
548
|
+
run_id=run_id,
|
|
549
|
+
run_information=run_information,
|
|
550
|
+
output_dir_path=output_dir_path,
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
def run_result_with_polling(
|
|
554
|
+
self,
|
|
555
|
+
run_id: str,
|
|
556
|
+
polling_options: PollingOptions = DEFAULT_POLLING_OPTIONS,
|
|
557
|
+
output_dir_path: Optional[str] = ".",
|
|
558
|
+
) -> RunResult:
|
|
559
|
+
"""
|
|
560
|
+
Get the result of a local run with polling.
|
|
561
|
+
|
|
562
|
+
This method is the local equivalent to
|
|
563
|
+
`cloud.Application.run_result_with_polling`, which retrieves the result
|
|
564
|
+
of a remote run in Nextmv Cloud. This method is used to get the result
|
|
565
|
+
of a run that was executed locally using the `new_run` or
|
|
566
|
+
`new_run_with_result` method.
|
|
567
|
+
|
|
568
|
+
Retrieves the result of a run including the run output. This method
|
|
569
|
+
polls for the result until the run finishes executing or the polling
|
|
570
|
+
strategy is exhausted.
|
|
571
|
+
|
|
572
|
+
Parameters
|
|
573
|
+
----------
|
|
574
|
+
run_id : str
|
|
575
|
+
ID of the run to retrieve the result for.
|
|
576
|
+
polling_options : PollingOptions, default=_DEFAULT_POLLING_OPTIONS
|
|
577
|
+
Options to use when polling for the run result.
|
|
578
|
+
output_dir_path : Optional[str], default="."
|
|
579
|
+
Path to a directory where non-JSON output files will be saved. This
|
|
580
|
+
is required if the output is non-JSON. If the directory does not
|
|
581
|
+
exist, it will be created. Uses the current directory by default.
|
|
582
|
+
|
|
583
|
+
Returns
|
|
584
|
+
-------
|
|
585
|
+
RunResult
|
|
586
|
+
Complete result of the run including output data.
|
|
587
|
+
|
|
588
|
+
Raises
|
|
589
|
+
------
|
|
590
|
+
requests.HTTPError
|
|
591
|
+
If the response status code is not 2xx.
|
|
592
|
+
TimeoutError
|
|
593
|
+
If the run does not complete after the polling strategy is
|
|
594
|
+
exhausted based on time duration.
|
|
595
|
+
RuntimeError
|
|
596
|
+
If the run does not complete after the polling strategy is
|
|
597
|
+
exhausted based on number of tries.
|
|
598
|
+
|
|
599
|
+
Examples
|
|
600
|
+
--------
|
|
601
|
+
>>> from nextmv.cloud import PollingOptions
|
|
602
|
+
>>> # Create custom polling options
|
|
603
|
+
>>> polling_opts = PollingOptions(max_tries=50, max_duration=600)
|
|
604
|
+
>>> # Get run result with polling
|
|
605
|
+
>>> result = app.run_result_with_polling("run-123", polling_opts)
|
|
606
|
+
>>> print(result.output)
|
|
607
|
+
{'solution': {...}}
|
|
608
|
+
"""
|
|
609
|
+
|
|
610
|
+
def polling_func() -> tuple[Any, bool]:
|
|
611
|
+
run_information = self.run_metadata(run_id=run_id)
|
|
612
|
+
if run_information.metadata.status_v2 in {
|
|
613
|
+
StatusV2.succeeded,
|
|
614
|
+
StatusV2.failed,
|
|
615
|
+
StatusV2.canceled,
|
|
616
|
+
}:
|
|
617
|
+
return run_information, True
|
|
618
|
+
|
|
619
|
+
return None, False
|
|
620
|
+
|
|
621
|
+
run_information = poll(polling_options=polling_options, polling_func=polling_func)
|
|
622
|
+
|
|
623
|
+
return self.__run_result(
|
|
624
|
+
run_id=run_id,
|
|
625
|
+
run_information=run_information,
|
|
626
|
+
output_dir_path=output_dir_path,
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
def run_visuals(self, run_id: str) -> None:
|
|
630
|
+
"""
|
|
631
|
+
Open the local run visuals in a web browser.
|
|
632
|
+
|
|
633
|
+
This method opens the visual representation of a locally executed run
|
|
634
|
+
in the default web browser. It assumes that the run was executed locally
|
|
635
|
+
using the `new_run` or `new_run_with_result` method and that
|
|
636
|
+
the necessary visualization files are present.
|
|
637
|
+
|
|
638
|
+
If the run was correctly configured to produce visual assets, then the
|
|
639
|
+
run will contain a `visuals` directory with one or more HTML files.
|
|
640
|
+
Each file is opened in a new tab in the default web browser.
|
|
641
|
+
|
|
642
|
+
Parameters
|
|
643
|
+
----------
|
|
644
|
+
run_id : str
|
|
645
|
+
ID of the local run to visualize.
|
|
646
|
+
|
|
647
|
+
Raises
|
|
648
|
+
------
|
|
649
|
+
ValueError
|
|
650
|
+
If the `.nextmv/runs` directory does not exist at the application
|
|
651
|
+
source, or if the specified run ID does not exist.
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
runs_dir = os.path.join(self.src, NEXTMV_DIR, RUNS_KEY)
|
|
655
|
+
if not os.path.exists(runs_dir):
|
|
656
|
+
raise ValueError(f"`.nextmv/runs` dir does not exist at app source: {self.src}")
|
|
657
|
+
|
|
658
|
+
run_dir = os.path.join(runs_dir, run_id)
|
|
659
|
+
if not os.path.exists(run_dir):
|
|
660
|
+
raise ValueError(f"`{run_id}` run dir does not exist at: {runs_dir}")
|
|
661
|
+
|
|
662
|
+
visuals_dir = os.path.join(run_dir, "visuals")
|
|
663
|
+
if not os.path.exists(visuals_dir):
|
|
664
|
+
raise ValueError(f"`visuals` dir does not exist at: {run_dir}")
|
|
665
|
+
|
|
666
|
+
for file in os.listdir(visuals_dir):
|
|
667
|
+
if file.endswith(".html"):
|
|
668
|
+
file_path = os.path.join(visuals_dir, file)
|
|
669
|
+
webbrowser.open_new_tab(f"file://{os.path.realpath(file_path)}")
|
|
670
|
+
|
|
645
671
|
def sync( # noqa: C901
|
|
646
672
|
self,
|
|
647
673
|
target: cloud.Application,
|
|
@@ -714,7 +740,7 @@ class Application:
|
|
|
714
740
|
# ".". During the sync process, we don't need to keep these outputs, so
|
|
715
741
|
# we can use a temp dir that will be deleted after the sync is done.
|
|
716
742
|
with tempfile.TemporaryDirectory(prefix="nextmv-sync-run-") as temp_results_dir:
|
|
717
|
-
runs_dir = os.path.join(self.src,
|
|
743
|
+
runs_dir = os.path.join(self.src, NEXTMV_DIR, RUNS_KEY)
|
|
718
744
|
if run_ids is None:
|
|
719
745
|
# If runs are not specified, by default we sync all local runs that
|
|
720
746
|
# can be found.
|
|
@@ -800,7 +826,7 @@ class Application:
|
|
|
800
826
|
"The output format is not JSON: an `output_dir_path` must be provided.",
|
|
801
827
|
)
|
|
802
828
|
|
|
803
|
-
runs_dir = os.path.join(self.src,
|
|
829
|
+
runs_dir = os.path.join(self.src, NEXTMV_DIR, RUNS_KEY)
|
|
804
830
|
solutions_dir = os.path.join(runs_dir, run_id, OUTPUTS_KEY, SOLUTIONS_KEY)
|
|
805
831
|
|
|
806
832
|
if output_type == OutputFormat.JSON:
|
nextmv/local/executor.py
CHANGED
|
@@ -32,28 +32,24 @@ import json
|
|
|
32
32
|
import os
|
|
33
33
|
import shutil
|
|
34
34
|
import subprocess
|
|
35
|
+
import sys
|
|
35
36
|
import tempfile
|
|
36
37
|
from datetime import datetime, timezone
|
|
37
38
|
from typing import Any, Optional, Union
|
|
38
39
|
|
|
39
40
|
from nextmv.input import INPUTS_KEY, InputFormat, load
|
|
40
41
|
from nextmv.local.geojson_handler import handle_geojson_visual
|
|
41
|
-
from nextmv.local.
|
|
42
|
-
from nextmv.local.runner import calculate_files_size
|
|
43
|
-
from nextmv.manifest import Manifest
|
|
44
|
-
from nextmv.output import (
|
|
45
|
-
ASSETS_KEY,
|
|
42
|
+
from nextmv.local.local import (
|
|
46
43
|
DEFAULT_OUTPUT_JSON_FILE,
|
|
47
44
|
LOGS_FILE,
|
|
48
45
|
LOGS_KEY,
|
|
46
|
+
NEXTMV_DIR,
|
|
49
47
|
OUTPUT_KEY,
|
|
50
|
-
|
|
51
|
-
SOLUTIONS_KEY,
|
|
52
|
-
STATISTICS_KEY,
|
|
53
|
-
Asset,
|
|
54
|
-
OutputFormat,
|
|
55
|
-
VisualSchema,
|
|
48
|
+
calculate_files_size,
|
|
56
49
|
)
|
|
50
|
+
from nextmv.local.plotly_handler import handle_plotly_visual
|
|
51
|
+
from nextmv.manifest import Manifest
|
|
52
|
+
from nextmv.output import ASSETS_KEY, OUTPUTS_KEY, SOLUTIONS_KEY, STATISTICS_KEY, Asset, OutputFormat, VisualSchema
|
|
57
53
|
from nextmv.status import StatusV2
|
|
58
54
|
|
|
59
55
|
|
|
@@ -123,7 +119,7 @@ def execute_run(
|
|
|
123
119
|
# place to work from, and be cleaned up afterwards.
|
|
124
120
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
125
121
|
temp_src = os.path.join(temp_dir, "src")
|
|
126
|
-
shutil.copytree(src, temp_src, ignore=shutil.ignore_patterns(
|
|
122
|
+
shutil.copytree(src, temp_src, ignore=shutil.ignore_patterns(NEXTMV_DIR))
|
|
127
123
|
|
|
128
124
|
manifest = Manifest.from_dict(manifest_dict)
|
|
129
125
|
|
|
@@ -148,7 +144,7 @@ def execute_run(
|
|
|
148
144
|
# supporting a Python-first experience, so we are not summoning
|
|
149
145
|
# applications that are not Python-based.
|
|
150
146
|
entrypoint = os.path.join(temp_src, manifest.entrypoint)
|
|
151
|
-
args = [
|
|
147
|
+
args = [sys.executable, entrypoint] + options_args(options)
|
|
152
148
|
|
|
153
149
|
result = subprocess.run(
|
|
154
150
|
args,
|
nextmv/local/local.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local module to hold convenience functions used in the `local` package.
|
|
3
|
+
|
|
4
|
+
Functions
|
|
5
|
+
----------
|
|
6
|
+
calculate_files_size
|
|
7
|
+
Function to calculate the total size of files in a directory.
|
|
8
|
+
|
|
9
|
+
Attributes
|
|
10
|
+
----------
|
|
11
|
+
OUTPUT_KEY : str
|
|
12
|
+
Output key constant used for identifying output in the run output.
|
|
13
|
+
LOGS_KEY : str
|
|
14
|
+
Logs key constant used for identifying logs in the run output.
|
|
15
|
+
LOGS_FILE : str
|
|
16
|
+
Constant used for identifying the file used for logging.
|
|
17
|
+
DEFAULT_OUTPUT_JSON_FILE : str
|
|
18
|
+
Constant for the default output JSON file name.
|
|
19
|
+
RUNS_KEY : str
|
|
20
|
+
Runs key constant used for identifying the runs directory in the nextmv
|
|
21
|
+
location.
|
|
22
|
+
NEXTMV_DIR : str
|
|
23
|
+
Constant for the Nextmv directory name.
|
|
24
|
+
DEFAULT_INPUT_JSON_FILE : str
|
|
25
|
+
Constant for the default input JSON file name.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
import json
|
|
29
|
+
import os
|
|
30
|
+
|
|
31
|
+
OUTPUT_KEY = "output"
|
|
32
|
+
"""
|
|
33
|
+
Output key constant used for identifying output in the run output.
|
|
34
|
+
"""
|
|
35
|
+
LOGS_KEY = "logs"
|
|
36
|
+
"""
|
|
37
|
+
Logs key constant used for identifying logs in the run output.
|
|
38
|
+
"""
|
|
39
|
+
LOGS_FILE = "stderr.log"
|
|
40
|
+
"""
|
|
41
|
+
Constant used for identifying the file used for logging.
|
|
42
|
+
"""
|
|
43
|
+
DEFAULT_OUTPUT_JSON_FILE = "solution.json"
|
|
44
|
+
"""
|
|
45
|
+
Constant for the default output JSON file name.
|
|
46
|
+
"""
|
|
47
|
+
RUNS_KEY = "runs"
|
|
48
|
+
"""
|
|
49
|
+
Runs key constant used for identifying the runs directory in the nextmv
|
|
50
|
+
location.
|
|
51
|
+
"""
|
|
52
|
+
NEXTMV_DIR = ".nextmv"
|
|
53
|
+
"""
|
|
54
|
+
Constant for the Nextmv directory name.
|
|
55
|
+
"""
|
|
56
|
+
DEFAULT_INPUT_JSON_FILE = "input.json"
|
|
57
|
+
"""
|
|
58
|
+
Constant for the default input JSON file name.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def calculate_files_size(run_dir: str, run_id: str, dir_path: str, metadata_key: str) -> None:
|
|
63
|
+
"""
|
|
64
|
+
Calculates the total size of the files in a directory, in bytes.
|
|
65
|
+
|
|
66
|
+
The calculated size is stored in the run information metadata under the
|
|
67
|
+
specified key.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
run_dir : str
|
|
72
|
+
The path to the run directory.
|
|
73
|
+
run_id : str
|
|
74
|
+
The ID of the run.
|
|
75
|
+
dir_path : str
|
|
76
|
+
The path to the directory whose size is to be calculated.
|
|
77
|
+
metadata_key : str
|
|
78
|
+
The key under which to store the calculated size in the run information
|
|
79
|
+
metadata.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
total_size = 0
|
|
83
|
+
for dirpath, _, filenames in os.walk(dir_path):
|
|
84
|
+
for f in filenames:
|
|
85
|
+
fp = os.path.join(dirpath, f)
|
|
86
|
+
# Skip if it is a symbolic link
|
|
87
|
+
if os.path.islink(fp):
|
|
88
|
+
continue
|
|
89
|
+
total_size += os.path.getsize(fp)
|
|
90
|
+
|
|
91
|
+
info_file = os.path.join(run_dir, f"{run_id}.json")
|
|
92
|
+
with open(info_file, "r+") as f:
|
|
93
|
+
info = json.load(f)
|
|
94
|
+
info["metadata"][metadata_key] = total_size
|
|
95
|
+
f.seek(0)
|
|
96
|
+
json.dump(info, f, indent=2)
|
|
97
|
+
f.truncate()
|