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.
- {codeocean-0.14.0 → codeocean-0.16.0}/CHANGELOG.md +9 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/PKG-INFO +1 -1
- {codeocean-0.14.0 → codeocean-0.16.0}/pyproject.toml +1 -1
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/capsule.py +13 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/client.py +1 -1
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/data_asset.py +6 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/capsule.py +23 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/computation.py +7 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/data_asset.py +12 -5
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/pipeline.py +9 -0
- codeocean-0.16.0/tests/test_git_sync.py +46 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/.flake8 +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/.gitignore +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/LICENSE +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/README.md +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/RELEASE.md +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/examples/create_data_asset.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/examples/run_capsule.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/examples/run_pipeline.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/__init__.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/components.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/computation.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/custom_metadata.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/enum.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/error.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/__init__.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/components.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/src/codeocean/models/folder.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/tests/__init__.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/tests/test_client.py +0 -0
- {codeocean-0.14.0 → codeocean-0.16.0}/tests/test_error.py +0 -0
- {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`.**
|
|
@@ -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.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|