Skip to content

Commit f6a62e8

Browse files
authored
Merge pull request #65 from quantori/fix-restore
Merge fix_restore branch into main
2 parents 9ef7b95 + 0e3f95d commit f6a62e8

25 files changed

+165
-282
lines changed

setup.cfg

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pesn-sdk
3-
version = 1.1.0
3+
version = 1.2.0
44
author = Quantori
55
author_email = [email protected]
66
description = Quantori Python SDK for PerkinElmer Signals Notebook
@@ -22,11 +22,11 @@ python_requires = >=3.9
2222
install_requires =
2323
Jinja2~=3.1.1
2424
requests~=2.27
25-
pydantic==1.9.1
25+
pydantic==1.10.4
2626
pandas~=1.4
2727

2828
[options.extras_require]
29-
dev = pytest==6.2.5;pytest-mock==3.7.0;arrow==1.2.2;factory-boy==3.2.1;pytest-factoryboy==2.1.0;pytest-cov==3.0.0;mypy==0.931
29+
dev = pytest==6.2.5;pytest-mock==3.7.0;arrow==1.2.2;factory-boy==3.2.1;pytest-factoryboy==2.1.0;pytest-cov==3.0.0;mypy==1.0.0
3030

3131
[options.packages.find]
3232
where = src

src/signals_notebook/api.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ def call(
9292
self,
9393
method: str,
9494
path: Union[str, Sequence[str]],
95-
params: Dict[str, Any] = None,
95+
params: Optional[Dict[str, Any]] = None,
9696
data: _Data = None,
97-
json: Union[list, Dict[str, Any]] = None,
98-
headers: Dict[str, str] = None,
97+
json: Optional[Union[list, Dict[str, Any]]] = None,
98+
headers: Optional[Dict[str, str]] = None,
9999
) -> requests.Response:
100100
"""Makes an API call
101101
@@ -148,6 +148,7 @@ def call(
148148
params=params,
149149
headers=headers,
150150
)
151+
151152
if not response.ok:
152153
log.error(
153154
'Error has been occurred while getting response, status code: %s',

src/signals_notebook/attributes/attribute.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def create(
117117
cls,
118118
name: str,
119119
description: str,
120-
options: list[str] = None,
120+
options: Optional[list[str]] = None,
121121
) -> 'Attribute':
122122
"""Create new Attribute
123123

src/signals_notebook/common_types.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ class ResponseData(GenericModel, Generic[EntityClass]):
297297
relationships: Optional[dict[str, Any]] = Field(default=None)
298298
meta: Optional[dict[str, Any]] = Field(default=None)
299299

300-
def __init__(self, _context: dict[str, Any] = None, **kwargs):
300+
def __init__(self, _context: Optional[dict[str, Any]] = None, **kwargs):
301301
attributes = kwargs.get('attributes', {})
302302

303303
if _context:
@@ -310,7 +310,7 @@ class Response(GenericModel, Generic[EntityClass]):
310310
links: Optional[Links] = None
311311
data: Union[ResponseData[EntityClass], List[ResponseData[EntityClass]]]
312312

313-
def __init__(self, _context: dict[str, Any] = None, **kwargs):
313+
def __init__(self, _context: Optional[dict[str, Any]] = None, **kwargs):
314314
data = kwargs.get('data', {})
315315

316316
if _context:

src/signals_notebook/entities/admin_defined_object.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import logging
33
from functools import cached_property
4-
from typing import Any, ClassVar, List, Literal, Optional
4+
from typing import Any, cast, ClassVar, List, Literal, Optional
55

66
from pydantic import BaseModel, Field
77

@@ -56,7 +56,7 @@ class Config:
5656

5757
class AdminDefinedObject(Container):
5858
type: Literal[EntityType.ADO] = Field(allow_mutation=False)
59-
ado: AdoType = Field(default=CUSTOM_SYSTEM_OBJECT)
59+
ado: AdoType
6060
_template_name: ClassVar = 'ado.html'
6161

6262
class Config:
@@ -111,11 +111,7 @@ def create(
111111
)
112112

113113
log.debug('Creating AdminDefinedObject for: %s', cls.__name__)
114-
return super()._create(
115-
digest=digest,
116-
force=force,
117-
request=request,
118-
)
114+
return cast('AdminDefinedObject', super()._create(digest=digest, force=force, request=request))
119115

120116
def get_html(self) -> str:
121117
"""Get in HTML format

src/signals_notebook/entities/chemical_drawing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def add_structures(
116116
self,
117117
structure: Structure,
118118
positions: ChemicalDrawingPosition,
119-
digest: str = None,
119+
digest: Optional[str] = None,
120120
force: bool = True,
121121
) -> Structure:
122122
"""Add reagent, reactant or product to ChemicalDrawing

src/signals_notebook/entities/container.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,10 @@ def dump_templates(cls, base_path: str, fs_handler: FSHandler) -> None:
120120
templates = EntityStore.get_list(
121121
include_types=[entity_type], include_options=[EntityStore.IncludeOptions.TEMPLATE]
122122
)
123-
try:
124-
for template in templates:
125-
template.dump(
126-
fs_handler.join_path(base_path, 'templates', entity_type),
127-
fs_handler,
128-
['Templates', entity_type.value],
129-
)
130-
131-
except TypeError:
132-
pass
123+
124+
for template in templates:
125+
template.dump(
126+
fs_handler.join_path(base_path, 'templates', entity_type),
127+
fs_handler,
128+
['Templates', entity_type.value],
129+
)

src/signals_notebook/entities/entity.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
from signals_notebook.api import SignalsNotebookApi
1111
from signals_notebook.common_types import (
1212
EID,
13-
EntityClass,
1413
EntityCreationRequestPayload,
1514
EntityShortDescription,
1615
EntityType,
@@ -209,7 +208,9 @@ def delete(self) -> None:
209208
log.debug('Entity: %s was deleted from EntityStore', self.eid)
210209

211210
@classmethod
212-
def _create(cls, *, digest: str = None, force: bool = True, request: EntityCreationRequestPayload) -> EntityClass:
211+
def _create(
212+
cls, *, digest: Optional[str] = None, force: bool = True, request: EntityCreationRequestPayload,
213+
) -> 'Entity':
213214
api = SignalsNotebookApi.get_default_api()
214215
log.debug('Create Entity: %s...', cls.__name__)
215216

src/signals_notebook/entities/entity_store.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from datetime import datetime
44
from enum import Enum
5-
from typing import cast, Generator, List, Union
5+
from typing import cast, Generator, List, Optional, Union
66

77
from signals_notebook.api import SignalsNotebookApi
88
from signals_notebook.common_types import EID, EntityType, Response, ResponseData
@@ -59,11 +59,11 @@ def get(cls, eid: EID) -> Entity:
5959
@classmethod
6060
def get_list(
6161
cls,
62-
include_types: List[EntityType] = None,
63-
exclude_types: List[EntityType] = None,
64-
include_options: List[IncludeOptions] = None,
65-
modified_after: datetime = None,
66-
modified_before: datetime = None,
62+
include_types: Optional[List[EntityType]] = None,
63+
exclude_types: Optional[List[EntityType]] = None,
64+
include_options: Optional[List[IncludeOptions]] = None,
65+
modified_after: Optional[datetime] = None,
66+
modified_before: Optional[datetime] = None,
6767
) -> Generator[Entity, None, None]:
6868
"""Get all entities
6969
@@ -133,7 +133,7 @@ def refresh(cls, entity: Entity) -> None:
133133
setattr(entity, field.name, new_value)
134134

135135
@classmethod
136-
def delete(cls, eid: EID, digest: str = None, force: bool = True) -> None:
136+
def delete(cls, eid: EID, digest: Optional[str] = None, force: bool = True) -> None:
137137
"""Delete Entity by ID
138138
139139
Args:

src/signals_notebook/entities/experiment.py

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import logging
33
from enum import Enum
44
from functools import cached_property
5-
from typing import Any, ClassVar, Generator, Literal, Optional, Union
5+
from typing import Any, cast, ClassVar, Dict, Generator, List, Literal, Optional, Union
66

77
from pydantic import BaseModel, Field
88

@@ -18,8 +18,12 @@
1818

1919

2020
class _Attributes(BaseModel):
21-
name: str
21+
name: Optional[str] = None
2222
description: Optional[str] = None
23+
organization: Optional[str] = None
24+
department: Optional[str] = None
25+
project: Optional[str] = None
26+
modality: Optional[str] = None
2327

2428

2529
class _Relationships(BaseModel):
@@ -58,12 +62,13 @@ def _get_entity_type(cls) -> EntityType:
5862
def create(
5963
cls,
6064
*,
61-
name: str,
65+
name: Optional[str] = None,
6266
description: Optional[str] = None,
6367
template: Optional['Experiment'] = None,
6468
notebook: Optional[Notebook] = None,
65-
digest: str = None,
69+
digest: Optional[str] = None,
6670
force: bool = True,
71+
attributes: Optional[Dict[str, Any]] = None,
6772
) -> 'Experiment':
6873
"""Create new Experiment in Signals Notebook
6974
@@ -74,6 +79,7 @@ def create(
7479
notebook: notebook where create experiment
7580
digest: Indicate digest
7681
force: Force to create without doing digest check
82+
attributes:
7783
7884
Returns:
7985
Experiment
@@ -89,20 +95,13 @@ def create(
8995
request = _RequestPayload(
9096
data=_RequestBody(
9197
type=cls._get_entity_type(),
92-
attributes=_Attributes(
93-
name=name,
94-
description=description,
95-
),
98+
attributes=_Attributes(name=name, description=description, **(attributes or {})),
9699
relationships=relationships,
97100
)
98101
)
99102

100103
log.debug('Creating Notebook for: %s', cls.__name__)
101-
return super()._create(
102-
digest=digest,
103-
force=force,
104-
request=request,
105-
)
104+
return cast('Experiment', super()._create(digest=digest, force=force, request=request))
106105

107106
@cached_property
108107
def stoichiometry(self) -> Union[Stoichiometry, list[Stoichiometry]]:
@@ -160,12 +159,62 @@ def _load(cls, path: str, fs_handler: FSHandler, parent: Any) -> None:
160159
from signals_notebook.item_mapper import ItemMapper
161160

162161
metadata = json.loads(fs_handler.read(fs_handler.join_path(path, 'metadata.json')))
163-
experiment = cls.create(
164-
notebook=parent, name=metadata['name'], description=metadata['description'], force=True
165-
)
162+
try:
163+
experiment = cls.create(
164+
notebook=parent,
165+
name=metadata['name'],
166+
description=metadata['description'],
167+
force=True,
168+
attributes=dict(
169+
organization=metadata['Organization'],
170+
project=metadata['Project'],
171+
modality=metadata['Modality'],
172+
department=metadata['Department'],
173+
),
174+
)
175+
except Exception as e:
176+
log.error(str(e))
177+
if 'According to template, name is auto generated, can not be specified' in str(e):
178+
log.error('Retrying create')
179+
experiment = cls.create(
180+
notebook=parent,
181+
description=metadata['description'],
182+
force=True,
183+
attributes=dict(
184+
organization=metadata['Organization'],
185+
project=metadata['Project'],
186+
modality=metadata['Modality'],
187+
department=metadata['Department'],
188+
),
189+
)
190+
else:
191+
raise e
166192
child_entities_folders = fs_handler.list_subfolders(path)
167193
for child_entity in child_entities_folders:
168194
child_entity_type = child_entity.split(':')[0]
169-
ItemMapper.get_item_class(child_entity_type)._load(
170-
fs_handler.join_path(path, child_entity), fs_handler, experiment
171-
)
195+
try:
196+
ItemMapper.get_item_class(child_entity_type)._load(
197+
fs_handler.join_path(path, child_entity), fs_handler, experiment
198+
)
199+
except NotImplementedError:
200+
log.error('Failed to load entity %s. Not supported' % child_entity_type)
201+
202+
def dump(self, base_path: str, fs_handler: FSHandler, alias: Optional[List[str]] = None) -> None:
203+
metadata = {k: v for k, v in self.dict().items() if k in ('name', 'description', 'eid')}
204+
self._reload_properties()
205+
for prop in self._properties:
206+
if prop.name in ('Department', 'Project', 'Modality', 'Organization'):
207+
metadata[prop.name] = prop.value
208+
209+
fs_handler.write(
210+
fs_handler.join_path(base_path, self.eid, 'metadata.json'),
211+
json.dumps(metadata),
212+
alias + [self.name, '__Metadata'] if alias else None,
213+
)
214+
for child in self.get_children():
215+
try:
216+
child.dump(
217+
fs_handler.join_path(base_path, self.eid), fs_handler, alias + [self.name] if alias else None
218+
)
219+
except Exception as e:
220+
log.error(str(e))

0 commit comments

Comments
 (0)