codeocean 0.1.2__tar.gz → 0.1.4__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.
@@ -0,0 +1,21 @@
1
+ CHANGELOG
2
+ =========
3
+
4
+ ## 0.1.4 (2024-08-02)
5
+
6
+ - Add support for Python >= 3.9
7
+
8
+ ## 0.1.3 (2024-07-09)
9
+
10
+ - [#8](https://github.com/codeocean/codeocean-sdk-python/pull/8) fix: add TCPKeepAlive to client requests
11
+ - [#6](https://github.com/codeocean/codeocean-sdk-python/pull/6) feat: Add ResultsInfo to DataAssetParams
12
+ - [#5](https://github.com/codeocean/codeocean-sdk-python/pull/5) fix: SourceBucket optional fields
13
+ - [#3](https://github.com/codeocean/codeocean-sdk-python/pull/3) feat: Add external field to SourceBucket
14
+
15
+ ## 0.1.2 (2024-03-20)
16
+
17
+ - Fix minimum required Pyhton version (3.11)
18
+
19
+ ## 0.1.0 (2024-03-18)
20
+
21
+ Initial version
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) [2024] [Code Ocean]
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) [2024] [Code Ocean]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: codeocean
3
- Version: 0.1.2
3
+ Version: 0.1.4
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
@@ -11,15 +11,15 @@ License-File: LICENSE
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: OS Independent
13
13
  Classifier: Programming Language :: Python :: 3
14
- Requires-Python: >=3.11
14
+ Requires-Python: >=3.9
15
+ Requires-Dist: backports-strenum>=1.3.1; python_version < '3.11'
15
16
  Requires-Dist: dataclasses
16
17
  Requires-Dist: dataclasses-json
17
18
  Requires-Dist: requests
18
19
  Requires-Dist: requests-toolbelt
19
20
  Provides-Extra: dev
20
21
  Requires-Dist: flake8; extra == 'dev'
21
- Provides-Extra: publish
22
- Requires-Dist: hatch; extra == 'publish'
22
+ Requires-Dist: hatch; extra == 'dev'
23
23
  Description-Content-Type: text/markdown
24
24
 
25
25
  ![Code Ocean Logo](https://raw.githubusercontent.com/codeocean/branding/main/logo/CO_logo_135x72.png)
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "codeocean"
7
- version = "0.1.2"
7
+ version = "0.1.4"
8
8
  authors = [
9
9
  { name="Code Ocean", email="dev@codeocean.com" },
10
10
  ]
11
11
  description = "Code Ocean Python SDK"
12
12
  readme = "README.md"
13
- requires-python = ">=3.11"
13
+ requires-python = ">=3.9"
14
14
  classifiers = [
15
15
  "Programming Language :: Python :: 3",
16
16
  "License :: OSI Approved :: MIT License",
@@ -21,12 +21,12 @@ dependencies = [
21
21
  "dataclasses_json",
22
22
  "requests",
23
23
  "requests_toolbelt",
24
+ "backports.strenum>=1.3.1; python_version<'3.11'",
24
25
  ]
25
26
  license = "MIT"
26
27
 
27
28
  [project.optional-dependencies]
28
- dev = ["flake8"]
29
- publish = ["hatch"]
29
+ dev = ["flake8", "hatch"]
30
30
 
31
31
  [project.urls]
32
32
  Homepage = "https://github.com/codeocean/codeocean-sdk-python"
@@ -45,3 +45,10 @@ exclude = [
45
45
 
46
46
  [tool.hatch.build.targets.wheel]
47
47
  packages = ["src/codeocean"]
48
+
49
+ [tool.hatch.envs.default.scripts]
50
+ lint = "flake8 src tests examples"
51
+ test = "python -m unittest -v"
52
+
53
+ [[tool.hatch.envs.test.matrix]]
54
+ python = ["3.9", "3.10", "3.11", "3.12"]
@@ -1,77 +1,82 @@
1
- from dataclasses import dataclass
2
- from dataclasses_json import dataclass_json
3
- from enum import StrEnum
4
- from typing import Optional
5
- from requests_toolbelt.sessions import BaseUrlSession
6
-
7
- from codeocean.computation import Computation
8
- from codeocean.data_asset import DataAssetAttachParams, DataAssetAttachResults
9
-
10
-
11
- class CapsuleStatus(StrEnum):
12
- NonPublished = "non-published"
13
- Submitted = "submitted"
14
- Publishing = "publishing"
15
- Published = "published"
16
- Verified = "verified"
17
-
18
-
19
- @dataclass_json
20
- @dataclass(frozen=True)
21
- class OriginalCapsuleInfo:
22
- id: Optional[str] = None
23
- major_version: Optional[int] = None
24
- minor_version: Optional[int] = None
25
- name: Optional[str] = None
26
- created: Optional[int] = None
27
- public: Optional[bool] = None
28
-
29
-
30
- @dataclass_json
31
- @dataclass(frozen=True)
32
- class Capsule:
33
- id: str
34
- created: int
35
- name: str
36
- status: CapsuleStatus
37
- owner: str
38
- slug: str
39
- article: Optional[dict] = None
40
- cloned_from_url: Optional[str] = None
41
- description: Optional[str] = None
42
- field: Optional[str] = None
43
- keywords: Optional[list[str]] = None
44
- original_capsule: Optional[OriginalCapsuleInfo] = None
45
- published_capsule: Optional[str] = None
46
- submission: Optional[dict] = None
47
- versions: Optional[list[dict]] = None
48
-
49
-
50
- @dataclass
51
- class Capsules:
52
-
53
- client: BaseUrlSession
54
-
55
- def get_capsule(self, capsule_id: str) -> Capsule:
56
- res = self.client.get(f"capsules/{capsule_id}")
57
-
58
- return Capsule.from_dict(res.json())
59
-
60
- def list_computations(self, capsule_id: str) -> list[Computation]:
61
- res = self.client.get(f"capsules/{capsule_id}/computations")
62
-
63
- return [Computation.from_dict(c) for c in res.json()]
64
-
65
- def attach_data_assets(self, capsule_id: str, attach_params: list[DataAssetAttachParams]) -> DataAssetAttachResults:
66
- res = self.client.post(
67
- f"capsules/{capsule_id}/data_assets",
68
- json=[j.to_dict() for j in attach_params],
69
- )
70
-
71
- return DataAssetAttachResults.from_dict(res.json())
72
-
73
- def detach_data_assets(self, capsule_id: str, data_assets: list[str]):
74
- self.client.delete(
75
- f"capsules/{capsule_id}/data_assets/",
76
- json=data_assets,
77
- )
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from dataclasses_json import dataclass_json
5
+ from typing import Optional
6
+ from requests_toolbelt.sessions import BaseUrlSession
7
+
8
+ from codeocean.computation import Computation
9
+ from codeocean.data_asset import DataAssetAttachParams, DataAssetAttachResults
10
+ from codeocean.enum import StrEnum
11
+
12
+
13
+ class CapsuleStatus(StrEnum):
14
+ NonPublished = "non-published"
15
+ Submitted = "submitted"
16
+ Publishing = "publishing"
17
+ Published = "published"
18
+ Verified = "verified"
19
+
20
+
21
+ @dataclass_json
22
+ @dataclass(frozen=True)
23
+ class OriginalCapsuleInfo:
24
+ id: Optional[str] = None
25
+ major_version: Optional[int] = None
26
+ minor_version: Optional[int] = None
27
+ name: Optional[str] = None
28
+ created: Optional[int] = None
29
+ public: Optional[bool] = None
30
+
31
+
32
+ @dataclass_json
33
+ @dataclass(frozen=True)
34
+ class Capsule:
35
+ id: str
36
+ created: int
37
+ name: str
38
+ status: CapsuleStatus
39
+ owner: str
40
+ slug: str
41
+ article: Optional[dict] = None
42
+ cloned_from_url: Optional[str] = None
43
+ description: Optional[str] = None
44
+ field: Optional[str] = None
45
+ keywords: Optional[list[str]] = None
46
+ original_capsule: Optional[OriginalCapsuleInfo] = None
47
+ published_capsule: Optional[str] = None
48
+ submission: Optional[dict] = None
49
+ versions: Optional[list[dict]] = None
50
+
51
+
52
+ @dataclass
53
+ class Capsules:
54
+
55
+ client: BaseUrlSession
56
+
57
+ def get_capsule(self, capsule_id: str) -> Capsule:
58
+ res = self.client.get(f"capsules/{capsule_id}")
59
+
60
+ return Capsule.from_dict(res.json())
61
+
62
+ def list_computations(self, capsule_id: str) -> list[Computation]:
63
+ res = self.client.get(f"capsules/{capsule_id}/computations")
64
+
65
+ return [Computation.from_dict(c) for c in res.json()]
66
+
67
+ def attach_data_assets(
68
+ self,
69
+ capsule_id: str,
70
+ attach_params: list[DataAssetAttachParams]) -> list[DataAssetAttachResults]:
71
+ res = self.client.post(
72
+ f"capsules/{capsule_id}/data_assets",
73
+ json=[j.to_dict() for j in attach_params],
74
+ )
75
+
76
+ return [DataAssetAttachResults.from_dict(c) for c in res.json()]
77
+
78
+ def detach_data_assets(self, capsule_id: str, data_assets: list[str]):
79
+ self.client.delete(
80
+ f"capsules/{capsule_id}/data_assets/",
81
+ json=data_assets,
82
+ )
@@ -1,25 +1,27 @@
1
- from requests_toolbelt.sessions import BaseUrlSession
2
- from dataclasses import dataclass
3
-
4
- from codeocean.capsule import Capsules
5
- from codeocean.computation import Computations
6
- from codeocean.data_asset import DataAssets
7
-
8
-
9
- @dataclass
10
- class CodeOcean:
11
-
12
- domain: str
13
- token: str
14
-
15
- def __post_init__(self):
16
- self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
17
- self.session.auth = (self.token, "")
18
- self.session.headers.update({"Content-Type": "application/json"})
19
- self.session.hooks["response"] = [
20
- lambda response, *args, **kwargs: response.raise_for_status()
21
- ]
22
-
23
- self.capsules = Capsules(client=self.session)
24
- self.computations = Computations(client=self.session)
25
- self.data_assets = DataAssets(client=self.session)
1
+ from requests_toolbelt.sessions import BaseUrlSession
2
+ from requests_toolbelt.adapters.socket_options import TCPKeepAliveAdapter
3
+ from dataclasses import dataclass
4
+
5
+ from codeocean.capsule import Capsules
6
+ from codeocean.computation import Computations
7
+ from codeocean.data_asset import DataAssets
8
+
9
+
10
+ @dataclass
11
+ class CodeOcean:
12
+
13
+ domain: str
14
+ token: str
15
+
16
+ def __post_init__(self):
17
+ self.session = BaseUrlSession(base_url=f"{self.domain}/api/v1/")
18
+ self.session.auth = (self.token, "")
19
+ self.session.mount(self.domain, TCPKeepAliveAdapter())
20
+ self.session.headers.update({"Content-Type": "application/json"})
21
+ self.session.hooks["response"] = [
22
+ lambda response, *args, **kwargs: response.raise_for_status()
23
+ ]
24
+
25
+ self.capsules = Capsules(client=self.session)
26
+ self.computations = Computations(client=self.session)
27
+ self.data_assets = DataAssets(client=self.session)
@@ -1,8 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses_json import dataclass_json
2
4
  from dataclasses import dataclass
3
- from enum import StrEnum
4
5
  from typing import Optional
5
6
 
7
+ from codeocean.enum import StrEnum
8
+
6
9
 
7
10
  class UserRole(StrEnum):
8
11
  Owner = "owner"
@@ -1,171 +1,174 @@
1
- from dataclasses import dataclass
2
- from dataclasses_json import dataclass_json
3
- from enum import StrEnum
4
- from requests_toolbelt.sessions import BaseUrlSession
5
- from typing import Optional
6
- from time import sleep
7
-
8
-
9
- class ComputationState(StrEnum):
10
- Initializing = "initializing"
11
- Running = "running"
12
- Finalizing = "finalizing"
13
- Completed = "completed"
14
- Failed = "failed"
15
-
16
-
17
- class ComputationEndStatus(StrEnum):
18
- Succeeded = "succeeded"
19
- Failed = "failed"
20
- Stopped = "stopped"
21
-
22
-
23
- @dataclass_json
24
- @dataclass(frozen=True)
25
- class Param:
26
- name: Optional[str] = None
27
- param_name: Optional[str] = None
28
- value: Optional[str] = None
29
-
30
-
31
- @dataclass_json
32
- @dataclass(frozen=True)
33
- class PipelineProcess:
34
- name: str
35
- capsule_id: str
36
- version: Optional[int] = None
37
- public: Optional[bool] = None
38
- parameters: Optional[list[Param]] = None
39
-
40
-
41
- @dataclass_json
42
- @dataclass(frozen=True)
43
- class InputDataAsset:
44
- id: str
45
- mount: Optional[str] = None
46
-
47
-
48
- @dataclass_json
49
- @dataclass(frozen=True)
50
- class Computation:
51
- id: str
52
- created: int
53
- name: str
54
- state: ComputationState
55
- run_time: int
56
- cloud_workstation: Optional[bool] = None
57
- data_assets: Optional[list[InputDataAsset]] = None
58
- end_status: Optional[ComputationEndStatus] = None
59
- has_results: Optional[bool] = None
60
- parameters: Optional[list[Param]] = None
61
- processes: Optional[list[PipelineProcess]] = None
62
-
63
-
64
- @dataclass_json
65
- @dataclass(frozen=True)
66
- class DataAssetsRunParam:
67
- id: str
68
- mount: str
69
-
70
-
71
- @dataclass_json
72
- @dataclass(frozen=True)
73
- class NamedRunParam:
74
- param_name: str
75
- value: str
76
-
77
-
78
- @dataclass_json
79
- @dataclass(frozen=True)
80
- class PipelineProcessParams:
81
- name: str
82
- parameters: Optional[list[str]] = None
83
- named_parameters: Optional[list[NamedRunParam]] = None
84
-
85
-
86
- @dataclass_json
87
- @dataclass(frozen=True)
88
- class RunParams:
89
- capsule_id: Optional[str] = None
90
- pipeline_id: Optional[str] = None
91
- version: Optional[int] = None
92
- resume_run_id: Optional[str] = None
93
- data_assets: Optional[list[DataAssetsRunParam]] = None
94
- parameters: Optional[list[str]] = None
95
- named_parameters: Optional[list[NamedRunParam]] = None
96
- processes: Optional[list[PipelineProcessParams]] = None
97
-
98
-
99
- @dataclass_json
100
- @dataclass(frozen=True)
101
- class FolderItem:
102
- name: str
103
- path: str
104
- type: str
105
- size: Optional[int] = None
106
-
107
-
108
- @dataclass_json
109
- @dataclass(frozen=True)
110
- class Folder:
111
- items: list[FolderItem]
112
-
113
-
114
- @dataclass_json
115
- @dataclass(frozen=True)
116
- class ListFolderParams:
117
- path: str
118
-
119
-
120
- @dataclass_json
121
- @dataclass(frozen=True)
122
- class DownloadFileURL:
123
- url: str
124
-
125
-
126
- @dataclass
127
- class Computations:
128
-
129
- client: BaseUrlSession
130
-
131
- def get_computation(self, computation_id: str) -> Computation:
132
- res = self.client.get(f"computations/{computation_id}")
133
-
134
- return Computation.from_dict(res.json())
135
-
136
- def run_capsule(self, run_params: RunParams) -> Computation:
137
- res = self.client.post("computations", json=run_params.to_dict())
138
-
139
- return Computation.from_dict(res.json())
140
-
141
- def wait_until_completed(self, computation: Computation) -> Computation:
142
- """
143
- Polls the given computation until it reaches the 'Completed' or 'Failed' state.
144
- """
145
- while True:
146
- comp = self.get_computation(computation.id)
147
-
148
- if comp.state in [ComputationState.Completed, ComputationState.Failed]:
149
- return comp
150
-
151
- sleep(5)
152
-
153
- def list_computation_results(self, computation_id: str, path: str = "") -> Folder:
154
- data = {
155
- "path": path,
156
- }
157
-
158
- res = self.client.post(f"computations/{computation_id}/results", json=data)
159
-
160
- return Folder.from_dict(res.json())
161
-
162
- def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL:
163
- res = self.client.get(
164
- f"computations/{computation_id}/results/download_url",
165
- params={"path": path},
166
- )
167
-
168
- return DownloadFileURL.from_dict(res.json())
169
-
170
- def delete_computation(self, computation_id: str):
171
- self.client.delete(f"computations/{computation_id}")
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from dataclasses_json import dataclass_json
5
+ from requests_toolbelt.sessions import BaseUrlSession
6
+ from typing import Optional
7
+ from time import sleep
8
+
9
+ from codeocean.enum import StrEnum
10
+
11
+
12
+ class ComputationState(StrEnum):
13
+ Initializing = "initializing"
14
+ Running = "running"
15
+ Finalizing = "finalizing"
16
+ Completed = "completed"
17
+ Failed = "failed"
18
+
19
+
20
+ class ComputationEndStatus(StrEnum):
21
+ Succeeded = "succeeded"
22
+ Failed = "failed"
23
+ Stopped = "stopped"
24
+
25
+
26
+ @dataclass_json
27
+ @dataclass(frozen=True)
28
+ class Param:
29
+ name: Optional[str] = None
30
+ param_name: Optional[str] = None
31
+ value: Optional[str] = None
32
+
33
+
34
+ @dataclass_json
35
+ @dataclass(frozen=True)
36
+ class PipelineProcess:
37
+ name: str
38
+ capsule_id: str
39
+ version: Optional[int] = None
40
+ public: Optional[bool] = None
41
+ parameters: Optional[list[Param]] = None
42
+
43
+
44
+ @dataclass_json
45
+ @dataclass(frozen=True)
46
+ class InputDataAsset:
47
+ id: str
48
+ mount: Optional[str] = None
49
+
50
+
51
+ @dataclass_json
52
+ @dataclass(frozen=True)
53
+ class Computation:
54
+ id: str
55
+ created: int
56
+ name: str
57
+ state: ComputationState
58
+ run_time: int
59
+ cloud_workstation: Optional[bool] = None
60
+ data_assets: Optional[list[InputDataAsset]] = None
61
+ end_status: Optional[ComputationEndStatus] = None
62
+ has_results: Optional[bool] = None
63
+ parameters: Optional[list[Param]] = None
64
+ processes: Optional[list[PipelineProcess]] = None
65
+
66
+
67
+ @dataclass_json
68
+ @dataclass(frozen=True)
69
+ class DataAssetsRunParam:
70
+ id: str
71
+ mount: str
72
+
73
+
74
+ @dataclass_json
75
+ @dataclass(frozen=True)
76
+ class NamedRunParam:
77
+ param_name: str
78
+ value: str
79
+
80
+
81
+ @dataclass_json
82
+ @dataclass(frozen=True)
83
+ class PipelineProcessParams:
84
+ name: str
85
+ parameters: Optional[list[str]] = None
86
+ named_parameters: Optional[list[NamedRunParam]] = None
87
+
88
+
89
+ @dataclass_json
90
+ @dataclass(frozen=True)
91
+ class RunParams:
92
+ capsule_id: Optional[str] = None
93
+ pipeline_id: Optional[str] = None
94
+ version: Optional[int] = None
95
+ resume_run_id: Optional[str] = None
96
+ data_assets: Optional[list[DataAssetsRunParam]] = None
97
+ parameters: Optional[list[str]] = None
98
+ named_parameters: Optional[list[NamedRunParam]] = None
99
+ processes: Optional[list[PipelineProcessParams]] = None
100
+
101
+
102
+ @dataclass_json
103
+ @dataclass(frozen=True)
104
+ class FolderItem:
105
+ name: str
106
+ path: str
107
+ type: str
108
+ size: Optional[int] = None
109
+
110
+
111
+ @dataclass_json
112
+ @dataclass(frozen=True)
113
+ class Folder:
114
+ items: list[FolderItem]
115
+
116
+
117
+ @dataclass_json
118
+ @dataclass(frozen=True)
119
+ class ListFolderParams:
120
+ path: str
121
+
122
+
123
+ @dataclass_json
124
+ @dataclass(frozen=True)
125
+ class DownloadFileURL:
126
+ url: str
127
+
128
+
129
+ @dataclass
130
+ class Computations:
131
+
132
+ client: BaseUrlSession
133
+
134
+ def get_computation(self, computation_id: str) -> Computation:
135
+ res = self.client.get(f"computations/{computation_id}")
136
+
137
+ return Computation.from_dict(res.json())
138
+
139
+ def run_capsule(self, run_params: RunParams) -> Computation:
140
+ res = self.client.post("computations", json=run_params.to_dict())
141
+
142
+ return Computation.from_dict(res.json())
143
+
144
+ def wait_until_completed(self, computation: Computation) -> Computation:
145
+ """
146
+ Polls the given computation until it reaches the 'Completed' or 'Failed' state.
147
+ """
148
+ while True:
149
+ comp = self.get_computation(computation.id)
150
+
151
+ if comp.state in [ComputationState.Completed, ComputationState.Failed]:
152
+ return comp
153
+
154
+ sleep(5)
155
+
156
+ def list_computation_results(self, computation_id: str, path: str = "") -> Folder:
157
+ data = {
158
+ "path": path,
159
+ }
160
+
161
+ res = self.client.post(f"computations/{computation_id}/results", json=data)
162
+
163
+ return Folder.from_dict(res.json())
164
+
165
+ def get_result_file_download_url(self, computation_id: str, path: str) -> DownloadFileURL:
166
+ res = self.client.get(
167
+ f"computations/{computation_id}/results/download_url",
168
+ params={"path": path},
169
+ )
170
+
171
+ return DownloadFileURL.from_dict(res.json())
172
+
173
+ def delete_computation(self, computation_id: str):
174
+ self.client.delete(f"computations/{computation_id}")
@@ -1,252 +1,270 @@
1
- from dataclasses_json import dataclass_json
2
- from dataclasses import dataclass
3
- from enum import StrEnum
4
- from requests_toolbelt.sessions import BaseUrlSession
5
- from time import sleep
6
- from typing import Optional
7
-
8
- from codeocean.components import SortOrder, SearchFilter, Permissions
9
-
10
-
11
- class DataAssetType(StrEnum):
12
- Dataset = "dataset"
13
- Result = "result"
14
-
15
-
16
- class DataAssetState(StrEnum):
17
- Draft = "draft"
18
- Ready = "ready"
19
- Failed = "failed"
20
-
21
-
22
- @dataclass_json
23
- @dataclass(frozen=True)
24
- class Provenance:
25
- commit: str
26
- run_script: str
27
- data_assets: str
28
- docker_image: str
29
- capsule: str
30
-
31
-
32
- class DataAssetOrigin(StrEnum):
33
- Local = "local"
34
- AWS = "aws"
35
- GCP = "gcp"
36
-
37
-
38
- @dataclass_json
39
- @dataclass(frozen=True)
40
- class SourceBucket:
41
- origin: DataAssetOrigin
42
- bucket: str
43
- prefix: str
44
-
45
-
46
- @dataclass_json
47
- @dataclass(frozen=True)
48
- class AppParameter:
49
- name: str
50
- value: str
51
-
52
-
53
- @dataclass_json
54
- @dataclass(frozen=True)
55
- class DataAsset:
56
- id: str
57
- created: int
58
- name: str
59
- mount: str
60
- state: DataAssetState
61
- type: DataAssetType
62
- last_used: int
63
- app_parameters: Optional[list[AppParameter]] = None
64
- custom_metadata: Optional[dict] = None
65
- description: Optional[str] = None
66
- failure_reason: Optional[str] = None
67
- files: Optional[int] = None
68
- provenance: Optional[Provenance] = None
69
- size: Optional[int] = None
70
- source_bucket: Optional[SourceBucket] = None
71
- tags: Optional[list[str]] = None
72
-
73
-
74
- @dataclass_json
75
- @dataclass(frozen=True)
76
- class DataAssetUpdateParams:
77
- name: str
78
- description: str
79
- tags: list[str]
80
- mount: str
81
- custom_metadata: Optional[dict] = None
82
-
83
-
84
- @dataclass_json
85
- @dataclass(frozen=True)
86
- class AWSS3Source:
87
- bucket: str
88
- prefix: Optional[str] = None
89
- keep_on_external_storage: Optional[bool] = None
90
- public: Optional[bool] = None
91
-
92
-
93
- @dataclass_json
94
- @dataclass(frozen=True)
95
- class GCPCloudStorageSource:
96
- bucket: str
97
- client_id: Optional[str] = None
98
- client_secret: Optional[str] = None
99
- prefix: Optional[str] = None
100
-
101
-
102
- @dataclass_json
103
- @dataclass(frozen=True)
104
- class ComputationSource:
105
- id: str
106
- path: Optional[str] = None
107
-
108
-
109
- @dataclass_json
110
- @dataclass(frozen=True)
111
- class Source:
112
- aws: Optional[AWSS3Source] = None
113
- gcp: Optional[GCPCloudStorageSource] = None
114
- computation: Optional[ComputationSource] = None
115
-
116
-
117
- @dataclass_json
118
- @dataclass(frozen=True)
119
- class AWSS3Target:
120
- bucket: str
121
- prefix: Optional[str] = None
122
-
123
-
124
- @dataclass_json
125
- @dataclass(frozen=True)
126
- class Target:
127
- aws: Optional[AWSS3Target] = None
128
-
129
-
130
- @dataclass_json
131
- @dataclass(frozen=True)
132
- class DataAssetParams:
133
- name: str
134
- tags: list[str]
135
- mount: str
136
- description: Optional[str] = None
137
- source: Optional[Source] = None
138
- target: Optional[Target] = None
139
- custom_metadata: Optional[dict] = None
140
- data_asset_ids: Optional[list[str]] = None
141
-
142
-
143
- @dataclass_json
144
- @dataclass(frozen=True)
145
- class DataAssetAttachParams:
146
- id: str
147
- mount: Optional[str] = None
148
-
149
-
150
- @dataclass_json
151
- @dataclass(frozen=True)
152
- class DataAssetAttachResults:
153
- id: str
154
- mount: str
155
- mount_state: str
156
- job_id: str
157
- external: bool
158
- ready: bool
159
-
160
-
161
- class DataAssetSortBy(StrEnum):
162
- Created = "created"
163
- Type = "type"
164
- Name = "name"
165
- Size = "size"
166
-
167
-
168
- class DataAssetOwnership(StrEnum):
169
- Private = "private"
170
- Shared = "shared"
171
- Created = "created"
172
-
173
-
174
- class DataAssetSearchOrigin(StrEnum):
175
- Internal = "internal"
176
- External = "external"
177
-
178
-
179
- @dataclass_json
180
- @dataclass(frozen=True)
181
- class DataAssetSearchParams:
182
- limit: int
183
- offset: int
184
- archived: bool
185
- favorite: bool
186
- query: Optional[str] = None
187
- sort_field: Optional[DataAssetSortBy] = None
188
- sort_order: Optional[SortOrder] = None
189
- type: Optional[DataAssetType] = None
190
- ownership: Optional[DataAssetOwnership] = None
191
- origin: Optional[DataAssetSearchOrigin] = None
192
- filters: Optional[list[SearchFilter]] = None
193
-
194
-
195
- @dataclass_json
196
- @dataclass(frozen=True)
197
- class DataAssetSearchResults:
198
- has_more: bool
199
- results: list[DataAsset]
200
-
201
-
202
- @dataclass
203
- class DataAssets:
204
-
205
- client: BaseUrlSession
206
-
207
- def get_data_asset(self, data_asset_id: str) -> DataAsset:
208
- res = self.client.get(f"data_assets/{data_asset_id}")
209
-
210
- return DataAsset.from_dict(res.json())
211
-
212
- def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset:
213
- res = self.client.put(f"data_assets/{data_asset_id}", json=update_params.to_dict())
214
-
215
- return DataAsset.from_dict(res.json())
216
-
217
- def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset:
218
- res = self.client.post("data_assets", json=data_asset_params.to_dict())
219
-
220
- return DataAsset.from_dict(res.json())
221
-
222
- def wait_until_ready(self, data_asset: DataAsset) -> DataAsset:
223
- """
224
- Polls the given data asset until it reaches the 'Ready' or 'Failed' state.
225
- """
226
- while True:
227
- da = self.get_data_asset(data_asset.id)
228
-
229
- if da.state in [DataAssetState.Ready, DataAssetState.Failed]:
230
- return da
231
-
232
- sleep(5)
233
-
234
- def delete_data_asset(self, data_asset_id: str):
235
- self.client.delete(f"data_assets/{data_asset_id}")
236
-
237
- def update_permissions(self, data_asset_id: str, permissions: Permissions):
238
- self.client.post(
239
- f"data_assets/{data_asset_id}/permissions",
240
- json=permissions.to_dict(),
241
- )
242
-
243
- def archive_data_asset(self, data_asset_id: str, archive: bool):
244
- self.client.patch(
245
- f"data_assets/{data_asset_id}/archive",
246
- params={"archive": archive},
247
- )
248
-
249
- def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults:
250
- res = self.client.post("data_assets/search", json=search_params.to_dict())
251
-
252
- return DataAssetSearchResults.from_dict(res.json())
1
+ from __future__ import annotations
2
+
3
+ from dataclasses_json import dataclass_json
4
+ from dataclasses import dataclass
5
+ from requests_toolbelt.sessions import BaseUrlSession
6
+ from time import sleep
7
+ from typing import Optional
8
+
9
+ from codeocean.components import SortOrder, SearchFilter, Permissions
10
+ from codeocean.computation import PipelineProcess, Param
11
+ from codeocean.enum import StrEnum
12
+
13
+
14
+ class DataAssetType(StrEnum):
15
+ Dataset = "dataset"
16
+ Result = "result"
17
+
18
+
19
+ class DataAssetState(StrEnum):
20
+ Draft = "draft"
21
+ Ready = "ready"
22
+ Failed = "failed"
23
+
24
+
25
+ @dataclass_json
26
+ @dataclass(frozen=True)
27
+ class Provenance:
28
+ commit: str
29
+ run_script: str
30
+ data_assets: str
31
+ docker_image: str
32
+ capsule: str
33
+
34
+
35
+ class DataAssetOrigin(StrEnum):
36
+ Local = "local"
37
+ AWS = "aws"
38
+ GCP = "gcp"
39
+
40
+
41
+ @dataclass_json
42
+ @dataclass(frozen=True)
43
+ class SourceBucket:
44
+ origin: DataAssetOrigin
45
+ bucket: Optional[str] = None
46
+ prefix: Optional[str] = None
47
+ external: Optional[bool] = None
48
+
49
+
50
+ @dataclass_json
51
+ @dataclass(frozen=True)
52
+ class AppParameter:
53
+ name: str
54
+ value: str
55
+
56
+
57
+ @dataclass_json
58
+ @dataclass(frozen=True)
59
+ class ResultsInfo:
60
+ capsule_id: Optional[str] = None
61
+ pipeline_id: Optional[str] = None
62
+ version: Optional[int] = None
63
+ commit: Optional[str] = None
64
+ run_script: Optional[str] = None
65
+ data_assets: Optional[list[str]] = None
66
+ parameters: Optional[list[Param]] = None
67
+ processes: Optional[list[PipelineProcess]] = None
68
+
69
+
70
+ @dataclass_json
71
+ @dataclass(frozen=True)
72
+ class DataAsset:
73
+ id: str
74
+ created: int
75
+ name: str
76
+ mount: str
77
+ state: DataAssetState
78
+ type: DataAssetType
79
+ last_used: int
80
+ app_parameters: Optional[list[AppParameter]] = None
81
+ custom_metadata: Optional[dict] = None
82
+ description: Optional[str] = None
83
+ failure_reason: Optional[str] = None
84
+ files: Optional[int] = None
85
+ provenance: Optional[Provenance] = None
86
+ size: Optional[int] = None
87
+ source_bucket: Optional[SourceBucket] = None
88
+ tags: Optional[list[str]] = None
89
+
90
+
91
+ @dataclass_json
92
+ @dataclass(frozen=True)
93
+ class DataAssetUpdateParams:
94
+ name: str
95
+ description: str
96
+ tags: list[str]
97
+ mount: str
98
+ custom_metadata: Optional[dict] = None
99
+
100
+
101
+ @dataclass_json
102
+ @dataclass(frozen=True)
103
+ class AWSS3Source:
104
+ bucket: str
105
+ prefix: Optional[str] = None
106
+ keep_on_external_storage: Optional[bool] = None
107
+ public: Optional[bool] = None
108
+
109
+
110
+ @dataclass_json
111
+ @dataclass(frozen=True)
112
+ class GCPCloudStorageSource:
113
+ bucket: str
114
+ client_id: Optional[str] = None
115
+ client_secret: Optional[str] = None
116
+ prefix: Optional[str] = None
117
+
118
+
119
+ @dataclass_json
120
+ @dataclass(frozen=True)
121
+ class ComputationSource:
122
+ id: str
123
+ path: Optional[str] = None
124
+
125
+
126
+ @dataclass_json
127
+ @dataclass(frozen=True)
128
+ class Source:
129
+ aws: Optional[AWSS3Source] = None
130
+ gcp: Optional[GCPCloudStorageSource] = None
131
+ computation: Optional[ComputationSource] = None
132
+
133
+
134
+ @dataclass_json
135
+ @dataclass(frozen=True)
136
+ class AWSS3Target:
137
+ bucket: str
138
+ prefix: Optional[str] = None
139
+
140
+
141
+ @dataclass_json
142
+ @dataclass(frozen=True)
143
+ class Target:
144
+ aws: Optional[AWSS3Target] = None
145
+
146
+
147
+ @dataclass_json
148
+ @dataclass(frozen=True)
149
+ class DataAssetParams:
150
+ name: str
151
+ tags: list[str]
152
+ mount: str
153
+ description: Optional[str] = None
154
+ source: Optional[Source] = None
155
+ target: Optional[Target] = None
156
+ custom_metadata: Optional[dict] = None
157
+ data_asset_ids: Optional[list[str]] = None
158
+ results_info: Optional[ResultsInfo] = None
159
+
160
+
161
+ @dataclass_json
162
+ @dataclass(frozen=True)
163
+ class DataAssetAttachParams:
164
+ id: str
165
+ mount: Optional[str] = None
166
+
167
+
168
+ @dataclass_json
169
+ @dataclass(frozen=True)
170
+ class DataAssetAttachResults:
171
+ id: str
172
+ mount: str
173
+ mount_state: str
174
+ job_id: str
175
+ external: bool
176
+ ready: bool
177
+
178
+
179
+ class DataAssetSortBy(StrEnum):
180
+ Created = "created"
181
+ Type = "type"
182
+ Name = "name"
183
+ Size = "size"
184
+
185
+
186
+ class DataAssetOwnership(StrEnum):
187
+ Private = "private"
188
+ Shared = "shared"
189
+ Created = "created"
190
+
191
+
192
+ class DataAssetSearchOrigin(StrEnum):
193
+ Internal = "internal"
194
+ External = "external"
195
+
196
+
197
+ @dataclass_json
198
+ @dataclass(frozen=True)
199
+ class DataAssetSearchParams:
200
+ limit: int
201
+ offset: int
202
+ archived: bool
203
+ favorite: bool
204
+ query: Optional[str] = None
205
+ sort_field: Optional[DataAssetSortBy] = None
206
+ sort_order: Optional[SortOrder] = None
207
+ type: Optional[DataAssetType] = None
208
+ ownership: Optional[DataAssetOwnership] = None
209
+ origin: Optional[DataAssetSearchOrigin] = None
210
+ filters: Optional[list[SearchFilter]] = None
211
+
212
+
213
+ @dataclass_json
214
+ @dataclass(frozen=True)
215
+ class DataAssetSearchResults:
216
+ has_more: bool
217
+ results: list[DataAsset]
218
+
219
+
220
+ @dataclass
221
+ class DataAssets:
222
+
223
+ client: BaseUrlSession
224
+
225
+ def get_data_asset(self, data_asset_id: str) -> DataAsset:
226
+ res = self.client.get(f"data_assets/{data_asset_id}")
227
+
228
+ return DataAsset.from_dict(res.json())
229
+
230
+ def update_metadata(self, data_asset_id: str, update_params: DataAssetUpdateParams) -> DataAsset:
231
+ res = self.client.put(f"data_assets/{data_asset_id}", json=update_params.to_dict())
232
+
233
+ return DataAsset.from_dict(res.json())
234
+
235
+ def create_data_asset(self, data_asset_params: DataAssetParams) -> DataAsset:
236
+ res = self.client.post("data_assets", json=data_asset_params.to_dict())
237
+
238
+ return DataAsset.from_dict(res.json())
239
+
240
+ def wait_until_ready(self, data_asset: DataAsset) -> DataAsset:
241
+ """
242
+ Polls the given data asset until it reaches the 'Ready' or 'Failed' state.
243
+ """
244
+ while True:
245
+ da = self.get_data_asset(data_asset.id)
246
+
247
+ if da.state in [DataAssetState.Ready, DataAssetState.Failed]:
248
+ return da
249
+
250
+ sleep(5)
251
+
252
+ def delete_data_asset(self, data_asset_id: str):
253
+ self.client.delete(f"data_assets/{data_asset_id}")
254
+
255
+ def update_permissions(self, data_asset_id: str, permissions: Permissions):
256
+ self.client.post(
257
+ f"data_assets/{data_asset_id}/permissions",
258
+ json=permissions.to_dict(),
259
+ )
260
+
261
+ def archive_data_asset(self, data_asset_id: str, archive: bool):
262
+ self.client.patch(
263
+ f"data_assets/{data_asset_id}/archive",
264
+ params={"archive": archive},
265
+ )
266
+
267
+ def search_data_assets(self, search_params: DataAssetSearchParams) -> DataAssetSearchResults:
268
+ res = self.client.post("data_assets/search", json=search_params.to_dict())
269
+
270
+ return DataAssetSearchResults.from_dict(res.json())
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ if sys.version_info >= (3, 11):
4
+ from enum import StrEnum
5
+ else:
6
+ from backports.strenum import StrEnum # noqa
File without changes
@@ -0,0 +1,23 @@
1
+ import sys
2
+ import unittest
3
+
4
+
5
+ class TestPackage(unittest.TestCase):
6
+ def test_import_package(self):
7
+ import codeocean # noqa
8
+
9
+ def test_strenum_install(self):
10
+ """
11
+ Check if the StrEnum backport is installed/not installed as appropriate in the test environment.
12
+
13
+ - the backport can only be installed for Python versions < 3.11 (https://pypi.org/project/backports.strenum/)
14
+ - for >= 3.11, the stdlib StrEnum should be used instead
15
+ """
16
+ if sys.version_info >= (3, 11):
17
+ with self.assertRaises(ImportError):
18
+ import backports.strenum # noqa
19
+ else:
20
+ try:
21
+ import backports.strenum # noqa
22
+ except ImportError:
23
+ self.fail("Failed to import the backports.strenum module")
@@ -1,10 +0,0 @@
1
- CHANGELOG
2
- =========
3
-
4
- ## 0.1.2 (2024-03-20)
5
-
6
- - Fix minimum required Pyhton version (3.11)
7
-
8
- ## 0.1.0 (2024-03-18)
9
-
10
- Initial version
File without changes
File without changes
File without changes