Skip to content

Commit a4ba71a

Browse files
committed
src(attachment): Use attrs + cattrs
1 parent 11bf3c4 commit a4ba71a

File tree

2 files changed

+45
-31
lines changed

2 files changed

+45
-31
lines changed

cellengine/resources/attachment.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,36 @@
11
from __future__ import annotations
2-
from dataclasses import dataclass, field
3-
from dataclasses_json.cfg import config
2+
from typing import Literal, 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
78

89

9-
@dataclass
10-
class Attachment(DataClassMixin):
10+
@define
11+
class Attachment:
1112
"""A class representing a CellEngine attachment.
1213
Attachments are non-data files that are stored in an experiment.
1314
"""
1415

16+
_id: str = field(on_setattr=readonly)
1517
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
18+
crc32c: str = field(on_setattr=readonly, repr=False)
19+
experiment_id: str = field(on_setattr=readonly, repr=False)
20+
md5: str = field(on_setattr=readonly, repr=False)
21+
size: int = field(on_setattr=readonly, repr=False)
22+
grid_id: Optional[str] = None
23+
type: Optional[Literal["textbox", "image"]] = None
24+
25+
@property
26+
def client(self):
27+
return ce.APIClient()
28+
29+
@property
30+
def path(self):
31+
return f"experiments/{self.experiment_id}/attachments/{self._id}".rstrip(
32+
"/None"
33+
)
2334

2435
@classmethod
2536
def get(cls, experiment_id: str, _id: str = None, name: str = None) -> Attachment:
@@ -39,20 +50,20 @@ def upload(experiment_id: str, filepath: str, filename: str = None) -> Attachmen
3950
return ce.APIClient().upload_attachment(experiment_id, filepath, filename)
4051

4152
def update(self):
42-
"""Save changes to this Attachment to CellEngine.
53+
"""Save changes to this Attachment to CellEngine."""
54+
res = self.client.update(self)
55+
self.__setstate__(res.__getstate__()) # type: ignore
4356

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__)
57+
@classmethod
58+
def from_dict(cls, data: dict):
59+
return converter.structure(data, cls)
5260

53-
def delete(self):
54-
"""Delete this attachment."""
55-
return ce.APIClient().delete_entity(self.experiment_id, "attachments", self._id)
61+
def to_dict(self):
62+
return converter.unstructure(self)
63+
64+
def asdict(self):
65+
"""Force use of cattrs"""
66+
return self.to_dict()
5667

5768
def download(self, to_file: str = None):
5869
"""Download the attachment.
@@ -74,3 +85,6 @@ def download(self, to_file: str = None):
7485
f.write(res)
7586
else:
7687
return res
88+
89+
def delete(self):
90+
return self.client.delete_entity(self.experiment_id, "attachments", self._id)

tests/unit/resources/test_attachment.py

Lines changed: 7 additions & 7 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,26 +24,26 @@ def attachments_tester(attachment):
2424

2525

2626
@responses.activate
27-
def test_should_get_attachment(ENDPOINT_BASE, attachment):
27+
def test_should_get_attachment(client, ENDPOINT_BASE, attachment):
2828
responses.add(
2929
responses.GET,
3030
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments",
3131
json=[attachment.to_dict()],
3232
)
33-
att = Attachment.get(EXP_ID, attachment._id)
33+
att = client.get_attachment(EXP_ID, attachment._id)
3434
attachments_tester(att)
3535

3636

3737
@responses.activate
38-
def test_should_create_attachment(ENDPOINT_BASE, experiment, attachments):
38+
def test_should_create_attachment(client, ENDPOINT_BASE, experiment, attachments):
3939
"""Test creation of a new attachment.
4040
This test must be run from the project root directory"""
4141
responses.add(
4242
responses.POST,
4343
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments",
4444
json=attachments[0],
4545
)
46-
att = Attachment.upload(experiment._id, "tests/data/text.txt")
46+
att = client.upload_attachment(experiment._id, "tests/data/text.txt")
4747
attachments_tester(att)
4848

4949

@@ -58,7 +58,7 @@ def test_should_delete_attachment(ENDPOINT_BASE, attachment):
5858

5959

6060
@responses.activate
61-
def test_update_attachment(ENDPOINT_BASE, experiment, attachment, attachments):
61+
def test_update_attachment(ENDPOINT_BASE, attachment):
6262
"""Test that the .update() method makes the correct call. Does not test
6363
that the correct response is made; this should be done with an integration
6464
test.
@@ -78,7 +78,7 @@ def test_update_attachment(ENDPOINT_BASE, experiment, attachment, attachments):
7878

7979

8080
@responses.activate
81-
def test_download_attachment(ENDPOINT_BASE, experiment, attachment):
81+
def test_download_attachment(client, ENDPOINT_BASE, attachment):
8282
responses.add(
8383
responses.GET,
8484
ENDPOINT_BASE + f"/experiments/{EXP_ID}/attachments/{attachment._id}",

0 commit comments

Comments
 (0)