codeocean 0.2.0__tar.gz → 0.3.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.
@@ -1,6 +1,15 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ ## 0.3.0 (2025-01-07)
5
+
6
+ - [#25](https://github.com/codeocean/codeocean-sdk-python/pull/25) feat: Add support for Python 3.13
7
+ - [#24](https://github.com/codeocean/codeocean-sdk-python/pull/24) feat: Paginated data asset search
8
+ - [#22](https://github.com/codeocean/codeocean-sdk-python/pull/22) feat: add support for rename computation API
9
+ - [#21](https://github.com/codeocean/codeocean-sdk-python/pull/21) feat: Code Ocean Version 3.1 updates
10
+ - [#18](https://github.com/codeocean/codeocean-sdk-python/pull/18) feat: Combined data asset
11
+ - [#16](https://github.com/codeocean/codeocean-sdk-python/pull/16) fix: revert default number of retries to 0
12
+
4
13
  ## 0.2.0 (2024-10-21)
5
14
 
6
15
  - [#15](https://github.com/codeocean/codeocean-sdk-python/pull/15) feat: Add option for custom retries
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.2.0
3
+ Version: 0.3.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
@@ -13,7 +13,6 @@ Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
14
  Requires-Python: >=3.9
15
15
  Requires-Dist: backports-strenum>=1.3.1; python_version < '3.11'
16
- Requires-Dist: dataclasses
17
16
  Requires-Dist: dataclasses-json
18
17
  Requires-Dist: requests
19
18
  Requires-Dist: requests-toolbelt
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeocean"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -17,8 +17,7 @@ classifiers = [
17
17
  "Operating System :: OS Independent",
18
18
  ]
19
19
  dependencies = [
20
- "dataclasses",
21
- "dataclasses_json",
20
+ "dataclasses-json",
22
21
  "requests",
23
22
  "requests_toolbelt",
24
23
  "backports.strenum>=1.3.1; python_version<'3.11'",
@@ -51,4 +50,4 @@ lint = "flake8 src tests examples"
51
50
  test = "python -m unittest -v"
52
51
 
53
52
  [[tool.hatch.envs.test.matrix]]
54
- python = ["3.9", "3.10", "3.11", "3.12"]
53
+ python = ["3.9", "3.10", "3.11", "3.12", "3.13"]
@@ -5,17 +5,21 @@ from dataclasses_json import dataclass_json
5
5
  from typing import Optional
6
6
  from requests_toolbelt.sessions import BaseUrlSession
7
7
 
8
+ from codeocean.components import Ownership, SortOrder, SearchFilter
8
9
  from codeocean.computation import Computation
9
10
  from codeocean.data_asset import DataAssetAttachParams, DataAssetAttachResults
10
11
  from codeocean.enum import StrEnum
11
12
 
12
13
 
13
14
  class CapsuleStatus(StrEnum):
14
- NonPublished = "non-published"
15
- Submitted = "submitted"
16
- Publishing = "publishing"
17
- Published = "published"
18
- Verified = "verified"
15
+ NonRelease = "non_release"
16
+ Release = "release"
17
+
18
+
19
+ class CapsuleSortBy(StrEnum):
20
+ Created = "created"
21
+ LastAccessed = "last_accessed"
22
+ Name = "name"
19
23
 
20
24
 
21
25
  @dataclass_json
@@ -42,13 +46,37 @@ class Capsule:
42
46
  cloned_from_url: Optional[str] = None
43
47
  description: Optional[str] = None
44
48
  field: Optional[str] = None
45
- keywords: Optional[list[str]] = None
49
+ tags: Optional[list[str]] = None
46
50
  original_capsule: Optional[OriginalCapsuleInfo] = None
47
- published_capsule: Optional[str] = None
51
+ release_capsule: Optional[str] = None
48
52
  submission: Optional[dict] = None
49
53
  versions: Optional[list[dict]] = None
50
54
 
51
55
 
56
+ @dataclass_json
57
+ @dataclass(frozen=True)
58
+ class CapsuleSearchParams:
59
+ query: Optional[str] = None
60
+ next_token: Optional[str] = None
61
+ offset: Optional[int] = None
62
+ limit: Optional[int] = None
63
+ sort_field: Optional[CapsuleSortBy] = None
64
+ sort_order: Optional[SortOrder] = None
65
+ ownership: Optional[Ownership] = None
66
+ status: Optional[CapsuleStatus] = None
67
+ favorite: Optional[bool] = None
68
+ archived: Optional[bool] = None
69
+ filters: Optional[list[SearchFilter]] = None
70
+
71
+
72
+ @dataclass_json
73
+ @dataclass(frozen=True)
74
+ class CapsuleSearchResults:
75
+ has_more: bool
76
+ results: list[Capsule]
77
+ next_token: Optional[str] = None
78
+
79
+
52
80
  @dataclass
53
81
  class Capsules:
54
82
 
@@ -80,3 +108,8 @@ class Capsules:
80
108
  f"capsules/{capsule_id}/data_assets/",
81
109
  json=data_assets,
82
110
  )
111
+
112
+ def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults:
113
+ res = self.client.post("capsules/search", json=search_params.to_dict())
114
+
115
+ return CapsuleSearchResults.from_dict(res.json())
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from requests_toolbelt.adapters.socket_options import TCPKeepAliveAdapter
3
5
  from requests_toolbelt.sessions import BaseUrlSession
@@ -14,7 +16,7 @@ class CodeOcean:
14
16
 
15
17
  domain: str
16
18
  token: str
17
- retries: Optional[Retry] = None
19
+ retries: Optional[Retry | int] = 0
18
20
 
19
21
  def __post_init__(self):
20
22
  self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
@@ -69,3 +69,9 @@ class SearchFilter:
69
69
  values: Optional[list[str | float]] = None
70
70
  range: Optional[SearchFilterRange] = None
71
71
  exclude: Optional[bool] = None
72
+
73
+
74
+ class Ownership(StrEnum):
75
+ Private = "private"
76
+ Shared = "shared"
77
+ Created = "created"
@@ -7,6 +7,7 @@ from typing import Optional
7
7
  from time import sleep, time
8
8
 
9
9
  from codeocean.enum import StrEnum
10
+ from codeocean.folder import Folder, DownloadFileURL
10
11
 
11
12
 
12
13
  class ComputationState(StrEnum):
@@ -54,14 +55,15 @@ class Computation:
54
55
  id: str
55
56
  created: int
56
57
  name: str
57
- state: ComputationState
58
58
  run_time: int
59
+ state: ComputationState
59
60
  cloud_workstation: Optional[bool] = None
60
61
  data_assets: Optional[list[InputDataAsset]] = None
61
- end_status: Optional[ComputationEndStatus] = None
62
- has_results: Optional[bool] = None
63
62
  parameters: Optional[list[Param]] = None
64
63
  processes: Optional[list[PipelineProcess]] = None
64
+ end_status: Optional[ComputationEndStatus] = None
65
+ exit_code: Optional[int] = None
66
+ has_results: Optional[bool] = None
65
67
 
66
68
 
67
69
  @dataclass_json
@@ -99,33 +101,6 @@ class RunParams:
99
101
  processes: Optional[list[PipelineProcessParams]] = None
100
102
 
101
103
 
102
- @dataclass_json
103
- @dataclass(frozen=True)
104
- class FolderItem:
105
- name: str
106
- path: str
107
- type: str
108
- size: Optional[int] = None
109
-
110
-
111
- @dataclass_json
112
- @dataclass(frozen=True)
113
- class Folder:
114
- items: list[FolderItem]
115
-
116
-
117
- @dataclass_json
118
- @dataclass(frozen=True)
119
- class ListFolderParams:
120
- path: str
121
-
122
-
123
- @dataclass_json
124
- @dataclass(frozen=True)
125
- class DownloadFileURL:
126
- url: str
127
-
128
-
129
104
  @dataclass
130
105
  class Computations:
131
106
 
@@ -195,3 +170,9 @@ class Computations:
195
170
 
196
171
  def delete_computation(self, computation_id: str):
197
172
  self.client.delete(f"computations/{computation_id}")
173
+
174
+ def rename_computation(self, computation_id: str, name: str):
175
+ self.client.patch(
176
+ f"computations/{computation_id}",
177
+ params={"name": name}
178
+ )
@@ -4,16 +4,19 @@ from dataclasses_json import dataclass_json
4
4
  from dataclasses import dataclass
5
5
  from requests_toolbelt.sessions import BaseUrlSession
6
6
  from time import sleep, time
7
- from typing import Optional
7
+ from typing import Optional, Iterator
8
8
 
9
- from codeocean.components import SortOrder, SearchFilter, Permissions
9
+ from codeocean.components import Ownership, SortOrder, SearchFilter, Permissions
10
10
  from codeocean.computation import PipelineProcess, Param
11
11
  from codeocean.enum import StrEnum
12
+ from codeocean.folder import Folder, DownloadFileURL
12
13
 
13
14
 
14
15
  class DataAssetType(StrEnum):
15
16
  Dataset = "dataset"
16
17
  Result = "result"
18
+ Combined = "combined"
19
+ Model = "model"
17
20
 
18
21
 
19
22
  class DataAssetState(StrEnum):
@@ -25,10 +28,10 @@ class DataAssetState(StrEnum):
25
28
  @dataclass_json
26
29
  @dataclass(frozen=True)
27
30
  class Provenance:
28
- commit: str
29
- run_script: str
30
- docker_image: str
31
- capsule: str
31
+ commit: Optional[str] = None
32
+ run_script: Optional[str] = None
33
+ docker_image: Optional[str] = None
34
+ capsule: Optional[str] = None
32
35
  data_assets: Optional[list[str]] = None
33
36
 
34
37
 
@@ -77,15 +80,18 @@ class DataAsset:
77
80
  state: DataAssetState
78
81
  type: DataAssetType
79
82
  last_used: int
80
- app_parameters: Optional[list[AppParameter]] = None
81
- custom_metadata: Optional[dict] = None
82
- description: Optional[str] = None
83
- failure_reason: Optional[str] = None
84
83
  files: Optional[int] = None
85
- provenance: Optional[Provenance] = None
86
84
  size: Optional[int] = None
87
- source_bucket: Optional[SourceBucket] = None
85
+ description: Optional[str] = None
88
86
  tags: Optional[list[str]] = None
87
+ provenance: Optional[Provenance] = None
88
+ source_bucket: Optional[SourceBucket] = None
89
+ custom_metadata: Optional[dict] = None
90
+ app_parameters: Optional[list[AppParameter]] = None
91
+ contained_data_assets: Optional[list[ContainedDataAsset]] = None
92
+ last_transferred: Optional[int] = None
93
+ transfer_error: Optional[str] = None
94
+ failure_reason: Optional[str] = None
89
95
 
90
96
 
91
97
  @dataclass_json
@@ -183,12 +189,6 @@ class DataAssetSortBy(StrEnum):
183
189
  Size = "size"
184
190
 
185
191
 
186
- class DataAssetOwnership(StrEnum):
187
- Private = "private"
188
- Shared = "shared"
189
- Created = "created"
190
-
191
-
192
192
  class DataAssetSearchOrigin(StrEnum):
193
193
  Internal = "internal"
194
194
  External = "external"
@@ -197,16 +197,17 @@ class DataAssetSearchOrigin(StrEnum):
197
197
  @dataclass_json
198
198
  @dataclass(frozen=True)
199
199
  class DataAssetSearchParams:
200
- limit: int
201
- offset: int
202
- archived: bool
203
- favorite: bool
204
200
  query: Optional[str] = None
201
+ next_token: Optional[str] = None
202
+ offset: Optional[int] = None
203
+ limit: Optional[int] = None
205
204
  sort_field: Optional[DataAssetSortBy] = None
206
205
  sort_order: Optional[SortOrder] = None
207
206
  type: Optional[DataAssetType] = None
208
- ownership: Optional[DataAssetOwnership] = None
207
+ ownership: Optional[Ownership] = None
209
208
  origin: Optional[DataAssetSearchOrigin] = None
209
+ favorite: Optional[bool] = None
210
+ archived: Optional[bool] = None
210
211
  filters: Optional[list[SearchFilter]] = None
211
212
 
212
213
 
@@ -215,6 +216,22 @@ class DataAssetSearchParams:
215
216
  class DataAssetSearchResults:
216
217
  has_more: bool
217
218
  results: list[DataAsset]
219
+ next_token: Optional[str] = None
220
+
221
+
222
+ @dataclass_json
223
+ @dataclass(frozen=True)
224
+ class ContainedDataAsset:
225
+ id: Optional[str] = None
226
+ mount: Optional[str] = None
227
+ size: Optional[int] = None
228
+
229
+
230
+ @dataclass_json
231
+ @dataclass(frozen=True)
232
+ class TransferDataParams:
233
+ target: Target
234
+ force: Optional[bool] = None
218
235
 
219
236
 
220
237
  @dataclass
@@ -291,3 +308,41 @@ class DataAssets:
291
308
  res = self.client.post("data_assets/search", json=search_params.to_dict())
292
309
 
293
310
  return DataAssetSearchResults.from_dict(res.json())
311
+
312
+ def search_data_assets_iterator(self, search_params: DataAssetSearchParams) -> Iterator[DataAsset]:
313
+ params = search_params.to_dict()
314
+ while True:
315
+ response = self.search_data_assets(
316
+ search_params=DataAssetSearchParams(**params)
317
+ )
318
+
319
+ for result in response.results:
320
+ yield result
321
+
322
+ if not response.has_more:
323
+ return
324
+
325
+ params["next_token"] = response.next_token
326
+
327
+ def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder:
328
+ data = {
329
+ "path": path,
330
+ }
331
+
332
+ res = self.client.post(f"data_assets/{data_asset_id}/files", json=data)
333
+
334
+ return Folder.from_dict(res.json())
335
+
336
+ def get_data_asset_file_download_url(self, data_asset_id: str, path: str) -> DownloadFileURL:
337
+ res = self.client.get(
338
+ f"data_assets/{data_asset_id}/files/download_url",
339
+ params={"path": path},
340
+ )
341
+
342
+ return DownloadFileURL.from_dict(res.json())
343
+
344
+ def transfer_data_asset(self, data_asset_id: str, transfer_params: TransferDataParams):
345
+ self.client.post(
346
+ f"data_assets/{data_asset_id}/transfer",
347
+ json=transfer_params.to_dict()
348
+ )
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses_json import dataclass_json
4
+ from dataclasses import dataclass
5
+ from typing import Optional
6
+
7
+
8
+ @dataclass_json
9
+ @dataclass(frozen=True)
10
+ class FolderItem:
11
+ name: str
12
+ path: str
13
+ type: str
14
+ size: Optional[int] = None
15
+
16
+
17
+ @dataclass_json
18
+ @dataclass(frozen=True)
19
+ class Folder:
20
+ items: list[FolderItem]
21
+
22
+
23
+ @dataclass_json
24
+ @dataclass(frozen=True)
25
+ class ListFolderParams:
26
+ path: str
27
+
28
+
29
+ @dataclass_json
30
+ @dataclass(frozen=True)
31
+ class DownloadFileURL:
32
+ url: str
File without changes
File without changes
File without changes
File without changes
File without changes