Skip to content

Commit dc72078

Browse files
committed
src(population): Use attrs + cattrs
1 parent 2d35c99 commit dc72078

File tree

4 files changed

+63
-27
lines changed

4 files changed

+63
-27
lines changed

cellengine/resources/population.py

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,55 @@
11
from __future__ import annotations
2-
from dataclasses import field
3-
from typing import Optional
2+
from typing import Dict, Optional
3+
from attr import define, field
44

5-
from dataclasses_json.cfg import config
6-
from cellengine.utils.dataclass_mixin import DataClassMixin, ReadOnly
7-
8-
from dataclasses import dataclass
95
import cellengine as ce
6+
from cellengine.utils import converter
7+
from cellengine.utils.readonly import readonly
108

119

12-
@dataclass
13-
class Population(DataClassMixin):
10+
@define
11+
class Population:
12+
_id: str = field(on_setattr=readonly)
13+
experiment_id: str = field(on_setattr=readonly)
1414
name: str
1515
gates: str
16+
unique_name: Optional[str] = field(default=None, on_setattr=readonly)
1617
parent_id: Optional[str] = None
1718
terminal_gate_gid: Optional[str] = None
18-
_id: str = field(
19-
metadata=config(field_name="_id"), default=ReadOnly()
20-
) # type: ignore
21-
experiment_id: str = field(default=ReadOnly()) # type: ignore
22-
unique_name: str = field(default=ReadOnly()) # type: ignore
2319

2420
def __repr__(self):
2521
return f"Population(_id='{self._id}', name='{self.name}')"
2622

23+
@property
24+
def path(self):
25+
return f"experiments/{self.experiment_id}/populations/{self._id}".rstrip(
26+
"/None"
27+
)
28+
2729
@classmethod
28-
def get(cls, experiment_id: str, _id: str = None, name: str = None):
30+
def get(cls, experiment_id: str, _id: str = None, name: str = None) -> Population:
31+
"""Get Population by name or ID for a specific experiment. Either
32+
`name` or `_id` must be specified.
33+
34+
Args:
35+
experiment_id: ID of the experiment this attachment is connected with.
36+
_id (optional): ID of the attachment.
37+
name (optional): Name of the experiment.
38+
"""
2939
kwargs = {"name": name} if name else {"_id": _id}
3040
return ce.APIClient().get_population(experiment_id, **kwargs)
3141

3242
@classmethod
33-
def create(cls, experiment_id: str, population: dict) -> Population:
34-
return ce.APIClient().post_population(experiment_id, population)
43+
def from_dict(cls, data: Dict):
44+
return converter.structure(data, cls)
45+
46+
def to_dict(self) -> Dict:
47+
return converter.unstructure(self)
3548

3649
def update(self):
3750
"""Save changes to this Population to CellEngine."""
38-
res = ce.APIClient().update_entity(
39-
self.experiment_id, self._id, "populations", self.to_dict()
40-
)
41-
self.__dict__.update(Population.from_dict(res).__dict__)
51+
res = ce.APIClient().update(self)
52+
self.__setstate__(res.__getstate__()) # type: ignore
4253

4354
def delete(self):
4455
ce.APIClient().delete_entity(self.experiment_id, "populations", self._id)

cellengine/utils/api_client/APIClient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ def get_populations(
554554

555555
def get_population(
556556
self, experiment_id, _id=None, name=None, as_dict=False
557-
) -> Union[Population, Dict[str, Any]]:
557+
) -> Population:
558558
_id = _id or self._get_id_by_name(name, "populations", experiment_id)
559559
population = self._get(
560560
f"{self.base_url}/experiments/{experiment_id}/populations/{_id}"

tests/fixtures/api-populations.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22

33

4-
@pytest.fixture(scope="session")
4+
@pytest.fixture(scope="function")
55
def populations():
66
populations = [
77
{

tests/unit/resources/test_population.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
EXP_ID = "5d38a6f79fae87499999a74b"
99

1010

11-
@pytest.fixture(scope="module")
11+
@pytest.fixture(scope="function")
1212
def population(client, populations):
13-
return Population.from_dict(populations[0])
13+
pop = populations[0]
14+
if "uniqueName" in pop.keys():
15+
pop.pop("uniqueName")
16+
return Population.from_dict(pop)
1417

1518

1619
def population_tester(population):
@@ -28,7 +31,18 @@ def population_tester(population):
2831

2932

3033
@responses.activate
31-
def test_should_get_population(ENDPOINT_BASE, population, populations):
34+
def test_client_gets_population(client, ENDPOINT_BASE, population, populations):
35+
responses.add(
36+
responses.GET,
37+
ENDPOINT_BASE + f"/experiments/{EXP_ID}/populations/{population._id}",
38+
json=populations[0],
39+
)
40+
pop = client.get_population(EXP_ID, population._id)
41+
population_tester(pop)
42+
43+
44+
@responses.activate
45+
def test_population_gets_population(client, ENDPOINT_BASE, population, populations):
3246
responses.add(
3347
responses.GET,
3448
ENDPOINT_BASE + f"/experiments/{EXP_ID}/populations/{population._id}",
@@ -39,14 +53,25 @@ def test_should_get_population(ENDPOINT_BASE, population, populations):
3953

4054

4155
@responses.activate
42-
def test_should_post_population(ENDPOINT_BASE, population, populations):
56+
def test_get_populations_returns_unique_name(client, ENDPOINT_BASE, populations):
57+
responses.add(
58+
responses.GET,
59+
ENDPOINT_BASE + f"/experiments/{EXP_ID}/populations",
60+
json=populations,
61+
)
62+
pops = client.get_populations(EXP_ID)
63+
assert all([pop.unique_name for pop in pops])
64+
65+
66+
@responses.activate
67+
def test_should_post_population(client, ENDPOINT_BASE, population, populations):
4368
responses.add(
4469
responses.POST,
4570
ENDPOINT_BASE + f"/experiments/{EXP_ID}/populations",
4671
json=populations[0],
4772
)
4873
payload = populations[0].copy()
49-
pop = Population.create(EXP_ID, payload)
74+
pop = client.post_population(EXP_ID, payload)
5075
population_tester(pop)
5176

5277

0 commit comments

Comments
 (0)