Skip to content

Commit 8b37037

Browse files
committed
src(attachment): Use attrs + cattrs
1 parent 84510d4 commit 8b37037

File tree

2 files changed

+66
-29
lines changed

2 files changed

+66
-29
lines changed

cellengine/resources/attachment.py

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
from __future__ import annotations
2-
from dataclasses import dataclass, field
3-
from dataclasses_json.cfg import config
2+
from typing import Optional
3+
4+
from attr import define, field
45

56
import cellengine as ce
6-
from cellengine.utils.dataclass_mixin import DataClassMixin, ReadOnly
7+
from cellengine.utils import converter, readonly
8+
79

10+
try:
11+
from typing import Literal
12+
except ImportError:
13+
from typing_extensions import Literal
814

9-
@dataclass
10-
class Attachment(DataClassMixin):
15+
16+
@define
17+
class Attachment:
1118
"""A class representing a CellEngine attachment.
1219
Attachments are non-data files that are stored in an experiment.
1320
"""
1421

22+
_id: str = field(on_setattr=readonly)
1523
filename: str
16-
_id: str = field(
17-
metadata=config(field_name="_id"), default=ReadOnly()
18-
) # type: ignore
19-
crc32c: str = field(repr=False, default=ReadOnly()) # type: ignore
20-
experiment_id: str = field(repr=False, default=ReadOnly()) # type: ignore
21-
md5: str = field(repr=False, default=ReadOnly()) # type: ignore
22-
size: int = field(repr=False, default=ReadOnly()) # type: ignore
24+
crc32c: str = field(on_setattr=readonly, repr=False)
25+
experiment_id: str = field(on_setattr=readonly, repr=False)
26+
md5: str = field(on_setattr=readonly, repr=False)
27+
size: int = field(on_setattr=readonly, repr=False)
28+
type: Optional[Literal["textbox", "image"]] = None
29+
30+
@property
31+
def path(self):
32+
return f"experiments/{self.experiment_id}/attachments/{self._id}".rstrip(
33+
"/None"
34+
)
2335

2436
@classmethod
2537
def get(cls, experiment_id: str, _id: str = None, name: str = None) -> Attachment:
@@ -39,20 +51,16 @@ def upload(experiment_id: str, filepath: str, filename: str = None) -> Attachmen
3951
return ce.APIClient().upload_attachment(experiment_id, filepath, filename)
4052

4153
def update(self):
42-
"""Save changes to this Attachment to CellEngine.
54+
"""Save changes to this Attachment to CellEngine."""
55+
res = ce.APIClient().update(self)
56+
self.__setstate__(res.__getstate__()) # type: ignore
4357

44-
Returns:
45-
None: Updates the Attachment on CellEngine and synchronizes the
46-
local Attachment object properties with remote state.
47-
"""
48-
res = ce.APIClient().update_entity(
49-
self.experiment_id, self._id, "attachments", body=self.to_dict()
50-
)
51-
self.__dict__.update(Attachment.from_dict(res).__dict__)
58+
@classmethod
59+
def from_dict(cls, data: dict):
60+
return converter.structure(data, cls)
5261

53-
def delete(self):
54-
"""Delete this attachment."""
55-
return ce.APIClient().delete_entity(self.experiment_id, "attachments", self._id)
62+
def to_dict(self):
63+
return converter.unstructure(self)
5664

5765
def download(self, to_file: str = None):
5866
"""Download the attachment.
@@ -74,3 +82,6 @@ def download(self, to_file: str = None):
7482
f.write(res)
7583
else:
7684
return res
85+
86+
def delete(self):
87+
return ce.APIClient().delete_entity(self.experiment_id, "attachments", self._id)

tests/unit/resources/test_attachment.py

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
@pytest.fixture(scope="module")
11-
def attachment(ENDPOINT_BASE, client, attachments):
11+
def attachment(client, ENDPOINT_BASE, attachments):
1212
att = attachments[0]
1313
att.update({"experimentId": EXP_ID})
1414
return Attachment.from_dict(att)
@@ -24,7 +24,18 @@ def attachments_tester(attachment):
2424

2525

2626
@responses.activate
27-
def test_should_get_attachment(ENDPOINT_BASE, attachment):
27+
def test_client_gets_attachment(client, ENDPOINT_BASE, attachment):
28+
responses.add(
29+
responses.GET,
30+
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments",
31+
json=[attachment.to_dict()],
32+
)
33+
att = client.get_attachment(EXP_ID, attachment._id)
34+
attachments_tester(att)
35+
36+
37+
@responses.activate
38+
def test_attachment_gets_attachment(client, ENDPOINT_BASE, attachment):
2839
responses.add(
2940
responses.GET,
3041
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments",
@@ -35,7 +46,22 @@ def test_should_get_attachment(ENDPOINT_BASE, attachment):
3546

3647

3748
@responses.activate
38-
def test_should_create_attachment(ENDPOINT_BASE, experiment, attachments):
49+
def test_should_create_attachment(client, ENDPOINT_BASE, experiment, attachments):
50+
"""Test creation of a new attachment.
51+
This test must be run from the project root directory"""
52+
responses.add(
53+
responses.POST,
54+
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments",
55+
json=attachments[0],
56+
)
57+
att = client.upload_attachment(experiment._id, "tests/data/text.txt")
58+
attachments_tester(att)
59+
60+
61+
@responses.activate
62+
def test_classmethod_should_create_attachment(
63+
client, ENDPOINT_BASE, experiment, attachments
64+
):
3965
"""Test creation of a new attachment.
4066
This test must be run from the project root directory"""
4167
responses.add(
@@ -58,7 +84,7 @@ def test_should_delete_attachment(ENDPOINT_BASE, attachment):
5884

5985

6086
@responses.activate
61-
def test_update_attachment(ENDPOINT_BASE, experiment, attachment, attachments):
87+
def test_update_attachment(ENDPOINT_BASE, attachment):
6288
"""Test that the .update() method makes the correct call. Does not test
6389
that the correct response is made; this should be done with an integration
6490
test.
@@ -78,7 +104,7 @@ def test_update_attachment(ENDPOINT_BASE, experiment, attachment, attachments):
78104

79105

80106
@responses.activate
81-
def test_download_attachment(ENDPOINT_BASE, experiment, attachment):
107+
def test_download_attachment(client, ENDPOINT_BASE, attachment):
82108
responses.add(
83109
responses.GET,
84110
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments/{attachment._id}",

0 commit comments

Comments
 (0)