codeoceansdk.Capsule

  1import logging
  2from dataclasses import asdict, dataclass, field as dc_field
  3from enum import Enum
  4from typing import Optional
  5
  6from codeoceansdk.CodeOcean import CodeOcean
  7from codeoceansdk.Computation import Computation
  8
  9logger = logging.getLogger(__name__)
 10
 11
 12@dataclass(frozen=True)
 13class OriginalCapsuleInfo:
 14    id: str = None
 15    """Metadata id"""
 16    major_version: int = None
 17    """Major version number"""
 18    minor_version: int = None
 19    """Minor version number"""
 20    name: str = None
 21    """Name of original pipeline"""
 22    created: int = None
 23    """Creation time"""
 24    public: bool = None
 25    """Is original capsule public"""
 26
 27
 28@dataclass(frozen=True)
 29class SubmissionInfo:
 30    timestamp: int = None
 31    """Submission time"""
 32    commit: str = None
 33    """Submission commit hash"""
 34    verification_capsule: str = None
 35    """Verification capsule ID"""
 36    verified: bool = None
 37    """Indicates whether the capsule was verified"""
 38    verified_timestamp: int = None
 39    """Verification time"""
 40
 41
 42@dataclass(frozen=True, slots=True)
 43class Version:
 44    """Capsule version"""
 45    major_version: int
 46    """Major version"""
 47    minor_version: int
 48    """Minor version"""
 49    publish_time: int
 50    """Publish timestamp"""
 51    doi: str = None
 52    """Digital identifier for capsule"""
 53
 54
 55@dataclass(frozen=True, slots=True)
 56class Article:
 57    """If capsule is attached to a publication, what article is it attached to"""
 58    url: str = None
 59    """Article URL"""
 60    id: str = None
 61    """Article ID"""
 62    doi: str = None
 63    """Digital identifier for publication"""
 64    citation: str = None
 65    """Article citation"""
 66    state: Enum("state", ["in_review", "published"]) = None
 67    """Publication state (i.e., has it been published yet)"""
 68    name: str = None
 69    """Article name"""
 70    journal_name: str = None
 71    """Journal the article appears in"""
 72    publish_time: int = None
 73    """Publication timestamp"""
 74
 75
 76@dataclass(frozen=True, slots=True)
 77class UserPermission:
 78    """Permissions for specific user"""
 79    email: str
 80    """Email address for user"""
 81    role: Enum("role", ["owner, editor, viewer", "discoverable"])
 82    """Permission level to set for user"""
 83
 84
 85@dataclass(frozen=True, slots=True)
 86class GroupPermission:
 87    """Permissions for group of users"""
 88    group: str
 89    """Group name"""
 90    role: Enum("role", ["owner", "editor", "viewer", "discoverable"])
 91    """Permission level to set for group"""
 92
 93
 94@dataclass(frozen=True, slots=True)
 95class EveryonePermission:
 96    """Permission for all users"""
 97    role: Enum("role", ["viewer", "discoverable", "none"])
 98    """Permission level to set for everyone"""
 99
100
101@dataclass(kw_only=True)
102class Capsule(CodeOcean):
103    id: str
104    """Capsule internal id"""
105    created: int = 0
106    """Capsule creation time"""
107    name: str = ""
108    """Capsule display name"""
109    status: Enum(
110        "status", ["non_published", "submitted", "publishing", "published", "verified"]
111    ) = "non_published"
112    """Whether or not capsule is published"""
113    owner: str = ""
114    """Capsule owner id"""
115    slug: str = ""
116    """Alternate capsule id"""
117    field: Optional[str] = None
118    """Capsule research field"""
119    description: Optional[str] = None
120    """Capsule description"""
121    cloned_from_url: Optional[str] = None
122    """If this is a capsule cloned from github, what url was it"""
123    keywords: Optional[list[str]] = dc_field(default_factory=list)
124    """Keywords describing capsule (optional)"""
125    article: Optional[Article] = None
126    """Capsule article info (optional)"""
127    versions: Optional[list[Version]] = dc_field(default_factory=list)
128    """Capsule versions (if published) (optional)"""
129    published_capsule: Optional[str] = None
130    """Published capsule id (separate from original)"""
131    original_capsule: Optional[OriginalCapsuleInfo] = None
132    """Original capsule info (if duplicated) (optional)"""
133    submission: Optional[SubmissionInfo] = None
134    """Submission info (if capsule submitted for publication) (optional)"""
135
136    def __post_init__(self):
137        super().__post_init__()
138        self.capsule_url = f"{self.api_url}/capsules/{self.id}"
139
140    def _parse_comp_response(self, computation_list: list):
141        """
142        Parse response from list of dictionaries containing
143        computational parameters
144
145        :param computation_list:  Input list of dictionary of computation parameters
146        :return: list of Computation objects
147        """
148        computations = []
149        for curr_dict in computation_list:
150            computations.append(
151                Computation.from_dict(
152                    computation_dict=curr_dict, api_key=self.api_key, domain=self.domain
153                )
154            )
155        return computations
156
157    def get_capsule_runs(self):
158        """Get previous capsule runs
159
160        :return: List of Computation objects.
161        """
162        input_url = f"{self.capsule_url}/computations"
163        logger.debug(f"Input url: {input_url}")
164        req = self.get(input_url)
165        computations = self._parse_comp_response(computation_list=req.json())
166
167        logger.info("Returned runs: {}".format(len(computations)))
168        return computations
169
170    def get_capsule(self):
171        """
172        Get capsule information
173        """
174        req = self.get(self.capsule_url)
175        new_capsule = self.from_dict(req.json(), self.domain, self.api_key)
176        self.__dict__.update(new_capsule.__dict__)
177
178    @staticmethod
179    def from_dict(capsule_dict, domain, api_key):
180        """
181        Parse dictionary to Capsule object
182
183        :param domain: Code Ocean domain
184        :param api_key: Capsule api.
185        :param capsule_dict:  Input dictionary of capsule parameters.
186        :return: Computation object.
187        """
188        if "article" in capsule_dict:
189            capsule_dict["article"] = Article(**capsule_dict["article"])
190        if "versions" in capsule_dict:
191            capsule_dict["versions"] = [Version(**x) for x in capsule_dict["versions"]]
192        if "original_capsule" in capsule_dict:
193            capsule_dict["original_capsule"] = OriginalCapsuleInfo(
194                **capsule_dict["original_capsule"]
195            )
196        if "submission" in capsule_dict:
197            capsule_dict["submission"] = SubmissionInfo(**capsule_dict["submission"])
198
199        capsule_dict["domain"] = domain
200        capsule_dict["api_key"] = api_key
201        return Capsule(**capsule_dict)
202
203    def run_capsule_computation(
204        self, parameters: list = None, data_assets: list = None
205    ):
206        """
207        Run a capsule.
208
209        :param parameters: List of parameters to pass to capsule.
210        Should be the same order as in the App Panel.
211        :param data_assets: List of dictionaries containing "id" and "mount" keys.
212        id is the data asset id, mount is the location to mount it in the capsule.
213        :return: Computation object.
214        """
215        logger.info(f"Running capsule computation on {self.id}")
216        input_url = f"{self.api_url}/computations"
217
218        logger.debug(f"Input url: {input_url}")
219        logger.debug(f"Parameters {parameters}")
220        logger.debug(f"Data assets {data_assets}")
221
222        payload = {"capsule_id": self.id}
223        if parameters:
224            payload["parameters"] = parameters
225        if data_assets:
226            payload["data_assets"] = data_assets
227        req = self.post(input_url, payload)
228        return Computation.from_dict(req.json(), self.domain, self.api_key)
229
230    def set_capsule_permissions(
231        self,
232        users: list[UserPermission] = None,
233        groups: list[GroupPermission] = None,
234        everyone: EveryonePermission = None,
235    ):
236        """
237
238        :param users: User permissions to set
239        :param groups: Group permissions to set
240        :param everyone: Permissions for everyone with access to the capsule
241        """
242        input_url = f"{self.api_url}/capsules/{self.id}/permissions"
243
244        logger.debug(f"Input url: {input_url}")
245        logger.debug(f"Users {users}")
246        logger.debug(f"groups {groups}")
247        logger.debug(f"everyone {everyone}")
248
249        payload = {}
250        if users:
251            payload["users"] = [asdict(x) for x in users]
252        if groups:
253            payload["groups"] = [asdict(x) for x in groups]
254        if everyone:
255            payload["everyone"] = asdict(everyone)["role"]
256
257        self.post(input_url, payload)
logger = <Logger codeoceansdk.Capsule (WARNING)>
@dataclass(frozen=True)
class OriginalCapsuleInfo:
13@dataclass(frozen=True)
14class OriginalCapsuleInfo:
15    id: str = None
16    """Metadata id"""
17    major_version: int = None
18    """Major version number"""
19    minor_version: int = None
20    """Minor version number"""
21    name: str = None
22    """Name of original pipeline"""
23    created: int = None
24    """Creation time"""
25    public: bool = None
26    """Is original capsule public"""
OriginalCapsuleInfo( id: str = None, major_version: int = None, minor_version: int = None, name: str = None, created: int = None, public: bool = None)
id: str = None

Metadata id

major_version: int = None

Major version number

minor_version: int = None

Minor version number

name: str = None

Name of original pipeline

created: int = None

Creation time

public: bool = None

Is original capsule public

@dataclass(frozen=True)
class SubmissionInfo:
29@dataclass(frozen=True)
30class SubmissionInfo:
31    timestamp: int = None
32    """Submission time"""
33    commit: str = None
34    """Submission commit hash"""
35    verification_capsule: str = None
36    """Verification capsule ID"""
37    verified: bool = None
38    """Indicates whether the capsule was verified"""
39    verified_timestamp: int = None
40    """Verification time"""
SubmissionInfo( timestamp: int = None, commit: str = None, verification_capsule: str = None, verified: bool = None, verified_timestamp: int = None)
timestamp: int = None

Submission time

commit: str = None

Submission commit hash

verification_capsule: str = None

Verification capsule ID

verified: bool = None

Indicates whether the capsule was verified

verified_timestamp: int = None

Verification time

@dataclass(frozen=True, slots=True)
class Version:
43@dataclass(frozen=True, slots=True)
44class Version:
45    """Capsule version"""
46    major_version: int
47    """Major version"""
48    minor_version: int
49    """Minor version"""
50    publish_time: int
51    """Publish timestamp"""
52    doi: str = None
53    """Digital identifier for capsule"""

Capsule version

Version( major_version: int, minor_version: int, publish_time: int, doi: str = None)
major_version: int

Major version

minor_version: int

Minor version

publish_time: int

Publish timestamp

doi: str

Digital identifier for capsule

@dataclass(frozen=True, slots=True)
class Article:
56@dataclass(frozen=True, slots=True)
57class Article:
58    """If capsule is attached to a publication, what article is it attached to"""
59    url: str = None
60    """Article URL"""
61    id: str = None
62    """Article ID"""
63    doi: str = None
64    """Digital identifier for publication"""
65    citation: str = None
66    """Article citation"""
67    state: Enum("state", ["in_review", "published"]) = None
68    """Publication state (i.e., has it been published yet)"""
69    name: str = None
70    """Article name"""
71    journal_name: str = None
72    """Journal the article appears in"""
73    publish_time: int = None
74    """Publication timestamp"""

If capsule is attached to a publication, what article is it attached to

Article( url: str = None, id: str = None, doi: str = None, citation: str = None, state: codeoceansdk.Capsule.state = None, name: str = None, journal_name: str = None, publish_time: int = None)
url: str

Article URL

id: str

Article ID

doi: str

Digital identifier for publication

citation: str

Article citation

state: codeoceansdk.Capsule.state

Publication state (i.e., has it been published yet)

name: str

Article name

journal_name: str

Journal the article appears in

publish_time: int

Publication timestamp

@dataclass(frozen=True, slots=True)
class UserPermission:
77@dataclass(frozen=True, slots=True)
78class UserPermission:
79    """Permissions for specific user"""
80    email: str
81    """Email address for user"""
82    role: Enum("role", ["owner, editor, viewer", "discoverable"])
83    """Permission level to set for user"""

Permissions for specific user

UserPermission(email: str, role: codeoceansdk.Capsule.role)
email: str

Email address for user

role: codeoceansdk.Capsule.role

Permission level to set for user

@dataclass(frozen=True, slots=True)
class GroupPermission:
86@dataclass(frozen=True, slots=True)
87class GroupPermission:
88    """Permissions for group of users"""
89    group: str
90    """Group name"""
91    role: Enum("role", ["owner", "editor", "viewer", "discoverable"])
92    """Permission level to set for group"""

Permissions for group of users

GroupPermission(group: str, role: codeoceansdk.Capsule.role)
group: str

Group name

role: codeoceansdk.Capsule.role

Permission level to set for group

@dataclass(frozen=True, slots=True)
class EveryonePermission:
95@dataclass(frozen=True, slots=True)
96class EveryonePermission:
97    """Permission for all users"""
98    role: Enum("role", ["viewer", "discoverable", "none"])
99    """Permission level to set for everyone"""

Permission for all users

EveryonePermission(role: codeoceansdk.Capsule.role)
role: codeoceansdk.Capsule.role

Permission level to set for everyone

@dataclass(kw_only=True)
class Capsule(codeoceansdk.CodeOcean.CodeOcean):
102@dataclass(kw_only=True)
103class Capsule(CodeOcean):
104    id: str
105    """Capsule internal id"""
106    created: int = 0
107    """Capsule creation time"""
108    name: str = ""
109    """Capsule display name"""
110    status: Enum(
111        "status", ["non_published", "submitted", "publishing", "published", "verified"]
112    ) = "non_published"
113    """Whether or not capsule is published"""
114    owner: str = ""
115    """Capsule owner id"""
116    slug: str = ""
117    """Alternate capsule id"""
118    field: Optional[str] = None
119    """Capsule research field"""
120    description: Optional[str] = None
121    """Capsule description"""
122    cloned_from_url: Optional[str] = None
123    """If this is a capsule cloned from github, what url was it"""
124    keywords: Optional[list[str]] = dc_field(default_factory=list)
125    """Keywords describing capsule (optional)"""
126    article: Optional[Article] = None
127    """Capsule article info (optional)"""
128    versions: Optional[list[Version]] = dc_field(default_factory=list)
129    """Capsule versions (if published) (optional)"""
130    published_capsule: Optional[str] = None
131    """Published capsule id (separate from original)"""
132    original_capsule: Optional[OriginalCapsuleInfo] = None
133    """Original capsule info (if duplicated) (optional)"""
134    submission: Optional[SubmissionInfo] = None
135    """Submission info (if capsule submitted for publication) (optional)"""
136
137    def __post_init__(self):
138        super().__post_init__()
139        self.capsule_url = f"{self.api_url}/capsules/{self.id}"
140
141    def _parse_comp_response(self, computation_list: list):
142        """
143        Parse response from list of dictionaries containing
144        computational parameters
145
146        :param computation_list:  Input list of dictionary of computation parameters
147        :return: list of Computation objects
148        """
149        computations = []
150        for curr_dict in computation_list:
151            computations.append(
152                Computation.from_dict(
153                    computation_dict=curr_dict, api_key=self.api_key, domain=self.domain
154                )
155            )
156        return computations
157
158    def get_capsule_runs(self):
159        """Get previous capsule runs
160
161        :return: List of Computation objects.
162        """
163        input_url = f"{self.capsule_url}/computations"
164        logger.debug(f"Input url: {input_url}")
165        req = self.get(input_url)
166        computations = self._parse_comp_response(computation_list=req.json())
167
168        logger.info("Returned runs: {}".format(len(computations)))
169        return computations
170
171    def get_capsule(self):
172        """
173        Get capsule information
174        """
175        req = self.get(self.capsule_url)
176        new_capsule = self.from_dict(req.json(), self.domain, self.api_key)
177        self.__dict__.update(new_capsule.__dict__)
178
179    @staticmethod
180    def from_dict(capsule_dict, domain, api_key):
181        """
182        Parse dictionary to Capsule object
183
184        :param domain: Code Ocean domain
185        :param api_key: Capsule api.
186        :param capsule_dict:  Input dictionary of capsule parameters.
187        :return: Computation object.
188        """
189        if "article" in capsule_dict:
190            capsule_dict["article"] = Article(**capsule_dict["article"])
191        if "versions" in capsule_dict:
192            capsule_dict["versions"] = [Version(**x) for x in capsule_dict["versions"]]
193        if "original_capsule" in capsule_dict:
194            capsule_dict["original_capsule"] = OriginalCapsuleInfo(
195                **capsule_dict["original_capsule"]
196            )
197        if "submission" in capsule_dict:
198            capsule_dict["submission"] = SubmissionInfo(**capsule_dict["submission"])
199
200        capsule_dict["domain"] = domain
201        capsule_dict["api_key"] = api_key
202        return Capsule(**capsule_dict)
203
204    def run_capsule_computation(
205        self, parameters: list = None, data_assets: list = None
206    ):
207        """
208        Run a capsule.
209
210        :param parameters: List of parameters to pass to capsule.
211        Should be the same order as in the App Panel.
212        :param data_assets: List of dictionaries containing "id" and "mount" keys.
213        id is the data asset id, mount is the location to mount it in the capsule.
214        :return: Computation object.
215        """
216        logger.info(f"Running capsule computation on {self.id}")
217        input_url = f"{self.api_url}/computations"
218
219        logger.debug(f"Input url: {input_url}")
220        logger.debug(f"Parameters {parameters}")
221        logger.debug(f"Data assets {data_assets}")
222
223        payload = {"capsule_id": self.id}
224        if parameters:
225            payload["parameters"] = parameters
226        if data_assets:
227            payload["data_assets"] = data_assets
228        req = self.post(input_url, payload)
229        return Computation.from_dict(req.json(), self.domain, self.api_key)
230
231    def set_capsule_permissions(
232        self,
233        users: list[UserPermission] = None,
234        groups: list[GroupPermission] = None,
235        everyone: EveryonePermission = None,
236    ):
237        """
238
239        :param users: User permissions to set
240        :param groups: Group permissions to set
241        :param everyone: Permissions for everyone with access to the capsule
242        """
243        input_url = f"{self.api_url}/capsules/{self.id}/permissions"
244
245        logger.debug(f"Input url: {input_url}")
246        logger.debug(f"Users {users}")
247        logger.debug(f"groups {groups}")
248        logger.debug(f"everyone {everyone}")
249
250        payload = {}
251        if users:
252            payload["users"] = [asdict(x) for x in users]
253        if groups:
254            payload["groups"] = [asdict(x) for x in groups]
255        if everyone:
256            payload["everyone"] = asdict(everyone)["role"]
257
258        self.post(input_url, payload)
Capsule( *, domain: str, api_key: str, id: str, created: int = 0, name: str = '', status: codeoceansdk.Capsule.status = 'non_published', owner: str = '', slug: str = '', field: Optional[str] = None, description: Optional[str] = None, cloned_from_url: Optional[str] = None, keywords: Optional[list[str]] = <factory>, article: Optional[Article] = None, versions: Optional[list[Version]] = <factory>, published_capsule: Optional[str] = None, original_capsule: Optional[OriginalCapsuleInfo] = None, submission: Optional[SubmissionInfo] = None)
id: str

Capsule internal id

created: int = 0

Capsule creation time

name: str = ''

Capsule display name

status: codeoceansdk.Capsule.status = 'non_published'

Whether or not capsule is published

owner: str = ''

Capsule owner id

slug: str = ''

Alternate capsule id

field: Optional[str] = None

Capsule research field

description: Optional[str] = None

Capsule description

cloned_from_url: Optional[str] = None

If this is a capsule cloned from github, what url was it

keywords: Optional[list[str]]

Keywords describing capsule (optional)

article: Optional[Article] = None

Capsule article info (optional)

versions: Optional[list[Version]]

Capsule versions (if published) (optional)

published_capsule: Optional[str] = None

Published capsule id (separate from original)

original_capsule: Optional[OriginalCapsuleInfo] = None

Original capsule info (if duplicated) (optional)

submission: Optional[SubmissionInfo] = None

Submission info (if capsule submitted for publication) (optional)

def get_capsule_runs(self):
158    def get_capsule_runs(self):
159        """Get previous capsule runs
160
161        :return: List of Computation objects.
162        """
163        input_url = f"{self.capsule_url}/computations"
164        logger.debug(f"Input url: {input_url}")
165        req = self.get(input_url)
166        computations = self._parse_comp_response(computation_list=req.json())
167
168        logger.info("Returned runs: {}".format(len(computations)))
169        return computations

Get previous capsule runs

Returns

List of Computation objects.

def get_capsule(self):
171    def get_capsule(self):
172        """
173        Get capsule information
174        """
175        req = self.get(self.capsule_url)
176        new_capsule = self.from_dict(req.json(), self.domain, self.api_key)
177        self.__dict__.update(new_capsule.__dict__)

Get capsule information

@staticmethod
def from_dict(capsule_dict, domain, api_key):
179    @staticmethod
180    def from_dict(capsule_dict, domain, api_key):
181        """
182        Parse dictionary to Capsule object
183
184        :param domain: Code Ocean domain
185        :param api_key: Capsule api.
186        :param capsule_dict:  Input dictionary of capsule parameters.
187        :return: Computation object.
188        """
189        if "article" in capsule_dict:
190            capsule_dict["article"] = Article(**capsule_dict["article"])
191        if "versions" in capsule_dict:
192            capsule_dict["versions"] = [Version(**x) for x in capsule_dict["versions"]]
193        if "original_capsule" in capsule_dict:
194            capsule_dict["original_capsule"] = OriginalCapsuleInfo(
195                **capsule_dict["original_capsule"]
196            )
197        if "submission" in capsule_dict:
198            capsule_dict["submission"] = SubmissionInfo(**capsule_dict["submission"])
199
200        capsule_dict["domain"] = domain
201        capsule_dict["api_key"] = api_key
202        return Capsule(**capsule_dict)

Parse dictionary to Capsule object

Parameters
  • domain: Code Ocean domain
  • api_key: Capsule api.
  • capsule_dict: Input dictionary of capsule parameters.
Returns

Computation object.

def run_capsule_computation(self, parameters: list = None, data_assets: list = None):
204    def run_capsule_computation(
205        self, parameters: list = None, data_assets: list = None
206    ):
207        """
208        Run a capsule.
209
210        :param parameters: List of parameters to pass to capsule.
211        Should be the same order as in the App Panel.
212        :param data_assets: List of dictionaries containing "id" and "mount" keys.
213        id is the data asset id, mount is the location to mount it in the capsule.
214        :return: Computation object.
215        """
216        logger.info(f"Running capsule computation on {self.id}")
217        input_url = f"{self.api_url}/computations"
218
219        logger.debug(f"Input url: {input_url}")
220        logger.debug(f"Parameters {parameters}")
221        logger.debug(f"Data assets {data_assets}")
222
223        payload = {"capsule_id": self.id}
224        if parameters:
225            payload["parameters"] = parameters
226        if data_assets:
227            payload["data_assets"] = data_assets
228        req = self.post(input_url, payload)
229        return Computation.from_dict(req.json(), self.domain, self.api_key)

Run a capsule.

Parameters
  • parameters: List of parameters to pass to capsule. Should be the same order as in the App Panel.
  • data_assets: List of dictionaries containing "id" and "mount" keys. id is the data asset id, mount is the location to mount it in the capsule.
Returns

Computation object.

def set_capsule_permissions( self, users: list[UserPermission] = None, groups: list[GroupPermission] = None, everyone: EveryonePermission = None):
231    def set_capsule_permissions(
232        self,
233        users: list[UserPermission] = None,
234        groups: list[GroupPermission] = None,
235        everyone: EveryonePermission = None,
236    ):
237        """
238
239        :param users: User permissions to set
240        :param groups: Group permissions to set
241        :param everyone: Permissions for everyone with access to the capsule
242        """
243        input_url = f"{self.api_url}/capsules/{self.id}/permissions"
244
245        logger.debug(f"Input url: {input_url}")
246        logger.debug(f"Users {users}")
247        logger.debug(f"groups {groups}")
248        logger.debug(f"everyone {everyone}")
249
250        payload = {}
251        if users:
252            payload["users"] = [asdict(x) for x in users]
253        if groups:
254            payload["groups"] = [asdict(x) for x in groups]
255        if everyone:
256            payload["everyone"] = asdict(everyone)["role"]
257
258        self.post(input_url, payload)
Parameters
  • users: User permissions to set
  • groups: Group permissions to set
  • everyone: Permissions for everyone with access to the capsule