nextmv 0.20.1__py3-none-any.whl → 0.21.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.
- nextmv/__about__.py +1 -1
- nextmv/cloud/__init__.py +2 -0
- nextmv/cloud/application.py +113 -6
- nextmv/cloud/run.py +116 -2
- nextmv/input.py +20 -0
- nextmv/output.py +23 -0
- {nextmv-0.20.1.dist-info → nextmv-0.21.1.dist-info}/METADATA +1 -1
- {nextmv-0.20.1.dist-info → nextmv-0.21.1.dist-info}/RECORD +10 -10
- {nextmv-0.20.1.dist-info → nextmv-0.21.1.dist-info}/WHEEL +0 -0
- {nextmv-0.20.1.dist-info → nextmv-0.21.1.dist-info}/licenses/LICENSE +0 -0
nextmv/__about__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "v0.
|
|
1
|
+
__version__ = "v0.21.1"
|
nextmv/cloud/__init__.py
CHANGED
|
@@ -48,6 +48,8 @@ from .run import RunLog as RunLog
|
|
|
48
48
|
from .run import RunResult as RunResult
|
|
49
49
|
from .run import RunType as RunType
|
|
50
50
|
from .run import RunTypeConfiguration as RunTypeConfiguration
|
|
51
|
+
from .run import TrackedRun as TrackedRun
|
|
52
|
+
from .run import TrackedRunStatus as TrackedRunStatus
|
|
51
53
|
from .secrets import Secret as Secret
|
|
52
54
|
from .secrets import SecretsCollection as SecretsCollection
|
|
53
55
|
from .secrets import SecretsCollectionSummary as SecretsCollectionSummary
|
nextmv/cloud/application.py
CHANGED
|
@@ -18,12 +18,14 @@ from nextmv.cloud.client import Client, get_size
|
|
|
18
18
|
from nextmv.cloud.input_set import InputSet
|
|
19
19
|
from nextmv.cloud.instance import Instance, InstanceConfiguration
|
|
20
20
|
from nextmv.cloud.manifest import Manifest
|
|
21
|
-
from nextmv.cloud.run import ExternalRunResult, RunConfiguration, RunInformation, RunLog, RunResult
|
|
21
|
+
from nextmv.cloud.run import ExternalRunResult, RunConfiguration, RunInformation, RunLog, RunResult, TrackedRun
|
|
22
22
|
from nextmv.cloud.secrets import Secret, SecretsCollection, SecretsCollectionSummary
|
|
23
23
|
from nextmv.cloud.status import StatusV2
|
|
24
24
|
from nextmv.cloud.version import Version
|
|
25
|
+
from nextmv.input import Input
|
|
25
26
|
from nextmv.logger import log
|
|
26
27
|
from nextmv.model import Model, ModelConfiguration
|
|
28
|
+
from nextmv.output import Output
|
|
27
29
|
|
|
28
30
|
_MAX_RUN_SIZE: int = 5 * 1024 * 1024
|
|
29
31
|
"""Maximum size of the run input/output. This value is used to determine
|
|
@@ -441,6 +443,7 @@ class Application:
|
|
|
441
443
|
id: Optional[str] = None,
|
|
442
444
|
description: Optional[str] = None,
|
|
443
445
|
is_workflow: Optional[bool] = None,
|
|
446
|
+
exist_ok: bool = False,
|
|
444
447
|
) -> "Application":
|
|
445
448
|
"""
|
|
446
449
|
Create a new application.
|
|
@@ -456,6 +459,9 @@ class Application:
|
|
|
456
459
|
The new application.
|
|
457
460
|
"""
|
|
458
461
|
|
|
462
|
+
if exist_ok and cls.exists(client=client, id=id):
|
|
463
|
+
return Application(client=client, id=id)
|
|
464
|
+
|
|
459
465
|
payload = {
|
|
460
466
|
"name": name,
|
|
461
467
|
}
|
|
@@ -735,8 +741,8 @@ class Application:
|
|
|
735
741
|
def new_instance(
|
|
736
742
|
self,
|
|
737
743
|
version_id: str,
|
|
738
|
-
id:
|
|
739
|
-
name:
|
|
744
|
+
id: str,
|
|
745
|
+
name: str,
|
|
740
746
|
description: Optional[str] = None,
|
|
741
747
|
configuration: Optional[InstanceConfiguration] = None,
|
|
742
748
|
) -> Instance:
|
|
@@ -934,8 +940,8 @@ class Application:
|
|
|
934
940
|
def new_secrets_collection(
|
|
935
941
|
self,
|
|
936
942
|
secrets: list[Secret],
|
|
937
|
-
id:
|
|
938
|
-
name:
|
|
943
|
+
id: str,
|
|
944
|
+
name: str,
|
|
939
945
|
description: Optional[str] = None,
|
|
940
946
|
) -> SecretsCollectionSummary:
|
|
941
947
|
"""
|
|
@@ -1284,6 +1290,107 @@ class Application:
|
|
|
1284
1290
|
|
|
1285
1291
|
return self.__run_result(run_id=run_id, run_information=run_information)
|
|
1286
1292
|
|
|
1293
|
+
def track_run(self, tracked_run: TrackedRun) -> str:
|
|
1294
|
+
"""
|
|
1295
|
+
Track an external run.
|
|
1296
|
+
|
|
1297
|
+
This method allows you to register in Nextmv a run that happened
|
|
1298
|
+
elsewhere, as though it were executed in the Nextmv platform. Having
|
|
1299
|
+
information about a run in Nextmv is useful for things like
|
|
1300
|
+
experimenting and testing.
|
|
1301
|
+
|
|
1302
|
+
Parameters
|
|
1303
|
+
----------
|
|
1304
|
+
tracked_run : TrackedRun
|
|
1305
|
+
The run to track.
|
|
1306
|
+
|
|
1307
|
+
Returns
|
|
1308
|
+
-------
|
|
1309
|
+
str
|
|
1310
|
+
The ID of the run that was tracked.
|
|
1311
|
+
|
|
1312
|
+
Raises
|
|
1313
|
+
------
|
|
1314
|
+
requests.HTTPError
|
|
1315
|
+
If the response status code is not 2xx.
|
|
1316
|
+
ValueError
|
|
1317
|
+
If the tracked run does not have an input or output.
|
|
1318
|
+
"""
|
|
1319
|
+
url_input = self.upload_url()
|
|
1320
|
+
|
|
1321
|
+
upload_input = tracked_run.input
|
|
1322
|
+
if isinstance(tracked_run.input, Input):
|
|
1323
|
+
upload_input = tracked_run.input.to_dict()
|
|
1324
|
+
|
|
1325
|
+
self.upload_large_input(input=upload_input, upload_url=url_input)
|
|
1326
|
+
|
|
1327
|
+
url_output = self.upload_url()
|
|
1328
|
+
|
|
1329
|
+
upload_output = tracked_run.output
|
|
1330
|
+
if isinstance(tracked_run.output, Output):
|
|
1331
|
+
upload_output = tracked_run.output.to_dict()
|
|
1332
|
+
|
|
1333
|
+
self.upload_large_input(input=upload_output, upload_url=url_output)
|
|
1334
|
+
|
|
1335
|
+
external_result = ExternalRunResult(
|
|
1336
|
+
output_upload_id=url_output.upload_id,
|
|
1337
|
+
status=tracked_run.status.value,
|
|
1338
|
+
execution_duration=tracked_run.duration,
|
|
1339
|
+
)
|
|
1340
|
+
|
|
1341
|
+
if tracked_run.logs is not None:
|
|
1342
|
+
url_stderr = self.upload_url()
|
|
1343
|
+
self.upload_large_input(input=tracked_run.logs_text(), upload_url=url_stderr)
|
|
1344
|
+
external_result.error_upload_id = url_stderr.upload_id
|
|
1345
|
+
|
|
1346
|
+
if tracked_run.error is not None and tracked_run.error != "":
|
|
1347
|
+
external_result.error_message = tracked_run.error
|
|
1348
|
+
|
|
1349
|
+
return self.new_run(upload_id=url_input.upload_id, external_result=external_result)
|
|
1350
|
+
|
|
1351
|
+
def track_run_with_result(
|
|
1352
|
+
self,
|
|
1353
|
+
tracked_run: TrackedRun,
|
|
1354
|
+
polling_options: PollingOptions = _DEFAULT_POLLING_OPTIONS,
|
|
1355
|
+
) -> RunResult:
|
|
1356
|
+
"""
|
|
1357
|
+
Track an external run and poll for the result. This is a convenience
|
|
1358
|
+
method that combines the `track_run` and `run_result_with_polling`
|
|
1359
|
+
methods. It applies polling logic to check when the run was
|
|
1360
|
+
successfully registered.
|
|
1361
|
+
|
|
1362
|
+
Parameters
|
|
1363
|
+
----------
|
|
1364
|
+
tracked_run : TrackedRun
|
|
1365
|
+
The run to track.
|
|
1366
|
+
polling_options : PollingOptions
|
|
1367
|
+
Options to use when polling for the run result.
|
|
1368
|
+
|
|
1369
|
+
Returns
|
|
1370
|
+
-------
|
|
1371
|
+
RunResult
|
|
1372
|
+
Result of the run.
|
|
1373
|
+
|
|
1374
|
+
Raises
|
|
1375
|
+
------
|
|
1376
|
+
requests.HTTPError
|
|
1377
|
+
If the response status code is not 2xx.
|
|
1378
|
+
ValueError
|
|
1379
|
+
If the tracked run does not have an input or output.
|
|
1380
|
+
TimeoutError
|
|
1381
|
+
If the run does not succeed after the polling strategy is
|
|
1382
|
+
exhausted based on time duration.
|
|
1383
|
+
RuntimeError
|
|
1384
|
+
If the run does not succeed after the polling strategy is
|
|
1385
|
+
exhausted based on number of tries.
|
|
1386
|
+
"""
|
|
1387
|
+
run_id = self.track_run(tracked_run=tracked_run)
|
|
1388
|
+
|
|
1389
|
+
return self.run_result_with_polling(
|
|
1390
|
+
run_id=run_id,
|
|
1391
|
+
polling_options=polling_options,
|
|
1392
|
+
)
|
|
1393
|
+
|
|
1287
1394
|
def update_instance(
|
|
1288
1395
|
self,
|
|
1289
1396
|
id: str,
|
|
@@ -1334,7 +1441,7 @@ class Application:
|
|
|
1334
1441
|
name: str,
|
|
1335
1442
|
description: str,
|
|
1336
1443
|
secrets: list[Secret],
|
|
1337
|
-
) ->
|
|
1444
|
+
) -> SecretsCollectionSummary:
|
|
1338
1445
|
"""
|
|
1339
1446
|
Update a secrets collection.
|
|
1340
1447
|
|
nextmv/cloud/run.py
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"""This module contains definitions for an app run."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
3
5
|
from datetime import datetime
|
|
4
6
|
from enum import Enum
|
|
5
|
-
from typing import Any, Optional
|
|
7
|
+
from typing import Any, Optional, Union
|
|
6
8
|
|
|
7
9
|
from pydantic import AliasChoices, Field
|
|
8
10
|
|
|
9
11
|
from nextmv.base_model import BaseModel
|
|
10
12
|
from nextmv.cloud.status import Status, StatusV2
|
|
11
|
-
from nextmv.input import InputFormat
|
|
13
|
+
from nextmv.input import Input, InputFormat
|
|
14
|
+
from nextmv.output import Output, OutputFormat
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class Metadata(BaseModel):
|
|
@@ -159,3 +162,114 @@ class ExternalRunResult(BaseModel):
|
|
|
159
162
|
valid_statuses = {"succeeded", "failed"}
|
|
160
163
|
if self.status is not None and self.status not in valid_statuses:
|
|
161
164
|
raise ValueError("Invalid status value, must be one of: " + ", ".join(valid_statuses))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class TrackedRunStatus(str, Enum):
|
|
168
|
+
"""
|
|
169
|
+
The status of a tracked run.
|
|
170
|
+
|
|
171
|
+
Attributes
|
|
172
|
+
----------
|
|
173
|
+
SUCCEEDED : str
|
|
174
|
+
The run succeeded.
|
|
175
|
+
FAILED : str
|
|
176
|
+
The run failed.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
SUCCEEDED = "succeeded"
|
|
180
|
+
"""The run succeeded."""
|
|
181
|
+
FAILED = "failed"
|
|
182
|
+
"""The run failed."""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class TrackedRun:
|
|
187
|
+
"""
|
|
188
|
+
An external run that is tracked in the Nextmv platform.
|
|
189
|
+
|
|
190
|
+
Attributes
|
|
191
|
+
----------
|
|
192
|
+
input : Union[Input, dict[str, any], str]
|
|
193
|
+
The input of the run being tracked. Please note that if the input
|
|
194
|
+
format is JSON, then the input data must be JSON serializable. This
|
|
195
|
+
field is required.
|
|
196
|
+
output : Union[Output, dict[str, any], str]
|
|
197
|
+
The output of the run being tracked. Please note that if the output
|
|
198
|
+
format is JSON, then the output data must be JSON serializable. This
|
|
199
|
+
field is required.
|
|
200
|
+
status : TrackedRunStatus
|
|
201
|
+
The status of the run being tracked. This field is required.
|
|
202
|
+
duration : Optional[int]
|
|
203
|
+
The duration of the run being tracked, in seconds. This field is
|
|
204
|
+
optional.
|
|
205
|
+
error : Optional[str]
|
|
206
|
+
An error message if the run failed. You should only specify this if the
|
|
207
|
+
run failed (the `status` is `TrackedRunStatus.FAILED`), otherwise an
|
|
208
|
+
exception will be raised. This field is optional.
|
|
209
|
+
logs : Optional[list[str]]
|
|
210
|
+
The logs of the run being tracked. Each element of the list is a line in
|
|
211
|
+
the log. This field is optional.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
input: Union[Input, dict[str, any], str]
|
|
215
|
+
"""The input of the run being tracked."""
|
|
216
|
+
output: Union[Output, dict[str, any], str]
|
|
217
|
+
"""The output of the run being tracked. Only JSON output_format is supported."""
|
|
218
|
+
status: TrackedRunStatus
|
|
219
|
+
"""The status of the run being tracked"""
|
|
220
|
+
|
|
221
|
+
duration: Optional[int] = None
|
|
222
|
+
"""The duration of the run being tracked, in seconds."""
|
|
223
|
+
error: Optional[str] = None
|
|
224
|
+
"""An error message if the run failed. You should only specify this if the
|
|
225
|
+
run failed, otherwise an exception will be raised."""
|
|
226
|
+
logs: Optional[list[str]] = None
|
|
227
|
+
"""The logs of the run being tracked. Each element of the list is a line in
|
|
228
|
+
the log."""
|
|
229
|
+
|
|
230
|
+
def __post_init__(self): # noqa: C901
|
|
231
|
+
"""Validations done after parsing the model."""
|
|
232
|
+
|
|
233
|
+
valid_statuses = {TrackedRunStatus.SUCCEEDED, TrackedRunStatus.FAILED}
|
|
234
|
+
if self.status not in valid_statuses:
|
|
235
|
+
raise ValueError("Invalid status value, must be one of: " + ", ".join(valid_statuses))
|
|
236
|
+
|
|
237
|
+
if self.error is not None and self.error != "" and self.status != TrackedRunStatus.FAILED:
|
|
238
|
+
raise ValueError("Error message must be empty if the run succeeded.")
|
|
239
|
+
|
|
240
|
+
if isinstance(self.input, Input):
|
|
241
|
+
if self.input.input_format != InputFormat.JSON:
|
|
242
|
+
raise ValueError("Input.input_format must be JSON.")
|
|
243
|
+
elif isinstance(self.input, dict):
|
|
244
|
+
try:
|
|
245
|
+
_ = json.dumps(self.input)
|
|
246
|
+
except (TypeError, OverflowError) as e:
|
|
247
|
+
raise ValueError("Input is dict[str, any] but it is not JSON serializable") from e
|
|
248
|
+
|
|
249
|
+
if isinstance(self.output, Output):
|
|
250
|
+
if self.output.output_format != OutputFormat.JSON:
|
|
251
|
+
raise ValueError("Output.output_format must be JSON.")
|
|
252
|
+
elif isinstance(self.output, dict):
|
|
253
|
+
try:
|
|
254
|
+
_ = json.dumps(self.output)
|
|
255
|
+
except (TypeError, OverflowError) as e:
|
|
256
|
+
raise ValueError("Output is dict[str, any] but it is not JSON serializable") from e
|
|
257
|
+
|
|
258
|
+
def logs_text(self) -> str:
|
|
259
|
+
"""
|
|
260
|
+
Returns the logs as a single string.
|
|
261
|
+
|
|
262
|
+
Parameters
|
|
263
|
+
----------
|
|
264
|
+
None
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
str
|
|
269
|
+
The logs as a single string.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
if self.logs is None:
|
|
273
|
+
return ""
|
|
274
|
+
|
|
275
|
+
return "\n".join(self.logs)
|
nextmv/input.py
CHANGED
|
@@ -91,6 +91,26 @@ class Input:
|
|
|
91
91
|
new_options = copy.deepcopy(init_options)
|
|
92
92
|
self.options = new_options
|
|
93
93
|
|
|
94
|
+
def to_dict(self) -> dict[str, any]:
|
|
95
|
+
"""
|
|
96
|
+
Convert the input to a dictionary.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
None
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
-------
|
|
104
|
+
dict[str, any]
|
|
105
|
+
The input as a dictionary.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"data": self.data,
|
|
110
|
+
"input_format": self.input_format.value,
|
|
111
|
+
"options": self.options.to_dict() if self.options is not None else None,
|
|
112
|
+
}
|
|
113
|
+
|
|
94
114
|
|
|
95
115
|
class InputLoader:
|
|
96
116
|
"""Base class for loading inputs."""
|
nextmv/output.py
CHANGED
|
@@ -322,6 +322,29 @@ class Output:
|
|
|
322
322
|
"output_format OutputFormat.CSV_ARCHIVE, supported type is `dict`"
|
|
323
323
|
)
|
|
324
324
|
|
|
325
|
+
def to_dict(self) -> dict[str, any]:
|
|
326
|
+
"""
|
|
327
|
+
Convert the `Output` object to a dictionary.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
dict[str, any]
|
|
332
|
+
The dictionary representation of the `Output` object.
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
output_dict = {
|
|
336
|
+
"options": self.options.to_dict() if self.options is not None else None,
|
|
337
|
+
"output_format": self.output_format,
|
|
338
|
+
"solution": self.solution,
|
|
339
|
+
"statistics": self.statistics.to_dict() if self.statistics is not None else None,
|
|
340
|
+
"assets": [asset.to_dict() for asset in self.assets] if self.assets is not None else None,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if self.output_format == OutputFormat.CSV_ARCHIVE:
|
|
344
|
+
output_dict["csv_configurations"] = self.csv_configurations
|
|
345
|
+
|
|
346
|
+
return output_dict
|
|
347
|
+
|
|
325
348
|
|
|
326
349
|
class OutputWriter:
|
|
327
350
|
"""Base class for writing outputs."""
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
nextmv/__about__.py,sha256=
|
|
1
|
+
nextmv/__about__.py,sha256=1EcNvwMaSDEkTRoL7sc_hMTild6kSCT-JOUHMHTrrw8,24
|
|
2
2
|
nextmv/__entrypoint__.py,sha256=o6xYGBBUgCY_htteW-qNJfp-S3Th8dzS4RreJcDCnzM,1333
|
|
3
3
|
nextmv/__init__.py,sha256=rz9oP7JGyFQJdUtYX4j9xx4Gv4LMGfNvXoEID5qUqig,1354
|
|
4
4
|
nextmv/base_model.py,sha256=mdaBe-epNK1cFgP4TxbOtn3So4pCi1vMTOrIBkCBp7A,1050
|
|
5
|
-
nextmv/input.py,sha256=
|
|
5
|
+
nextmv/input.py,sha256=tYjiD9KM37Phqzm4faO4QLem8IXnU6HS160c1E-Yhc8,12732
|
|
6
6
|
nextmv/logger.py,sha256=5qQ7E3Aaw3zzkIeiUuwYGzTcB7VhVqIzNZm5PHmdpUI,852
|
|
7
7
|
nextmv/model.py,sha256=rwBdgmKSEp1DPv43zF0azj3QnbHO6O6wKs0PIGvVS40,9861
|
|
8
8
|
nextmv/options.py,sha256=DZmjNxZyaTwnG2MEEKbIiBx3hI5F2DG70rY7DdeX44M,17336
|
|
9
|
-
nextmv/output.py,sha256=
|
|
10
|
-
nextmv/cloud/__init__.py,sha256=
|
|
9
|
+
nextmv/output.py,sha256=cxw7Z0dTj5u42w2MQLOz2gesj046tvYFSWmhSZtRSXU,20082
|
|
10
|
+
nextmv/cloud/__init__.py,sha256=ojgA2vQRQnR8QO2tJme1TbGrjhr9yq9LIauxKJ5F40U,3305
|
|
11
11
|
nextmv/cloud/acceptance_test.py,sha256=NtqGhj-UYibxGBbU2kfjr-lYcngojb_5VMvK2WZwibI,6620
|
|
12
12
|
nextmv/cloud/account.py,sha256=mZUGzV-uMGBA5BC_FPtsiCMFuz5jxEZ3O1BbELZIm18,1841
|
|
13
|
-
nextmv/cloud/application.py,sha256=
|
|
13
|
+
nextmv/cloud/application.py,sha256=VQfi7Acv19ezAUM6ODj4NsIka8XYB3ibgOkuKvkuQNw,55909
|
|
14
14
|
nextmv/cloud/batch_experiment.py,sha256=UxrMNAm2c7-RZ9TWRhZBtiVyEeUG1pjsLNhYkoT5Jog,2236
|
|
15
15
|
nextmv/cloud/client.py,sha256=E4C8wOGOdgyMf-DCDt4YfI7ib8HwmUhAtKMdrSBJc2M,9004
|
|
16
16
|
nextmv/cloud/input_set.py,sha256=ovkP17-jYs0yWrbqTM6Nl5ubWQabD_UrDqAHNo8aE2s,672
|
|
17
17
|
nextmv/cloud/instance.py,sha256=UfyfZXfL1ugCGAB6zwZJIAi8qxI1JCUGsluwaGdjfN4,1223
|
|
18
18
|
nextmv/cloud/manifest.py,sha256=Fdjw4c14_ph20ASOzBs2mLslyLs1jGSxWThipGFp8rk,7458
|
|
19
19
|
nextmv/cloud/package.py,sha256=Y3RethLiXXW7Y6_QBJDJdd7mFNaYaSBMyasIeGLav8o,12287
|
|
20
|
-
nextmv/cloud/run.py,sha256=
|
|
20
|
+
nextmv/cloud/run.py,sha256=4hEANGXysDiOI1SQtfs7t_IVk-bxCldIs2TOvCI7a3E,8689
|
|
21
21
|
nextmv/cloud/secrets.py,sha256=kqlN4ceww_L4kVTrAU8BZykRzXINO3zhMT_BLYea6tk,1764
|
|
22
22
|
nextmv/cloud/status.py,sha256=C-ax8cLw0jPeh7CPsJkCa0s4ImRyFI4NDJJxI0_1sr4,602
|
|
23
23
|
nextmv/cloud/version.py,sha256=sjVRNRtohHA97j6IuyM33_DSSsXYkZPusYgpb6hlcrc,1244
|
|
24
|
-
nextmv-0.
|
|
25
|
-
nextmv-0.
|
|
26
|
-
nextmv-0.
|
|
27
|
-
nextmv-0.
|
|
24
|
+
nextmv-0.21.1.dist-info/METADATA,sha256=fLNFHuNeJvQST1BsX0vY7UmqrkFtrb5h8sp0ptN6ew4,14557
|
|
25
|
+
nextmv-0.21.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
26
|
+
nextmv-0.21.1.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
|
|
27
|
+
nextmv-0.21.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|