codeocean 0.6.0__tar.gz → 0.7.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.6.0 → codeocean-0.7.0}/CHANGELOG.md +3 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/PKG-INFO +1 -1
- {codeocean-0.6.0 → codeocean-0.7.0}/pyproject.toml +1 -1
- codeocean-0.7.0/src/codeocean/capsule.py +281 -0
- codeocean-0.7.0/src/codeocean/components.py +148 -0
- codeocean-0.7.0/src/codeocean/computation.py +349 -0
- codeocean-0.7.0/src/codeocean/data_asset.py +849 -0
- codeocean-0.7.0/src/codeocean/folder.py +55 -0
- codeocean-0.6.0/src/codeocean/capsule.py +0 -136
- codeocean-0.6.0/src/codeocean/components.py +0 -77
- codeocean-0.6.0/src/codeocean/computation.py +0 -180
- codeocean-0.6.0/src/codeocean/data_asset.py +0 -351
- codeocean-0.6.0/src/codeocean/folder.py +0 -32
- {codeocean-0.6.0 → codeocean-0.7.0}/.flake8 +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/.gitignore +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/LICENSE +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/README.md +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/RELEASE.md +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/examples/create_data_asset.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/examples/run_capsule.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/examples/run_pipeline.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/src/codeocean/__init__.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/src/codeocean/client.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/src/codeocean/enum.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/tests/__init__.py +0 -0
- {codeocean-0.6.0 → codeocean-0.7.0}/tests/test_package.py +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
CHANGELOG
|
|
2
2
|
=========
|
|
3
3
|
|
|
4
|
+
## 0.7.0 (2025-06-24)
|
|
5
|
+
- [#46](https://github.com/codeocean/codeocean-sdk-python/pull/46) feat: Add API documentation
|
|
6
|
+
|
|
4
7
|
## 0.6.0 (2025-06-03)
|
|
5
8
|
- [#44](https://github.com/codeocean/codeocean-sdk-python/pull/44) feat: support for nextflow_profiles in data assets and computations
|
|
6
9
|
- [#43](https://github.com/codeocean/codeocean-sdk-python/pull/43) fix: updates .gitignore to exclude .env
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field as dataclass_field
|
|
4
|
+
from dataclasses_json import dataclass_json
|
|
5
|
+
from typing import Optional, Iterator
|
|
6
|
+
from requests_toolbelt.sessions import BaseUrlSession
|
|
7
|
+
|
|
8
|
+
from codeocean.components import Ownership, SortOrder, SearchFilter, Permissions
|
|
9
|
+
from codeocean.computation import Computation
|
|
10
|
+
from codeocean.data_asset import DataAssetAttachParams, DataAssetAttachResults
|
|
11
|
+
from codeocean.enum import StrEnum
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CapsuleStatus(StrEnum):
|
|
15
|
+
"""Status of a capsule indicating its release state."""
|
|
16
|
+
|
|
17
|
+
NonRelease = "non_release"
|
|
18
|
+
Release = "release"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CapsuleSortBy(StrEnum):
|
|
22
|
+
"""Fields available for sorting capsule search results."""
|
|
23
|
+
|
|
24
|
+
Created = "created"
|
|
25
|
+
LastAccessed = "last_accessed"
|
|
26
|
+
Name = "name"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass_json
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class OriginalCapsuleInfo:
|
|
32
|
+
"""Information about the original capsule when this capsule is duplicated from
|
|
33
|
+
another."""
|
|
34
|
+
|
|
35
|
+
id: Optional[str] = dataclass_field(
|
|
36
|
+
default=None,
|
|
37
|
+
metadata={"description": "Original capsule ID"},
|
|
38
|
+
)
|
|
39
|
+
major_version: Optional[int] = dataclass_field(
|
|
40
|
+
default=None,
|
|
41
|
+
metadata={"description": "Original capsule major version"},
|
|
42
|
+
)
|
|
43
|
+
minor_version: Optional[int] = dataclass_field(
|
|
44
|
+
default=None,
|
|
45
|
+
metadata={"description": "Original capsule minor version"},
|
|
46
|
+
)
|
|
47
|
+
name: Optional[str] = dataclass_field(
|
|
48
|
+
default=None,
|
|
49
|
+
metadata={"description": "Original capsule name"},
|
|
50
|
+
)
|
|
51
|
+
created: Optional[int] = dataclass_field(
|
|
52
|
+
default=None,
|
|
53
|
+
metadata={"description": "Original capsule creation time (int64 timestamp)"},
|
|
54
|
+
)
|
|
55
|
+
public: Optional[bool] = dataclass_field(
|
|
56
|
+
default=None,
|
|
57
|
+
metadata={"description": "Indicates whether the original capsule is public"},
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass_json
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class Capsule:
|
|
64
|
+
"""Represents a Code Ocean capsule with its metadata and properties."""
|
|
65
|
+
|
|
66
|
+
id: str = dataclass_field(
|
|
67
|
+
metadata={"description": "Capsule ID"},
|
|
68
|
+
)
|
|
69
|
+
created: int = dataclass_field(
|
|
70
|
+
metadata={"description": "Capsule creation time (int64 timestamp)"},
|
|
71
|
+
)
|
|
72
|
+
name: str = dataclass_field(
|
|
73
|
+
metadata={"description": "Capsule display name"},
|
|
74
|
+
)
|
|
75
|
+
status: CapsuleStatus = dataclass_field(
|
|
76
|
+
metadata={"description": "Status of the capsule (non_release or release)"},
|
|
77
|
+
)
|
|
78
|
+
owner: str = dataclass_field(
|
|
79
|
+
metadata={"description": "Capsule owner's ID"},
|
|
80
|
+
)
|
|
81
|
+
slug: str = dataclass_field(
|
|
82
|
+
metadata={"description": "Alternate capsule ID (URL-friendly identifier)"},
|
|
83
|
+
)
|
|
84
|
+
article: Optional[dict] = dataclass_field(
|
|
85
|
+
default=None,
|
|
86
|
+
metadata={
|
|
87
|
+
"description": "Capsule article info with URL, ID, DOI, "
|
|
88
|
+
"citation, state, name, journal_name, and "
|
|
89
|
+
"publish_time"
|
|
90
|
+
},
|
|
91
|
+
)
|
|
92
|
+
cloned_from_url: Optional[str] = dataclass_field(
|
|
93
|
+
default=None,
|
|
94
|
+
metadata={"description": "URL to external Git repository linked to capsule"},
|
|
95
|
+
)
|
|
96
|
+
description: Optional[str] = dataclass_field(
|
|
97
|
+
default=None,
|
|
98
|
+
metadata={"description": "Capsule description"},
|
|
99
|
+
)
|
|
100
|
+
field: Optional[str] = dataclass_field(
|
|
101
|
+
default=None,
|
|
102
|
+
metadata={"description": "Capsule research field"},
|
|
103
|
+
)
|
|
104
|
+
tags: Optional[list[str]] = dataclass_field(
|
|
105
|
+
default=None,
|
|
106
|
+
metadata={"description": "List of tags associated with the capsule"},
|
|
107
|
+
)
|
|
108
|
+
original_capsule: Optional[OriginalCapsuleInfo] = dataclass_field(
|
|
109
|
+
default=None,
|
|
110
|
+
metadata={
|
|
111
|
+
"description": "Original capsule info when this capsule is duplicated from another"
|
|
112
|
+
},
|
|
113
|
+
)
|
|
114
|
+
release_capsule: Optional[str] = dataclass_field(
|
|
115
|
+
default=None,
|
|
116
|
+
metadata={"description": "Release capsule ID"},
|
|
117
|
+
)
|
|
118
|
+
submission: Optional[dict] = dataclass_field(
|
|
119
|
+
default=None,
|
|
120
|
+
metadata={
|
|
121
|
+
"description": "Submission info with timestamp, commit hash, "
|
|
122
|
+
"verification_capsule, verified status, and "
|
|
123
|
+
"verified_timestamp"
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
versions: Optional[list[dict]] = dataclass_field(
|
|
127
|
+
default=None,
|
|
128
|
+
metadata={
|
|
129
|
+
"description": "Capsule versions with major_version, minor_version, release_time, and DOI"
|
|
130
|
+
},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass_json
|
|
135
|
+
@dataclass(frozen=True)
|
|
136
|
+
class CapsuleSearchParams:
|
|
137
|
+
"""Parameters for searching capsules with various filters and pagination
|
|
138
|
+
options."""
|
|
139
|
+
|
|
140
|
+
query: Optional[str] = dataclass_field(
|
|
141
|
+
default=None,
|
|
142
|
+
metadata={
|
|
143
|
+
"description": "Search query in free text or structured format (name:... tag:...)"
|
|
144
|
+
},
|
|
145
|
+
)
|
|
146
|
+
next_token: Optional[str] = dataclass_field(
|
|
147
|
+
default=None,
|
|
148
|
+
metadata={
|
|
149
|
+
"description": "Token for next page of results from previous response"
|
|
150
|
+
},
|
|
151
|
+
)
|
|
152
|
+
offset: Optional[int] = dataclass_field(
|
|
153
|
+
default=None,
|
|
154
|
+
metadata={
|
|
155
|
+
"description": "Starting index for search results (ignored if next_token is set)"
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
limit: Optional[int] = dataclass_field(
|
|
159
|
+
default=None,
|
|
160
|
+
metadata={
|
|
161
|
+
"description": "Number of items to return (up to 1000, defaults to 100)"
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
sort_field: Optional[CapsuleSortBy] = dataclass_field(
|
|
165
|
+
default=None,
|
|
166
|
+
metadata={"description": "Field to sort by (created, name, or last_accessed)"},
|
|
167
|
+
)
|
|
168
|
+
sort_order: Optional[SortOrder] = dataclass_field(
|
|
169
|
+
default=None,
|
|
170
|
+
metadata={
|
|
171
|
+
"description": "Sort order ('asc' or 'desc') - must be provided with a sort_field parameter as well!"
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
ownership: Optional[Ownership] = dataclass_field(
|
|
175
|
+
default=None,
|
|
176
|
+
metadata={
|
|
177
|
+
"description": "Filter by ownership ('private', 'created' or 'shared') - defaults to all accessible"
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
status: Optional[CapsuleStatus] = dataclass_field(
|
|
181
|
+
default=None,
|
|
182
|
+
metadata={"description": "Filter by status (release or non_release) - defaults to all"},
|
|
183
|
+
)
|
|
184
|
+
favorite: Optional[bool] = dataclass_field(
|
|
185
|
+
default=None,
|
|
186
|
+
metadata={"description": "Search only favorite capsules"},
|
|
187
|
+
)
|
|
188
|
+
archived: Optional[bool] = dataclass_field(
|
|
189
|
+
default=None,
|
|
190
|
+
metadata={"description": "Search only archived capsules"},
|
|
191
|
+
)
|
|
192
|
+
filters: Optional[list[SearchFilter]] = dataclass_field(
|
|
193
|
+
default=None,
|
|
194
|
+
metadata={
|
|
195
|
+
"description": "Additional field-level filters for name, description, tags, or custom fields"
|
|
196
|
+
},
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
@dataclass_json
|
|
201
|
+
@dataclass(frozen=True)
|
|
202
|
+
class CapsuleSearchResults:
|
|
203
|
+
"""Results from a capsule search operation with pagination support."""
|
|
204
|
+
|
|
205
|
+
has_more: bool = dataclass_field(
|
|
206
|
+
metadata={"description": "Indicates if there are more results available"},
|
|
207
|
+
)
|
|
208
|
+
results: list[Capsule] = dataclass_field(
|
|
209
|
+
metadata={"description": "Array of capsules found matching the search criteria"},
|
|
210
|
+
)
|
|
211
|
+
next_token: Optional[str] = dataclass_field(
|
|
212
|
+
default=None,
|
|
213
|
+
metadata={"description": "Token for fetching the next page of results"},
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@dataclass
|
|
218
|
+
class Capsules:
|
|
219
|
+
"""Client for interacting with Code Ocean capsule APIs."""
|
|
220
|
+
|
|
221
|
+
client: BaseUrlSession
|
|
222
|
+
|
|
223
|
+
def get_capsule(self, capsule_id: str) -> Capsule:
|
|
224
|
+
"""Retrieve metadata for a specific capsule by its ID."""
|
|
225
|
+
res = self.client.get(f"capsules/{capsule_id}")
|
|
226
|
+
|
|
227
|
+
return Capsule.from_dict(res.json())
|
|
228
|
+
|
|
229
|
+
def list_computations(self, capsule_id: str) -> list[Computation]:
|
|
230
|
+
"""Get all computations associated with a specific capsule."""
|
|
231
|
+
res = self.client.get(f"capsules/{capsule_id}/computations")
|
|
232
|
+
|
|
233
|
+
return [Computation.from_dict(c) for c in res.json()]
|
|
234
|
+
|
|
235
|
+
def update_permissions(self, capsule_id: str, permissions: Permissions):
|
|
236
|
+
"""Update permissions for a capsule."""
|
|
237
|
+
self.client.post(
|
|
238
|
+
f"capsules/{capsule_id}/permissions",
|
|
239
|
+
json=permissions.to_dict(),
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def attach_data_assets(
|
|
243
|
+
self,
|
|
244
|
+
capsule_id: str,
|
|
245
|
+
attach_params: list[DataAssetAttachParams],
|
|
246
|
+
) -> list[DataAssetAttachResults]:
|
|
247
|
+
"""Attach one or more data assets to a capsule with optional mount paths."""
|
|
248
|
+
res = self.client.post(
|
|
249
|
+
f"capsules/{capsule_id}/data_assets",
|
|
250
|
+
json=[j.to_dict() for j in attach_params],
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return [DataAssetAttachResults.from_dict(c) for c in res.json()]
|
|
254
|
+
|
|
255
|
+
def detach_data_assets(self, capsule_id: str, data_assets: list[str]):
|
|
256
|
+
"""Detach one or more data assets from a capsule by their IDs."""
|
|
257
|
+
self.client.delete(
|
|
258
|
+
f"capsules/{capsule_id}/data_assets/",
|
|
259
|
+
json=data_assets,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults:
|
|
263
|
+
"""Search for capsules with filtering, sorting, and pagination
|
|
264
|
+
options."""
|
|
265
|
+
res = self.client.post("capsules/search", json=search_params.to_dict())
|
|
266
|
+
|
|
267
|
+
return CapsuleSearchResults.from_dict(res.json())
|
|
268
|
+
|
|
269
|
+
def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsule]:
|
|
270
|
+
"""Iterate through all capsules matching search criteria with automatic pagination."""
|
|
271
|
+
params = search_params.to_dict()
|
|
272
|
+
while True:
|
|
273
|
+
response = self.search_capsules(search_params=CapsuleSearchParams(**params))
|
|
274
|
+
|
|
275
|
+
for result in response.results:
|
|
276
|
+
yield result
|
|
277
|
+
|
|
278
|
+
if not response.has_more:
|
|
279
|
+
return
|
|
280
|
+
|
|
281
|
+
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"
|