Skip to content

Commit 717f625

Browse files
zvikagartdrorhilmanjake-valsamis
authored
feat: Add API documentation (#46)
* refactor: enhance Capsule and search parameters with detailed metadata descriptions * refactor: enhance documentation and metadata for computation-related classes * refactor: enhance metadata descriptions for data asset classes and methods * refactor: enhance metadata descriptions for FolderItem, Folder, ListFolderParams, and DownloadFileURL classes * refactor: enhance metadata descriptions for user, group, and permission classes * refactor: standardize field imports in Capsule and related classes Preserves the required attribute name - You can keep field as needed Eliminates the runtime error - No more TypeError * refactor: enhance metadata descriptions for CapsuleSearchParams class * linting issues * linter issues - shorter lines * refactor: linting fix * line too long * remove whitespace * formatting * fix * formatting * lint * clarifications to descriptions in capsule.py and components.py * fix line too long formatting * clarifications for metadata in computation.py and data_asset.py --------- Co-authored-by: Dror Hilman <dror.hilman@codeocean.com> Co-authored-by: jake-valsamis <jake@codeocean.com>
1 parent 1fd7338 commit 717f625

File tree

5 files changed

+1126
-220
lines changed

5 files changed

+1126
-220
lines changed

src/codeocean/capsule.py

Lines changed: 188 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from dataclasses import dataclass
3+
from dataclasses import dataclass, field as dataclass_field
44
from dataclasses_json import dataclass_json
55
from typing import Optional, Iterator
66
from requests_toolbelt.sessions import BaseUrlSession
@@ -12,11 +12,15 @@
1212

1313

1414
class CapsuleStatus(StrEnum):
15+
"""Status of a capsule indicating its release state."""
16+
1517
NonRelease = "non_release"
1618
Release = "release"
1719

1820

1921
class CapsuleSortBy(StrEnum):
22+
"""Fields available for sorting capsule search results."""
23+
2024
Created = "created"
2125
LastAccessed = "last_accessed"
2226
Name = "name"
@@ -25,83 +29,222 @@ class CapsuleSortBy(StrEnum):
2529
@dataclass_json
2630
@dataclass(frozen=True)
2731
class OriginalCapsuleInfo:
28-
id: Optional[str] = None
29-
major_version: Optional[int] = None
30-
minor_version: Optional[int] = None
31-
name: Optional[str] = None
32-
created: Optional[int] = None
33-
public: Optional[bool] = None
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+
)
3459

3560

3661
@dataclass_json
3762
@dataclass(frozen=True)
3863
class Capsule:
39-
id: str
40-
created: int
41-
name: str
42-
status: CapsuleStatus
43-
owner: str
44-
slug: str
45-
article: Optional[dict] = None
46-
cloned_from_url: Optional[str] = None
47-
description: Optional[str] = None
48-
field: Optional[str] = None
49-
tags: Optional[list[str]] = None
50-
original_capsule: Optional[OriginalCapsuleInfo] = None
51-
release_capsule: Optional[str] = None
52-
submission: Optional[dict] = None
53-
versions: Optional[list[dict]] = None
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+
)
54132

55133

56134
@dataclass_json
57135
@dataclass(frozen=True)
58136
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
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+
)
70198

71199

72200
@dataclass_json
73201
@dataclass(frozen=True)
74202
class CapsuleSearchResults:
75-
has_more: bool
76-
results: list[Capsule]
77-
next_token: Optional[str] = None
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+
)
78215

79216

80217
@dataclass
81218
class Capsules:
219+
"""Client for interacting with Code Ocean capsule APIs."""
82220

83221
client: BaseUrlSession
84222

85223
def get_capsule(self, capsule_id: str) -> Capsule:
224+
"""Retrieve metadata for a specific capsule by its ID."""
86225
res = self.client.get(f"capsules/{capsule_id}")
87226

88227
return Capsule.from_dict(res.json())
89228

90229
def list_computations(self, capsule_id: str) -> list[Computation]:
230+
"""Get all computations associated with a specific capsule."""
91231
res = self.client.get(f"capsules/{capsule_id}/computations")
92232

93233
return [Computation.from_dict(c) for c in res.json()]
94234

95235
def update_permissions(self, capsule_id: str, permissions: Permissions):
236+
"""Update permissions for a capsule."""
96237
self.client.post(
97238
f"capsules/{capsule_id}/permissions",
98239
json=permissions.to_dict(),
99240
)
100241

101242
def attach_data_assets(
102-
self,
103-
capsule_id: str,
104-
attach_params: list[DataAssetAttachParams]) -> list[DataAssetAttachResults]:
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."""
105248
res = self.client.post(
106249
f"capsules/{capsule_id}/data_assets",
107250
json=[j.to_dict() for j in attach_params],
@@ -110,22 +253,24 @@ def attach_data_assets(
110253
return [DataAssetAttachResults.from_dict(c) for c in res.json()]
111254

112255
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."""
113257
self.client.delete(
114258
f"capsules/{capsule_id}/data_assets/",
115259
json=data_assets,
116260
)
117261

118262
def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults:
263+
"""Search for capsules with filtering, sorting, and pagination
264+
options."""
119265
res = self.client.post("capsules/search", json=search_params.to_dict())
120266

121267
return CapsuleSearchResults.from_dict(res.json())
122268

123-
def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsules]:
269+
def search_capsules_iterator(self, search_params: CapsuleSearchParams) -> Iterator[Capsule]:
270+
"""Iterate through all capsules matching search criteria with automatic pagination."""
124271
params = search_params.to_dict()
125272
while True:
126-
response = self.search_capsules(
127-
search_params=CapsuleSearchParams(**params)
128-
)
273+
response = self.search_capsules(search_params=CapsuleSearchParams(**params))
129274

130275
for result in response.results:
131276
yield result

0 commit comments

Comments
 (0)