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.
Files changed (36) hide show
  1. albert/__init__.py +1 -1
  2. albert/client.py +5 -0
  3. albert/collections/custom_templates.py +3 -0
  4. albert/collections/data_templates.py +118 -264
  5. albert/collections/entity_types.py +19 -3
  6. albert/collections/inventory.py +1 -1
  7. albert/collections/notebooks.py +154 -26
  8. albert/collections/parameters.py +1 -0
  9. albert/collections/property_data.py +384 -280
  10. albert/collections/reports.py +4 -0
  11. albert/collections/synthesis.py +292 -0
  12. albert/collections/tasks.py +2 -1
  13. albert/collections/worksheets.py +3 -0
  14. albert/core/shared/models/base.py +3 -1
  15. albert/core/shared/models/patch.py +1 -1
  16. albert/resources/batch_data.py +4 -2
  17. albert/resources/cas.py +3 -1
  18. albert/resources/custom_fields.py +3 -1
  19. albert/resources/data_templates.py +60 -12
  20. albert/resources/inventory.py +6 -4
  21. albert/resources/lists.py +3 -1
  22. albert/resources/notebooks.py +12 -7
  23. albert/resources/parameter_groups.py +3 -1
  24. albert/resources/property_data.py +64 -5
  25. albert/resources/sheets.py +16 -14
  26. albert/resources/synthesis.py +61 -0
  27. albert/resources/tags.py +3 -1
  28. albert/resources/tasks.py +4 -7
  29. albert/resources/workflows.py +4 -2
  30. albert/utils/data_template.py +392 -37
  31. albert/utils/property_data.py +638 -0
  32. albert/utils/tasks.py +3 -3
  33. {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/METADATA +1 -1
  34. {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/RECORD +36 -33
  35. {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/WHEEL +0 -0
  36. {albert-1.10.0rc2.dist-info → albert-1.11.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
  from typing import Any
3
5
 
@@ -123,7 +125,7 @@ class ParameterValue(BaseAlbertModel):
123
125
  return value
124
126
 
125
127
  @model_validator(mode="after")
126
- def set_parameter_fields(self) -> "ParameterValue":
128
+ def set_parameter_fields(self) -> ParameterValue:
127
129
  if self.parameter is None and self.id is None:
128
130
  raise ValueError("Please provide either an id or an parameter object.")
129
131
 
@@ -1,4 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
4
+ from pathlib import Path
2
5
  from typing import Any, Literal
3
6
 
4
7
  import pandas as pd
@@ -20,7 +23,12 @@ from albert.core.shared.identifiers import (
20
23
  from albert.core.shared.models.base import BaseResource
21
24
  from albert.core.shared.models.patch import PatchDatum
22
25
  from albert.core.shared.types import SerializeAsEntityLink
23
- from albert.resources.data_templates import DataTemplate
26
+ from albert.resources.data_templates import (
27
+ CurveDBMetadata,
28
+ DataTemplate,
29
+ ImportMode,
30
+ StorageKeyReference,
31
+ )
24
32
  from albert.resources.lots import Lot
25
33
  from albert.resources.units import Unit
26
34
  from albert.resources.workflows import Workflow
@@ -173,7 +181,7 @@ class BulkPropertyData(BaseAlbertModel):
173
181
  )
174
182
 
175
183
  @classmethod
176
- def from_dataframe(cls, df: pd.DataFrame) -> "BulkPropertyData":
184
+ def from_dataframe(cls, df: pd.DataFrame) -> BulkPropertyData:
177
185
  """
178
186
  Converts a DataFrame to a BulkPropertyData object.
179
187
 
@@ -205,6 +213,51 @@ class TaskPropertyValue(BaseAlbertModel):
205
213
  value: str | None = Field(default=None)
206
214
 
207
215
 
216
+ class ImagePropertyValue(BaseAlbertModel):
217
+ """
218
+ Image property value input.
219
+
220
+ Attributes
221
+ ----------
222
+ file_path : str | Path
223
+ Local path to the image file to upload.
224
+ """
225
+
226
+ file_path: str | Path
227
+
228
+
229
+ class CurvePropertyValue(BaseAlbertModel):
230
+ """
231
+ Curve property value input.
232
+
233
+ Attributes
234
+ ----------
235
+ file_path : str | Path
236
+ Local path to the CSV file containing curve data.
237
+ mode : ImportMode
238
+ Import mode for the curve data.
239
+ field_mapping : dict[str, str] | None
240
+ Optional mapping from CSV headers to curve result identifiers.
241
+ """
242
+
243
+ file_path: str | Path
244
+ mode: ImportMode = ImportMode.CSV
245
+ field_mapping: dict[str, str] | None = None
246
+
247
+
248
+ class ImagePropertyValuePayload(BaseAlbertModel):
249
+ file_name: str = Field(alias="fileName")
250
+ s3_key: StorageKeyReference = Field(alias="s3Key")
251
+
252
+
253
+ class CurvePropertyValuePayload(BaseAlbertModel):
254
+ file_name: str = Field(alias="fileName")
255
+ s3_key: StorageKeyReference = Field(alias="s3Key")
256
+ job_id: str = Field(alias="jobId")
257
+ csv_mapping: dict[str, str] = Field(alias="csvMapping")
258
+ athena: CurveDBMetadata
259
+
260
+
208
261
  class TaskDataColumn(BaseAlbertModel):
209
262
  data_column_id: DataColumnId = Field(alias="id")
210
263
  column_sequence: str | None = Field(default=None, alias="columnId")
@@ -263,9 +316,15 @@ class TaskPropertyCreate(BaseResource):
263
316
  description="The interval combination, which can be found using `Workflow.get_interval_id`.",
264
317
  )
265
318
  data_column: TaskDataColumn = Field(
266
- ..., alias="DataColumns", description="The data column associated with the task property."
319
+ alias="DataColumns", description="The data column associated with the task property."
320
+ )
321
+ value: str | ImagePropertyValue | CurvePropertyValue | None = Field(
322
+ default=None,
323
+ description=(
324
+ "The value of the task property. Use ImagePropertyValue for image data columns or "
325
+ "CurvePropertyValue for curve data columns."
326
+ ),
267
327
  )
268
- value: str | None = Field(default=None, description="The value of the task property.")
269
328
  trial_number: int = Field(
270
329
  alias="trialNo",
271
330
  default=None,
@@ -283,7 +342,7 @@ class TaskPropertyCreate(BaseResource):
283
342
  )
284
343
 
285
344
  @model_validator(mode="after")
286
- def set_visible_trial_number(self) -> "TaskPropertyCreate":
345
+ def set_visible_trial_number(self) -> TaskPropertyCreate:
287
346
  if self.visible_trial_number is None:
288
347
  if self.trial_number is not None:
289
348
  self.visible_trial_number = self.trial_number
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from enum import Enum
2
4
  from typing import Any, ForwardRef, TypedDict, Union
3
5
 
@@ -165,7 +167,7 @@ class Component(BaseResource):
165
167
  _cell: Cell = None # read only property set on registrstion
166
168
 
167
169
  @model_validator(mode="after")
168
- def _ensure_inventory_reference(self: "Component") -> "Component":
170
+ def _ensure_inventory_reference(self: Component) -> Component:
169
171
  item = self.inventory_item
170
172
  if item is None and self.inventory_id is None:
171
173
  raise ValueError("Component requires either 'inventory_item' or 'inventory_id'.")
@@ -217,9 +219,9 @@ class Design(BaseSessionResource):
217
219
  id: str = Field(alias="albertId")
218
220
  design_type: DesignType = Field(alias="designType")
219
221
  _grid: pd.DataFrame | None = PrivateAttr(default=None)
220
- _rows: list["Row"] | None = PrivateAttr(default=None)
221
- _columns: list["Column"] | None = PrivateAttr(default=None)
222
- _sheet: Union["Sheet", None] = PrivateAttr(default=None) # noqa
222
+ _rows: list[Row] | None = PrivateAttr(default=None)
223
+ _columns: list[Column] | None = PrivateAttr(default=None)
224
+ _sheet: Union[Sheet, None] = PrivateAttr(default=None) # noqa
223
225
  _leftmost_pinned_column: str | None = PrivateAttr(default=None)
224
226
 
225
227
  def _grid_to_cell_df(self, *, grid_response):
@@ -284,7 +286,7 @@ class Design(BaseSessionResource):
284
286
  self._grid = self._get_grid()
285
287
  return self._grid
286
288
 
287
- def _get_columns(self, *, grid_response: dict) -> list["Column"]:
289
+ def _get_columns(self, *, grid_response: dict) -> list[Column]:
288
290
  """
289
291
  Normalizes inventory IDs (always prefixed "INV") and—for the
290
292
  "Inventory ID" header—falls back to the row's top-level `id`
@@ -358,7 +360,7 @@ class Design(BaseSessionResource):
358
360
 
359
361
  return cols
360
362
 
361
- def _get_rows(self, *, grid_response: dict) -> list["Row"]:
363
+ def _get_rows(self, *, grid_response: dict) -> list[Row]:
362
364
  """
363
365
  Parse the /grid response into a list of Row models.
364
366
 
@@ -413,13 +415,13 @@ class Design(BaseSessionResource):
413
415
  return self._grid_to_cell_df(grid_response=resp_json)
414
416
 
415
417
  @property
416
- def columns(self) -> list["Column"]:
418
+ def columns(self) -> list[Column]:
417
419
  if not self._columns:
418
420
  self._get_grid()
419
421
  return self._columns
420
422
 
421
423
  @property
422
- def rows(self) -> list["Row"]:
424
+ def rows(self) -> list[Row]:
423
425
  if not self._rows:
424
426
  self._get_grid()
425
427
  return self._rows
@@ -494,7 +496,7 @@ class Sheet(BaseSessionResource): # noqa:F811
494
496
  return self._process_design
495
497
 
496
498
  @model_validator(mode="after")
497
- def set_sheet_fields(self: "Sheet") -> "Sheet":
499
+ def set_sheet_fields(self: Sheet) -> Sheet:
498
500
  for _idx, d in enumerate(self.designs): # Instead of creating a new list
499
501
  d._sheet = self # Set the reference to the sheet
500
502
  if d.design_type == DesignType.APPS:
@@ -542,12 +544,12 @@ class Sheet(BaseSessionResource): # noqa:F811
542
544
  return self._leftmost_pinned_column
543
545
 
544
546
  @property
545
- def columns(self) -> list["Column"]:
547
+ def columns(self) -> list[Column]:
546
548
  """The columns of a given sheet"""
547
549
  return self.product_design.columns
548
550
 
549
551
  @property
550
- def rows(self) -> list["Row"]:
552
+ def rows(self) -> list[Row]:
551
553
  """The rows of a given sheet"""
552
554
  rows = []
553
555
  for d in self.designs:
@@ -607,7 +609,7 @@ class Sheet(BaseSessionResource): # noqa:F811
607
609
  new_dicts.append(this_dict)
608
610
  return new_dicts
609
611
 
610
- def _clear_formulation_from_column(self, *, column: "Column"):
612
+ def _clear_formulation_from_column(self, *, column: Column):
611
613
  cleared_cells = []
612
614
  for cell in column.cells:
613
615
  if cell.type == CellType.INVENTORY:
@@ -674,7 +676,7 @@ class Sheet(BaseSessionResource): # noqa:F811
674
676
  inventory_id: InventoryId,
675
677
  existing_cells,
676
678
  enforce_order,
677
- product_rows: list["Row"],
679
+ product_rows: list[Row],
678
680
  ):
679
681
  sheet_inv_id = inventory_id
680
682
  matching_rows = [row for row in product_rows if row.inventory_id == sheet_inv_id]
@@ -736,7 +738,7 @@ class Sheet(BaseSessionResource): # noqa:F811
736
738
  *,
737
739
  formulation_names: list[str],
738
740
  starting_position: dict | None = None,
739
- ) -> list["Column"]:
741
+ ) -> list[Column]:
740
742
  if starting_position is None:
741
743
  starting_position = {
742
744
  "reference_id": self.leftmost_pinned_column,
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from pydantic import Field
6
+
7
+ from albert.core.base import BaseAlbertModel
8
+ from albert.core.shared.identifiers import NotebookId, SynthesisId
9
+ from albert.core.shared.models.base import AuditFields
10
+
11
+
12
+ class ColumnDescriptor(BaseAlbertModel):
13
+ id: str
14
+ label: str | None = None
15
+ category: str | None = None
16
+ default: Any | None = None
17
+ type: str | None = None
18
+
19
+
20
+ class ColumnSequence(BaseAlbertModel):
21
+ reactants: list[ColumnDescriptor] = Field(default_factory=list)
22
+ products: list[ColumnDescriptor] = Field(default_factory=list)
23
+
24
+
25
+ class RowSequence(BaseAlbertModel):
26
+ reactants: list[str] = Field(default_factory=list)
27
+ products: list[str] = Field(default_factory=list)
28
+
29
+
30
+ class ReactionParticipant(BaseAlbertModel):
31
+ row_id: str = Field(alias="rowId")
32
+ smiles: str | None = None
33
+ values: dict[str, Any] | None = None
34
+ type: str | None = None
35
+ limiting_reagent: str | bool | None = Field(default=None, alias="limitingReagent")
36
+
37
+
38
+ class Synthesis(BaseAlbertModel):
39
+ id: SynthesisId = Field(alias="albertId")
40
+ parent_id: NotebookId | str | None = Field(default=None, alias="parentId")
41
+ name: str | None = None
42
+ status: str | None = None
43
+ block_id: str | None = Field(default=None, alias="blockId")
44
+ inventory_id: str | None = Field(default=None, alias="inventoryId")
45
+ hide_reaction_worksheet: str | bool | None = Field(default=None, alias="hideReactionWorksheet")
46
+ s3_key: str | None = Field(default=None, alias="s3Key")
47
+ canvas_data: dict[str, Any] | None = Field(default=None, alias="canvasData")
48
+ smiles: list[str | None] = Field(default_factory=list)
49
+ reactants: list[ReactionParticipant] = Field(default_factory=list)
50
+ products: list[ReactionParticipant] = Field(default_factory=list)
51
+ column_sequence: ColumnSequence | None = Field(default=None, alias="columnSequence")
52
+ row_sequence: RowSequence | None = Field(default=None, alias="rowSequence")
53
+ created: AuditFields | None = Field(default=None, alias="Created")
54
+ updated: AuditFields | None = Field(default=None, alias="Updated")
55
+
56
+
57
+ class ReactantValues(BaseAlbertModel):
58
+ mass: float | None = None
59
+ moles: float | None = None
60
+ eq: float | None = None
61
+ concentration: float | int | None = None
albert/resources/tags.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
 
@@ -45,7 +47,7 @@ class Tag(BaseResource):
45
47
  )
46
48
 
47
49
  @classmethod
48
- def from_string(cls, tag: str) -> "Tag":
50
+ def from_string(cls, tag: str) -> Tag:
49
51
  """
50
52
  Creates a Tag object from a string.
51
53
 
albert/resources/tasks.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from datetime import datetime
2
4
  from enum import Enum
3
5
  from typing import Annotated, Any, Literal
@@ -450,11 +452,6 @@ class TaskSearchItem(BaseAlbertModel, HydrationMixin[BaseTask]):
450
452
  parent_batch_status: str | None = Field(default=None, alias="parentBatchStatus")
451
453
 
452
454
 
453
- class ImportMode(str, Enum):
454
- SCRIPT = "SCRIPT"
455
- CSV = "CSV"
456
-
457
-
458
455
  # TODO: refactor TaskMetadata models to reuse existing models where possible
459
456
  class TaskMetadataDataTemplate(BaseAlbertModel):
460
457
  """Metadata summary describing a data template on the task."""
@@ -581,7 +578,7 @@ class CsvTableInput(BaseAlbertModel):
581
578
 
582
579
  script_s3_url: str = Field(alias="scriptS3URL")
583
580
  data_s3_url: str = Field(alias="dataS3URL")
584
- task_metadata: "TaskMetadata" = Field(alias="TaskMetadata")
581
+ task_metadata: TaskMetadata = Field(alias="TaskMetadata")
585
582
 
586
583
 
587
584
  class CsvTableResponseItem(BaseAlbertModel):
@@ -596,7 +593,7 @@ class CsvCurveInput(BaseAlbertModel):
596
593
  script_s3_url: str = Field(alias="scriptS3URL")
597
594
  data_s3_url: str = Field(alias="dataS3URL")
598
595
  result_s3_url: str = Field(alias="resultS3URL")
599
- task_metadata: "TaskMetadata" = Field(alias="TaskMetadata")
596
+ task_metadata: TaskMetadata = Field(alias="TaskMetadata")
600
597
 
601
598
 
602
599
  class CsvCurveResponse(BaseAlbertModel):
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from collections.abc import Mapping
2
4
  from typing import Any
3
5
 
@@ -60,7 +62,7 @@ class Interval(BaseAlbertModel):
60
62
  row_id: RowId | None = Field(default=None, alias="rowId", exclude=True)
61
63
 
62
64
  @model_validator(mode="after")
63
- def validate_interval(self) -> "Interval":
65
+ def validate_interval(self) -> Interval:
64
66
  if not self.value:
65
67
  raise ValueError("Interval: 'value' is required.")
66
68
  if self.unit and not getattr(self.unit, "id", None):
@@ -133,7 +135,7 @@ class ParameterSetpoint(BaseAlbertModel):
133
135
  sequence: str | None = Field(default=None, alias="prgPrmRowId")
134
136
 
135
137
  @model_validator(mode="after")
136
- def validate_shape(self) -> "ParameterSetpoint":
138
+ def validate_shape(self) -> ParameterSetpoint:
137
139
  def has_id(obj: Any) -> bool:
138
140
  if isinstance(obj, Mapping):
139
141
  return bool(obj.get("id"))