11from __future__ import annotations
22
3- from dataclasses import dataclass
3+ from dataclasses import dataclass , field as dataclass_field
44from dataclasses_json import dataclass_json
55from typing import Optional , Iterator
66from requests_toolbelt .sessions import BaseUrlSession
1212
1313
1414class CapsuleStatus (StrEnum ):
15+ """Status of a capsule indicating its release state."""
16+
1517 NonRelease = "non_release"
1618 Release = "release"
1719
1820
1921class 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 )
2731class 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 )
3863class 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 )
58136class 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 )
74202class 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
81218class 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