55from typing import Optional , Iterator
66from requests_toolbelt .sessions import BaseUrlSession
77
8- from codeocean .components import (
9- Ownership ,
10- SortOrder ,
11- SearchFilter ,
12- Permissions ,
13- AppPanel ,
14- )
8+ from codeocean .components import Ownership , SortOrder , SearchFilter , Permissions
159from codeocean .computation import Computation
1610from codeocean .data_asset import DataAssetAttachParams , DataAssetAttachResults
1711from codeocean .enum import StrEnum
@@ -32,6 +26,29 @@ class CapsuleSortBy(StrEnum):
3226 Name = "name"
3327
3428
29+ class AppPanelDataAssetKind (StrEnum ):
30+ """The kind of data asset displayed in an app panel.
31+
32+ - 'Internal' → Data stored inside Code Ocean.
33+ - 'External' → Data stored external to Code Ocean.
34+ - 'Combined' → Data containing multiple external data assets.
35+
36+ In pipelines, a data asset can only be replaced with one of the same kind.
37+ """
38+
39+ Internal = "internal"
40+ External = "external"
41+ Combined = "combined"
42+
43+
44+ class AppPanelParameterType (StrEnum ):
45+ """The type of parameter displayed in an app panel."""
46+
47+ Text = "text"
48+ List = "list"
49+ File = "file"
50+
51+
3552@dataclass_json
3653@dataclass (frozen = True )
3754class OriginalCapsuleInfo :
@@ -255,38 +272,232 @@ class CapsuleSearchResults:
255272 )
256273
257274
275+ @dataclass_json
276+ @dataclass (frozen = True )
277+ class AppPanelCategories :
278+ """Categories for a capsule's App Panel parameters."""
279+
280+ id : str = dataclass_field (
281+ metadata = {"description" : "Unique identifier for the category." },
282+ )
283+ name : str = dataclass_field (
284+ metadata = {"description" : "Human-readable name of the category." },
285+ )
286+ description : Optional [str ] = dataclass_field (
287+ default = None ,
288+ metadata = {"description" : "Optional detailed description of the category." },
289+ )
290+ help_text : Optional [str ] = dataclass_field (
291+ default = None ,
292+ metadata = {"description" : "Optional help text providing guidance or additional information about the category." },
293+ )
294+
295+
296+ @dataclass_json
297+ @dataclass (frozen = True )
298+ class AppPanelParameters :
299+ """Parameters for a capsule's App Panel."""
300+
301+ name : str = dataclass_field (
302+ metadata = {"description" : "Parameter label/display name." }
303+ )
304+ type : AppPanelParameterType = dataclass_field (
305+ metadata = {"description" : "Type of the parameter (text, list, file)." }
306+ )
307+ category : Optional [str ] = dataclass_field (
308+ default = None ,
309+ metadata = {"description" : "ID of category the parameter belongs to." }
310+ )
311+ param_name : Optional [str ] = dataclass_field (
312+ default = None ,
313+ metadata = {"description" : "The parameter name/argument key" }
314+ )
315+ description : Optional [str ] = dataclass_field (
316+ default = None ,
317+ metadata = {"description" : "Description of the parameter." }
318+ )
319+ help_text : Optional [str ] = dataclass_field (
320+ default = None ,
321+ metadata = {"description" : "Help text for the parameter." }
322+ )
323+ value_type : Optional [str ] = dataclass_field (
324+ default = None ,
325+ metadata = {"description" : "Value type of the parameter." }
326+ )
327+ default_value : Optional [str ] = dataclass_field (
328+ default = None ,
329+ metadata = {"description" : "Default value of the parameter." }
330+ )
331+ required : Optional [bool ] = dataclass_field (
332+ default = None ,
333+ metadata = {"description" : "Indicates if the parameter is required." }
334+ )
335+ hidden : Optional [bool ] = dataclass_field (
336+ default = None ,
337+ metadata = {"description" : "Indicates if the parameter is hidden." }
338+ )
339+ minimum : Optional [float ] = dataclass_field (
340+ default = None ,
341+ metadata = {"description" : "Minimum value for the parameter." }
342+ )
343+ maximum : Optional [float ] = dataclass_field (
344+ default = None ,
345+ metadata = {"description" : "Maximum value for the parameter." }
346+ )
347+ pattern : Optional [str ] = dataclass_field (
348+ default = None ,
349+ metadata = {"description" : "Regular expression pattern for the parameter." }
350+ )
351+ value_options : Optional [list [str ]] = dataclass_field (
352+ default = None ,
353+ metadata = {"description" : "Allowed values for the parameter." }
354+ )
355+
356+
357+ @dataclass_json
358+ @dataclass (frozen = True )
359+ class AppPanelGeneral :
360+ """General information about a capsule's App Panel."""
361+
362+ title : Optional [str ] = dataclass_field (
363+ default = None ,
364+ metadata = {"description" : "Title of the App Panel." }
365+ )
366+ instructions : Optional [str ] = dataclass_field (
367+ default = None ,
368+ metadata = {"description" : "Instructions for using the App Panel." }
369+ )
370+ help_text : Optional [str ] = dataclass_field (
371+ default = None ,
372+ metadata = {"description" : "Help text for the App Panel." }
373+ )
374+
375+
376+ @dataclass_json
377+ @dataclass (frozen = True )
378+ class AppPanelDataAsset :
379+ """Data asset parameter for the App Panel."""
380+
381+ id : str = dataclass_field (
382+ metadata = {"description" : "Unique identifier for the data asset." }
383+ )
384+ mount : str = dataclass_field (
385+ metadata = {"description" : "Mount path of the data asset within the capsule. "
386+ "Use this mount path to replace the currently attached data asset with your own" }
387+ )
388+ name : str = dataclass_field (
389+ metadata = {"description" : "Display name of the data asset." }
390+ )
391+ kind : AppPanelDataAssetKind = dataclass_field (
392+ metadata = {"description" : "Kind of the data asset (internal, external, combined)." }
393+ )
394+ accessible : bool = dataclass_field (
395+ metadata = {"description" : "Indicates if the data asset is accessible to the user." }
396+ )
397+ description : Optional [str ] = dataclass_field (
398+ default = None ,
399+ metadata = {"description" : "Optional description of the data asset parameter." }
400+ )
401+ help_text : Optional [str ] = dataclass_field (
402+ default = None ,
403+ metadata = {"description" : "Optional help text for the data asset parameter." }
404+ )
405+
406+
407+ @dataclass_json
408+ @dataclass (frozen = True )
409+ class AppPanelResult :
410+ """Selected result files to display once the computation is complete."""
411+
412+ file_name : str = dataclass_field (
413+ metadata = {"description" : "Name of the result file." }
414+ )
415+
416+
417+ @dataclass_json
418+ @dataclass (frozen = True )
419+ class AppPanelProcess :
420+ """Pipeline process name and its corresponding app panel (for pipelines of capsules only)"""
421+
422+ name : str = dataclass_field (
423+ metadata = {"description" : "Name of the pipeline process." }
424+ )
425+ categories : Optional [AppPanelCategories ] = dataclass_field (
426+ default = None ,
427+ metadata = {"description" : "Categories for the pipeline process's app panel parameters." }
428+ )
429+ parameters : Optional [AppPanelParameters ] = dataclass_field (
430+ default = None ,
431+ metadata = {"description" : "Parameters for the pipeline process's app panel." }
432+ )
433+
434+
435+ @dataclass_json
436+ @dataclass (frozen = True )
437+ class AppPanel :
438+ """App Panel configuration for a capsule or pipeline, including general info, data assets,
439+ categories, parameters, and results.
440+ """
441+
442+ general : Optional [AppPanelGeneral ] = dataclass_field (
443+ default = None ,
444+ metadata = {"description" : "General information about the App Panel." }
445+ )
446+ data_assets : Optional [list [AppPanelDataAsset ]] = dataclass_field (
447+ default = None ,
448+ metadata = {"description" : "List of data assets used in the App Panel." }
449+ )
450+ categories : Optional [list [AppPanelCategories ]] = dataclass_field (
451+ default = None ,
452+ metadata = {"description" : "Categories for organizing App Panel parameters." }
453+ )
454+ parameters : Optional [list [AppPanelParameters ]] = dataclass_field (
455+ default = None ,
456+ metadata = {"description" : "Parameters for the App Panel." }
457+ )
458+ results : Optional [list [AppPanelResult ]] = dataclass_field (
459+ default = None ,
460+ metadata = {"description" : "Result files to display after computation." }
461+ )
462+ processes : Optional [list [AppPanelProcess ]] = dataclass_field (
463+ default = None ,
464+ metadata = {"description" : "Pipeline processes and their App Panels." }
465+ )
466+
467+
258468@dataclass
259469class Capsules :
260470 """Client for interacting with Code Ocean capsule APIs."""
261471
262472 client : BaseUrlSession
473+ _route : str = "capsules"
263474
264475 def get_capsule (self , capsule_id : str ) -> Capsule :
265476 """Retrieve metadata for a specific capsule by its ID."""
266- res = self .client .get (f"capsules /{ capsule_id } " )
477+ res = self .client .get (f"{ self . _route } /{ capsule_id } " )
267478
268479 return Capsule .from_dict (res .json ())
269480
270481 def delete_capsule (self , capsule_id : str ):
271482 """Delete a capsule permanently."""
272- self .client .delete (f"capsules /{ capsule_id } " )
483+ self .client .delete (f"{ self . _route } /{ capsule_id } " )
273484
274485 def get_capsule_app_panel (self , capsule_id : str , version : Optional [int ] = None ) -> AppPanel :
275486 """Retrieve app panel information for a specific capsule by its ID."""
276- res = self .client .get (f"capsules /{ capsule_id } /app_panel" , params = {"version" : version } if version else None )
487+ res = self .client .get (f"{ self . _route } /{ capsule_id } /app_panel" , params = {"version" : version } if version else None )
277488
278489 return AppPanel .from_dict (res .json ())
279490
280491 def list_computations (self , capsule_id : str ) -> list [Computation ]:
281492 """Get all computations associated with a specific capsule."""
282- res = self .client .get (f"capsules /{ capsule_id } /computations" )
493+ res = self .client .get (f"{ self . _route } /{ capsule_id } /computations" )
283494
284495 return [Computation .from_dict (c ) for c in res .json ()]
285496
286497 def update_permissions (self , capsule_id : str , permissions : Permissions ):
287498 """Update permissions for a capsule."""
288499 self .client .post (
289- f"capsules /{ capsule_id } /permissions" ,
500+ f"{ self . _route } /{ capsule_id } /permissions" ,
290501 json = permissions .to_dict (),
291502 )
292503
@@ -297,7 +508,7 @@ def attach_data_assets(
297508 ) -> list [DataAssetAttachResults ]:
298509 """Attach one or more data assets to a capsule with optional mount paths."""
299510 res = self .client .post (
300- f"capsules /{ capsule_id } /data_assets" ,
511+ f"{ self . _route } /{ capsule_id } /data_assets" ,
301512 json = [j .to_dict () for j in attach_params ],
302513 )
303514
@@ -306,21 +517,21 @@ def attach_data_assets(
306517 def detach_data_assets (self , capsule_id : str , data_assets : list [str ]):
307518 """Detach one or more data assets from a capsule by their IDs."""
308519 self .client .delete (
309- f"capsules /{ capsule_id } /data_assets/" ,
520+ f"{ self . _route } /{ capsule_id } /data_assets/" ,
310521 json = data_assets ,
311522 )
312523
313524 def archive_capsule (self , capsule_id : str , archive : bool ):
314525 """Archive or unarchive a capsule to control its visibility and accessibility."""
315526 self .client .patch (
316- f"capsules /{ capsule_id } /archive" ,
527+ f"{ self . _route } /{ capsule_id } /archive" ,
317528 params = {"archive" : archive },
318529 )
319530
320531 def search_capsules (self , search_params : CapsuleSearchParams ) -> CapsuleSearchResults :
321532 """Search for capsules with filtering, sorting, and pagination
322533 options."""
323- res = self .client .post ("capsules /search" , json = search_params .to_dict ())
534+ res = self .client .post (f" { self . _route } /search" , json = search_params .to_dict ())
324535
325536 return CapsuleSearchResults .from_dict (res .json ())
326537
0 commit comments