albert 1.10.0rc2__py3-none-any.whl → 1.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- albert/__init__.py +1 -1
- albert/client.py +5 -0
- albert/collections/custom_templates.py +3 -0
- albert/collections/data_templates.py +118 -264
- albert/collections/entity_types.py +19 -3
- albert/collections/inventory.py +1 -1
- albert/collections/notebooks.py +154 -26
- albert/collections/parameters.py +1 -0
- albert/collections/property_data.py +384 -280
- albert/collections/reports.py +4 -0
- albert/collections/synthesis.py +292 -0
- albert/collections/tasks.py +2 -1
- albert/collections/worksheets.py +3 -0
- albert/core/shared/models/base.py +3 -1
- albert/core/shared/models/patch.py +1 -1
- albert/resources/batch_data.py +4 -2
- albert/resources/cas.py +3 -1
- albert/resources/custom_fields.py +3 -1
- albert/resources/data_templates.py +60 -12
- albert/resources/inventory.py +6 -4
- albert/resources/lists.py +3 -1
- albert/resources/notebooks.py +12 -7
- albert/resources/parameter_groups.py +3 -1
- albert/resources/property_data.py +64 -5
- albert/resources/sheets.py +16 -14
- albert/resources/synthesis.py +61 -0
- albert/resources/tags.py +3 -1
- albert/resources/tasks.py +4 -7
- albert/resources/workflows.py +4 -2
- albert/utils/data_template.py +392 -37
- albert/utils/property_data.py +638 -0
- albert/utils/tasks.py +3 -3
- {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/METADATA +1 -1
- {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/RECORD +36 -33
- {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/WHEEL +0 -0
- {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/licenses/LICENSE +0 -0
albert/collections/reports.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
+
from pydantic import validate_call
|
|
4
|
+
|
|
3
5
|
from albert.collections.base import BaseCollection
|
|
4
6
|
from albert.core.session import AlbertSession
|
|
5
7
|
from albert.core.shared.identifiers import ReportId
|
|
@@ -136,6 +138,7 @@ class ReportCollection(BaseCollection):
|
|
|
136
138
|
input_data=input_data,
|
|
137
139
|
)
|
|
138
140
|
|
|
141
|
+
@validate_call
|
|
139
142
|
def get_full_report(self, *, report_id: ReportId) -> FullAnalyticalReport:
|
|
140
143
|
"""Get a full analytical report by its ID.
|
|
141
144
|
|
|
@@ -192,6 +195,7 @@ class ReportCollection(BaseCollection):
|
|
|
192
195
|
response = self.session.post(path, json=report_data)
|
|
193
196
|
return FullAnalyticalReport(**response.json())
|
|
194
197
|
|
|
198
|
+
@validate_call
|
|
195
199
|
def delete(self, *, id: ReportId) -> None:
|
|
196
200
|
"""Delete a report.
|
|
197
201
|
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import validate_call
|
|
6
|
+
|
|
7
|
+
from albert.collections.base import BaseCollection
|
|
8
|
+
from albert.core.session import AlbertSession
|
|
9
|
+
from albert.core.shared.identifiers import NotebookId, SynthesisId
|
|
10
|
+
from albert.exceptions import AlbertException
|
|
11
|
+
from albert.resources.synthesis import ReactantValues, RowSequence, Synthesis
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SynthesisCollection(BaseCollection):
|
|
15
|
+
"""
|
|
16
|
+
Collection for interacting with synthesis records used by notebook Ketcher blocks.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_api_version = "v3"
|
|
20
|
+
_updatable_attributes = {"name", "status", "hide_reaction_worksheet"}
|
|
21
|
+
|
|
22
|
+
def __init__(self, *, session: AlbertSession):
|
|
23
|
+
"""
|
|
24
|
+
Initialize the SynthesisCollection.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
session : AlbertSession
|
|
29
|
+
The Albert session information.
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(session=session)
|
|
32
|
+
self.base_path = f"/api/{SynthesisCollection._api_version}/synthesis"
|
|
33
|
+
|
|
34
|
+
@validate_call
|
|
35
|
+
def create(
|
|
36
|
+
self, *, parent_id: NotebookId | str, name: str, block_id: str, smiles: str | None = None
|
|
37
|
+
) -> Synthesis:
|
|
38
|
+
"""
|
|
39
|
+
Create a synthesis record for a notebook Ketcher block.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
parent_id : NotebookId | str
|
|
44
|
+
The notebook ID that owns the synthesis record.
|
|
45
|
+
name : str
|
|
46
|
+
The synthesis name.
|
|
47
|
+
block_id : str
|
|
48
|
+
The Ketcher block ID associated with the synthesis.
|
|
49
|
+
smiles : str | None, optional
|
|
50
|
+
The initial SMILES string for the synthesis.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
Synthesis
|
|
55
|
+
The created synthesis record.
|
|
56
|
+
"""
|
|
57
|
+
payload = {"name": name, "blockId": block_id, "smiles": smiles}
|
|
58
|
+
response = self.session.post(
|
|
59
|
+
url=self.base_path,
|
|
60
|
+
params={"parentId": parent_id},
|
|
61
|
+
json=payload,
|
|
62
|
+
)
|
|
63
|
+
return Synthesis(**response.json())
|
|
64
|
+
|
|
65
|
+
@validate_call
|
|
66
|
+
def get_by_id(
|
|
67
|
+
self,
|
|
68
|
+
*,
|
|
69
|
+
id: SynthesisId,
|
|
70
|
+
include_recommendations: bool = False,
|
|
71
|
+
include_predictions: bool = False,
|
|
72
|
+
version: str | None = None,
|
|
73
|
+
) -> Synthesis:
|
|
74
|
+
"""
|
|
75
|
+
Retrieve a synthesis record by ID.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
id : SynthesisId
|
|
80
|
+
The synthesis ID.
|
|
81
|
+
include_recommendations : bool, optional
|
|
82
|
+
Whether to include recommendations in the response.
|
|
83
|
+
include_predictions : bool, optional
|
|
84
|
+
Whether to include predictions in the response.
|
|
85
|
+
version : str | None, optional
|
|
86
|
+
The specific version to retrieve.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
Synthesis
|
|
91
|
+
The requested synthesis record.
|
|
92
|
+
"""
|
|
93
|
+
params: dict[str, Any] = {
|
|
94
|
+
"recommendations": include_recommendations,
|
|
95
|
+
"predictions": include_predictions,
|
|
96
|
+
}
|
|
97
|
+
if version:
|
|
98
|
+
params["version"] = version
|
|
99
|
+
response = self.session.get(
|
|
100
|
+
url=f"{self.base_path}/{id}",
|
|
101
|
+
params=params,
|
|
102
|
+
)
|
|
103
|
+
return Synthesis(**response.json())
|
|
104
|
+
|
|
105
|
+
@validate_call
|
|
106
|
+
def update_canvas_data(
|
|
107
|
+
self, *, synthesis_id: SynthesisId, smiles: str, data: str, png: str
|
|
108
|
+
) -> Synthesis:
|
|
109
|
+
"""
|
|
110
|
+
Update the Ketcher canvas data for a synthesis record.
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
synthesis_id : SynthesisId
|
|
115
|
+
The synthesis ID.
|
|
116
|
+
smiles : str
|
|
117
|
+
The updated SMILES string.
|
|
118
|
+
data : str
|
|
119
|
+
The serialized canvas data.
|
|
120
|
+
png : str
|
|
121
|
+
The base64-encoded PNG for the canvas.
|
|
122
|
+
|
|
123
|
+
Returns
|
|
124
|
+
-------
|
|
125
|
+
Synthesis
|
|
126
|
+
The updated synthesis record.
|
|
127
|
+
"""
|
|
128
|
+
payload = {
|
|
129
|
+
"smiles": smiles,
|
|
130
|
+
"canvasData": {"data": data, "png": png},
|
|
131
|
+
}
|
|
132
|
+
response = self.session.put(
|
|
133
|
+
url=f"{self.base_path}/{synthesis_id}",
|
|
134
|
+
json=payload,
|
|
135
|
+
)
|
|
136
|
+
return Synthesis(**response.json())
|
|
137
|
+
|
|
138
|
+
@validate_call
|
|
139
|
+
def update(self, *, synthesis: Synthesis) -> Synthesis:
|
|
140
|
+
"""
|
|
141
|
+
Update a synthesis record.
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
synthesis : Synthesis
|
|
146
|
+
The synthesis record containing updated fields.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
Synthesis
|
|
151
|
+
The refreshed synthesis record.
|
|
152
|
+
|
|
153
|
+
Raises
|
|
154
|
+
------
|
|
155
|
+
AlbertException
|
|
156
|
+
If the synthesis record is missing an ID.
|
|
157
|
+
"""
|
|
158
|
+
if synthesis.id is None:
|
|
159
|
+
msg = "Synthesis id is required to update the record."
|
|
160
|
+
raise AlbertException(msg)
|
|
161
|
+
existing = self.get_by_id(id=synthesis.id)
|
|
162
|
+
patch_data = self._generate_patch_payload(existing=existing, updated=synthesis)
|
|
163
|
+
if len(patch_data.data) == 0:
|
|
164
|
+
return existing
|
|
165
|
+
self.session.patch(
|
|
166
|
+
url=f"{self.base_path}/{synthesis.id}",
|
|
167
|
+
json=patch_data.model_dump(by_alias=True, mode="json"),
|
|
168
|
+
)
|
|
169
|
+
return self.get_by_id(id=synthesis.id)
|
|
170
|
+
|
|
171
|
+
@validate_call
|
|
172
|
+
def update_reactant_row_values(
|
|
173
|
+
self,
|
|
174
|
+
*,
|
|
175
|
+
synthesis_id: SynthesisId,
|
|
176
|
+
row_id: str,
|
|
177
|
+
values: ReactantValues,
|
|
178
|
+
) -> Synthesis:
|
|
179
|
+
"""
|
|
180
|
+
Update the values for a reactant row.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
synthesis_id : SynthesisId
|
|
185
|
+
The synthesis ID.
|
|
186
|
+
row_id : str
|
|
187
|
+
The reactant row ID to update.
|
|
188
|
+
values : ReactantValues
|
|
189
|
+
The values to apply to the reactant row.
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
Synthesis
|
|
194
|
+
The updated synthesis record.
|
|
195
|
+
"""
|
|
196
|
+
payload = {
|
|
197
|
+
"data": [
|
|
198
|
+
{
|
|
199
|
+
"rowId": row_id,
|
|
200
|
+
"operation": "update",
|
|
201
|
+
"attribute": "values",
|
|
202
|
+
"newValue": values.model_dump(by_alias=True, mode="json"),
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
self.session.patch(
|
|
207
|
+
url=f"{self.base_path}/{synthesis_id}/reactants/rows",
|
|
208
|
+
json=payload,
|
|
209
|
+
)
|
|
210
|
+
return self.get_by_id(id=synthesis_id)
|
|
211
|
+
|
|
212
|
+
@validate_call
|
|
213
|
+
def create_reactant_productant_table(self, *, synthesis_id: SynthesisId) -> Synthesis:
|
|
214
|
+
"""
|
|
215
|
+
Initialize the reactant/product table for a synthesis.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
synthesis_id : SynthesisId
|
|
220
|
+
The synthesis ID.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
Synthesis
|
|
225
|
+
The synthesis record.
|
|
226
|
+
"""
|
|
227
|
+
synthesis = self.get_by_id(id=synthesis_id)
|
|
228
|
+
if synthesis.inventory_id is not None:
|
|
229
|
+
return synthesis
|
|
230
|
+
row_sequence: RowSequence | None = synthesis.row_sequence
|
|
231
|
+
reactant_row_ids = row_sequence.reactants if row_sequence else []
|
|
232
|
+
if not reactant_row_ids and synthesis.reactants:
|
|
233
|
+
reactant_row_ids = [r.row_id for r in synthesis.reactants if r.row_id]
|
|
234
|
+
if not reactant_row_ids:
|
|
235
|
+
return synthesis
|
|
236
|
+
|
|
237
|
+
self.update_reactant_row_values(
|
|
238
|
+
synthesis_id=synthesis_id,
|
|
239
|
+
row_id=reactant_row_ids[0],
|
|
240
|
+
values=ReactantValues(
|
|
241
|
+
mass=None,
|
|
242
|
+
moles=None,
|
|
243
|
+
eq=None,
|
|
244
|
+
concentration=100,
|
|
245
|
+
),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
self._send_patch(
|
|
249
|
+
synthesis_id=synthesis_id,
|
|
250
|
+
payload={
|
|
251
|
+
"data": [
|
|
252
|
+
{
|
|
253
|
+
"attribute": "hideReactionWorksheet",
|
|
254
|
+
"operation": "update",
|
|
255
|
+
"newValue": "false",
|
|
256
|
+
}
|
|
257
|
+
]
|
|
258
|
+
},
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
self._send_patch(
|
|
262
|
+
synthesis_id=synthesis_id,
|
|
263
|
+
payload={
|
|
264
|
+
"data": [
|
|
265
|
+
{
|
|
266
|
+
"attribute": "inventoryId",
|
|
267
|
+
"operation": "add",
|
|
268
|
+
}
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
)
|
|
272
|
+
return self.get_by_id(id=synthesis_id)
|
|
273
|
+
|
|
274
|
+
def _send_patch(self, *, synthesis_id: SynthesisId, payload: dict[str, Any]) -> None:
|
|
275
|
+
"""
|
|
276
|
+
Send a PATCH request to the synthesis endpoint.
|
|
277
|
+
|
|
278
|
+
Parameters
|
|
279
|
+
----------
|
|
280
|
+
synthesis_id : SynthesisId
|
|
281
|
+
The synthesis ID.
|
|
282
|
+
payload : dict[str, Any]
|
|
283
|
+
The patch payload to send.
|
|
284
|
+
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
None
|
|
288
|
+
"""
|
|
289
|
+
self.session.patch(
|
|
290
|
+
url=f"{self.base_path}/{synthesis_id}",
|
|
291
|
+
json=payload,
|
|
292
|
+
)
|
albert/collections/tasks.py
CHANGED
|
@@ -27,6 +27,7 @@ from albert.core.shared.identifiers import (
|
|
|
27
27
|
)
|
|
28
28
|
from albert.exceptions import AlbertHTTPError
|
|
29
29
|
from albert.resources.attachments import AttachmentCategory
|
|
30
|
+
from albert.resources.data_templates import ImportMode
|
|
30
31
|
from albert.resources.tasks import (
|
|
31
32
|
BaseTask,
|
|
32
33
|
BatchTask,
|
|
@@ -34,7 +35,6 @@ from albert.resources.tasks import (
|
|
|
34
35
|
CsvTableResponseItem,
|
|
35
36
|
GeneralTask,
|
|
36
37
|
HistoryEntity,
|
|
37
|
-
ImportMode,
|
|
38
38
|
PropertyTask,
|
|
39
39
|
TaskAdapter,
|
|
40
40
|
TaskCategory,
|
|
@@ -735,6 +735,7 @@ class TaskCollection(BaseCollection):
|
|
|
735
735
|
|
|
736
736
|
return self.get_by_id(id=task.id)
|
|
737
737
|
|
|
738
|
+
@validate_call
|
|
738
739
|
def get_history(
|
|
739
740
|
self,
|
|
740
741
|
*,
|
albert/collections/worksheets.py
CHANGED
|
@@ -48,6 +48,7 @@ class WorksheetCollection(BaseCollection):
|
|
|
48
48
|
response_json = self._add_session_to_sheets(response_json)
|
|
49
49
|
return Worksheet(**response_json)
|
|
50
50
|
|
|
51
|
+
@validate_call
|
|
51
52
|
def setup_worksheet(self, *, project_id: ProjectId, add_sheet=False) -> Worksheet:
|
|
52
53
|
"""Setup a new worksheet for a project.
|
|
53
54
|
|
|
@@ -69,6 +70,7 @@ class WorksheetCollection(BaseCollection):
|
|
|
69
70
|
self.session.post(path, json=params)
|
|
70
71
|
return self.get_by_project_id(project_id=project_id)
|
|
71
72
|
|
|
73
|
+
@validate_call
|
|
72
74
|
def setup_new_sheet_from_template(
|
|
73
75
|
self, *, project_id: ProjectId, sheet_template_id: str, sheet_name: str
|
|
74
76
|
) -> Worksheet:
|
|
@@ -94,6 +96,7 @@ class WorksheetCollection(BaseCollection):
|
|
|
94
96
|
self.session.post(path, json=payload, params=params)
|
|
95
97
|
return self.get_by_project_id(project_id=project_id)
|
|
96
98
|
|
|
99
|
+
@validate_call
|
|
97
100
|
def add_sheet(self, *, project_id: ProjectId, sheet_name: str) -> Worksheet:
|
|
98
101
|
"""Create a new blank sheet in the Worksheet with the specified name.
|
|
99
102
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from datetime import datetime
|
|
2
4
|
|
|
3
5
|
from pydantic import Field, PrivateAttr
|
|
@@ -21,7 +23,7 @@ class EntityLink(BaseAlbertModel):
|
|
|
21
23
|
name: str | None = Field(default=None, exclude=True)
|
|
22
24
|
category: str | None = Field(default=None, exclude=True)
|
|
23
25
|
|
|
24
|
-
def to_entity_link(self) ->
|
|
26
|
+
def to_entity_link(self) -> EntityLink:
|
|
25
27
|
# Convience method to return self, so you can call this method on objects that are already entity links
|
|
26
28
|
return self
|
|
27
29
|
|
|
@@ -43,7 +43,7 @@ class PGPatchDatum(PatchDatum):
|
|
|
43
43
|
|
|
44
44
|
class GeneralPatchDatum(PGPatchDatum):
|
|
45
45
|
colId: str | None = Field(default=None)
|
|
46
|
-
actions: list[PGPatchDatum | DTPatchDatum] | None = None
|
|
46
|
+
actions: list[PGPatchDatum | DTPatchDatum | PatchDatum] | None = None
|
|
47
47
|
operation: str | None = Field(default=None)
|
|
48
48
|
|
|
49
49
|
|
albert/resources/batch_data.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
|
|
3
5
|
from pydantic import Field
|
|
@@ -53,7 +55,7 @@ class BatchDataRow(BaseAlbertModel):
|
|
|
53
55
|
is_formula: bool | None = Field(default=None, alias="isFormula")
|
|
54
56
|
is_lot_parent: bool | None = Field(default=None, alias="isLotParent")
|
|
55
57
|
values: list[BatchDataValue] = Field(default_factory=list, alias="Values")
|
|
56
|
-
child_rows: list[
|
|
58
|
+
child_rows: list[BatchDataRow] = Field(default_factory=list, alias="ChildRows")
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
class BatchDataColumn(BaseAlbertModel):
|
|
@@ -67,7 +69,7 @@ class BatchDataColumn(BaseAlbertModel):
|
|
|
67
69
|
product_total: float | None = Field(default=None, alias="productTotal")
|
|
68
70
|
parent_id: str | None = Field(default=None, alias="parentId")
|
|
69
71
|
design_col_id: str | None = Field(default=None, alias="designColId")
|
|
70
|
-
lots: list[
|
|
72
|
+
lots: list[BatchDataColumn] = Field(default_factory=list, alias="Lots")
|
|
71
73
|
|
|
72
74
|
|
|
73
75
|
class BatchData(BaseResource):
|
albert/resources/cas.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
|
|
3
5
|
from pydantic import Field
|
|
@@ -53,7 +55,7 @@ class Cas(BaseResource):
|
|
|
53
55
|
metadata: dict[str, MetadataItem] = Field(alias="Metadata", default_factory=dict)
|
|
54
56
|
|
|
55
57
|
@classmethod
|
|
56
|
-
def from_string(cls, *, number: str) ->
|
|
58
|
+
def from_string(cls, *, number: str) -> Cas:
|
|
57
59
|
"""
|
|
58
60
|
Creates a Cas object from a string.
|
|
59
61
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
from typing import Annotated, Any, Literal
|
|
3
5
|
|
|
@@ -171,7 +173,7 @@ class CustomField(BaseResource):
|
|
|
171
173
|
api: CustomFieldAPI | None = Field(default=None)
|
|
172
174
|
|
|
173
175
|
@model_validator(mode="after")
|
|
174
|
-
def confirm_field_compatability(self) ->
|
|
176
|
+
def confirm_field_compatability(self) -> CustomField:
|
|
175
177
|
if self.field_type == FieldType.LIST and self.category is None:
|
|
176
178
|
raise ValueError("Category must be set for list fields")
|
|
177
179
|
return self
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Literal
|
|
2
6
|
|
|
3
7
|
from pydantic import AliasChoices, Field, model_validator
|
|
4
8
|
|
|
5
9
|
from albert.core.base import BaseAlbertModel
|
|
6
10
|
from albert.core.shared.enums import SecurityClass
|
|
7
|
-
from albert.core.shared.identifiers import DataColumnId, DataTemplateId
|
|
11
|
+
from albert.core.shared.identifiers import AttachmentId, DataColumnId, DataTemplateId
|
|
8
12
|
from albert.core.shared.models.base import (
|
|
9
13
|
AuditFields,
|
|
10
14
|
BaseResource,
|
|
@@ -15,7 +19,7 @@ from albert.core.shared.models.base import (
|
|
|
15
19
|
from albert.core.shared.types import MetadataItem, SerializeAsEntityLink
|
|
16
20
|
from albert.resources._mixins import HydrationMixin
|
|
17
21
|
from albert.resources.data_columns import DataColumn
|
|
18
|
-
from albert.resources.parameter_groups import ParameterValue, ValueValidation
|
|
22
|
+
from albert.resources.parameter_groups import DataType, ParameterValue, ValueValidation
|
|
19
23
|
from albert.resources.tagged_base import BaseTaggedResource
|
|
20
24
|
from albert.resources.units import Unit
|
|
21
25
|
from albert.resources.users import User
|
|
@@ -30,6 +34,11 @@ class CSVMapping(BaseAlbertModel):
|
|
|
30
34
|
)
|
|
31
35
|
|
|
32
36
|
|
|
37
|
+
class Axis(str, Enum):
|
|
38
|
+
X = "X"
|
|
39
|
+
Y = "Y"
|
|
40
|
+
|
|
41
|
+
|
|
33
42
|
class CurveDBMetadata(BaseAlbertModel):
|
|
34
43
|
table_name: str | None = Field(default=None, alias="tableName")
|
|
35
44
|
partition_key: str | None = Field(default=None, alias="partitionKey")
|
|
@@ -39,6 +48,9 @@ class StorageKeyReference(BaseAlbertModel):
|
|
|
39
48
|
rawfile: str | None = None
|
|
40
49
|
s3_input: str | None = Field(default=None, alias="s3Input")
|
|
41
50
|
s3_output: str | None = Field(default=None, alias="s3Output")
|
|
51
|
+
preview: str | None = None
|
|
52
|
+
thumb: str | None = None
|
|
53
|
+
original: str | None = None
|
|
42
54
|
|
|
43
55
|
|
|
44
56
|
class JobSummary(BaseAlbertModel):
|
|
@@ -46,11 +58,6 @@ class JobSummary(BaseAlbertModel):
|
|
|
46
58
|
state: str | None = None
|
|
47
59
|
|
|
48
60
|
|
|
49
|
-
class Axis(str, Enum):
|
|
50
|
-
X = "X"
|
|
51
|
-
Y = "Y"
|
|
52
|
-
|
|
53
|
-
|
|
54
61
|
class CurveDataEntityLink(EntityLinkWithName):
|
|
55
62
|
id: DataColumnId
|
|
56
63
|
axis: Axis | None = Field(default=None)
|
|
@@ -102,11 +109,6 @@ class DataColumnValue(BaseResource):
|
|
|
102
109
|
return self
|
|
103
110
|
|
|
104
111
|
|
|
105
|
-
class Axis(str, Enum):
|
|
106
|
-
X = "X"
|
|
107
|
-
Y = "Y"
|
|
108
|
-
|
|
109
|
-
|
|
110
112
|
class DataTemplate(BaseTaggedResource):
|
|
111
113
|
name: str
|
|
112
114
|
id: DataTemplateId | None = Field(None, alias="albertId")
|
|
@@ -131,6 +133,52 @@ class DataTemplate(BaseTaggedResource):
|
|
|
131
133
|
full_name: str | None = Field(default=None, alias="fullName", exclude=True, frozen=True)
|
|
132
134
|
|
|
133
135
|
|
|
136
|
+
class ImportMode(str, Enum):
|
|
137
|
+
SCRIPT = "SCRIPT"
|
|
138
|
+
CSV = "CSV"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class CurveExample(BaseAlbertModel):
|
|
142
|
+
"""
|
|
143
|
+
Curve example data for a data template column.
|
|
144
|
+
|
|
145
|
+
Attributes
|
|
146
|
+
----------
|
|
147
|
+
mode : ImportMode
|
|
148
|
+
``ImportMode.CSV`` ingests the CSV directly; ``ImportMode.SCRIPT`` runs the attached
|
|
149
|
+
script first (requires a script attachment on the column).
|
|
150
|
+
field_mapping : dict[str, str] | None
|
|
151
|
+
Optional header-to-curve-result mapping, e.g. ``{"visc": "Viscosity"}``. Overrides
|
|
152
|
+
auto-detected mappings.
|
|
153
|
+
file_path : str | Path | None
|
|
154
|
+
Local path to source CSV file.
|
|
155
|
+
attachment_id : AttachmentId | None
|
|
156
|
+
Existing attachment ID of source CSV file.
|
|
157
|
+
Provide exactly one source CSV (local path or existing attachment).
|
|
158
|
+
"""
|
|
159
|
+
|
|
160
|
+
type: Literal[DataType.CURVE] = DataType.CURVE
|
|
161
|
+
mode: ImportMode = ImportMode.CSV
|
|
162
|
+
field_mapping: dict[str, str] | None = None
|
|
163
|
+
file_path: str | Path | None = None
|
|
164
|
+
attachment_id: AttachmentId | None = None
|
|
165
|
+
|
|
166
|
+
@model_validator(mode="after")
|
|
167
|
+
def _require_curve_source(self) -> CurveExample:
|
|
168
|
+
if (self.file_path is None) == (self.attachment_id is None):
|
|
169
|
+
raise ValueError(
|
|
170
|
+
"Provide exactly one of file_path or attachment_id for curve examples."
|
|
171
|
+
)
|
|
172
|
+
return self
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class ImageExample(BaseAlbertModel):
|
|
176
|
+
"""Example data for an image data column."""
|
|
177
|
+
|
|
178
|
+
type: Literal[DataType.IMAGE] = DataType.IMAGE
|
|
179
|
+
file_path: str | Path
|
|
180
|
+
|
|
181
|
+
|
|
134
182
|
class DataTemplateSearchItemDataColumn(BaseAlbertModel):
|
|
135
183
|
id: str
|
|
136
184
|
name: str | None = None
|
albert/resources/inventory.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
@@ -104,7 +106,7 @@ class CasAmount(BaseAlbertModel):
|
|
|
104
106
|
)
|
|
105
107
|
|
|
106
108
|
@model_validator(mode="after")
|
|
107
|
-
def set_cas_attributes(self:
|
|
109
|
+
def set_cas_attributes(self: CasAmount) -> CasAmount:
|
|
108
110
|
"""Set attributes after model initialization from the Cas object, if provided."""
|
|
109
111
|
if self.cas is not None:
|
|
110
112
|
object.__setattr__(self, "id", self.cas.id)
|
|
@@ -132,7 +134,7 @@ class InventoryMinimum(BaseAlbertModel):
|
|
|
132
134
|
minimum: float = Field(ge=0, le=1000000000000000)
|
|
133
135
|
|
|
134
136
|
@model_validator(mode="after")
|
|
135
|
-
def check_id_or_location(self:
|
|
137
|
+
def check_id_or_location(self: InventoryMinimum) -> InventoryMinimum:
|
|
136
138
|
"""
|
|
137
139
|
Ensure that either an id or a location is provided.
|
|
138
140
|
"""
|
|
@@ -235,7 +237,7 @@ class InventoryItem(BaseTaggedResource):
|
|
|
235
237
|
return value
|
|
236
238
|
|
|
237
239
|
@model_validator(mode="after")
|
|
238
|
-
def set_unit_category(self) ->
|
|
240
|
+
def set_unit_category(self) -> InventoryItem:
|
|
239
241
|
"""Set unit category from category if not defined."""
|
|
240
242
|
if self.unit_category is None:
|
|
241
243
|
if self.category in [InventoryCategory.RAW_MATERIALS, InventoryCategory.FORMULAS]:
|
|
@@ -245,7 +247,7 @@ class InventoryItem(BaseTaggedResource):
|
|
|
245
247
|
return self
|
|
246
248
|
|
|
247
249
|
@model_validator(mode="after")
|
|
248
|
-
def validate_formula_fields(self) ->
|
|
250
|
+
def validate_formula_fields(self) -> InventoryItem:
|
|
249
251
|
"""Ensure required fields are present for formulas."""
|
|
250
252
|
if self.category == InventoryCategory.FORMULAS and not self.project_id and not self.id:
|
|
251
253
|
# Some legacy on platform formulas don't have a project_id so check if its already on platform
|
albert/resources/lists.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
|
|
3
5
|
from pydantic import Field, model_validator
|
|
@@ -34,7 +36,7 @@ class ListItem(BaseResource):
|
|
|
34
36
|
list_type: str | None = Field(default=None, alias="listType")
|
|
35
37
|
|
|
36
38
|
@model_validator(mode="after")
|
|
37
|
-
def validate_list_type(self) ->
|
|
39
|
+
def validate_list_type(self) -> ListItem:
|
|
38
40
|
if (
|
|
39
41
|
self.category == ListItemCategory.PROJECTS
|
|
40
42
|
and self.list_type is not None
|
albert/resources/notebooks.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import re
|
|
2
4
|
import uuid
|
|
3
5
|
from datetime import datetime
|
|
4
6
|
from enum import Enum
|
|
7
|
+
from pathlib import Path
|
|
5
8
|
from typing import Annotated, Any, Literal
|
|
6
9
|
|
|
7
10
|
from pandas import DataFrame
|
|
@@ -104,6 +107,7 @@ class AttachesContent(BaseAlbertModel):
|
|
|
104
107
|
namespace: str = Field(default="result")
|
|
105
108
|
file_key: str | None = Field(default=None, alias="fileKey")
|
|
106
109
|
format: str | None = Field(default=None, alias="mimeType")
|
|
110
|
+
file_path: str | Path | None = Field(default=None, exclude=True)
|
|
107
111
|
signed_url: str | None = Field(default=None, alias="signedURL", exclude=True, frozen=True)
|
|
108
112
|
|
|
109
113
|
|
|
@@ -120,6 +124,7 @@ class ImageContent(BaseAlbertModel):
|
|
|
120
124
|
with_border: bool = Field(default=False, alias="withBorder")
|
|
121
125
|
file_key: str | None = Field(default=None, alias="fileKey")
|
|
122
126
|
format: str | None = Field(default=None, alias="mimeType")
|
|
127
|
+
file_path: str | Path | None = Field(default=None, exclude=True)
|
|
123
128
|
signed_url: str | None = Field(default=None, alias="signedURL", exclude=True, frozen=True)
|
|
124
129
|
|
|
125
130
|
|
|
@@ -130,14 +135,14 @@ class ImageBlock(BaseBlock):
|
|
|
130
135
|
|
|
131
136
|
class KetcherContent(BaseAlbertModel):
|
|
132
137
|
synthesis_id: SynthesisId | None = Field(default=None, alias="synthesisId")
|
|
133
|
-
name: str | None = Field(default=None)
|
|
134
138
|
id: str | None = Field(default=None)
|
|
135
139
|
block_id: str | None = Field(default=None, alias="blockId")
|
|
136
|
-
data: str
|
|
140
|
+
data: str = Field(default=None, exclude=True)
|
|
137
141
|
file_key: str | None = Field(default=None, alias="fileKey")
|
|
138
|
-
s3_key: str | None = Field(default=None, alias="s3Key"
|
|
139
|
-
png: str | None = Field(default=None, exclude=True
|
|
140
|
-
|
|
142
|
+
s3_key: str | None = Field(default=None, alias="s3Key")
|
|
143
|
+
png: str | None = Field(default=None, exclude=True)
|
|
144
|
+
state_type: str = Field(default="project", alias="stateType")
|
|
145
|
+
smiles: str | None = Field(default=None, alias="smiles", exclude=True)
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
class KetcherBlock(BaseBlock):
|
|
@@ -179,7 +184,7 @@ class TableBlock(BaseBlock):
|
|
|
179
184
|
|
|
180
185
|
class NotebookListItem(BaseAlbertModel):
|
|
181
186
|
content: str | None
|
|
182
|
-
items: list[
|
|
187
|
+
items: list[NotebookListItem] = Field(default_factory=list)
|
|
183
188
|
|
|
184
189
|
|
|
185
190
|
class BulletedListContent(BaseAlbertModel):
|
|
@@ -264,7 +269,7 @@ class PutBlockDatum(BaseAlbertModel):
|
|
|
264
269
|
previous_block_id: str | None = Field(default=None, alias="previousBlockId")
|
|
265
270
|
|
|
266
271
|
@model_validator(mode="after")
|
|
267
|
-
def content_matches_type(self) ->
|
|
272
|
+
def content_matches_type(self) -> PutBlockDatum:
|
|
268
273
|
if self.content is None:
|
|
269
274
|
return self # skip check if there's no content
|
|
270
275
|
|