|
1 | 1 | from __future__ import annotations
|
2 |
| -import os |
| 2 | +from functools import lru_cache |
| 3 | +from getpass import getpass |
3 | 4 | import json
|
| 5 | +import os |
| 6 | +from typing import Any, Dict, List, Optional, TypeVar, Union, overload |
| 7 | + |
4 | 8 | import pandas
|
5 |
| -from getpass import getpass |
6 |
| -from typing import Any, Dict, List, Union, Optional |
7 |
| -from functools import lru_cache |
8 | 9 | from requests_toolbelt.multipart.encoder import MultipartEncoder
|
9 | 10 |
|
10 |
| -from cellengine.utils.api_client.BaseAPIClient import BaseAPIClient |
| 11 | +from cellengine.utils import converter |
11 | 12 | from cellengine.utils.api_client.APIError import APIError
|
| 13 | +from cellengine.utils.api_client.BaseAPIClient import BaseAPIClient |
12 | 14 | from cellengine.utils.singleton import Singleton
|
| 15 | + |
13 | 16 | from ...resources.attachment import Attachment
|
14 | 17 | from ...resources.compensation import Compensation
|
15 | 18 | from ...resources.experiment import Experiment
|
|
20 | 23 | from ...resources.scaleset import ScaleSet
|
21 | 24 |
|
22 | 25 |
|
| 26 | +CE = TypeVar( |
| 27 | + "CE", |
| 28 | + bound=Union[ |
| 29 | + Attachment, Compensation, Experiment, FcsFile, Gate, Plot, Population, ScaleSet |
| 30 | + ], |
| 31 | +) |
| 32 | + |
| 33 | + |
23 | 34 | class APIClient(BaseAPIClient, metaclass=Singleton):
|
24 | 35 | _API_NAME = "CellEngine Python Toolkit"
|
25 | 36 |
|
@@ -119,6 +130,85 @@ def update_entity(self, experiment_id, _id, entity_type, body) -> dict:
|
119 | 130 | json=body,
|
120 | 131 | )
|
121 | 132 |
|
| 133 | + def _get_path(self, entity: CE) -> str: |
| 134 | + fullpath = f"{self.base_url}/{entity.path}" |
| 135 | + return fullpath |
| 136 | + |
| 137 | + # fmt: off |
| 138 | + # temporary fix for https://github.com/psf/black/issues/1797 |
| 139 | + @overload |
| 140 | + def create(self, entity: Attachment, **kwargs) -> Attachment: ... |
| 141 | + @overload |
| 142 | + def create(self, entity: Compensation, **kwargs) -> Compensation: ... |
| 143 | + @overload |
| 144 | + def create(self, entity: Experiment, **kwargs) -> Experiment: ... |
| 145 | + @overload |
| 146 | + def create(self, entity: FcsFile, **kwargs) -> FcsFile: ... |
| 147 | + @overload |
| 148 | + def create(self, entity: Gate, **kwargs) -> Gate: ... |
| 149 | + @overload |
| 150 | + def create(self, entity: Population, **kwargs) -> Population: ... |
| 151 | + @overload |
| 152 | + def create(self, entity: ScaleSet, **kwargs) -> ScaleSet: ... |
| 153 | + # fmt: on |
| 154 | + |
| 155 | + def create( |
| 156 | + self, entity, **kwargs |
| 157 | + ) -> Union[ |
| 158 | + Attachment, |
| 159 | + Compensation, |
| 160 | + Experiment, |
| 161 | + FcsFile, |
| 162 | + Gate, |
| 163 | + Population, |
| 164 | + ScaleSet, |
| 165 | + ]: |
| 166 | + """Create a local entity on CellEngine.""" |
| 167 | + |
| 168 | + def unstructure_and_clean(entity, **kwargs): |
| 169 | + (cls, path, body) = ( |
| 170 | + entity.__class__, |
| 171 | + entity.path, |
| 172 | + converter.unstructure(entity), |
| 173 | + ) |
| 174 | + if body["_id"] == "None" or body["_id"] is None: |
| 175 | + del body["_id"] # https://github.com/primitybio/cellengine/issues/5800 |
| 176 | + return (cls, path, body) |
| 177 | + |
| 178 | + def post_and_restructure(cls, path, body, **kwargs): |
| 179 | + res = self._post(f"{self.base_url}/{path}", json=body, params=kwargs) |
| 180 | + return converter.structure(res, cls) |
| 181 | + |
| 182 | + if isinstance(entity, list): |
| 183 | + body = [unstructure_and_clean(thing) for thing in entity] |
| 184 | + # group [(class), (path), (body)] |
| 185 | + unzipped = list(zip(*body)) |
| 186 | + # remove duplicates to ensure all entities are of the same type |
| 187 | + (cls, path, body) = (set(unzipped[0]), set(unzipped[1]), unzipped[2]) |
| 188 | + |
| 189 | + if len(cls) == 1 and len(path) == 1: |
| 190 | + # then post to bulk endpoint |
| 191 | + res = post_and_restructure( |
| 192 | + List[cls.pop()], path.pop(), list(body) # type: ignore |
| 193 | + ) |
| 194 | + else: |
| 195 | + # post each item individually |
| 196 | + res = [post_and_restructure(*thing) for thing in body] |
| 197 | + else: |
| 198 | + body = unstructure_and_clean(entity) |
| 199 | + res = post_and_restructure(*body) |
| 200 | + return res # type: ignore |
| 201 | + |
| 202 | + def update(self, entity, params: Dict = None): |
| 203 | + path = self._get_path(entity) |
| 204 | + data = converter.unstructure(entity) |
| 205 | + res = self._patch(path, json=data, params=params) |
| 206 | + return converter.structure(res, entity.__class__) |
| 207 | + |
| 208 | + def delete(self, entity, params: Dict = None) -> None: |
| 209 | + path = self._get_path(entity) |
| 210 | + self._delete(path, params=params) |
| 211 | + |
122 | 212 | def delete_entity(self, experiment_id, entity_type, _id):
|
123 | 213 | url = f"{self.base_url}/experiments/{experiment_id}/{entity_type}/{_id}"
|
124 | 214 | self._delete(url)
|
|
0 commit comments