codeocean 0.14.0__tar.gz → 0.16.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 (32) hide show
  1. {codeocean-0.14.0 → codeocean-0.16.0}/CHANGELOG.md +9 -0
  2. {codeocean-0.14.0 → codeocean-0.16.0}/PKG-INFO +1 -1
  3. {codeocean-0.14.0 → codeocean-0.16.0}/pyproject.toml +1 -1
  4. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/capsule.py +13 -0
  5. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/client.py +1 -1
  6. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/data_asset.py +6 -0
  7. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/capsule.py +23 -0
  8. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/computation.py +7 -0
  9. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/data_asset.py +12 -5
  10. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/pipeline.py +9 -0
  11. codeocean-0.16.0/tests/test_git_sync.py +46 -0
  12. {codeocean-0.14.0 → codeocean-0.16.0}/.flake8 +0 -0
  13. {codeocean-0.14.0 → codeocean-0.16.0}/.gitignore +0 -0
  14. {codeocean-0.14.0 → codeocean-0.16.0}/LICENSE +0 -0
  15. {codeocean-0.14.0 → codeocean-0.16.0}/README.md +0 -0
  16. {codeocean-0.14.0 → codeocean-0.16.0}/RELEASE.md +0 -0
  17. {codeocean-0.14.0 → codeocean-0.16.0}/examples/create_data_asset.py +0 -0
  18. {codeocean-0.14.0 → codeocean-0.16.0}/examples/run_capsule.py +0 -0
  19. {codeocean-0.14.0 → codeocean-0.16.0}/examples/run_pipeline.py +0 -0
  20. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/__init__.py +0 -0
  21. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/components.py +0 -0
  22. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/computation.py +0 -0
  23. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/custom_metadata.py +0 -0
  24. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/enum.py +0 -0
  25. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/error.py +0 -0
  26. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/__init__.py +0 -0
  27. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/components.py +0 -0
  28. {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/folder.py +0 -0
  29. {codeocean-0.14.0 → codeocean-0.16.0}/tests/__init__.py +0 -0
  30. {codeocean-0.14.0 → codeocean-0.16.0}/tests/test_client.py +0 -0
  31. {codeocean-0.14.0 → codeocean-0.16.0}/tests/test_error.py +0 -0
  32. {codeocean-0.14.0 → codeocean-0.16.0}/tests/test_package.py +0 -0
@@ -1,6 +1,15 @@
1
1
  CHANGELOG
2
2
  =========
3
3
 
4
+ ## 0.16.0 (2026-06-08)
5
+ - [#71](https://github.com/codeocean/codeocean-sdk-python/pull/71) feat: add Git sync support for capsules and pipelines
6
+ - **Minimum Code Ocean platform version updated to `4.6.0`.**
7
+
8
+ ## 0.15.0 (2026-04-10)
9
+ - [#69](https://github.com/codeocean/codeocean-sdk-python/pull/69) feat: Code Ocean version 4.3 functionality
10
+ - [#68](https://github.com/codeocean/codeocean-sdk-python/pull/68) feat: add get_permissions method for capsules, pipelines, data assets
11
+ - **Minimum Code Ocean platform version updated to `4.3.0`.**
12
+
4
13
  ## 0.14.0 (2026-01-29)
5
14
  - [#66](https://github.com/codeocean/codeocean-sdk-python/pull/66) feat: Code Ocean version 4.2 functionality
6
15
  - **Minimum Code Ocean platform version updated to `4.2.0`.**
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codeocean
3
- Version: 0.14.0
3
+ Version: 0.16.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.14.0"
7
+ version = "0.16.0"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
@@ -9,6 +9,7 @@ from codeocean.models.capsule import (
9
9
  CapsuleSearchParams,
10
10
  CapsuleSearchResults,
11
11
  AppPanel,
12
+ GitSyncResults,
12
13
  )
13
14
  # Re-exports for backward compatibility
14
15
  from codeocean.models.capsule import ( # noqa: F401
@@ -58,6 +59,12 @@ class Capsules:
58
59
 
59
60
  return [Computation.from_dict(c) for c in res.json()]
60
61
 
62
+ def get_permissions(self, capsule_id: str) -> Permissions:
63
+ """Get permissions for a specific capsule."""
64
+ res = self.client.get(f"{self._route}/{capsule_id}/permissions")
65
+
66
+ return Permissions.from_dict(res.json())
67
+
61
68
  def update_permissions(self, capsule_id: str, permissions: Permissions):
62
69
  """Update permissions for a capsule."""
63
70
  self.client.post(
@@ -85,6 +92,12 @@ class Capsules:
85
92
  json=data_assets,
86
93
  )
87
94
 
95
+ def sync_capsule(self, capsule_id: str) -> GitSyncResults:
96
+ """Sync a capsule with its linked external Git repository."""
97
+ res = self.client.post(f"{self._route}/{capsule_id}/sync")
98
+
99
+ return GitSyncResults.from_dict(res.json())
100
+
88
101
  def archive_capsule(self, capsule_id: str, archive: bool):
89
102
  """Archive or unarchive a capsule to control its visibility and accessibility."""
90
103
  self.client.patch(
@@ -38,7 +38,7 @@ class CodeOcean:
38
38
  agent_id: Optional[str] = None
39
39
 
40
40
  # Minimum server version required by this SDK
41
- MIN_SERVER_VERSION = "4.2.0"
41
+ MIN_SERVER_VERSION = "4.6.0"
42
42
 
43
43
  def __post_init__(self):
44
44
  self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
@@ -183,6 +183,12 @@ class DataAssets:
183
183
 
184
184
  params["next_token"] = response.next_token
185
185
 
186
+ def get_permissions(self, data_asset_id: str) -> Permissions:
187
+ """Get permissions for a specific data asset."""
188
+ res = self.client.get(f"data_assets/{data_asset_id}/permissions")
189
+
190
+ return Permissions.from_dict(res.json())
191
+
186
192
  def list_data_asset_files(self, data_asset_id: str, path: str = "") -> Folder:
187
193
  """
188
194
  List files and folders within an internal data asset at the specified path.
@@ -101,6 +101,10 @@ class Capsule:
101
101
  slug: str = dataclass_field(
102
102
  metadata={"description": "Alternate capsule ID (URL-friendly identifier)"},
103
103
  )
104
+ owner_email: Optional[str] = dataclass_field(
105
+ default=None,
106
+ metadata={"description": "Capsule owner's email address"},
107
+ )
104
108
  last_accessed: Optional[int] = dataclass_field(
105
109
  default=None,
106
110
  metadata={"description": "Capsule last accessed time (int64 timestamp)"},
@@ -155,6 +159,25 @@ class Capsule:
155
159
  )
156
160
 
157
161
 
162
+ @dataclass_json
163
+ @dataclass(frozen=True)
164
+ class GitSyncResults:
165
+ """Results of syncing a capsule or pipeline with its external Git remote."""
166
+
167
+ pushed: int = dataclass_field(
168
+ default=0,
169
+ metadata={"description": "Number of commits pushed to the external Git remote"},
170
+ )
171
+ pulled: int = dataclass_field(
172
+ default=0,
173
+ metadata={"description": "Number of commits pulled from the external Git remote"},
174
+ )
175
+ new_branch: bool = dataclass_field(
176
+ default=False,
177
+ metadata={"description": "Whether the current branch was newly created on the external remote by this sync"},
178
+ )
179
+
180
+
158
181
  @dataclass_json
159
182
  @dataclass(frozen=True)
160
183
  class CapsuleSearchParams:
@@ -97,6 +97,9 @@ class Computation:
97
97
  name: str = field(
98
98
  metadata={"description": "Display name of the computation"},
99
99
  )
100
+ owner: str = field(
101
+ metadata={"description": "Computation owner's ID"},
102
+ )
100
103
  run_time: int = field(
101
104
  metadata={"description": "Total run time in seconds"},
102
105
  )
@@ -105,6 +108,10 @@ class Computation:
105
108
  "description": "Current state of the computation (initializing, running, finalizing, completed, failed)",
106
109
  },
107
110
  )
111
+ owner_email: Optional[str] = field(
112
+ default=None,
113
+ metadata={"description": "Computation owner's email address"},
114
+ )
108
115
  cloud_workstation: Optional[bool] = field(
109
116
  default=None,
110
117
  metadata={
@@ -173,6 +173,14 @@ class DataAsset:
173
173
  mount: str = field(
174
174
  metadata={"description": "The default mount folder of the data asset"},
175
175
  )
176
+ last_used: int = field(
177
+ metadata={
178
+ "description": "Time data asset was last used in seconds from unix epoch",
179
+ },
180
+ )
181
+ owner: str = field(
182
+ metadata={"description": "Data asset owner's ID"},
183
+ )
176
184
  state: DataAssetState = field(
177
185
  metadata={"description": "Data asset creation state (draft, ready, failed)"},
178
186
  )
@@ -181,11 +189,6 @@ class DataAsset:
181
189
  "description": "Type of the data asset (dataset, result, combined, model)",
182
190
  },
183
191
  )
184
- last_used: int = field(
185
- metadata={
186
- "description": "Time data asset was last used in seconds from unix epoch",
187
- },
188
- )
189
192
  files: Optional[int] = field(
190
193
  default=None,
191
194
  metadata={"description": "Number of files in the data asset"},
@@ -198,6 +201,10 @@ class DataAsset:
198
201
  default=None,
199
202
  metadata={"description": "Data asset description"},
200
203
  )
204
+ owner_email: Optional[str] = field(
205
+ default=None,
206
+ metadata={"description": "Data asset owner's email address"},
207
+ )
201
208
  tags: Optional[list[str]] = field(
202
209
  default=None,
203
210
  metadata={"description": "Keywords for searching the data asset"},
@@ -10,6 +10,7 @@ from codeocean.models.capsule import (
10
10
  CapsuleSearchParams,
11
11
  CapsuleSearchResults,
12
12
  AppPanel,
13
+ GitSyncResults,
13
14
  )
14
15
  from codeocean.models.components import Permissions
15
16
  from codeocean.models.computation import Computation
@@ -42,6 +43,10 @@ class Pipelines:
42
43
  """Get all computations associated with a specific pipeline."""
43
44
  return self._capsules.list_computations(pipeline_id)
44
45
 
46
+ def get_permissions(self, pipeline_id: str) -> Permissions:
47
+ """Get permissions for a specific pipeline."""
48
+ return self._capsules.get_permissions(pipeline_id)
49
+
45
50
  def update_permissions(self, pipeline_id: str, permissions: Permissions):
46
51
  """Update permissions for a pipeline."""
47
52
  return self._capsules.update_permissions(pipeline_id, permissions)
@@ -58,6 +63,10 @@ class Pipelines:
58
63
  """Detach one or more data assets from a pipeline by their IDs."""
59
64
  return self._capsules.detach_data_assets(pipeline_id, data_assets)
60
65
 
66
+ def sync_pipeline(self, pipeline_id: str) -> GitSyncResults:
67
+ """Sync a pipeline with its linked external Git repository."""
68
+ return self._capsules.sync_capsule(pipeline_id)
69
+
61
70
  def archive_pipeline(self, pipeline_id: str, archive: bool):
62
71
  """Archive or unarchive a pipeline to control its visibility and accessibility."""
63
72
  return self._capsules.archive_capsule(pipeline_id, archive)
@@ -0,0 +1,46 @@
1
+ import unittest
2
+ from unittest.mock import MagicMock
3
+
4
+ from codeocean.capsule import Capsules, GitSyncResults
5
+ from codeocean.pipeline import Pipelines
6
+
7
+
8
+ class TestGitSync(unittest.TestCase):
9
+ """Test cases for capsule and pipeline Git sync."""
10
+
11
+ def _mock_session(self, body):
12
+ """Build a mock session whose post() returns a response with the given JSON body."""
13
+ session = MagicMock()
14
+ response = MagicMock()
15
+ response.json.return_value = body
16
+ session.post.return_value = response
17
+ return session
18
+
19
+ def test_sync_capsule_returns_results(self):
20
+ """sync_capsule posts to the capsule sync route and parses the results."""
21
+ session = self._mock_session({"pushed": 2, "pulled": 1, "new_branch": True})
22
+ capsules = Capsules(client=session)
23
+
24
+ result = capsules.sync_capsule("cap-123")
25
+
26
+ session.post.assert_called_once_with("capsules/cap-123/sync")
27
+ self.assertEqual(result, GitSyncResults(pushed=2, pulled=1, new_branch=True))
28
+
29
+ def test_sync_capsule_empty_body_defaults(self):
30
+ """An empty body (all fields omitempty on the server) deserializes to zero defaults."""
31
+ session = self._mock_session({})
32
+ capsules = Capsules(client=session)
33
+
34
+ result = capsules.sync_capsule("cap-123")
35
+
36
+ self.assertEqual(result, GitSyncResults(pushed=0, pulled=0, new_branch=False))
37
+
38
+ def test_sync_pipeline_returns_results(self):
39
+ """sync_pipeline posts to the pipeline sync route and parses the results."""
40
+ session = self._mock_session({"pushed": 0, "pulled": 3, "new_branch": False})
41
+ pipelines = Pipelines(client=session)
42
+
43
+ result = pipelines.sync_pipeline("pipe-456")
44
+
45
+ session.post.assert_called_once_with("pipelines/pipe-456/sync")
46
+ self.assertEqual(result, GitSyncResults(pushed=0, pulled=3, new_branch=False))
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes