codeocean 0.5.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.
Files changed (26) hide show
  1. {codeocean-0.5.0 → codeocean-0.7.0}/.gitignore +2 -1
  2. {codeocean-0.5.0 → codeocean-0.7.0}/CHANGELOG.md +8 -0
  3. {codeocean-0.5.0 → codeocean-0.7.0}/PKG-INFO +1 -1
  4. {codeocean-0.5.0 → codeocean-0.7.0}/pyproject.toml +1 -1
  5. codeocean-0.7.0/src/codeocean/capsule.py +281 -0
  6. codeocean-0.7.0/src/codeocean/components.py +148 -0
  7. codeocean-0.7.0/src/codeocean/computation.py +349 -0
  8. codeocean-0.7.0/src/codeocean/data_asset.py +849 -0
  9. codeocean-0.7.0/src/codeocean/folder.py +55 -0
  10. codeocean-0.5.0/src/codeocean/capsule.py +0 -136
  11. codeocean-0.5.0/src/codeocean/components.py +0 -77
  12. codeocean-0.5.0/src/codeocean/computation.py +0 -178
  13. codeocean-0.5.0/src/codeocean/data_asset.py +0 -349
  14. codeocean-0.5.0/src/codeocean/folder.py +0 -32
  15. {codeocean-0.5.0 → codeocean-0.7.0}/.flake8 +0 -0
  16. {codeocean-0.5.0 → codeocean-0.7.0}/LICENSE +0 -0
  17. {codeocean-0.5.0 → codeocean-0.7.0}/README.md +0 -0
  18. {codeocean-0.5.0 → codeocean-0.7.0}/RELEASE.md +0 -0
  19. {codeocean-0.5.0 → codeocean-0.7.0}/examples/create_data_asset.py +0 -0
  20. {codeocean-0.5.0 → codeocean-0.7.0}/examples/run_capsule.py +0 -0
  21. {codeocean-0.5.0 → codeocean-0.7.0}/examples/run_pipeline.py +0 -0
  22. {codeocean-0.5.0 → codeocean-0.7.0}/src/codeocean/__init__.py +0 -0
  23. {codeocean-0.5.0 → codeocean-0.7.0}/src/codeocean/client.py +0 -0
  24. {codeocean-0.5.0 → codeocean-0.7.0}/src/codeocean/enum.py +0 -0
  25. {codeocean-0.5.0 → codeocean-0.7.0}/tests/__init__.py +0 -0
  26. {codeocean-0.5.0 → codeocean-0.7.0}/tests/test_package.py +0 -0
@@ -6,7 +6,8 @@ dist/
6
6
  *.py[cod]
7
7
 
8
8
  # Environments
9
- .[v]env
9
+ .venv
10
+ .env
10
11
 
11
12
  # IDEs
12
13
  .idea
@@ -1,6 +1,14 @@
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
+
7
+ ## 0.6.0 (2025-06-03)
8
+ - [#44](https://github.com/codeocean/codeocean-sdk-python/pull/44) feat: support for nextflow_profiles in data assets and computations
9
+ - [#43](https://github.com/codeocean/codeocean-sdk-python/pull/43) fix: updates .gitignore to exclude .env
10
+ - **Minimum Code Ocean platform version updated to `3.6.0`.**
11
+
4
12
  ## 0.5.0 (2025-03-03)
5
13
  - [#38](https://github.com/codeocean/codeocean-sdk-python/pull/38) feat: add update_permissions function for capsules
6
14
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.5.0
3
+ Version: 0.7.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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeocean"
7
- version = "0.5.0"
7
+ version = "0.7.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -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"