albert 1.9.1__py3-none-any.whl → 1.9.3__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 CHANGED
@@ -4,4 +4,4 @@ from albert.core.auth.sso import AlbertSSOClient
4
4
 
5
5
  __all__ = ["Albert", "AlbertClientCredentials", "AlbertSSOClient"]
6
6
 
7
- __version__ = "1.9.1"
7
+ __version__ = "1.9.3"
@@ -7,10 +7,11 @@ from albert.collections.base import BaseCollection
7
7
  from albert.core.logging import logger
8
8
  from albert.core.pagination import AlbertPaginator
9
9
  from albert.core.session import AlbertSession
10
- from albert.core.shared.enums import PaginationMode
11
- from albert.core.shared.identifiers import InventoryId, LotId
10
+ from albert.core.shared.enums import OrderBy, PaginationMode
11
+ from albert.core.shared.identifiers import InventoryId, LotId, TaskId
12
12
  from albert.core.shared.models.patch import PatchDatum, PatchOperation, PatchPayload
13
- from albert.resources.lots import Lot
13
+ from albert.resources.inventory import InventoryCategory
14
+ from albert.resources.lots import Lot, LotSearchItem
14
15
 
15
16
  # 14 decimal places for inventory on hand delta calculations
16
17
  DECIMAL_DELTA_QUANTIZE = Decimal("0.00000000000000")
@@ -120,6 +121,120 @@ class LotCollection(BaseCollection):
120
121
  url = f"{self.base_path}?id={id}"
121
122
  self.session.delete(url)
122
123
 
124
+ @validate_call
125
+ def search(
126
+ self,
127
+ *,
128
+ text: str | None = None,
129
+ inventory_id: InventoryId | list[InventoryId] | None = None,
130
+ location_id: str | list[str] | None = None,
131
+ storage_location_id: str | list[str] | None = None,
132
+ task_id: TaskId | list[TaskId] | None = None,
133
+ category: InventoryCategory | str | list[InventoryCategory | str] | None = None,
134
+ external_barcode_id: str | list[str] | None = None,
135
+ search_field: str | list[str] | None = None,
136
+ source_field: str | list[str] | None = None,
137
+ additional_field: str | list[str] | None = None,
138
+ is_drop_down: bool | None = None,
139
+ order_by: OrderBy = OrderBy.DESCENDING,
140
+ sort_by: str | None = None,
141
+ offset: int | None = None,
142
+ max_items: int | None = None,
143
+ ) -> Iterator[LotSearchItem]:
144
+ """
145
+ Search for Lot records matching the provided filters.
146
+
147
+ ⚠️ This method returns partial (unhydrated) entities to optimize performance.
148
+ To retrieve fully detailed entities, use :meth:`get_all` instead.
149
+
150
+ Parameters
151
+ ----------
152
+ text : str, optional
153
+ Free-text query matched against lot fields.
154
+ inventory_id : InventoryId or list[InventoryId], optional
155
+ Filter by parent inventory IDs.
156
+ location_id : str or list[str], optional
157
+ Filter by specific location IDs.
158
+ storage_location_id : str or list[str], optional
159
+ Filter by storage location IDs.
160
+ task_id : TaskId or list[TaskId], optional
161
+ Filter by source task IDs.
162
+ category : InventoryCategory or list[str], optional
163
+ Filter by parent inventory categories.
164
+ external_barcode_id : str or list[str], optional
165
+ Filter by external barcode IDs.
166
+ search_field : str or list[str], optional
167
+ Restrict the fields the `text` query searches.
168
+ source_field : str or list[str], optional
169
+ Restrict which fields are returned in the response.
170
+ additional_field : str or list[str], optional
171
+ Request additional columns from the search index.
172
+ is_drop_down : bool, optional
173
+ Use dropdown sanitization for the search text when True.
174
+ order_by : OrderBy, optional
175
+ Sort order for the results, default DESCENDING.
176
+ sort_by : str, optional
177
+ Attribute to sort by.
178
+ offset : int, optional
179
+ Pagination offset to start from.
180
+ max_items : int, optional
181
+ Maximum number of items to return in total. If None, fetches all available items.
182
+
183
+ Returns
184
+ -------
185
+ Iterator[LotSearchItem]
186
+ An iterator of matching partial (unhydrated) lot entities.
187
+ """
188
+
189
+ search_text = text if (text is None or len(text) < 50) else text[:50]
190
+
191
+ def _ensure_list(value):
192
+ if value is None:
193
+ return None
194
+ if isinstance(value, list | tuple | set):
195
+ return list(value)
196
+ return [value]
197
+
198
+ def _format_categories(value):
199
+ raw = _ensure_list(value)
200
+ if raw is None:
201
+ return None
202
+ formatted: list[str] = []
203
+ for category in raw:
204
+ formatted.append(
205
+ category.value if isinstance(category, InventoryCategory) else category
206
+ )
207
+ return formatted
208
+
209
+ params = {
210
+ "offset": offset,
211
+ "order": order_by.value,
212
+ "text": search_text,
213
+ "sortBy": sort_by,
214
+ "isDropDown": is_drop_down,
215
+ "inventoryId": _ensure_list(inventory_id),
216
+ "locationId": _ensure_list(location_id),
217
+ "storageLocationId": _ensure_list(storage_location_id),
218
+ "taskId": _ensure_list(task_id),
219
+ "category": _format_categories(category),
220
+ "externalBarcodeId": _ensure_list(external_barcode_id),
221
+ "searchField": _ensure_list(search_field),
222
+ "sourceField": _ensure_list(source_field),
223
+ "additionalField": _ensure_list(additional_field),
224
+ }
225
+ params = {key: value for key, value in params.items() if value is not None}
226
+
227
+ return AlbertPaginator(
228
+ mode=PaginationMode.OFFSET,
229
+ path=f"{self.base_path}/search",
230
+ session=self.session,
231
+ params=params,
232
+ max_items=max_items,
233
+ deserialize=lambda items: [
234
+ LotSearchItem(**item)._bind_collection(self) for item in items
235
+ ],
236
+ )
237
+
123
238
  @validate_call
124
239
  def get_all(
125
240
  self,
albert/resources/lots.py CHANGED
@@ -3,9 +3,11 @@ from typing import Any
3
3
 
4
4
  from pydantic import Field, NonNegativeFloat, field_serializer, field_validator
5
5
 
6
+ from albert.core.base import BaseAlbertModel
6
7
  from albert.core.shared.identifiers import InventoryId, LotId
7
8
  from albert.core.shared.models.base import BaseResource
8
9
  from albert.core.shared.types import MetadataItem, SerializeAsEntityLink
10
+ from albert.resources._mixins import HydrationMixin
9
11
  from albert.resources.inventory import InventoryCategory
10
12
  from albert.resources.locations import Location
11
13
  from albert.resources.storage_locations import StorageLocation
@@ -143,3 +145,18 @@ class Lot(BaseResource):
143
145
  @field_serializer("inventory_on_hand", return_type=str)
144
146
  def serialize_inventory_on_hand(self, inventory_on_hand: NonNegativeFloat):
145
147
  return self._format_decimal(inventory_on_hand)
148
+
149
+
150
+ class LotSearchItem(BaseAlbertModel, HydrationMixin[Lot]):
151
+ """Lightweight representation of a Lot returned from search()."""
152
+
153
+ id: LotId = Field(alias="albertId")
154
+ inventory_id: InventoryId | None = Field(default=None, alias="parentId")
155
+ parent_name: str | None = Field(default=None, alias="parentName")
156
+ parent_unit: str | None = Field(default=None, alias="parentUnit")
157
+ parent_category: InventoryCategory | None = Field(default=None, alias="parentIdCategory")
158
+ task_id: str | None = Field(default=None, alias="taskId")
159
+ barcode_id: str | None = Field(default=None, alias="barcodeId")
160
+ expiration_date: str | None = Field(default=None, alias="expirationDate")
161
+ manufacturer_lot_number: str | None = Field(default=None, alias="manufacturerLotNumber")
162
+ lot_number: str | None = Field(default=None, alias="number")
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Any, ForwardRef, Union
2
+ from typing import Any, ForwardRef, TypedDict, Union
3
3
 
4
4
  import pandas as pd
5
5
  from pydantic import Field, PrivateAttr, field_validator, model_validator, validate_call
@@ -7,6 +7,7 @@ from pydantic import Field, PrivateAttr, field_validator, model_validator, valid
7
7
  from albert.core.base import BaseAlbertModel
8
8
  from albert.core.shared.identifiers import InventoryId
9
9
  from albert.core.shared.models.base import BaseResource, BaseSessionResource
10
+ from albert.core.shared.models.patch import PatchDatum
10
11
  from albert.exceptions import AlbertException
11
12
  from albert.resources.inventory import InventoryItem
12
13
 
@@ -15,6 +16,18 @@ Row = ForwardRef("Row")
15
16
  Column = ForwardRef("Column")
16
17
  Sheet = ForwardRef("Sheet")
17
18
 
19
+ CellAttributeValue = str | float | int | dict[str, Any] | list[Any] | None
20
+
21
+
22
+ class CellChangeId(TypedDict):
23
+ rowId: str
24
+ colId: str
25
+
26
+
27
+ class CellChangePayload(TypedDict):
28
+ Id: CellChangeId
29
+ data: list[PatchDatum]
30
+
18
31
 
19
32
  class CellColor(str, Enum):
20
33
  """The allowed colors for a cell"""
@@ -611,6 +624,7 @@ class Sheet(BaseSessionResource): # noqa:F811
611
624
  enforce_order: bool = False,
612
625
  clear: bool = True,
613
626
  ) -> Column:
627
+ all_cells: list[Cell] = []
614
628
  existing_formulation_names = [x.name for x in self.columns]
615
629
  if clear and formulation_name in existing_formulation_names:
616
630
  # get the existing column and clear it out to put the new formulation in
@@ -618,10 +632,10 @@ class Sheet(BaseSessionResource): # noqa:F811
618
632
  self._clear_formulation_from_column(column=col)
619
633
  else:
620
634
  col = self.add_formulation_columns(formulation_names=[formulation_name])[0]
621
- col_id = col.column_id
635
+ column_id = col.column_id
622
636
 
623
- all_cells = []
624
637
  self.grid = None # reset the grid for saftey
638
+ product_rows = list(self.product_design.rows)
625
639
 
626
640
  for component in components:
627
641
  component_inventory_id = component.inventory_item_id
@@ -629,6 +643,7 @@ class Sheet(BaseSessionResource): # noqa:F811
629
643
  inventory_id=component_inventory_id,
630
644
  existing_cells=all_cells,
631
645
  enforce_order=enforce_order,
646
+ product_rows=product_rows,
632
647
  )
633
648
  if row_id is None:
634
649
  raise AlbertException(f"No Component with id {component_inventory_id}")
@@ -637,7 +652,7 @@ class Sheet(BaseSessionResource): # noqa:F811
637
652
  min_value = str(component.min_value) if component.min_value is not None else None
638
653
  max_value = str(component.max_value) if component.max_value is not None else None
639
654
  this_cell = Cell(
640
- column_id=col_id,
655
+ column_id=column_id,
641
656
  row_id=row_id,
642
657
  value=value,
643
658
  calculation="",
@@ -651,48 +666,70 @@ class Sheet(BaseSessionResource): # noqa:F811
651
666
  all_cells.append(this_cell)
652
667
 
653
668
  self.update_cells(cells=all_cells)
654
- return self.get_column(column_id=col_id)
669
+ return self.get_column(column_id=column_id)
655
670
 
656
671
  def _get_row_id_for_component(
657
- self, *, inventory_id: InventoryId, existing_cells, enforce_order
672
+ self,
673
+ *,
674
+ inventory_id: InventoryId,
675
+ existing_cells,
676
+ enforce_order,
677
+ product_rows: list["Row"],
658
678
  ):
659
- self.grid = None
660
-
661
- # within a sheet, the "INV" prefix is dropped
662
679
  sheet_inv_id = inventory_id
663
- matching_rows = [x for x in self.product_design.rows if x.inventory_id == sheet_inv_id]
680
+ matching_rows = [row for row in product_rows if row.inventory_id == sheet_inv_id]
681
+
682
+ used_row_ids = [cell.row_id for cell in existing_cells]
664
683
 
665
- used_row_ids = [x.row_id for x in existing_cells]
684
+ existing_inv_order: list[str] = []
685
+ index_last_row = 0
666
686
  if enforce_order:
667
687
  existing_inv_order = [
668
- x.row_id for x in self.product_design.rows if x.inventory_id is not None
688
+ row.row_id for row in product_rows if row.inventory_id is not None
669
689
  ]
670
- index_last_row = 0
671
690
  for row_id in used_row_ids:
672
691
  if row_id in existing_inv_order:
673
692
  this_row_index = existing_inv_order.index(row_id)
674
693
  if this_row_index > index_last_row:
675
694
  index_last_row = this_row_index
676
- for r in matching_rows:
677
- if r.row_id not in used_row_ids:
678
- if enforce_order:
679
- if existing_inv_order.index(r.row_id) >= index_last_row:
680
- return r.row_id
681
- else:
682
- continue
683
- else:
684
- return r.row_id
685
- # Otherwise I need to add a new row
695
+
696
+ for row in matching_rows:
697
+ if row.row_id in used_row_ids:
698
+ continue
699
+ if not enforce_order:
700
+ return row.row_id
701
+
702
+ if row.row_id in existing_inv_order:
703
+ if existing_inv_order.index(row.row_id) >= index_last_row:
704
+ return row.row_id
705
+ continue
706
+
686
707
  if enforce_order:
687
- return self.add_inventory_row(
688
- inventory_id=inventory_id,
689
- position={
690
- "reference_id": existing_inv_order[index_last_row],
691
- "position": "below",
692
- },
693
- ).row_id
694
- else:
695
- return self.add_inventory_row(inventory_id=inventory_id).row_id
708
+ if existing_inv_order:
709
+ reference_row_id = existing_inv_order[index_last_row]
710
+ new_row = self.add_inventory_row(
711
+ inventory_id=inventory_id,
712
+ position={"reference_id": reference_row_id, "position": "below"},
713
+ )
714
+
715
+ insert_position = None
716
+ for idx, row in enumerate(product_rows):
717
+ if row.row_id == reference_row_id:
718
+ insert_position = idx + 1
719
+ break
720
+ if insert_position is None:
721
+ product_rows.append(new_row)
722
+ else:
723
+ product_rows.insert(insert_position, new_row)
724
+ return new_row.row_id
725
+
726
+ new_row = self.add_inventory_row(inventory_id=inventory_id)
727
+ product_rows.append(new_row)
728
+ return new_row.row_id
729
+
730
+ new_row = self.add_inventory_row(inventory_id=inventory_id)
731
+ product_rows.append(new_row)
732
+ return new_row.row_id
696
733
 
697
734
  def add_formulation_columns(
698
735
  self,
@@ -816,69 +853,79 @@ class Sheet(BaseSessionResource): # noqa:F811
816
853
  return (updated, failed)
817
854
 
818
855
  def _get_current_cell(self, *, cell: Cell) -> Cell:
819
- filtered_columns = [
820
- col for col in self.grid.columns if col.startswith(cell.column_id + "#")
821
- ]
822
- filtered_rows = [
823
- idx for idx in self.grid.index if idx.startswith(cell.design_id + "#" + cell.row_id)
824
- ]
856
+ def _matches_column(column_label: str) -> bool:
857
+ col_parts = column_label.split("#", 1)
858
+ return col_parts[0] == cell.column_id
859
+
860
+ def _matches_row(index_label: str) -> bool:
861
+ row_parts = index_label.split("#", 2)
862
+ if len(row_parts) < 2:
863
+ return False
864
+ return row_parts[0] == cell.design_id and row_parts[1] == cell.row_id
865
+
866
+ filtered_columns = [col for col in self.grid.columns if _matches_column(col)]
867
+ filtered_rows = [idx for idx in self.grid.index if _matches_row(idx)]
825
868
 
826
- first_value = None
827
869
  for row in filtered_rows:
828
870
  for col in filtered_columns:
829
- first_value = self.grid.loc[row, col]
830
- return first_value
831
- return first_value
871
+ return self.grid.loc[row, col]
872
+ return None
832
873
 
833
- def _generate_attribute_change(self, *, new_value, old_value, api_attribute_name):
874
+ def _generate_attribute_change(
875
+ self,
876
+ *,
877
+ new_value: CellAttributeValue,
878
+ old_value: CellAttributeValue,
879
+ api_attribute_name: str,
880
+ ) -> PatchDatum | None:
834
881
  """Generates a change dictionary for a single attribute."""
835
882
  if new_value == old_value:
836
883
  return None
837
884
 
838
885
  if new_value is None or new_value in ("", {}):
839
- return {
840
- "operation": "delete",
841
- "attribute": api_attribute_name,
842
- "oldValue": old_value,
843
- }
886
+ return PatchDatum(
887
+ operation="delete",
888
+ attribute=api_attribute_name,
889
+ old_value=old_value,
890
+ )
844
891
  if old_value is None or old_value in ("", {}):
845
- return {
846
- "operation": "add",
847
- "attribute": api_attribute_name,
848
- "newValue": new_value,
849
- }
850
- return {
851
- "operation": "update",
852
- "attribute": api_attribute_name,
853
- "oldValue": old_value,
854
- "newValue": new_value,
855
- }
892
+ return PatchDatum(
893
+ operation="add",
894
+ attribute=api_attribute_name,
895
+ new_value=new_value,
896
+ )
897
+ return PatchDatum(
898
+ operation="update",
899
+ attribute=api_attribute_name,
900
+ old_value=old_value,
901
+ new_value=new_value,
902
+ )
856
903
 
857
- def _get_cell_changes(self, *, cell: Cell) -> dict:
904
+ def _get_cell_changes(self, *, cell: Cell) -> CellChangePayload | None:
858
905
  current_cell = self._get_current_cell(cell=cell)
859
906
  if current_cell is None:
860
907
  return None
861
908
 
862
- data = []
909
+ data: list[PatchDatum] = []
863
910
 
864
911
  # Handle format change
865
912
  if cell.format != current_cell.format:
866
913
  if cell.format is None or cell.format == {}:
867
914
  data.append(
868
- {
869
- "operation": "delete",
870
- "attribute": "cellFormat",
871
- "oldValue": current_cell.format,
872
- }
915
+ PatchDatum(
916
+ operation="delete",
917
+ attribute="cellFormat",
918
+ old_value=current_cell.format,
919
+ )
873
920
  )
874
921
  else:
875
922
  data.append(
876
- {
877
- "operation": "update",
878
- "attribute": "cellFormat",
879
- "oldValue": current_cell.format,
880
- "newValue": cell.format,
881
- }
923
+ PatchDatum(
924
+ operation="update",
925
+ attribute="cellFormat",
926
+ old_value=current_cell.format,
927
+ new_value=cell.format,
928
+ )
882
929
  )
883
930
 
884
931
  # Handle calculation change
@@ -937,9 +984,9 @@ class Sheet(BaseSessionResource): # noqa:F811
937
984
  return False
938
985
 
939
986
  def update_cells(self, *, cells: list[Cell]):
940
- request_path_dict = {}
941
- updated = []
942
- failed = []
987
+ request_path_dict: dict[str, list[Cell]] = {}
988
+ updated: list[Cell] = []
989
+ failed: list[Cell] = []
943
990
  # sort by design ID
944
991
  for c in cells:
945
992
  if c.design_id not in request_path_dict:
@@ -948,57 +995,89 @@ class Sheet(BaseSessionResource): # noqa:F811
948
995
  request_path_dict[c.design_id].append(c)
949
996
 
950
997
  for design_id, cell_list in request_path_dict.items():
951
- payloads = []
998
+ payload_entries: list[tuple[CellChangePayload, Cell]] = []
952
999
  for cell in cell_list:
953
1000
  change_dict = self._get_cell_changes(cell=cell)
954
- if change_dict is not None:
955
- # For non-calculation cells, only one change is allowed at a time.
956
- is_calculation_cell = cell.calculation is not None and cell.calculation != ""
957
- max_items = 2 if is_calculation_cell else 1
958
-
959
- if len(change_dict["data"]) > max_items:
960
- for item in change_dict["data"]:
961
- payloads.append(
962
- {
963
- "Id": change_dict["Id"],
964
- "data": [item],
965
- }
966
- )
967
- else:
968
- payloads.append(change_dict)
969
-
970
- if not payloads:
1001
+ if change_dict is None:
1002
+ continue
1003
+
1004
+ is_calculation_cell = cell.calculation is not None and cell.calculation != ""
1005
+ max_items = 2 if is_calculation_cell else 1
1006
+
1007
+ if len(change_dict["data"]) > max_items:
1008
+ for item in change_dict["data"]:
1009
+ single_change: CellChangePayload = {
1010
+ "Id": change_dict["Id"],
1011
+ "data": [item],
1012
+ }
1013
+ payload_entries.append((single_change, cell))
1014
+ else:
1015
+ payload_entries.append((change_dict, cell))
1016
+
1017
+ if not payload_entries:
971
1018
  continue
972
1019
 
973
1020
  this_url = f"/api/v3/worksheet/{design_id}/values"
974
- for payload in payloads:
975
- response = self.session.patch(
976
- this_url,
977
- json=[payload], # The API expects a list of changes
978
- )
979
-
980
- original_cell = next(
981
- (
982
- c
983
- for c in cell_list
984
- if c.row_id == payload["Id"]["rowId"]
985
- and c.column_id == payload["Id"]["colId"]
986
- ),
987
- None,
988
- )
1021
+ pending_by_cell: dict[tuple[str, str], list[tuple[CellChangePayload, Cell]]] = {}
1022
+ for payload, cell in payload_entries:
1023
+ key = (payload["Id"]["rowId"], payload["Id"]["colId"])
1024
+ pending_by_cell.setdefault(key, []).append((payload, cell))
1025
+
1026
+ ordered_keys = list(pending_by_cell.keys())
1027
+
1028
+ def _unique_cells(cells: list[Cell]) -> list[Cell]:
1029
+ seen: set[tuple[str, str, str]] = set()
1030
+ result: list[Cell] = []
1031
+ for c in cells:
1032
+ key = (c.design_id, c.row_id, c.column_id)
1033
+ if key not in seen:
1034
+ seen.add(key)
1035
+ result.append(c)
1036
+ return result
1037
+
1038
+ batch_index = 0
1039
+ while True:
1040
+ batch_payloads: list[CellChangePayload] = []
1041
+ batch_cells: list[Cell] = []
1042
+ for key in ordered_keys:
1043
+ queue = pending_by_cell.get(key)
1044
+ if queue:
1045
+ payload, cell = queue.pop(0)
1046
+ batch_payloads.append(payload)
1047
+ batch_cells.append(cell)
1048
+ if not batch_payloads:
1049
+ break
1050
+
1051
+ payload_body = [
1052
+ {
1053
+ "Id": payload["Id"],
1054
+ "data": [datum.model_dump(by_alias=True) for datum in payload["data"]],
1055
+ }
1056
+ for payload in batch_payloads
1057
+ ]
1058
+ response = self.session.patch(this_url, json=payload_body)
1059
+ target_cells = _unique_cells(batch_cells)
989
1060
 
990
1061
  if response.status_code == 204:
991
- if original_cell and original_cell not in updated:
992
- updated.append(original_cell)
1062
+ for c in target_cells:
1063
+ if c not in updated:
1064
+ updated.append(c)
993
1065
  elif response.status_code == 206:
994
1066
  cell_results = self._filter_cells(
995
- cells=[original_cell], response_dict=response.json()
1067
+ cells=target_cells, response_dict=response.json()
996
1068
  )
997
- updated.extend(cell_results[0])
998
- failed.extend(cell_results[1])
1069
+ for c in cell_results[0]:
1070
+ if c not in updated:
1071
+ updated.append(c)
1072
+ for c in cell_results[1]:
1073
+ if c not in failed:
1074
+ failed.append(c)
999
1075
  else:
1000
- if original_cell and original_cell not in failed:
1001
- failed.append(original_cell)
1076
+ for c in target_cells:
1077
+ if c not in failed:
1078
+ failed.append(c)
1079
+
1080
+ batch_index += 1
1002
1081
 
1003
1082
  # reset the in-memory grid after updates
1004
1083
  self.grid = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: albert
3
- Version: 1.9.1
3
+ Version: 1.9.3
4
4
  Summary: The official Python SDK for the Albert Invent platform.
5
5
  Project-URL: Homepage, https://www.albertinvent.com/
6
6
  Project-URL: Documentation, https://docs.developer.albertinvent.com/albert-python
@@ -1,4 +1,4 @@
1
- albert/__init__.py,sha256=PIGmPqEsDxhqTL_X1_Dg-9z-EnstUBpfdI2oJJan1Bk,238
1
+ albert/__init__.py,sha256=zLLzY0TKwldKjgnRluQjODLzglf5m3AKMUtIlW0-MlU,238
2
2
  albert/client.py,sha256=1BSaI5a_8AycVQgzwhack5CnaqfnHNQ6I18fbNAPluI,11857
3
3
  albert/exceptions.py,sha256=-oxOJGE0A__aPUhri3qqb5YQ5qanECcTqamS73vGajM,3172
4
4
  albert/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -22,7 +22,7 @@ albert/collections/inventory.py,sha256=GeQ7CEPdFvBnqxPgl8Gt25jX7uAv3KIaltsBDKJiv
22
22
  albert/collections/links.py,sha256=HhKWPTnnsp1hpA6K69rb6Cbl3KTpudtynBek9SS-fgg,3837
23
23
  albert/collections/lists.py,sha256=VoVPcsMSCWCqeipgjouXyoxQYuEDA8ATci-RATobXyo,5965
24
24
  albert/collections/locations.py,sha256=Pa49oGYAt7kb22RxyaoGq3u1uz76E0ZJTcyeTEhlOZc,5929
25
- albert/collections/lots.py,sha256=n7BYzG4erjiz_Aj8GTa4kryTq2QwtO3peajlBWgJSKs,8176
25
+ albert/collections/lots.py,sha256=3ixur5xzqubIjIZkvwHM28CCDxKInuqMkJtbS5oOwWg,12892
26
26
  albert/collections/notebooks.py,sha256=MIMMx_NF6jBzraC5j8ML2mRmPQXjQNPx9N6aj_8yT4k,8556
27
27
  albert/collections/notes.py,sha256=l5duv5mdlCiRSfAo3nqOzMOV8oJAnAtp4kjHYwapyWY,3099
28
28
  albert/collections/parameter_groups.py,sha256=8j5X3WJ7dcLFVLdtM7xg-iZzTijK2GCrWKpMYZ3tp6g,8869
@@ -83,7 +83,7 @@ albert/resources/inventory.py,sha256=jq6XsbcP5djwwkwY6j6AHg0VTGYiOz20uKc3YK5WGHk
83
83
  albert/resources/links.py,sha256=te7KIO7vfXrA-Qjngvb4JHy--SUVTG5M7GBFz8XdKto,1032
84
84
  albert/resources/lists.py,sha256=oxQR8obusOs-SRh0elZtuFQ94VlGXKN_v-Htq9YxQ9E,1653
85
85
  albert/resources/locations.py,sha256=xqbSoO-IjLSyK74W1jSdsNL9kUN5jD6Wf9XNxw8czYA,821
86
- albert/resources/lots.py,sha256=HayF-2j5iix0-R9wfEXU6YjXGZA77auNXWY2Vl9_NXU,5893
86
+ albert/resources/lots.py,sha256=8YC6fHv08uYvhh09ULzNtFmdyfLeM1z6Hkm9wcTvcX4,6839
87
87
  albert/resources/notebooks.py,sha256=lnlvPToPS2LfNt8i0k14206twmINYwDAuh6cG41GlII,9418
88
88
  albert/resources/notes.py,sha256=VtmVtgSbL8-5PmtBABoAWGKHOV5OFsVwiJ0LVj9BQA4,851
89
89
  albert/resources/parameter_groups.py,sha256=r7Wvmvt6A_cnLaFuPVPx7BSJpfVLmoENg8__rl__d9M,6310
@@ -95,7 +95,7 @@ albert/resources/property_data.py,sha256=vRJcBs7QcAh0rywFNZYkpSHKIw2nv0LBNvWf7ZE
95
95
  albert/resources/report_templates.py,sha256=I1rIpRiVqWD9r2-5eoRrL4Vs-hY84toHQqwSC-Xw740,5713
96
96
  albert/resources/reports.py,sha256=T2_0LBn9EEhgN8zVLmzBz1AT78lPSGHiwy47K7vvp0s,5977
97
97
  albert/resources/roles.py,sha256=pgZBJ2-QWU8EvDBZ21KgdA0bPM9a3EbDeDgmhkcvrRs,732
98
- albert/resources/sheets.py,sha256=ieCDs8B32PsTy0c1gyJJ0T_atUJY5w-ju-YRjRagSV0,44392
98
+ albert/resources/sheets.py,sha256=BB2fXHdf9Oj9vLMjyJq77vhFw93IAlyCxFCXJsrgiRE,47267
99
99
  albert/resources/storage_classes.py,sha256=In87gE3sXfzVwDoojDyfiUKDkOImWBxYeaxIkvTBhtA,713
100
100
  albert/resources/storage_locations.py,sha256=2qL-MlOvxq0_wJj1jpBghPmKTgI1mExnUbotWktjhZw,825
101
101
  albert/resources/substance.py,sha256=Zst2PAMKV7XLbDMytAxq24ddn-FXNSJR-TcfXxG6_Nk,32394
@@ -111,7 +111,7 @@ albert/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
111
111
  albert/utils/_auth.py,sha256=YjzaGIzI9qP53nwdyE2Ezs-9UokzA38kgdE7Sxnyjd8,124
112
112
  albert/utils/_patch.py,sha256=VdMHpRdqTU9DNpXU7wndFRP-u4KEnVVrUpPr9dNriqs,22723
113
113
  albert/utils/inventory.py,sha256=ViHxb62DVxniEJqOfD7Gf8HltLeCbq21y4rS1xkVRnY,5349
114
- albert-1.9.1.dist-info/METADATA,sha256=Uc_fj_LhGxrPrr3FKS8Vd6TTsoFXyW4KIHmjwFbO5fI,15395
115
- albert-1.9.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
116
- albert-1.9.1.dist-info/licenses/LICENSE,sha256=S7_vRdIhQmG7PmTlU8-BCCveuEcFZ6_3IUVdcoaJMuA,11348
117
- albert-1.9.1.dist-info/RECORD,,
114
+ albert-1.9.3.dist-info/METADATA,sha256=jjgV8JEQFxN2F-b_vMkhtK7P3CUTGgAsSONfRu3FLyI,15395
115
+ albert-1.9.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
116
+ albert-1.9.3.dist-info/licenses/LICENSE,sha256=S7_vRdIhQmG7PmTlU8-BCCveuEcFZ6_3IUVdcoaJMuA,11348
117
+ albert-1.9.3.dist-info/RECORD,,
File without changes