nextmv 0.37.1.dev0__py3-none-any.whl → 0.38.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.
nextmv/__about__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "v0.37.1.dev.0"
1
+ __version__ = "v0.38.0"
nextmv/cloud/__init__.py CHANGED
@@ -57,6 +57,7 @@ from .account import Queue as Queue
57
57
  from .account import QueuedRun as QueuedRun
58
58
  from .application import Application as Application
59
59
  from .application import poll as poll
60
+ from .assets import RunAsset as RunAsset
60
61
  from .batch_experiment import BatchExperiment as BatchExperiment
61
62
  from .batch_experiment import BatchExperimentInformation as BatchExperimentInformation
62
63
  from .batch_experiment import BatchExperimentMetadata as BatchExperimentMetadata
@@ -22,8 +22,10 @@ poll
22
22
  Function to poll for results with configurable options.
23
23
  """
24
24
 
25
+ import io
25
26
  import json
26
27
  import os
28
+ import pathlib
27
29
  import shutil
28
30
  import tarfile
29
31
  import tempfile
@@ -37,6 +39,7 @@ from nextmv._serialization import deflated_serialize_json
37
39
  from nextmv.base_model import BaseModel
38
40
  from nextmv.cloud import package
39
41
  from nextmv.cloud.acceptance_test import AcceptanceTest, Metric
42
+ from nextmv.cloud.assets import RunAsset
40
43
  from nextmv.cloud.batch_experiment import (
41
44
  BatchExperiment,
42
45
  BatchExperimentInformation,
@@ -2539,6 +2542,120 @@ class Application:
2539
2542
  except OSError as e:
2540
2543
  raise Exception(f"error deleting output directory: {e}") from e
2541
2544
 
2545
+ def list_assets(self, run_id: str) -> list[RunAsset]:
2546
+ """
2547
+ List the assets of a run.
2548
+
2549
+ Retrieves a list of assets associated with a specific run. This method ONLY
2550
+ returns the asset metadata, the content needs to be fetched via the
2551
+ `download_asset_content` method.
2552
+
2553
+ Parameters
2554
+ ----------
2555
+ run_id : str
2556
+ ID of the run to list assets for.
2557
+
2558
+ Returns
2559
+ -------
2560
+ list[RunAsset]
2561
+ List of assets associated with the run.
2562
+
2563
+ Raises
2564
+ ------
2565
+ requests.HTTPError
2566
+ If the response status code is not 2xx.
2567
+
2568
+ Examples
2569
+ --------
2570
+ >>> assets = app.list_assets("run-123")
2571
+ >>> for asset in assets:
2572
+ ... print(asset.id, asset.name)
2573
+ b459daa6-1c13-48c6-b4c3-a262ea94cd04 clustering_polygons
2574
+ a1234567-89ab-cdef-0123-456789abcdef histogram
2575
+ """
2576
+ response = self.client.request(
2577
+ method="GET",
2578
+ endpoint=f"{self.endpoint}/runs/{run_id}/assets",
2579
+ )
2580
+ assets_data = response.json().get("items", [])
2581
+ for asset_data in assets_data:
2582
+ asset_data["run_id"] = run_id
2583
+ return [RunAsset.from_dict(asset) for asset in assets_data]
2584
+
2585
+ def download_asset_content(
2586
+ self,
2587
+ asset: RunAsset,
2588
+ destination: str | pathlib.Path | io.BytesIO | None = None,
2589
+ ) -> Any | None:
2590
+ """
2591
+ Downloads an asset's content to a specified destination.
2592
+
2593
+ Parameters
2594
+ ----------
2595
+ asset : RunAsset
2596
+ The asset to be downloaded.
2597
+ destination : Union[str, pathlib.Path, io.BytesIO, None]
2598
+ The destination where the asset will be saved. This can be a file path
2599
+ (as a string or pathlib.Path) or an io.BytesIO object. If None, the asset
2600
+ content will not be saved to a file, but returned immediately. If the asset
2601
+ type is JSON, the content will be returned as a dict.
2602
+
2603
+ Returns
2604
+ -------
2605
+ Any or None
2606
+ If `destination` is None, returns the asset content: for JSON assets, a
2607
+ `dict` parsed from the JSON response; for other asset types, the raw
2608
+ `bytes` content. If `destination` is provided, the content is written
2609
+ to the given destination and the method returns `None`.
2610
+
2611
+ Raises
2612
+ ------
2613
+ requests.HTTPError
2614
+ If the response status code is not 2xx.
2615
+
2616
+ Examples
2617
+ --------
2618
+ >>> assets = app.list_assets("run-123")
2619
+ >>> asset = assets[0] # Assume we want to download the first asset
2620
+ >>> # Download to a file path
2621
+ >>> app.download_asset_content(asset, "polygons.geojson")
2622
+ >>> # Download to an in-memory bytes buffer
2623
+ >>> import io
2624
+ >>> buffer = io.BytesIO()
2625
+ >>> app.download_asset_content(asset, buffer)
2626
+ >>> # Download and get content directly (for JSON assets)
2627
+ >>> content = app.download_asset_content(asset)
2628
+ >>> print(content)
2629
+ {'type': 'FeatureCollection', 'features': [...]}
2630
+ """
2631
+ # First, get the download_url for the asset.
2632
+ download_url_response = self.client.request(
2633
+ method="GET",
2634
+ endpoint=f"{self.endpoint}/runs/{asset.run_id}/assets/{asset.id}",
2635
+ ).json()
2636
+ download_url = download_url_response["download_url"]
2637
+ asset_type = download_url_response.get("type", "json")
2638
+
2639
+ # Now, download the asset content using the download_url.
2640
+ download_response = self.client.request(
2641
+ method="GET",
2642
+ endpoint=download_url,
2643
+ headers={"Content-Type": "application/json" if asset_type == "json" else "application/octet-stream"},
2644
+ )
2645
+
2646
+ # Save the content to the specified destination.
2647
+ if destination is None:
2648
+ if asset_type == "json":
2649
+ return download_response.json()
2650
+ return download_response.content
2651
+ elif isinstance(destination, io.BytesIO):
2652
+ destination.write(download_response.content)
2653
+ return None
2654
+ else:
2655
+ with open(destination, "wb") as file:
2656
+ file.write(download_response.content)
2657
+ return None
2658
+
2542
2659
  def run_input(self, run_id: str) -> dict[str, Any]:
2543
2660
  """
2544
2661
  Get the input of a run.
nextmv/cloud/assets.py ADDED
@@ -0,0 +1,48 @@
1
+ from nextmv.base_model import BaseModel
2
+ from nextmv.output import Visual
3
+
4
+
5
+ class RunAsset(BaseModel):
6
+ """
7
+ Represents an asset associated with a Nextmv Cloud run.
8
+
9
+ You can import the `RunAsset` class from `nextmv.cloud`:
10
+
11
+ ```python
12
+ from nextmv.cloud import RunAsset
13
+ ```
14
+
15
+ A run asset represents metadata about an asset associated with a Nextmv Cloud run.
16
+
17
+ Parameters
18
+ ----------
19
+ id : str
20
+ Unique identifier of the asset.
21
+ run_id : str
22
+ Identifier of the run associated with the asset.
23
+ name : str
24
+ Name of the asset.
25
+ created_at : str
26
+ Timestamp of when the asset was created.
27
+ size : int
28
+ Size of the asset content in bytes.
29
+ content_type : str
30
+ Content type of the asset. Only `json` is allowed at the moment.
31
+ visual : Visual | None, optional
32
+ Visual schema of the asset, by default None.
33
+ """
34
+
35
+ id: str
36
+ """Unique identifier of the asset."""
37
+ run_id: str
38
+ """Identifier of the run associated with the asset."""
39
+ name: str
40
+ """Name of the asset."""
41
+ created_at: str
42
+ """Timestamp of when the asset was created."""
43
+ size: int
44
+ """Size of the asset content in bytes."""
45
+ content_type: str
46
+ """Content type of the asset. Only `json` is allowed at the moment."""
47
+ visual: Visual | None = None
48
+ """Visual schema of the asset."""
nextmv/output.py CHANGED
@@ -469,9 +469,17 @@ class Asset(BaseModel):
469
469
 
470
470
  name: str
471
471
  """Name of the asset."""
472
- content: Any
473
- """Content of the asset. The type must be serializable to JSON."""
474
472
 
473
+ id: str | None = None
474
+ """
475
+ The ID of the asset. This ID will be populated by the Nextmv platform and can be used
476
+ to download the asset later.
477
+ """
478
+ content: Any | None = None
479
+ """
480
+ Content of the asset. The type must be serializable to JSON. Can be empty when
481
+ fetching the asset metadata only (e.g.: via the asset list endpoint).
482
+ """
475
483
  content_type: str | None = "json"
476
484
  """Content type of the asset. Only `json` is allowed"""
477
485
  description: str | None = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextmv
3
- Version: 0.37.1.dev0
3
+ Version: 0.38.0
4
4
  Summary: The all-purpose Python SDK for Nextmv
5
5
  Project-URL: Homepage, https://www.nextmv.io
6
6
  Project-URL: Documentation, https://nextmv-py.docs.nextmv.io/en/latest/nextmv/
@@ -1,4 +1,4 @@
1
- nextmv/__about__.py,sha256=jmJy02hqA-ohOlVowf2bmUsdVIOh5HJI-wmoH6yjah0,30
1
+ nextmv/__about__.py,sha256=TdPLKw4e3k7e30CulJMVTdoN7MAhlwuoBr5ZI1SLWBI,24
2
2
  nextmv/__entrypoint__.py,sha256=dA0iwwHtrq6Z9w9FxmxKLoBGLyhe7jWtUAU-Y3PEgHg,1094
3
3
  nextmv/__init__.py,sha256=mC-gAzCdoZJ0BOVe2fDzKNdBtbXzx8XOxHP_7DdPMdQ,3857
4
4
  nextmv/_serialization.py,sha256=jYitMS1MU8ldsmObT-K_8V8P2Wx69tnDiEHCCgPGun4,2834
@@ -9,15 +9,16 @@ nextmv/logger.py,sha256=kNIbu46MisrzYe4T0hNMpWfRTKKacDVvbtQcNys_c_E,2513
9
9
  nextmv/manifest.py,sha256=v9En9zMZVKZn6G_HThoKUZowMtZr5hxzwWiK9wkVHPU,49023
10
10
  nextmv/model.py,sha256=9g-pAUg8E--CyUFU39J7e4MZcj0WrWtgIqfwGmoF7UA,14998
11
11
  nextmv/options.py,sha256=rfQV0F5_it-L27wjqrs902wr4Q7sgSN7dw5o0c5sdEg,37842
12
- nextmv/output.py,sha256=f7LZTs6vr8MEpzg2OFzZar0LOMVzglM3EpR7kYW-vrk,56007
12
+ nextmv/output.py,sha256=kjGWfgXfsEL6Np9xDhJdKyXYA17bMGjuuthDAWHzl2M,56288
13
13
  nextmv/polling.py,sha256=Mka7tChVR7Ws-Hg9W-Yzo7wthhVg-Qp-FK54E70Fzdw,9711
14
14
  nextmv/run.py,sha256=qeT3a0eqVHKvbQiFxmX_2epaj-rz2GyfpdZJ4ROFC4U,53135
15
15
  nextmv/safe.py,sha256=VAK4fGEurbLNji4Pg5Okga5XQSbI4aI9JJf95_68Z20,3867
16
16
  nextmv/status.py,sha256=SCDLhh2om3yeO5FxO0x-_RShQsZNXEpjHNdCGdb3VUI,2787
17
- nextmv/cloud/__init__.py,sha256=4gV_w84BCenHlG5kTOWyGlg_I-kFxi4_XVb0K8No8vI,5295
17
+ nextmv/cloud/__init__.py,sha256=n8laWdl0BjKmJOgGdPLHYUY_eaqhmXlW0-421A3SfwM,5336
18
18
  nextmv/cloud/acceptance_test.py,sha256=fZdp4O6pZrl7TaiUrTFPp7O4VJt-4R_W2yo4s8UAS5I,27691
19
19
  nextmv/cloud/account.py,sha256=jIdGNyI3l3dVh2PuriAwAOrEuWRM150WgzxcBMVBNRw,6058
20
- nextmv/cloud/application.py,sha256=VU3F9M8VqPhUZL-6g6am-NFh_LQQ9j3gZCRAhY0qU_o,139892
20
+ nextmv/cloud/application.py,sha256=tJRi6lfzfsPemqe37p4MigpVEnvi5fSiufUmErLxKk4,144220
21
+ nextmv/cloud/assets.py,sha256=alw634ub-DR0CHQXZy_ObeGvQthPXFLdgzgbvbH1SGY,1376
21
22
  nextmv/cloud/batch_experiment.py,sha256=VmKgjBW6BpkH4loO0afMGNXrwJ5whhijQ99pm94vBto,10349
22
23
  nextmv/cloud/client.py,sha256=Yj4FE4GKsLHkYijAYXcotlyNfhAWANMuWetHXsYPg1M,18101
23
24
  nextmv/cloud/ensemble.py,sha256=_hKPjSLtuGH1xGG70ZBsmY_IL5XinZqVHHwBxtX9Omw,8570
@@ -44,7 +45,7 @@ nextmv/local/geojson_handler.py,sha256=7FavJdkUonop-yskjis0x3qFGB8A5wZyoBUblw-bV
44
45
  nextmv/local/local.py,sha256=cp56UpI8h19Ob6Jvb_Ni0ceXH5Vv3ET_iPTDe6ftq3Y,2617
45
46
  nextmv/local/plotly_handler.py,sha256=bLb50e3AkVr_W-F6S7lXfeRdN60mG2jk3UElNmhoMWU,1930
46
47
  nextmv/local/runner.py,sha256=Fa-G4g5yaBgLeBfYU-ePs65Q3Ses_xYvXGhPtHpAkrU,8546
47
- nextmv-0.37.1.dev0.dist-info/METADATA,sha256=G1Emcy0_HHozlRmqPaOR8MW6SXPbFA8du6i5ocvXOPg,15995
48
- nextmv-0.37.1.dev0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
49
- nextmv-0.37.1.dev0.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
50
- nextmv-0.37.1.dev0.dist-info/RECORD,,
48
+ nextmv-0.38.0.dist-info/METADATA,sha256=pwHkld0mBlgrNOibG0ruEVsRWDuJlC-rd1IyfuDGD3E,15990
49
+ nextmv-0.38.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
50
+ nextmv-0.38.0.dist-info/licenses/LICENSE,sha256=ZIbK-sSWA-OZprjNbmJAglYRtl5_K4l9UwAV3PGJAPc,11349
51
+ nextmv-0.38.0.dist-info/RECORD,,