codeocean 0.9.0__tar.gz → 0.10.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 (25) hide show
  1. {codeocean-0.9.0 → codeocean-0.10.0}/CHANGELOG.md +6 -1
  2. {codeocean-0.9.0 → codeocean-0.10.0}/LICENSE +1 -1
  3. {codeocean-0.9.0 → codeocean-0.10.0}/PKG-INFO +1 -1
  4. {codeocean-0.9.0 → codeocean-0.10.0}/pyproject.toml +1 -1
  5. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/capsule.py +269 -1
  6. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/client.py +3 -1
  7. codeocean-0.10.0/src/codeocean/custom_metadata.py +96 -0
  8. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/data_asset.py +24 -2
  9. {codeocean-0.9.0 → codeocean-0.10.0}/.flake8 +0 -0
  10. {codeocean-0.9.0 → codeocean-0.10.0}/.gitignore +0 -0
  11. {codeocean-0.9.0 → codeocean-0.10.0}/README.md +0 -0
  12. {codeocean-0.9.0 → codeocean-0.10.0}/RELEASE.md +0 -0
  13. {codeocean-0.9.0 → codeocean-0.10.0}/examples/create_data_asset.py +0 -0
  14. {codeocean-0.9.0 → codeocean-0.10.0}/examples/run_capsule.py +0 -0
  15. {codeocean-0.9.0 → codeocean-0.10.0}/examples/run_pipeline.py +0 -0
  16. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/__init__.py +0 -0
  17. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/components.py +0 -0
  18. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/computation.py +0 -0
  19. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/enum.py +0 -0
  20. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/error.py +0 -0
  21. {codeocean-0.9.0 → codeocean-0.10.0}/src/codeocean/folder.py +0 -0
  22. {codeocean-0.9.0 → codeocean-0.10.0}/tests/__init__.py +0 -0
  23. {codeocean-0.9.0 → codeocean-0.10.0}/tests/test_client.py +0 -0
  24. {codeocean-0.9.0 → codeocean-0.10.0}/tests/test_error.py +0 -0
  25. {codeocean-0.9.0 → codeocean-0.10.0}/tests/test_package.py +0 -0
@@ -1,10 +1,15 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ ## 0.10.0 (2025-09-01)
5
+ - [#54](https://github.com/codeocean/codeocean-sdk-python/pull/54) feat: Code Ocean version 3.9 functionality
6
+
7
+ ## 0.9.1 (2025-08-29)
8
+ - [#56](https://github.com/codeocean/codeocean-sdk-python/pull/56) fix: update query description in search APIs
9
+
4
10
  ## 0.9.0 (2025-08-05)
5
11
  - [#47](https://github.com/codeocean/codeocean-sdk-python/pull/47) feat: add error handling
6
12
 
7
-
8
13
  ## 0.8.0 (2025-07-11)
9
14
  - [#50](https://github.com/codeocean/codeocean-sdk-python/pull/50) feat: Add Min-Server-Version header support
10
15
  - [#49](https://github.com/codeocean/codeocean-sdk-python/pull/49) feat: Identify AI agents in API requests
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) [2024] [Code Ocean]
3
+ Copyright (c) [2024-present] [Code Ocean]
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.9.0
3
+ Version: 0.10.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.9.0"
7
+ version = "0.10.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -26,6 +26,29 @@ class CapsuleSortBy(StrEnum):
26
26
  Name = "name"
27
27
 
28
28
 
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
+
29
52
  @dataclass_json
30
53
  @dataclass(frozen=True)
31
54
  class OriginalCapsuleInfo:
@@ -81,6 +104,10 @@ class Capsule:
81
104
  slug: str = dataclass_field(
82
105
  metadata={"description": "Alternate capsule ID (URL-friendly identifier)"},
83
106
  )
107
+ last_accessed: Optional[int] = dataclass_field(
108
+ default=None,
109
+ metadata={"description": "Capsule last accessed time (int64 timestamp)"},
110
+ )
84
111
  article: Optional[dict] = dataclass_field(
85
112
  default=None,
86
113
  metadata={
@@ -140,7 +167,38 @@ class CapsuleSearchParams:
140
167
  query: Optional[str] = dataclass_field(
141
168
  default=None,
142
169
  metadata={
143
- "description": "Search query in free text or structured format (name:... tag:...)"
170
+ "description": """Search expression supporting free text and field:value filters.
171
+ Valid fields:
172
+ - id
173
+ - name
174
+ - doi
175
+ - tag
176
+ - field
177
+ - affiliation
178
+ - journal
179
+ - article
180
+ - author
181
+
182
+ Free text:
183
+ - Matches across weighted fields (name, tags, description, authors, etc.)
184
+
185
+ Syntax rules:
186
+ - Same field repeated = OR
187
+ - Different fields = AND
188
+ - Quotes = exact phrase
189
+ - No explicit OR operator
190
+ - No wildcards (*)
191
+ - Not case sensitive
192
+
193
+ Notes:
194
+ - "description" is not directly searchable; it is covered by free-text matching.
195
+
196
+ Examples:
197
+ - name:RNA-seq tag:genomics
198
+ - name:"single cell analysis"
199
+ - Synergy
200
+ - name:Synergy
201
+ """
144
202
  },
145
203
  )
146
204
  next_token: Optional[str] = dataclass_field(
@@ -214,6 +272,199 @@ class CapsuleSearchResults:
214
272
  )
215
273
 
216
274
 
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
+
217
468
  @dataclass
218
469
  class Capsules:
219
470
  """Client for interacting with Code Ocean capsule APIs."""
@@ -226,6 +477,16 @@ class Capsules:
226
477
 
227
478
  return Capsule.from_dict(res.json())
228
479
 
480
+ def delete_capsule(self, capsule_id: str):
481
+ """Delete a capsule permanently."""
482
+ self.client.delete(f"capsules/{capsule_id}")
483
+
484
+ def get_capsule_app_panel(self, capsule_id: str, version: Optional[int] = None) -> AppPanel:
485
+ """Retrieve app panel information for a specific capsule by its ID."""
486
+ res = self.client.get(f"capsules/{capsule_id}/app_panel", params={"version": version} if version else None)
487
+
488
+ return AppPanel.from_dict(res.json())
489
+
229
490
  def list_computations(self, capsule_id: str) -> list[Computation]:
230
491
  """Get all computations associated with a specific capsule."""
231
492
  res = self.client.get(f"capsules/{capsule_id}/computations")
@@ -259,6 +520,13 @@ class Capsules:
259
520
  json=data_assets,
260
521
  )
261
522
 
523
+ def archive_capsule(self, capsule_id: str, archive: bool):
524
+ """Archive or unarchive a capsule to control its visibility and accessibility."""
525
+ self.client.patch(
526
+ f"capsules/{capsule_id}/archive",
527
+ params={"archive": archive},
528
+ )
529
+
262
530
  def search_capsules(self, search_params: CapsuleSearchParams) -> CapsuleSearchResults:
263
531
  """Search for capsules with filtering, sorting, and pagination
264
532
  options."""
@@ -9,6 +9,7 @@ import requests
9
9
 
10
10
  from codeocean.capsule import Capsules
11
11
  from codeocean.computation import Computations
12
+ from codeocean.custom_metadata import CustomMetadataSchema
12
13
  from codeocean.data_asset import DataAssets
13
14
  from codeocean.error import Error
14
15
 
@@ -36,7 +37,7 @@ class CodeOcean:
36
37
  agent_id: Optional[str] = None
37
38
 
38
39
  # Minimum server version required by this SDK
39
- MIN_SERVER_VERSION = "3.6.0"
40
+ MIN_SERVER_VERSION = "3.9.0"
40
41
 
41
42
  def __post_init__(self):
42
43
  self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
@@ -52,6 +53,7 @@ class CodeOcean:
52
53
 
53
54
  self.capsules = Capsules(client=self.session)
54
55
  self.computations = Computations(client=self.session)
56
+ self.custom_metadata = CustomMetadataSchema(client=self.session)
55
57
  self.data_assets = DataAssets(client=self.session)
56
58
 
57
59
  def _error_handler(self, response, *args, **kwargs):
@@ -0,0 +1,96 @@
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, Union
6
+ from requests_toolbelt.sessions import BaseUrlSession
7
+
8
+ from codeocean.enum import StrEnum
9
+
10
+
11
+ class CustomMetadataFieldType(StrEnum):
12
+ """ Type of the custom metadata field value. """
13
+
14
+ String = "string"
15
+ Number = "number"
16
+ Date = "date"
17
+
18
+
19
+ @dataclass_json
20
+ @dataclass(frozen=True)
21
+ class CustomMetadataFieldRange:
22
+ """ Range of valid values for a custom metadata field. """
23
+
24
+ min: Optional[float] = dataclass_field(
25
+ default=None,
26
+ metadata={"description": "Minimum valid value"}
27
+ )
28
+ max: Optional[float] = dataclass_field(
29
+ default=None,
30
+ metadata={"description": "Maximum valid value"}
31
+ )
32
+
33
+
34
+ @dataclass_json
35
+ @dataclass(frozen=True)
36
+ class CustomMetadataField:
37
+ """ Represents a custom metadata field in the Code Ocean platform. """
38
+
39
+ name: str = dataclass_field(
40
+ metadata={"description": "Name of the custom metadata field"}
41
+ )
42
+ type: CustomMetadataFieldType = dataclass_field(
43
+ metadata={"description": "Type of the custom metadata field value (string, number, date)"}
44
+ )
45
+ range: Optional[CustomMetadataFieldRange] = dataclass_field(
46
+ default=None,
47
+ metadata={"description": "Range of valid values for the field"}
48
+ )
49
+ allowed_values: Optional[Union[list[str], list[float]]] = dataclass_field(
50
+ default=None,
51
+ metadata={"description": "Allowed values for the field (item type according to field type)"}
52
+ )
53
+ multiple: Optional[bool] = dataclass_field(
54
+ default=None,
55
+ metadata={"description": "Whether multiple values are allowed"}
56
+ )
57
+ units: Optional[str] = dataclass_field(
58
+ default=None,
59
+ metadata={"description": "Units of the field value"}
60
+ )
61
+ category: Optional[str] = dataclass_field(
62
+ default=None,
63
+ metadata={"description": "Category of the field"}
64
+ )
65
+ required: Optional[bool] = dataclass_field(
66
+ default=None,
67
+ metadata={"description": "Whether the field is required"}
68
+ )
69
+
70
+
71
+ @dataclass_json
72
+ @dataclass(frozen=True)
73
+ class CustomMetadata:
74
+ """ Represents the custom metadata schema in the Code Ocean platform. """
75
+
76
+ fields: Optional[list[CustomMetadataField]] = dataclass_field(
77
+ default=None,
78
+ metadata={"description": "List of custom metadata fields"}
79
+ )
80
+ categories: Optional[list[str]] = dataclass_field(
81
+ default=None,
82
+ metadata={"description": "List of categories for custom metadata fields"}
83
+ )
84
+
85
+
86
+ @dataclass
87
+ class CustomMetadataSchema:
88
+ """Client for getting the Code Ocean custom metadata schema."""
89
+
90
+ client: BaseUrlSession
91
+
92
+ def get_custom_metadata(self) -> CustomMetadata:
93
+ """Retrieve the Code Ocean deployment's custom metadata schema."""
94
+ res = self.client.get("custom_metadata")
95
+
96
+ return CustomMetadata.from_dict(res.json())
@@ -538,8 +538,30 @@ class DataAssetSearchParams:
538
538
  query: Optional[str] = field(
539
539
  default=None,
540
540
  metadata={
541
- "description": "Search query in free text or structured format "
542
- "(name:... tag:... run_script:... commit_id:...)",
541
+ "description": """Search expression supporting free text and field:value filters.
542
+ Valid fields:
543
+ - name
544
+ - tag
545
+ - run_script
546
+ - commit_id
547
+ - contained_data_id
548
+
549
+ Free text:
550
+ - Matches across weighted fields (name, tags, description, custom metadata)
551
+
552
+ Syntax rules:
553
+ - Same field repeated = OR
554
+ - Different fields = AND
555
+ - Quotes = exact phrase
556
+ - No explicit OR operator
557
+ - No wildcards (*)
558
+ - Not case sensitive
559
+
560
+ Examples:
561
+ - name:RNA-seq tag:genomics
562
+ - name:"analysis pipeline"
563
+ - name:Synergy
564
+ """
543
565
  },
544
566
  )
545
567
  next_token: Optional[str] = field(
File without changes
File without changes
File without changes
File without changes
File without changes