albert 1.10.0rc2__py3-none-any.whl → 1.11.1__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/entity_types.py +15 -4
- 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.1.dist-info}/METADATA +1 -1
- {albert-1.10.0rc2.dist-info → albert-1.11.1.dist-info}/RECORD +37 -34
- {albert-1.10.0rc2.dist-info → albert-1.11.1.dist-info}/WHEEL +0 -0
- {albert-1.10.0rc2.dist-info → albert-1.11.1.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/entity_types.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
from enum import Enum
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
4
|
-
from pydantic import Field
|
|
6
|
+
from pydantic import Field, model_validator
|
|
5
7
|
|
|
6
8
|
from albert.core.shared.identifiers import CustomFieldId, EntityTypeId, RuleId
|
|
7
9
|
from albert.core.shared.models.base import BaseAlbertModel, BaseResource, EntityLink
|
|
@@ -173,8 +175,8 @@ class EntityType(BaseResource):
|
|
|
173
175
|
----------
|
|
174
176
|
id : EntityTypeId
|
|
175
177
|
The unique identifier for the entity type.
|
|
176
|
-
category : EntityCategory
|
|
177
|
-
The category the entity type belongs to.
|
|
178
|
+
category : EntityCategory | None
|
|
179
|
+
The category the entity type belongs to. Required for tasks and inventories.
|
|
178
180
|
custom_category : str | None, optional
|
|
179
181
|
A custom category name for the entity type.
|
|
180
182
|
label : str
|
|
@@ -194,7 +196,7 @@ class EntityType(BaseResource):
|
|
|
194
196
|
"""
|
|
195
197
|
|
|
196
198
|
id: EntityTypeId | None = Field(alias="albertId", default=None)
|
|
197
|
-
category: EntityCategory
|
|
199
|
+
category: EntityCategory | None = None
|
|
198
200
|
custom_category: str | None = Field(
|
|
199
201
|
default=None, max_length=100, min_length=1, alias="customCategory"
|
|
200
202
|
)
|
|
@@ -215,6 +217,15 @@ class EntityType(BaseResource):
|
|
|
215
217
|
alias="searchQueryString", default=None
|
|
216
218
|
)
|
|
217
219
|
|
|
220
|
+
@model_validator(mode="after")
|
|
221
|
+
def validate_category(self) -> EntityType:
|
|
222
|
+
if (
|
|
223
|
+
self.service in {EntityServiceType.TASKS, EntityServiceType.INVENTORIES}
|
|
224
|
+
and self.category is None
|
|
225
|
+
):
|
|
226
|
+
raise ValueError("category is required for tasks and inventories entity types.")
|
|
227
|
+
return self
|
|
228
|
+
|
|
218
229
|
|
|
219
230
|
class EntityTypeOptionType(str, Enum):
|
|
220
231
|
"""Types of options that can be used in entity type fields.
|
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
|