codeocean 0.13.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.
Files changed (31) hide show
  1. {codeocean-0.13.0 → codeocean-0.14.0}/CHANGELOG.md +4 -0
  2. {codeocean-0.13.0 → codeocean-0.14.0}/PKG-INFO +1 -1
  3. {codeocean-0.13.0 → codeocean-0.14.0}/examples/run_pipeline.py +2 -10
  4. {codeocean-0.13.0 → codeocean-0.14.0}/pyproject.toml +1 -1
  5. codeocean-0.14.0/src/codeocean/capsule.py +114 -0
  6. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/client.py +1 -1
  7. codeocean-0.14.0/src/codeocean/computation.py +164 -0
  8. codeocean-0.14.0/src/codeocean/data_asset.py +237 -0
  9. codeocean-0.14.0/src/codeocean/models/__init__.py +1 -0
  10. {codeocean-0.13.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/capsule.py +2 -90
  11. codeocean-0.14.0/src/codeocean/models/components.py +148 -0
  12. {codeocean-0.13.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/computation.py +0 -128
  13. {codeocean-0.13.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/data_asset.py +36 -205
  14. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/pipeline.py +5 -5
  15. {codeocean-0.13.0 → codeocean-0.14.0}/.flake8 +0 -0
  16. {codeocean-0.13.0 → codeocean-0.14.0}/.gitignore +0 -0
  17. {codeocean-0.13.0 → codeocean-0.14.0}/LICENSE +0 -0
  18. {codeocean-0.13.0 → codeocean-0.14.0}/README.md +0 -0
  19. {codeocean-0.13.0 → codeocean-0.14.0}/RELEASE.md +0 -0
  20. {codeocean-0.13.0 → codeocean-0.14.0}/examples/create_data_asset.py +0 -0
  21. {codeocean-0.13.0 → codeocean-0.14.0}/examples/run_capsule.py +0 -0
  22. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/__init__.py +0 -0
  23. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/components.py +0 -0
  24. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/custom_metadata.py +0 -0
  25. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/enum.py +0 -0
  26. {codeocean-0.13.0 → codeocean-0.14.0}/src/codeocean/error.py +0 -0
  27. {codeocean-0.13.0/src/codeocean → codeocean-0.14.0/src/codeocean/models}/folder.py +0 -0
  28. {codeocean-0.13.0 → codeocean-0.14.0}/tests/__init__.py +0 -0
  29. {codeocean-0.13.0 → codeocean-0.14.0}/tests/test_client.py +0 -0
  30. {codeocean-0.13.0 → codeocean-0.14.0}/tests/test_error.py +0 -0
  31. {codeocean-0.13.0 → codeocean-0.14.0}/tests/test_package.py +0 -0
@@ -1,6 +1,10 @@
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
+
4
8
  ## 0.13.0 (2026-01-23)
5
9
  - [#64](https://github.com/codeocean/codeocean-sdk-python/pull/64) feat: Code Ocean version 4.1 functionality
6
10
  - **Minimum Code Ocean platform version updated to `4.1.0`.**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.13.0
3
+ Version: 0.14.0
4
4
  Summary: Code Ocean Python SDK
5
5
  Project-URL: Homepage, https://github.com/codeocean/codeocean-sdk-python
6
6
  Project-URL: Issues, https://github.com/codeocean/codeocean-sdk-python/issues
@@ -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.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeocean"
7
- version = "0.13.0"
7
+ version = "0.14.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -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
@@ -38,7 +38,7 @@ class CodeOcean:
38
38
  agent_id: Optional[str] = None
39
39
 
40
40
  # Minimum server version required by this SDK
41
- MIN_SERVER_VERSION = "4.1.0"
41
+ MIN_SERVER_VERSION = "4.2.0"
42
42
 
43
43
  def __post_init__(self):
44
44
  self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
@@ -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."""
@@ -2,13 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field as dataclass_field
4
4
  from dataclasses_json import dataclass_json
5
- from typing import Optional, Iterator
6
- from requests_toolbelt.sessions import BaseUrlSession
5
+ from typing import Optional
7
6
 
8
- from codeocean.components import Ownership, SortOrder, SearchFilter, Permissions
9
- from codeocean.computation import Computation
10
- from codeocean.data_asset import DataAssetAttachParams, DataAssetAttachResults
11
7
  from codeocean.enum import StrEnum
8
+ from codeocean.models.components import Ownership, SortOrder, SearchFilter
12
9
 
13
10
 
14
11
  class CapsuleStatus(StrEnum):
@@ -463,88 +460,3 @@ class AppPanel:
463
460
  default=None,
464
461
  metadata={"description": "Pipeline processes and their App Panels."}
465
462
  )
466
-
467
-
468
- @dataclass
469
- class Capsules:
470
- """Client for interacting with Code Ocean capsule APIs."""
471
-
472
- client: BaseUrlSession
473
- _route: str = "capsules"
474
-
475
- def get_capsule(self, capsule_id: str) -> Capsule:
476
- """Retrieve metadata for a specific capsule by its ID."""
477
- res = self.client.get(f"{self._route}/{capsule_id}")
478
-
479
- return Capsule.from_dict(res.json())
480
-
481
- def delete_capsule(self, capsule_id: str):
482
- """Delete a capsule permanently."""
483
- self.client.delete(f"{self._route}/{capsule_id}")
484
-
485
- def get_capsule_app_panel(self, capsule_id: str, version: Optional[int] = None) -> AppPanel:
486
- """Retrieve app panel information for a specific capsule by its ID."""
487
- res = self.client.get(f"{self._route}/{capsule_id}/app_panel", params={"version": version} if version else None)
488
-
489
- return AppPanel.from_dict(res.json())
490
-
491
- def list_computations(self, capsule_id: str) -> list[Computation]:
492
- """Get all computations associated with a specific capsule."""
493
- res = self.client.get(f"{self._route}/{capsule_id}/computations")
494
-
495
- return [Computation.from_dict(c) for c in res.json()]
496
-
497
- def update_permissions(self, capsule_id: str, permissions: Permissions):
498
- """Update permissions for a capsule."""
499
- self.client.post(
500
- f"{self._route}/{capsule_id}/permissions",
501
- json=permissions.to_dict(),
502
- )
503
-
504
- def attach_data_assets(
505
- self,
506
- capsule_id: str,
507
- attach_params: list[DataAssetAttachParams],
508
- ) -> list[DataAssetAttachResults]:
509
- """Attach one or more data assets to a capsule with optional mount paths."""
510
- res = self.client.post(
511
- f"{self._route}/{capsule_id}/data_assets",
512
- json=[j.to_dict() for j in attach_params],
513
- )
514
-
515
- return [DataAssetAttachResults.from_dict(c) for c in res.json()]
516
-
517
- def detach_data_assets(self, capsule_id: str, data_assets: list[str]):
518
- """Detach one or more data assets from a capsule by their IDs."""
519
- self.client.delete(
520
- f"{self._route}/{capsule_id}/data_assets/",
521
- json=data_assets,
522
- )
523
-
524
- def archive_capsule(self, capsule_id: str, archive: bool):
525
- """Archive or unarchive a capsule to control its visibility and accessibility."""
526
- self.client.patch(
527
- f"{self._route}/{capsule_id}/archive",
528
- params={"archive": archive},
529
- )
530
-
531
- def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults:
532
- """Search for capsules with filtering, sorting, and pagination
533
- options."""
534
- res = self.client.post(f"{self._route}/search", json=search_params.to_dict())
535
-
536
- return CapsuleSearchResults.from_dict(res.json())
537
-
538
- def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsule]:
539
- """Iterate through all capsules matching search criteria with automatic pagination."""
540
- params = search_params.to_dict()
541
- while True:
542
- response = self.search_capsules(search_params=CapsuleSearchParams(**params))
543
-
544
- for result in response.results:
545
- yield result
546
-
547
- if not response.has_more:
548
- return
549
-
550
- params["next_token"] = response.next_token
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses_json import dataclass_json
4
+ from dataclasses import dataclass, field
5
+ from typing import Optional
6
+
7
+ from codeocean.enum import StrEnum
8
+
9
+
10
+ class UserRole(StrEnum):
11
+ """Role levels for user permissions of Code Ocean resources."""
12
+
13
+ Owner = "owner"
14
+ Editor = "editor"
15
+ Viewer = "viewer"
16
+
17
+
18
+ @dataclass_json
19
+ @dataclass(frozen=True)
20
+ class UserPermissions:
21
+ """User permission configuration with email and role assignment."""
22
+
23
+ email: str = field(
24
+ metadata={"description": "User email address for permission assignment"},
25
+ )
26
+ role: UserRole = field(
27
+ metadata={
28
+ "description": "Permission level granted to the user (owner, editor, or viewer)",
29
+ },
30
+ )
31
+
32
+
33
+ class GroupRole(StrEnum):
34
+ """Role levels for group permissions of Code Ocean resources."""
35
+
36
+ Owner = "owner"
37
+ Editor = "editor"
38
+ Viewer = "viewer"
39
+ Discoverable = "discoverable"
40
+
41
+
42
+ @dataclass_json
43
+ @dataclass(frozen=True)
44
+ class GroupPermissions:
45
+ """Group permission configuration with group identifier and role assignment."""
46
+
47
+ group: str = field(
48
+ metadata={"description": "Group identifier for permission assignment"},
49
+ )
50
+ role: GroupRole = field(
51
+ metadata={
52
+ "description": "Permission level granted to the group (owner, editor, viewer, or discoverable)",
53
+ },
54
+ )
55
+
56
+
57
+ class EveryoneRole(StrEnum):
58
+ """Role levels for public access permissions of Code Ocean resources."""
59
+
60
+ Viewer = "viewer"
61
+ Discoverable = "discoverable"
62
+ None_ = "none"
63
+
64
+
65
+ @dataclass_json
66
+ @dataclass(frozen=True)
67
+ class Permissions:
68
+ """Complete permission configuration for Code Ocean resources including users, groups, and public access."""
69
+
70
+ users: Optional[list[UserPermissions]] = field(
71
+ default=None,
72
+ metadata={"description": "List of user-specific permissions"},
73
+ )
74
+ groups: Optional[list[GroupPermissions]] = field(
75
+ default=None,
76
+ metadata={"description": "List of group-specific permissions"},
77
+ )
78
+ everyone: Optional[EveryoneRole] = field(
79
+ default=None,
80
+ metadata={"description": "Public access level (viewer, discoverable, or none)"},
81
+ )
82
+ share_assets: Optional[bool] = field(
83
+ default=None,
84
+ metadata={
85
+ "description": "Whether to share all related assets (attached data assets and "
86
+ "pipeline capsules) with added users and groups",
87
+ },
88
+ )
89
+
90
+
91
+ class SortOrder(StrEnum):
92
+ """Sort order options for search operations."""
93
+
94
+ Ascending = "asc"
95
+ Descending = "desc"
96
+
97
+
98
+ @dataclass_json
99
+ @dataclass(frozen=True)
100
+ class SearchFilterRange:
101
+ """Numeric range filter for search operations with minimum and maximum values."""
102
+
103
+ min: float = field(
104
+ metadata={"description": "Minimum value for range filter"},
105
+ )
106
+ max: float = field(
107
+ metadata={"description": "Maximum value for range filter"},
108
+ )
109
+
110
+
111
+ @dataclass_json
112
+ @dataclass(frozen=True)
113
+ class SearchFilter:
114
+ """Search filter configuration for field-level filtering with various value types and range support."""
115
+
116
+ key: str = field(
117
+ metadata={
118
+ "description": "Field name to filter on (name, description, tags, or custom field key)",
119
+ },
120
+ )
121
+ value: Optional[str | float] = field(
122
+ default=None,
123
+ metadata={"description": "Single field value to include/exclude"},
124
+ )
125
+ values: Optional[list[str | float]] = field(
126
+ default=None,
127
+ metadata={"description": "Multiple field values for inclusion/exclusion"},
128
+ )
129
+ range: Optional[SearchFilterRange] = field(
130
+ default=None,
131
+ metadata={
132
+ "description": "Numeric range filter (only one of min/max must be set)",
133
+ },
134
+ )
135
+ exclude: Optional[bool] = field(
136
+ default=None,
137
+ metadata={
138
+ "description": "Whether to include (false) or exclude (true) the specified values",
139
+ },
140
+ )
141
+
142
+
143
+ class Ownership(StrEnum):
144
+ """Ownership filter options for search operations."""
145
+
146
+ Private = "private"
147
+ Shared = "shared"
148
+ Created = "created"
@@ -2,13 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass, field
4
4
  from dataclasses_json import dataclass_json
5
- from requests_toolbelt.sessions import BaseUrlSession
6
5
  from typing import Optional
7
- from time import sleep, time
8
- from warnings import warn
9
6
 
10
7
  from codeocean.enum import StrEnum
11
- from codeocean.folder import FileURLs, Folder, DownloadFileURL
12
8
 
13
9
 
14
10
  class ComputationState(StrEnum):
@@ -245,127 +241,3 @@ class RunParams:
245
241
  default=None,
246
242
  metadata={"description": "Process-specific parameters for pipeline runs"},
247
243
  )
248
-
249
-
250
- @dataclass
251
- class Computations:
252
- """Client for interacting with Code Ocean computation APIs."""
253
-
254
- client: BaseUrlSession
255
-
256
- def get_computation(self, computation_id: str) -> Computation:
257
- """Retrieve metadata and status information for a specific computation by its ID."""
258
- res = self.client.get(f"computations/{computation_id}")
259
-
260
- return Computation.from_dict(res.json())
261
-
262
- def run_capsule(self, run_params: RunParams) -> Computation:
263
- """
264
- Execute a capsule or pipeline with specified parameters and data assets.
265
-
266
- For capsule execution: Set run_params.capsule_id and optionally provide data_assets,
267
- parameters, or named_parameters.
268
-
269
- For pipeline execution: Set run_params.pipeline_id and optionally provide data_assets,
270
- processes (with process-specific parameters), and nextflow_profile configuration.
271
-
272
- """
273
- res = self.client.post("computations", json=run_params.to_dict())
274
-
275
- return Computation.from_dict(res.json())
276
-
277
- # Alias for run_capsule
278
- run_pipeline = run_capsule
279
-
280
- def wait_until_completed(
281
- self,
282
- computation: Computation,
283
- polling_interval: float = 5,
284
- timeout: Optional[float] = None,
285
- ) -> Computation:
286
- """
287
- Poll a computation until it reaches 'Completed' or 'Failed' state with configurable timing.
288
-
289
- Args:
290
- computation: The computation object to monitor
291
- polling_interval: Time between status checks in seconds (minimum 5 seconds)
292
- timeout: Maximum time to wait in seconds, or None for no timeout
293
-
294
- Returns:
295
- Updated computation object once completed or failed
296
-
297
- Raises:
298
- ValueError: If polling_interval < 5 or timeout constraints are violated
299
- TimeoutError: If computation doesn't complete within the timeout period
300
- """
301
- if polling_interval < 5:
302
- raise ValueError(
303
- f"Polling interval {polling_interval} should be greater than or equal to 5"
304
- )
305
- if timeout is not None and timeout < polling_interval:
306
- raise ValueError(
307
- f"Timeout {timeout} should be greater than or equal to polling interval {polling_interval}"
308
- )
309
- if timeout is not None and timeout < 0:
310
- raise ValueError(
311
- f"Timeout {timeout} should be greater than or equal to 0 (seconds), or None"
312
- )
313
- t0 = time()
314
- while True:
315
- comp = self.get_computation(computation.id)
316
-
317
- if comp.state in [ComputationState.Completed, ComputationState.Failed]:
318
- return comp
319
-
320
- if timeout is not None and (time() - t0) > timeout:
321
- raise TimeoutError(
322
- f"Computation {computation.id} did not complete within {timeout} seconds"
323
- )
324
-
325
- sleep(polling_interval)
326
-
327
- def list_computation_results(self, computation_id: str, path: str = "") -> Folder:
328
- """List result files and folders generated by a computation
329
- at the specified path. Empty path retrieves the /results root folder."""
330
- data = {
331
- "path": path,
332
- }
333
-
334
- res = self.client.post(f"computations/{computation_id}/results", json=data)
335
-
336
- return Folder.from_dict(res.json())
337
-
338
- def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL:
339
- """[DEPRECATED] Generate a download URL for a specific result file from a computation.
340
-
341
- Deprecated: Use get_result_file_urls instead.
342
- """
343
- warn(
344
- "get_result_file_download_url is deprecated and will be removed in a future release. "
345
- "Use get_result_file_urls instead.",
346
- DeprecationWarning,
347
- stacklevel=2,
348
- )
349
- res = self.client.get(
350
- f"computations/{computation_id}/results/download_url",
351
- params={"path": path},
352
- )
353
-
354
- return DownloadFileURL.from_dict(res.json())
355
-
356
- def get_result_file_urls(self, computation_id: str, path: str) -> FileURLs:
357
- """Generate view and download URLs for a specific result file from a computation."""
358
- res = self.client.get(
359
- f"computations/{computation_id}/results/urls",
360
- params={"path": path},
361
- )
362
-
363
- return FileURLs.from_dict(res.json())
364
-
365
- def delete_computation(self, computation_id: str):
366
- """Delete a computation and stop it if currently running."""
367
- self.client.delete(f"computations/{computation_id}")
368
-
369
- def rename_computation(self, computation_id: str, name: str):
370
- """Rename an existing computation with a new display name."""
371
- self.client.patch(f"computations/{computation_id}", params={"name": name})
@@ -2,15 +2,11 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses_json import dataclass_json
4
4
  from dataclasses import dataclass, field
5
- from requests_toolbelt.sessions import BaseUrlSession
6
- from time import sleep, time
7
- from typing import Optional, Iterator
8
- from warnings import warn
5
+ from typing import Optional
9
6
 
10
- from codeocean.components import Ownership, SortOrder, SearchFilter, Permissions
11
- from codeocean.computation import PipelineProcess, Param
12
7
  from codeocean.enum import StrEnum
13
- from codeocean.folder import FileURLs, Folder, DownloadFileURL
8
+ from codeocean.models.components import Ownership, SortOrder, SearchFilter
9
+ from codeocean.models.computation import PipelineProcess, Param
14
10
 
15
11
 
16
12
  class DataAssetType(StrEnum):
@@ -382,6 +378,32 @@ class ComputationSource:
382
378
  )
383
379
 
384
380
 
381
+ @dataclass_json
382
+ @dataclass(frozen=True)
383
+ class CloudWorkstationSource:
384
+ """Cloud Workstation session source configuration for creating data assets."""
385
+
386
+ id: str = field(
387
+ metadata={"description": "Computation ID of the Cloud Workstation session"},
388
+ )
389
+ path: str = field(
390
+ metadata={
391
+ "description": (
392
+ "Path within the Cloud Workstation to create the data asset from"
393
+ ),
394
+ },
395
+ )
396
+ run_script: Optional[str] = field(
397
+ default=None,
398
+ metadata={
399
+ "description": (
400
+ "Path to the script that was executed, relative to the capsule folder. "
401
+ "Existence determines if the data would be of type result"
402
+ ),
403
+ },
404
+ )
405
+
406
+
385
407
  @dataclass_json
386
408
  @dataclass(frozen=True)
387
409
  class Source:
@@ -399,6 +421,10 @@ class Source:
399
421
  default=None,
400
422
  metadata={"description": "Computation source configuration"},
401
423
  )
424
+ cloud_workstation: Optional[CloudWorkstationSource] = field(
425
+ default=None,
426
+ metadata={"description": "Cloud Workstation source configuration"},
427
+ )
402
428
 
403
429
 
404
430
  @dataclass_json
@@ -459,7 +485,9 @@ class DataAssetParams:
459
485
  )
460
486
  source: Optional[Source] = field(
461
487
  default=None,
462
- metadata={"description": "Source configuration (AWS S3, GCP, or computation)"},
488
+ metadata={
489
+ "description": "Source configuration (AWS S3, GCP, computation, or cloud workstation)",
490
+ },
463
491
  )
464
492
  target: Optional[Target] = field(
465
493
  default=None,
@@ -708,200 +736,3 @@ class TransferDataParams:
708
736
  ),
709
737
  },
710
738
  )
711
-
712
-
713
- @dataclass
714
- class DataAssets:
715
- """Client for interacting with Code Ocean data asset APIs."""
716
-
717
- client: BaseUrlSession
718
-
719
- def get_data_asset(self, data_asset_id: str) -> DataAsset:
720
- """Retrieve metadata for a specific data asset by its ID."""
721
- res = self.client.get(f"data_assets/{data_asset_id}")
722
-
723
- return DataAsset.from_dict(res.json())
724
-
725
- def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset:
726
- """
727
- Update metadata for a data asset including name, description, tags, mount,
728
- and custom metadata.
729
-
730
- Supports updating various metadata types:
731
- - Basic metadata: name (display name), description (free text description)
732
- - Organization: tags (keywords for searching), mount (default mount folder path)
733
- - Custom metadata: admin-defined custom fields with user-set values according to
734
- deployment configuration
735
- (string, number, or date fields in unix epoch format)
736
- """
737
- res = self.client.put(
738
- f"data_assets/{data_asset_id}",
739
- json=update_params.to_dict(),
740
- )
741
-
742
- return DataAsset.from_dict(res.json())
743
-
744
- def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset:
745
- """
746
- Create a new data asset from various sources including S3 buckets,
747
- computation results, or combined assets.
748
-
749
- Data assets are versioned, immutable collections of files that serve as inputs
750
- or outputs for computational workflows in Code Ocean. Internal data assets
751
- store files within Code Ocean's infrastructure, while external data assets
752
- reference files in external storage (S3/GCP) without copying.
753
-
754
- Supports creating data assets from AWS S3, GCP Cloud Storage, computation
755
- results, or combining existing data assets. Returns confirmation of creation
756
- request validity, not success, as creation takes time. Use wait_until_ready()
757
- to monitor creation progress.
758
-
759
- """
760
- res = self.client.post("data_assets", json=data_asset_params.to_dict())
761
-
762
- return DataAsset.from_dict(res.json())
763
-
764
- def wait_until_ready(
765
- self,
766
- data_asset: DataAsset,
767
- polling_interval: float = 5,
768
- timeout: float | None = None,
769
- ) -> DataAsset:
770
- """
771
- Poll a data asset until it reaches 'Ready' or 'Failed' state with configurable
772
- timing.
773
-
774
- Args:
775
- data_asset: The data asset object to monitor
776
- polling_interval: Time between status checks in seconds
777
- (minimum 5 seconds)
778
- timeout: Maximum time to wait in seconds, or None for no timeout
779
-
780
- Returns:
781
- Updated data asset object once ready or failed
782
-
783
- Raises:
784
- ValueError: If polling_interval < 5 or timeout constraints are violated
785
- TimeoutError: If data asset doesn't become ready within timeout period
786
- """
787
- if polling_interval < 5:
788
- raise ValueError(
789
- f"Polling interval {polling_interval} should be greater than or equal to 5"
790
- )
791
- if timeout is not None and timeout < polling_interval:
792
- raise ValueError(
793
- f"Timeout {timeout} should be greater than or equal to polling interval {polling_interval}"
794
- )
795
- if timeout is not None and timeout < 0:
796
- raise ValueError(
797
- f"Timeout {timeout} should be greater than or equal to 0 (seconds), or None"
798
- )
799
- t0 = time()
800
- while True:
801
- da = self.get_data_asset(data_asset.id)
802
-
803
- if da.state in [DataAssetState.Ready, DataAssetState.Failed]:
804
- return da
805
-
806
- if timeout is not None and (time() - t0) > timeout:
807
- raise TimeoutError(
808
- f"Data asset {data_asset.id} was not ready within {timeout} seconds"
809
- )
810
-
811
- sleep(polling_interval)
812
-
813
- def delete_data_asset(self, data_asset_id: str):
814
- """Delete a data asset permanently."""
815
- self.client.delete(f"data_assets/{data_asset_id}")
816
-
817
- def update_permissions(self, data_asset_id: str, permissions: Permissions):
818
- """Update permissions for a data asset to control user and group access."""
819
- self.client.post(
820
- f"data_assets/{data_asset_id}/permissions",
821
- json=permissions.to_dict(),
822
- )
823
-
824
- def archive_data_asset(self, data_asset_id: str, archive: bool):
825
- """Archive or unarchive a data asset to control its visibility and accessibility."""
826
- self.client.patch(
827
- f"data_assets/{data_asset_id}/archive",
828
- params={"archive": archive},
829
- )
830
-
831
- def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults:
832
- """Search for data assets with filtering, sorting, and pagination options."""
833
- res = self.client.post("data_assets/search", json=search_params.to_dict())
834
-
835
- return DataAssetSearchResults.from_dict(res.json())
836
-
837
- def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> Iterator[DataAsset]:
838
- """
839
- Iterate through all data assets matching search criteria with
840
- automatic pagination.
841
- """
842
- params = search_params.to_dict()
843
- while True:
844
- response = self.search_data_assets(
845
- search_params=DataAssetSearchParams(**params),
846
- )
847
-
848
- for result in response.results:
849
- yield result
850
-
851
- if not response.has_more:
852
- return
853
-
854
- params["next_token"] = response.next_token
855
-
856
- def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder:
857
- """
858
- List files and folders within an internal data asset at the specified path.
859
- Empty path retrieves root level contents.
860
- """
861
- data = {
862
- "path": path,
863
- }
864
-
865
- res = self.client.post(f"data_assets/{data_asset_id}/files", json=data)
866
-
867
- return Folder.from_dict(res.json())
868
-
869
- def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> DownloadFileURL:
870
- """(Deprecated) Generate a download URL for a specific file from an internal data asset.
871
-
872
- Deprecated: Use get_data_asset_file_urls instead.
873
- """
874
- warn(
875
- "get_data_asset_file_download_url is deprecated and will be removed in a future release. "
876
- "Use get_data_asset_file_urls instead.",
877
- DeprecationWarning,
878
- stacklevel=2,
879
- )
880
- res = self.client.get(
881
- f"data_assets/{data_asset_id}/files/download_url",
882
- params={"path": path},
883
- )
884
-
885
- return DownloadFileURL.from_dict(res.json())
886
-
887
- def get_data_asset_file_urls(self, data_asset_id: str, path: str) -> FileURLs:
888
- """Generate view and download URLs for a specific file from an internal data asset."""
889
- res = self.client.get(
890
- f"data_assets/{data_asset_id}/files/urls",
891
- params={"path": path},
892
- )
893
-
894
- return FileURLs.from_dict(res.json())
895
-
896
- def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataParams):
897
- """
898
- Transfer a data asset's files to a different S3 storage location (Admin only).
899
-
900
- Can convert internal data assets to external or change storage location of
901
- external data assets. Maintains provenance for result data assets. Use
902
- force=True when transferring data assets used by release pipelines.
903
- """
904
- self.client.post(
905
- f"data_assets/{data_asset_id}/transfer",
906
- json=transfer_params.to_dict(),
907
- )
@@ -4,16 +4,16 @@ from dataclasses import dataclass, field
4
4
  from typing import Iterator
5
5
  from requests_toolbelt.sessions import BaseUrlSession
6
6
 
7
- from codeocean.capsule import (
7
+ from codeocean.capsule import Capsules
8
+ from codeocean.models.capsule import (
8
9
  Capsule,
9
- Capsules,
10
10
  CapsuleSearchParams,
11
11
  CapsuleSearchResults,
12
12
  AppPanel,
13
13
  )
14
- from codeocean.components import Permissions
15
- from codeocean.computation import Computation
16
- from codeocean.data_asset import DataAssetAttachParams, DataAssetAttachResults
14
+ from codeocean.models.components import Permissions
15
+ from codeocean.models.computation import Computation
16
+ from codeocean.models.data_asset import DataAssetAttachParams, DataAssetAttachResults
17
17
 
18
18
 
19
19
  @dataclass
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes