Skip to content

Commit 4d3d608

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

File tree

2 files changed

+51
-31
lines changed

2 files changed

+51
-31
lines changed

cellengine/resources/attachment.py

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,42 @@
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+
grid_id: Optional[str] = None
29+
type: Optional[Literal["textbox", "image"]] = None
30+
31+
@property
32+
def client(self):
33+
return ce.APIClient()
34+
35+
@property
36+
def path(self):
37+
return f"experiments/{self.experiment_id}/attachments/{self._id}".rstrip(
38+
"/None"
39+
)
2340

2441
@classmethod
2542
def get(cls, experiment_id: str, _id: str = None, name: str = None) -> Attachment:
@@ -39,20 +56,20 @@ def upload(experiment_id: str, filepath: str, filename: str = None) -> Attachmen
3956
return ce.APIClient().upload_attachment(experiment_id, filepath, filename)
4057

4158
def update(self):
42-
"""Save changes to this Attachment to CellEngine.
59+
"""Save changes to this Attachment to CellEngine."""
60+
res = self.client.update(self)
61+
self.__setstate__(res.__getstate__()) # type: ignore
4362

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__)
63+
@classmethod
64+
def from_dict(cls, data: dict):
65+
return converter.structure(data, cls)
5266

53-
def delete(self):
54-
"""Delete this attachment."""
55-
return ce.APIClient().delete_entity(self.experiment_id, "attachments", self._id)
67+
def to_dict(self):
68+
return converter.unstructure(self)
69+
70+
def asdict(self):
71+
"""Force use of cattrs"""
72+
return self.to_dict()
5673

5774
def download(self, to_file: str = None):
5875
"""Download the attachment.
@@ -74,3 +91,6 @@ def download(self, to_file: str = None):
7491
f.write(res)
7592
else:
7693
return res
94+
95+
def delete(self):
96+
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)