codeocean 0.12.0__tar.gz → 0.14.0__tar.gz
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.
- {codeocean-0.12.0 → codeocean-0.14.0}/CHANGELOG.md +8 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/PKG-INFO +1 -1
- {codeocean-0.12.0 → codeocean-0.14.0}/RELEASE.md +1 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/examples/run_pipeline.py +3 -11
- {codeocean-0.12.0 → codeocean-0.14.0}/pyproject.toml +1 -1
- codeocean-0.14.0/src/codeocean/capsule.py +114 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/src/codeocean/client.py +3 -1
- codeocean-0.14.0/src/codeocean/computation.py +164 -0
- codeocean-0.14.0/src/codeocean/data_asset.py +237 -0
- codeocean-0.14.0/src/codeocean/models/__init__.py +1 -0
- {codeocean-0.12.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/capsule.py +2 -110
- codeocean-0.14.0/src/codeocean/models/components.py +148 -0
- {codeocean-0.12.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/computation.py +0 -125
- {codeocean-0.12.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/data_asset.py +53 -205
- codeocean-0.14.0/src/codeocean/pipeline.py +71 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/.flake8 +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/.gitignore +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/LICENSE +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/README.md +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/examples/create_data_asset.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/examples/run_capsule.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/src/codeocean/__init__.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/src/codeocean/components.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/src/codeocean/custom_metadata.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/src/codeocean/enum.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/src/codeocean/error.py +0 -0
- {codeocean-0.12.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/folder.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/tests/__init__.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/tests/test_client.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/tests/test_error.py +0 -0
- {codeocean-0.12.0 → codeocean-0.14.0}/tests/test_package.py +0 -0
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
## 0.14.0 (2026-01-29)
|
|
5
|
+
- [#66](https://github.com/codeocean/codeocean-sdk-python/pull/66) feat: Code Ocean version 4.2 functionality
|
|
6
|
+
- **Minimum Code Ocean platform version updated to `4.2.0`.**
|
|
7
|
+
|
|
8
|
+
## 0.13.0 (2026-01-23)
|
|
9
|
+
- [#64](https://github.com/codeocean/codeocean-sdk-python/pull/64) feat: Code Ocean version 4.1 functionality
|
|
10
|
+
- **Minimum Code Ocean platform version updated to `4.1.0`.**
|
|
11
|
+
|
|
4
12
|
## 0.12.0 (2025-10-20)
|
|
5
13
|
- [#61](https://github.com/codeocean/codeocean-sdk-python/pull/61) feat: add search_pipelines method
|
|
6
14
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
1. Open a PR with the following changes:
|
|
4
4
|
1. Bump the version in [pyproject.toml](pyproject.toml).
|
|
5
5
|
1. Update the [CHANGELOG.md](CHANGELOG.md).
|
|
6
|
+
1. If the change requires a newer Code Ocean server version, update `MIN_SERVER_VERSION` in [client.py](src/codeocean/client.py).
|
|
6
7
|
1. Commit the updates with the message `Bump version to X.Y.Z`.
|
|
7
8
|
1. Merge the PR.
|
|
8
9
|
1. Locally, sync your clone with GitHub:
|
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
3
|
from codeocean import CodeOcean
|
|
4
|
-
from codeocean.computation import RunParams
|
|
5
|
-
from codeocean.data_asset import
|
|
6
|
-
DataAssetParams,
|
|
7
|
-
DataAssetsRunParam,
|
|
8
|
-
PipelineProcessParams,
|
|
9
|
-
Source,
|
|
10
|
-
ComputationSource,
|
|
11
|
-
Target,
|
|
12
|
-
AWSS3Target,
|
|
13
|
-
)
|
|
4
|
+
from codeocean.computation import RunParams, DataAssetsRunParam, PipelineProcessParams
|
|
5
|
+
from codeocean.data_asset import DataAssetParams, Source, ComputationSource, Target, AWSS3Target
|
|
14
6
|
|
|
15
7
|
|
|
16
8
|
# Create the client using your domain and API token.
|
|
@@ -38,7 +30,7 @@ run_params = RunParams(
|
|
|
38
30
|
],
|
|
39
31
|
)
|
|
40
32
|
|
|
41
|
-
computation = client.computations.
|
|
33
|
+
computation = client.computations.run_pipeline(run_params)
|
|
42
34
|
|
|
43
35
|
# Wait for pipeline to finish.
|
|
44
36
|
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from requests_toolbelt.sessions import BaseUrlSession
|
|
5
|
+
from typing import Optional, Iterator
|
|
6
|
+
|
|
7
|
+
from codeocean.models.capsule import (
|
|
8
|
+
Capsule,
|
|
9
|
+
CapsuleSearchParams,
|
|
10
|
+
CapsuleSearchResults,
|
|
11
|
+
AppPanel,
|
|
12
|
+
)
|
|
13
|
+
# Re-exports for backward compatibility
|
|
14
|
+
from codeocean.models.capsule import ( # noqa: F401
|
|
15
|
+
CapsuleStatus,
|
|
16
|
+
CapsuleSortBy,
|
|
17
|
+
OriginalCapsuleInfo,
|
|
18
|
+
AppPanelDataAssetKind,
|
|
19
|
+
AppPanelParameterType,
|
|
20
|
+
AppPanelCategories,
|
|
21
|
+
AppPanelParameters,
|
|
22
|
+
AppPanelGeneral,
|
|
23
|
+
AppPanelDataAsset,
|
|
24
|
+
AppPanelResult,
|
|
25
|
+
AppPanelProcess,
|
|
26
|
+
)
|
|
27
|
+
from codeocean.models.components import Permissions
|
|
28
|
+
from codeocean.models.computation import Computation
|
|
29
|
+
from codeocean.models.data_asset import DataAssetAttachParams, DataAssetAttachResults
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class Capsules:
|
|
34
|
+
"""Client for interacting with Code Ocean capsule APIs."""
|
|
35
|
+
|
|
36
|
+
client: BaseUrlSession
|
|
37
|
+
_route: str = "capsules"
|
|
38
|
+
|
|
39
|
+
def get_capsule(self, capsule_id: str) -> Capsule:
|
|
40
|
+
"""Retrieve metadata for a specific capsule by its ID."""
|
|
41
|
+
res = self.client.get(f"{self._route}/{capsule_id}")
|
|
42
|
+
|
|
43
|
+
return Capsule.from_dict(res.json())
|
|
44
|
+
|
|
45
|
+
def delete_capsule(self, capsule_id: str):
|
|
46
|
+
"""Delete a capsule permanently."""
|
|
47
|
+
self.client.delete(f"{self._route}/{capsule_id}")
|
|
48
|
+
|
|
49
|
+
def get_capsule_app_panel(self, capsule_id: str, version: Optional[int] = None) -> AppPanel:
|
|
50
|
+
"""Retrieve app panel information for a specific capsule by its ID."""
|
|
51
|
+
res = self.client.get(f"{self._route}/{capsule_id}/app_panel", params={"version": version} if version else None)
|
|
52
|
+
|
|
53
|
+
return AppPanel.from_dict(res.json())
|
|
54
|
+
|
|
55
|
+
def list_computations(self, capsule_id: str) -> list[Computation]:
|
|
56
|
+
"""Get all computations associated with a specific capsule."""
|
|
57
|
+
res = self.client.get(f"{self._route}/{capsule_id}/computations")
|
|
58
|
+
|
|
59
|
+
return [Computation.from_dict(c) for c in res.json()]
|
|
60
|
+
|
|
61
|
+
def update_permissions(self, capsule_id: str, permissions: Permissions):
|
|
62
|
+
"""Update permissions for a capsule."""
|
|
63
|
+
self.client.post(
|
|
64
|
+
f"{self._route}/{capsule_id}/permissions",
|
|
65
|
+
json=permissions.to_dict(),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
def attach_data_assets(
|
|
69
|
+
self,
|
|
70
|
+
capsule_id: str,
|
|
71
|
+
attach_params: list[DataAssetAttachParams],
|
|
72
|
+
) -> list[DataAssetAttachResults]:
|
|
73
|
+
"""Attach one or more data assets to a capsule with optional mount paths."""
|
|
74
|
+
res = self.client.post(
|
|
75
|
+
f"{self._route}/{capsule_id}/data_assets",
|
|
76
|
+
json=[j.to_dict() for j in attach_params],
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
return [DataAssetAttachResults.from_dict(c) for c in res.json()]
|
|
80
|
+
|
|
81
|
+
def detach_data_assets(self, capsule_id: str, data_assets: list[str]):
|
|
82
|
+
"""Detach one or more data assets from a capsule by their IDs."""
|
|
83
|
+
self.client.delete(
|
|
84
|
+
f"{self._route}/{capsule_id}/data_assets/",
|
|
85
|
+
json=data_assets,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def archive_capsule(self, capsule_id: str, archive: bool):
|
|
89
|
+
"""Archive or unarchive a capsule to control its visibility and accessibility."""
|
|
90
|
+
self.client.patch(
|
|
91
|
+
f"{self._route}/{capsule_id}/archive",
|
|
92
|
+
params={"archive": archive},
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults:
|
|
96
|
+
"""Search for capsules with filtering, sorting, and pagination
|
|
97
|
+
options."""
|
|
98
|
+
res = self.client.post(f"{self._route}/search", json=search_params.to_dict())
|
|
99
|
+
|
|
100
|
+
return CapsuleSearchResults.from_dict(res.json())
|
|
101
|
+
|
|
102
|
+
def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsule]:
|
|
103
|
+
"""Iterate through all capsules matching search criteria with automatic pagination."""
|
|
104
|
+
params = search_params.to_dict()
|
|
105
|
+
while True:
|
|
106
|
+
response = self.search_capsules(search_params=CapsuleSearchParams(**params))
|
|
107
|
+
|
|
108
|
+
for result in response.results:
|
|
109
|
+
yield result
|
|
110
|
+
|
|
111
|
+
if not response.has_more:
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
params["next_token"] = response.next_token
|
|
@@ -12,6 +12,7 @@ from codeocean.computation import Computations
|
|
|
12
12
|
from codeocean.custom_metadata import CustomMetadataSchema
|
|
13
13
|
from codeocean.data_asset import DataAssets
|
|
14
14
|
from codeocean.error import Error
|
|
15
|
+
from codeocean.pipeline import Pipelines
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
@dataclass
|
|
@@ -37,7 +38,7 @@ class CodeOcean:
|
|
|
37
38
|
agent_id: Optional[str] = None
|
|
38
39
|
|
|
39
40
|
# Minimum server version required by this SDK
|
|
40
|
-
MIN_SERVER_VERSION = "4.
|
|
41
|
+
MIN_SERVER_VERSION = "4.2.0"
|
|
41
42
|
|
|
42
43
|
def __post_init__(self):
|
|
43
44
|
self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
|
|
@@ -55,6 +56,7 @@ class CodeOcean:
|
|
|
55
56
|
self.computations = Computations(client=self.session)
|
|
56
57
|
self.custom_metadata = CustomMetadataSchema(client=self.session)
|
|
57
58
|
self.data_assets = DataAssets(client=self.session)
|
|
59
|
+
self.pipelines = Pipelines(client=self.session)
|
|
58
60
|
|
|
59
61
|
def _error_handler(self, response, *args, **kwargs):
|
|
60
62
|
try:
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from requests_toolbelt.sessions import BaseUrlSession
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from time import sleep, time
|
|
7
|
+
from warnings import warn
|
|
8
|
+
|
|
9
|
+
from codeocean.models.computation import Computation, ComputationState, RunParams
|
|
10
|
+
# Re-exports for backward compatibility
|
|
11
|
+
from codeocean.models.computation import ( # noqa: F401
|
|
12
|
+
ComputationEndStatus,
|
|
13
|
+
Param,
|
|
14
|
+
PipelineProcess,
|
|
15
|
+
InputDataAsset,
|
|
16
|
+
DataAssetsRunParam,
|
|
17
|
+
NamedRunParam,
|
|
18
|
+
PipelineProcessParams,
|
|
19
|
+
)
|
|
20
|
+
from codeocean.models.data_asset import DataAssetAttachParams, DataAssetAttachResults
|
|
21
|
+
from codeocean.models.folder import FileURLs, Folder, DownloadFileURL
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class Computations:
|
|
26
|
+
"""Client for interacting with Code Ocean computation APIs."""
|
|
27
|
+
|
|
28
|
+
client: BaseUrlSession
|
|
29
|
+
|
|
30
|
+
def get_computation(self, computation_id: str) -> Computation:
|
|
31
|
+
"""Retrieve metadata and status information for a specific computation by its ID."""
|
|
32
|
+
res = self.client.get(f"computations/{computation_id}")
|
|
33
|
+
|
|
34
|
+
return Computation.from_dict(res.json())
|
|
35
|
+
|
|
36
|
+
def run_capsule(self, run_params: RunParams) -> Computation:
|
|
37
|
+
"""
|
|
38
|
+
Execute a capsule or pipeline with specified parameters and data assets.
|
|
39
|
+
|
|
40
|
+
For capsule execution: Set run_params.capsule_id and optionally provide data_assets,
|
|
41
|
+
parameters, or named_parameters.
|
|
42
|
+
|
|
43
|
+
For pipeline execution: Set run_params.pipeline_id and optionally provide data_assets,
|
|
44
|
+
processes (with process-specific parameters), and nextflow_profile configuration.
|
|
45
|
+
|
|
46
|
+
"""
|
|
47
|
+
res = self.client.post("computations", json=run_params.to_dict())
|
|
48
|
+
|
|
49
|
+
return Computation.from_dict(res.json())
|
|
50
|
+
|
|
51
|
+
# Alias for run_capsule
|
|
52
|
+
run_pipeline = run_capsule
|
|
53
|
+
|
|
54
|
+
def wait_until_completed(
|
|
55
|
+
self,
|
|
56
|
+
computation: Computation,
|
|
57
|
+
polling_interval: float = 5,
|
|
58
|
+
timeout: Optional[float] = None,
|
|
59
|
+
) -> Computation:
|
|
60
|
+
"""
|
|
61
|
+
Poll a computation until it reaches 'Completed' or 'Failed' state with configurable timing.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
computation: The computation object to monitor
|
|
65
|
+
polling_interval: Time between status checks in seconds (minimum 5 seconds)
|
|
66
|
+
timeout: Maximum time to wait in seconds, or None for no timeout
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Updated computation object once completed or failed
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If polling_interval < 5 or timeout constraints are violated
|
|
73
|
+
TimeoutError: If computation doesn't complete within the timeout period
|
|
74
|
+
"""
|
|
75
|
+
if polling_interval < 5:
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Polling interval {polling_interval} should be greater than or equal to 5"
|
|
78
|
+
)
|
|
79
|
+
if timeout is not None and timeout < polling_interval:
|
|
80
|
+
raise ValueError(
|
|
81
|
+
f"Timeout {timeout} should be greater than or equal to polling interval {polling_interval}"
|
|
82
|
+
)
|
|
83
|
+
if timeout is not None and timeout < 0:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
f"Timeout {timeout} should be greater than or equal to 0 (seconds), or None"
|
|
86
|
+
)
|
|
87
|
+
t0 = time()
|
|
88
|
+
while True:
|
|
89
|
+
comp = self.get_computation(computation.id)
|
|
90
|
+
|
|
91
|
+
if comp.state in [ComputationState.Completed, ComputationState.Failed]:
|
|
92
|
+
return comp
|
|
93
|
+
|
|
94
|
+
if timeout is not None and (time() - t0) > timeout:
|
|
95
|
+
raise TimeoutError(
|
|
96
|
+
f"Computation {computation.id} did not complete within {timeout} seconds"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
sleep(polling_interval)
|
|
100
|
+
|
|
101
|
+
def attach_data_assets(
|
|
102
|
+
self,
|
|
103
|
+
computation_id: str,
|
|
104
|
+
attach_params: list[DataAssetAttachParams],
|
|
105
|
+
) -> list[DataAssetAttachResults]:
|
|
106
|
+
"""Attach one or more data assets to a cloud workstation session computation."""
|
|
107
|
+
res = self.client.post(
|
|
108
|
+
f"computations/{computation_id}/data_assets",
|
|
109
|
+
json=[j.to_dict() for j in attach_params],
|
|
110
|
+
)
|
|
111
|
+
return [DataAssetAttachResults.from_dict(c) for c in res.json()]
|
|
112
|
+
|
|
113
|
+
def detach_data_assets(self, computation_id: str, data_assets: list[str]):
|
|
114
|
+
"""Detach one or more data assets from a cloud workstation session computation by their IDs."""
|
|
115
|
+
self.client.delete(
|
|
116
|
+
f"computations/{computation_id}/data_assets/",
|
|
117
|
+
json=data_assets,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def list_computation_results(self, computation_id: str, path: str = "") -> Folder:
|
|
121
|
+
"""List result files and folders generated by a computation
|
|
122
|
+
at the specified path. Empty path retrieves the /results root folder."""
|
|
123
|
+
data = {
|
|
124
|
+
"path": path,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
res = self.client.post(f"computations/{computation_id}/results", json=data)
|
|
128
|
+
|
|
129
|
+
return Folder.from_dict(res.json())
|
|
130
|
+
|
|
131
|
+
def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL:
|
|
132
|
+
"""[DEPRECATED] Generate a download URL for a specific result file from a computation.
|
|
133
|
+
|
|
134
|
+
Deprecated: Use get_result_file_urls instead.
|
|
135
|
+
"""
|
|
136
|
+
warn(
|
|
137
|
+
"get_result_file_download_url is deprecated and will be removed in a future release. "
|
|
138
|
+
"Use get_result_file_urls instead.",
|
|
139
|
+
DeprecationWarning,
|
|
140
|
+
stacklevel=2,
|
|
141
|
+
)
|
|
142
|
+
res = self.client.get(
|
|
143
|
+
f"computations/{computation_id}/results/download_url",
|
|
144
|
+
params={"path": path},
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return DownloadFileURL.from_dict(res.json())
|
|
148
|
+
|
|
149
|
+
def get_result_file_urls(self, computation_id: str, path: str) -> FileURLs:
|
|
150
|
+
"""Generate view and download URLs for a specific result file from a computation."""
|
|
151
|
+
res = self.client.get(
|
|
152
|
+
f"computations/{computation_id}/results/urls",
|
|
153
|
+
params={"path": path},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return FileURLs.from_dict(res.json())
|
|
157
|
+
|
|
158
|
+
def delete_computation(self, computation_id: str):
|
|
159
|
+
"""Delete a computation and stop it if currently running."""
|
|
160
|
+
self.client.delete(f"computations/{computation_id}")
|
|
161
|
+
|
|
162
|
+
def rename_computation(self, computation_id: str, name: str):
|
|
163
|
+
"""Rename an existing computation with a new display name."""
|
|
164
|
+
self.client.patch(f"computations/{computation_id}", params={"name": name})
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from requests_toolbelt.sessions import BaseUrlSession
|
|
5
|
+
from time import sleep, time
|
|
6
|
+
from typing import Iterator
|
|
7
|
+
from warnings import warn
|
|
8
|
+
|
|
9
|
+
from codeocean.models.components import Permissions
|
|
10
|
+
from codeocean.models.data_asset import (
|
|
11
|
+
DataAsset,
|
|
12
|
+
DataAssetState,
|
|
13
|
+
DataAssetUpdateParams,
|
|
14
|
+
DataAssetParams,
|
|
15
|
+
DataAssetSearchParams,
|
|
16
|
+
DataAssetSearchResults,
|
|
17
|
+
TransferDataParams,
|
|
18
|
+
)
|
|
19
|
+
# Re-exports for backward compatibility
|
|
20
|
+
from codeocean.models.data_asset import ( # noqa: F401
|
|
21
|
+
DataAssetType,
|
|
22
|
+
DataAssetOrigin,
|
|
23
|
+
Provenance,
|
|
24
|
+
SourceBucket,
|
|
25
|
+
AppParameter,
|
|
26
|
+
ResultsInfo,
|
|
27
|
+
Source,
|
|
28
|
+
AWSS3Source,
|
|
29
|
+
GCPCloudStorageSource,
|
|
30
|
+
ComputationSource,
|
|
31
|
+
CloudWorkstationSource,
|
|
32
|
+
Target,
|
|
33
|
+
AWSS3Target,
|
|
34
|
+
DataAssetAttachParams,
|
|
35
|
+
DataAssetAttachResults,
|
|
36
|
+
DataAssetSortBy,
|
|
37
|
+
DataAssetSearchOrigin,
|
|
38
|
+
ContainedDataAsset,
|
|
39
|
+
)
|
|
40
|
+
from codeocean.models.folder import FileURLs, Folder, DownloadFileURL
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class DataAssets:
|
|
45
|
+
"""Client for interacting with Code Ocean data asset APIs."""
|
|
46
|
+
|
|
47
|
+
client: BaseUrlSession
|
|
48
|
+
|
|
49
|
+
def get_data_asset(self, data_asset_id: str) -> DataAsset:
|
|
50
|
+
"""Retrieve metadata for a specific data asset by its ID."""
|
|
51
|
+
res = self.client.get(f"data_assets/{data_asset_id}")
|
|
52
|
+
|
|
53
|
+
return DataAsset.from_dict(res.json())
|
|
54
|
+
|
|
55
|
+
def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset:
|
|
56
|
+
"""
|
|
57
|
+
Update metadata for a data asset including name, description, tags, mount,
|
|
58
|
+
and custom metadata.
|
|
59
|
+
|
|
60
|
+
Supports updating various metadata types:
|
|
61
|
+
- Basic metadata: name (display name), description (free text description)
|
|
62
|
+
- Organization: tags (keywords for searching), mount (default mount folder path)
|
|
63
|
+
- Custom metadata: admin-defined custom fields with user-set values according to
|
|
64
|
+
deployment configuration
|
|
65
|
+
(string, number, or date fields in unix epoch format)
|
|
66
|
+
"""
|
|
67
|
+
res = self.client.put(
|
|
68
|
+
f"data_assets/{data_asset_id}",
|
|
69
|
+
json=update_params.to_dict(),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return DataAsset.from_dict(res.json())
|
|
73
|
+
|
|
74
|
+
def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset:
|
|
75
|
+
"""
|
|
76
|
+
Create a new data asset from various sources including S3 buckets,
|
|
77
|
+
computation results, or combined assets.
|
|
78
|
+
|
|
79
|
+
Data assets are versioned, immutable collections of files that serve as inputs
|
|
80
|
+
or outputs for computational workflows in Code Ocean. Internal data assets
|
|
81
|
+
store files within Code Ocean's infrastructure, while external data assets
|
|
82
|
+
reference files in external storage (S3/GCP) without copying.
|
|
83
|
+
|
|
84
|
+
Supports creating data assets from AWS S3, GCP Cloud Storage, computation
|
|
85
|
+
results, or combining existing data assets. Returns confirmation of creation
|
|
86
|
+
request validity, not success, as creation takes time. Use wait_until_ready()
|
|
87
|
+
to monitor creation progress.
|
|
88
|
+
|
|
89
|
+
"""
|
|
90
|
+
res = self.client.post("data_assets", json=data_asset_params.to_dict())
|
|
91
|
+
|
|
92
|
+
return DataAsset.from_dict(res.json())
|
|
93
|
+
|
|
94
|
+
def wait_until_ready(
|
|
95
|
+
self,
|
|
96
|
+
data_asset: DataAsset,
|
|
97
|
+
polling_interval: float = 5,
|
|
98
|
+
timeout: float | None = None,
|
|
99
|
+
) -> DataAsset:
|
|
100
|
+
"""
|
|
101
|
+
Poll a data asset until it reaches 'Ready' or 'Failed' state with configurable
|
|
102
|
+
timing.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
data_asset: The data asset object to monitor
|
|
106
|
+
polling_interval: Time between status checks in seconds
|
|
107
|
+
(minimum 5 seconds)
|
|
108
|
+
timeout: Maximum time to wait in seconds, or None for no timeout
|
|
109
|
+
|
|
110
|
+
Returns:
|
|
111
|
+
Updated data asset object once ready or failed
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
ValueError: If polling_interval < 5 or timeout constraints are violated
|
|
115
|
+
TimeoutError: If data asset doesn't become ready within timeout period
|
|
116
|
+
"""
|
|
117
|
+
if polling_interval < 5:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Polling interval {polling_interval} should be greater than or equal to 5"
|
|
120
|
+
)
|
|
121
|
+
if timeout is not None and timeout < polling_interval:
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Timeout {timeout} should be greater than or equal to polling interval {polling_interval}"
|
|
124
|
+
)
|
|
125
|
+
if timeout is not None and timeout < 0:
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Timeout {timeout} should be greater than or equal to 0 (seconds), or None"
|
|
128
|
+
)
|
|
129
|
+
t0 = time()
|
|
130
|
+
while True:
|
|
131
|
+
da = self.get_data_asset(data_asset.id)
|
|
132
|
+
|
|
133
|
+
if da.state in [DataAssetState.Ready, DataAssetState.Failed]:
|
|
134
|
+
return da
|
|
135
|
+
|
|
136
|
+
if timeout is not None and (time() - t0) > timeout:
|
|
137
|
+
raise TimeoutError(
|
|
138
|
+
f"Data asset {data_asset.id} was not ready within {timeout} seconds"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
sleep(polling_interval)
|
|
142
|
+
|
|
143
|
+
def delete_data_asset(self, data_asset_id: str):
|
|
144
|
+
"""Delete a data asset permanently."""
|
|
145
|
+
self.client.delete(f"data_assets/{data_asset_id}")
|
|
146
|
+
|
|
147
|
+
def update_permissions(self, data_asset_id: str, permissions: Permissions):
|
|
148
|
+
"""Update permissions for a data asset to control user and group access."""
|
|
149
|
+
self.client.post(
|
|
150
|
+
f"data_assets/{data_asset_id}/permissions",
|
|
151
|
+
json=permissions.to_dict(),
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def archive_data_asset(self, data_asset_id: str, archive: bool):
|
|
155
|
+
"""Archive or unarchive a data asset to control its visibility and accessibility."""
|
|
156
|
+
self.client.patch(
|
|
157
|
+
f"data_assets/{data_asset_id}/archive",
|
|
158
|
+
params={"archive": archive},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults:
|
|
162
|
+
"""Search for data assets with filtering, sorting, and pagination options."""
|
|
163
|
+
res = self.client.post("data_assets/search", json=search_params.to_dict())
|
|
164
|
+
|
|
165
|
+
return DataAssetSearchResults.from_dict(res.json())
|
|
166
|
+
|
|
167
|
+
def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> Iterator[DataAsset]:
|
|
168
|
+
"""
|
|
169
|
+
Iterate through all data assets matching search criteria with
|
|
170
|
+
automatic pagination.
|
|
171
|
+
"""
|
|
172
|
+
params = search_params.to_dict()
|
|
173
|
+
while True:
|
|
174
|
+
response = self.search_data_assets(
|
|
175
|
+
search_params=DataAssetSearchParams(**params),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
for result in response.results:
|
|
179
|
+
yield result
|
|
180
|
+
|
|
181
|
+
if not response.has_more:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
params["next_token"] = response.next_token
|
|
185
|
+
|
|
186
|
+
def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder:
|
|
187
|
+
"""
|
|
188
|
+
List files and folders within an internal data asset at the specified path.
|
|
189
|
+
Empty path retrieves root level contents.
|
|
190
|
+
"""
|
|
191
|
+
data = {
|
|
192
|
+
"path": path,
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
res = self.client.post(f"data_assets/{data_asset_id}/files", json=data)
|
|
196
|
+
|
|
197
|
+
return Folder.from_dict(res.json())
|
|
198
|
+
|
|
199
|
+
def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> DownloadFileURL:
|
|
200
|
+
"""(Deprecated) Generate a download URL for a specific file from an internal data asset.
|
|
201
|
+
|
|
202
|
+
Deprecated: Use get_data_asset_file_urls instead.
|
|
203
|
+
"""
|
|
204
|
+
warn(
|
|
205
|
+
"get_data_asset_file_download_url is deprecated and will be removed in a future release. "
|
|
206
|
+
"Use get_data_asset_file_urls instead.",
|
|
207
|
+
DeprecationWarning,
|
|
208
|
+
stacklevel=2,
|
|
209
|
+
)
|
|
210
|
+
res = self.client.get(
|
|
211
|
+
f"data_assets/{data_asset_id}/files/download_url",
|
|
212
|
+
params={"path": path},
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return DownloadFileURL.from_dict(res.json())
|
|
216
|
+
|
|
217
|
+
def get_data_asset_file_urls(self, data_asset_id: str, path: str) -> FileURLs:
|
|
218
|
+
"""Generate view and download URLs for a specific file from an internal data asset."""
|
|
219
|
+
res = self.client.get(
|
|
220
|
+
f"data_assets/{data_asset_id}/files/urls",
|
|
221
|
+
params={"path": path},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
return FileURLs.from_dict(res.json())
|
|
225
|
+
|
|
226
|
+
def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataParams):
|
|
227
|
+
"""
|
|
228
|
+
Transfer a data asset's files to a different S3 storage location (Admin only).
|
|
229
|
+
|
|
230
|
+
Can convert internal data assets to external or change storage location of
|
|
231
|
+
external data assets. Maintains provenance for result data assets. Use
|
|
232
|
+
force=True when transferring data assets used by release pipelines.
|
|
233
|
+
"""
|
|
234
|
+
self.client.post(
|
|
235
|
+
f"data_assets/{data_asset_id}/transfer",
|
|
236
|
+
json=transfer_params.to_dict(),
|
|
237
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Models for Code Ocean SDK."""
|