albert 1.13.0b2__py3-none-any.whl → 1.14.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 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.13.0b2"
7
+ __version__ = "1.14.0"
@@ -152,20 +152,30 @@ class AttachmentCollection(BaseCollection):
152
152
  The name of the file, by default ""
153
153
  upload_key : str | None, optional
154
154
  Override the storage key used when signing and uploading the file.
155
- Defaults to the provided ``file_name``.
155
+ Defaults to ``{parent_id}/{note_id}/{file_name}``.
156
156
 
157
157
  Returns
158
158
  -------
159
159
  Note
160
160
  The created note.
161
161
  """
162
- upload_name = upload_key or file_name
163
- if not upload_name:
162
+ if not (upload_key or file_name):
164
163
  raise ValueError("A file name or upload key must be provided for attachment upload.")
165
164
 
166
- file_type = mimetypes.guess_type(file_name or upload_name)[0]
167
- file_collection = self._get_file_collection()
168
165
  note_collection = self._get_note_collection()
166
+ note = Note(
167
+ parent_id=parent_id,
168
+ note=note_text,
169
+ )
170
+ registered_note = note_collection.create(note=note)
171
+ if upload_key:
172
+ attachment_name = file_name or Path(upload_key).name
173
+ upload_name = upload_key
174
+ else:
175
+ attachment_name = file_name
176
+ upload_name = f"{parent_id}/{registered_note.id}/{file_name}"
177
+ file_type = mimetypes.guess_type(attachment_name or upload_name)[0]
178
+ file_collection = self._get_file_collection()
169
179
 
170
180
  file_collection.sign_and_upload_file(
171
181
  data=file_data,
@@ -176,16 +186,12 @@ class AttachmentCollection(BaseCollection):
176
186
  file_info = file_collection.get_by_name(
177
187
  name=upload_name, namespace=FileNamespace.RESULT.value
178
188
  )
179
- note = Note(
180
- parent_id=parent_id,
181
- note=note_text,
182
- )
183
- registered_note = note_collection.create(note=note)
184
189
  self.attach_file_to_note(
185
190
  note_id=registered_note.id,
186
- file_name=file_name or Path(upload_name).name,
191
+ file_name=attachment_name,
187
192
  file_key=file_info.name,
188
193
  )
194
+
189
195
  return note_collection.get_by_id(id=registered_note.id)
190
196
 
191
197
  @validate_call
@@ -142,17 +142,17 @@ class ProjectCollection(BaseCollection):
142
142
  ----------
143
143
  text : str, optional
144
144
  Full-text search query.
145
- status : list of str, optional
145
+ status : list[str], optional
146
146
  Filter by project statuses.
147
- market_segment : list of str, optional
147
+ market_segment : list[str], optional
148
148
  Filter by market segment.
149
- application : list of str, optional
149
+ application : list[str], optional
150
150
  Filter by application.
151
- technology : list of str, optional
151
+ technology : list[str], optional
152
152
  Filter by technology tags.
153
- created_by : list of str, optional
153
+ created_by : list[str], optional
154
154
  Filter by user names who created the project.
155
- location : list of str, optional
155
+ location : list[str], optional
156
156
  Filter by location(s).
157
157
  from_created_at : str, optional
158
158
  Earliest creation date in 'YYYY-MM-DD' format.
@@ -162,15 +162,15 @@ class ProjectCollection(BaseCollection):
162
162
  Facet field to filter on.
163
163
  facet_text : str, optional
164
164
  Facet text to search for.
165
- contains_field : list of str, optional
165
+ contains_field : list[str], optional
166
166
  Fields to search inside.
167
- contains_text : list of str, optional
167
+ contains_text : list[str], optional
168
168
  Values to search for within the `contains_field`.
169
169
  linked_to : str, optional
170
170
  Entity ID the project is linked to.
171
171
  my_project : bool, optional
172
172
  If True, return only projects owned by current user.
173
- my_role : list of str, optional
173
+ my_role : list[str], optional
174
174
  User roles to filter by.
175
175
  order_by : OrderBy, optional
176
176
  Sort order. Default is DESCENDING.
@@ -1,9 +1,18 @@
1
1
  from pydantic import validate_call
2
2
 
3
3
  from albert.collections.base import BaseCollection
4
+ from albert.collections.custom_templates import CustomTemplatesCollection
4
5
  from albert.core.session import AlbertSession
5
6
  from albert.core.shared.identifiers import ProjectId
7
+ from albert.resources.acls import ACLContainer
8
+ from albert.resources.custom_templates import CustomTemplate
6
9
  from albert.resources.worksheets import Worksheet
10
+ from albert.utils.worksheets import (
11
+ get_columns_to_copy,
12
+ get_prg_rows_to_copy,
13
+ get_sheet_from_worksheet,
14
+ get_task_rows_to_copy,
15
+ )
7
16
 
8
17
 
9
18
  class WorksheetCollection(BaseCollection):
@@ -116,3 +125,159 @@ class WorksheetCollection(BaseCollection):
116
125
  url = f"{self.base_path}/project/{project_id}/sheets"
117
126
  self.session.put(url=url, json=payload)
118
127
  return self.get_by_project_id(project_id=project_id)
128
+
129
+ @validate_call
130
+ def duplicate_sheet(
131
+ self,
132
+ *,
133
+ project_id: ProjectId,
134
+ source_sheet_name: str,
135
+ new_sheet_name: str,
136
+ copy_all_pd_rows: bool = True,
137
+ copy_all_pinned_columns: bool = True,
138
+ copy_all_unpinned_columns: bool = True,
139
+ column_names: list[str] | None = None,
140
+ task_row_names: list[str] | None = None,
141
+ ) -> Worksheet:
142
+ """Duplicate an existing sheet within the same project.
143
+
144
+ This creates a new sheet based on the specified source sheet. You can control
145
+ which Product Design (PD) & Results rows and columns are copied using the available options.
146
+ The final list of columns copied is the union of:
147
+ - all pinned columns (if copy_all_pinned_columns is True)
148
+ - all unpinned columns (if copy_all_unpinned_columns is True)
149
+ - explicitly listed column names (column_names)
150
+
151
+ Parameters
152
+ ----------
153
+ project_id : str
154
+ The project ID under which the sheet exists.
155
+ source_sheet_name : str
156
+ The name of the existing sheet to duplicate.
157
+ new_sheet_name : str
158
+ The name of the new sheet to create.
159
+ copy_all_pd_rows : bool, optional
160
+ When True, all PD (Product Design) rows from the source sheet are copied.
161
+ When False, only rows corresponding to the selected columns will be copied.
162
+ Default is True.
163
+ copy_all_pinned_columns : bool, optional
164
+ If True, includes all pinned columns from the source sheet. Default is True.
165
+ copy_all_unpinned_columns : bool, optional
166
+ If True, includes all unpinned columns from the source sheet. Default is True.
167
+ column_names : list[str], optional
168
+ A list of column names to explicitly copy. These are resolved internally
169
+ to column IDs using the sheet's product design grid.
170
+ task_row_names : list[str], optional
171
+ List of task row names to include from the tasks.
172
+
173
+ Returns
174
+ -------
175
+ Worksheet
176
+ The Worksheet entity containing newly created sheet.
177
+ """
178
+
179
+ worksheet = self.get_by_project_id(project_id=project_id)
180
+ sheet = get_sheet_from_worksheet(sheet_name=source_sheet_name, worksheet=worksheet)
181
+ columns = get_columns_to_copy(
182
+ sheet=sheet,
183
+ copy_all_pinned_columns=copy_all_pinned_columns,
184
+ copy_all_unpinned_columns=copy_all_unpinned_columns,
185
+ input_column_names=column_names,
186
+ )
187
+ task_rows = get_task_rows_to_copy(sheet=sheet, input_row_names=task_row_names)
188
+
189
+ payload = {
190
+ "name": new_sheet_name,
191
+ "sourceData": {
192
+ "projectId": project_id,
193
+ "sheetId": sheet.id,
194
+ "Columns": [{"id": col_id} for col_id in columns],
195
+ "copyAllPDRows": copy_all_pd_rows,
196
+ "TaskRows": [{"id": row_id} for row_id in task_rows],
197
+ },
198
+ }
199
+
200
+ path = f"{self.base_path}/project/{project_id}/sheets"
201
+ self.session.put(path, json=payload)
202
+ return self.get_by_project_id(project_id=project_id)
203
+
204
+ @validate_call
205
+ def create_sheet_template(
206
+ self,
207
+ *,
208
+ project_id: ProjectId,
209
+ source_sheet_name: str,
210
+ template_name: str,
211
+ copy_all_pd_rows: bool = True,
212
+ copy_all_pinned_columns: bool = True,
213
+ copy_all_unpinned_columns: bool = True,
214
+ column_names: list[str] | None = None,
215
+ task_row_names: list[str] | None = None,
216
+ prg_row_names: list[str] | None = None,
217
+ acl: ACLContainer | None = None,
218
+ ) -> CustomTemplate:
219
+ """Create a new sheet template from an existing sheet.
220
+
221
+ Parameters
222
+ ----------
223
+ project_id : str
224
+ The project ID under which the sheet exists.
225
+ source_sheet_name : str
226
+ The name of the existing sheet to use as the template source.
227
+ template_name : str
228
+ The name of the new template.
229
+ copy_all_pd_rows : bool, optional
230
+ When True, all PD (Product Design) rows from the source sheet are copied.
231
+ When False, only rows corresponding to the selected columns will be copied.
232
+ copy_all_pinned_columns : bool, optional
233
+ If True, includes all pinned columns from the source sheet. Default is True.
234
+ copy_all_unpinned_columns : bool, optional
235
+ If True, includes all unpinned columns from the source sheet. Default is True.
236
+ column_names : list[str], optional
237
+ A list of column names to explicitly copy. These are resolved internally
238
+ to column IDs using the sheet's product design grid.
239
+ task_row_names : list[str], optional
240
+ List of task row names to include from the tasks.
241
+ prg_row_names : list[str], optional
242
+ List of parameter group row names to include.
243
+ acl : ACLContainer, optional
244
+ ACL for the template.
245
+
246
+ Returns
247
+ -------
248
+ CustomTemplate
249
+ The CustomTemplate for the created sheet template.
250
+ """
251
+ worksheet = self.get_by_project_id(project_id=project_id)
252
+ sheet = get_sheet_from_worksheet(sheet_name=source_sheet_name, worksheet=worksheet)
253
+ columns = get_columns_to_copy(
254
+ sheet=sheet,
255
+ copy_all_pinned_columns=copy_all_pinned_columns,
256
+ copy_all_unpinned_columns=copy_all_unpinned_columns,
257
+ input_column_names=column_names,
258
+ )
259
+ if not columns:
260
+ raise ValueError("At least one column must be selected to create a template.")
261
+ task_rows = get_task_rows_to_copy(sheet=sheet, input_row_names=task_row_names)
262
+ prg_rows = get_prg_rows_to_copy(sheet=sheet, input_row_names=prg_row_names)
263
+
264
+ payload = {
265
+ "name": template_name,
266
+ "sourceData": {
267
+ "projectId": project_id,
268
+ "sheetId": sheet.id,
269
+ "Columns": [{"id": col_id} for col_id in columns],
270
+ "copyAllPDRows": copy_all_pd_rows,
271
+ "TaskRows": [{"id": row_id} for row_id in task_rows],
272
+ "PRGRows": [{"id": row_id} for row_id in prg_rows],
273
+ },
274
+ }
275
+
276
+ if acl is not None:
277
+ payload["ACL"] = acl.model_dump(exclude_none=True, by_alias=True, mode="json")
278
+
279
+ path = f"{self.base_path}/sheet/template"
280
+ response = self.session.post(path, json=payload)
281
+ response_json = response.json()
282
+ ctp_id = response_json.get("ctpId")
283
+ return CustomTemplatesCollection(session=self.session).get_by_id(id=ctp_id)
@@ -31,6 +31,10 @@ class RequestHandler(BaseHTTPRequestHandler):
31
31
  status = "successful" if self.server.token else "failed (no token found)"
32
32
  self.send_response(200)
33
33
  self.send_header("Content-Type", "text/html")
34
+ self.send_header(
35
+ "Content-Security-Policy",
36
+ "default-src 'none'; frame-ancestors 'none'; base-uri 'none';",
37
+ )
34
38
  self.end_headers()
35
39
  self.wfile.write(
36
40
  f"""
@@ -38,8 +42,6 @@ class RequestHandler(BaseHTTPRequestHandler):
38
42
  <body>
39
43
  <h1>Authentication {status}</h1>
40
44
  <p>You can close this window now.</p>
41
- <script>window.close()</script>
42
- <button onclick="window.close()">Close Window</button>
43
45
  </body>
44
46
  </html>
45
47
  """.encode()
@@ -19,6 +19,7 @@ class SecurityClass(str, Enum):
19
19
  SHARED = "shared"
20
20
  RESTRICTED = "restricted"
21
21
  CONFIDENTIAL = "confidential"
22
+ # only used by "PROJECTS"
22
23
  PRIVATE = "private"
23
24
 
24
25
 
albert/resources/acls.py CHANGED
@@ -3,10 +3,11 @@ from enum import Enum
3
3
  from pydantic import Field
4
4
 
5
5
  from albert.core.base import BaseAlbertModel
6
+ from albert.core.shared.models.base import BaseResource
6
7
 
7
8
 
8
9
  class AccessControlLevel(str, Enum):
9
- """The fine grain control"""
10
+ """Access control levels you can grant users."""
10
11
 
11
12
  PROJECT_OWNER = "ProjectOwner"
12
13
  PROJECT_EDITOR = "ProjectEditor"
@@ -22,9 +23,32 @@ class AccessControlLevel(str, Enum):
22
23
 
23
24
 
24
25
  class ACL(BaseAlbertModel):
25
- """The Access Control List (ACL) for a user"""
26
+ """A single access rule for a user.
27
+
28
+ Attributes
29
+ ----------
30
+ id : str
31
+ The user or team this rule applies to.
32
+ fgc : AccessControlLevel | None
33
+ The access level for that user or team.
34
+ """
26
35
 
27
36
  id: str = Field(description="The id of the user for which this ACL applies")
28
37
  fgc: AccessControlLevel | None = Field(
29
38
  default=None, description="The Fine-Grain Control Level"
30
39
  )
40
+
41
+
42
+ class ACLContainer(BaseResource):
43
+ """Access settings with a default class and a list of rules.
44
+
45
+ Attributes
46
+ ----------
47
+ acl_class : str | None
48
+ The default access class (for example, "restricted" or "confidential").
49
+ fgclist : list[ACL] | None
50
+ Specific access rules for users or teams.
51
+ """
52
+
53
+ acl_class: str | None = Field(default=None, alias="class")
54
+ fgclist: list[ACL] | None = Field(default=None, alias="fgclist")
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  import uuid
5
+ import warnings
5
6
  from datetime import datetime
6
7
  from enum import Enum
7
8
  from pathlib import Path
@@ -14,7 +15,7 @@ from albert.core.base import BaseAlbertModel
14
15
  from albert.core.shared.identifiers import LinkId, NotebookId, ProjectId, SynthesisId, TaskId
15
16
  from albert.core.shared.models.base import BaseResource, EntityLink
16
17
  from albert.exceptions import AlbertException
17
- from albert.resources.acls import ACL
18
+ from albert.resources.acls import ACL, ACLContainer
18
19
 
19
20
 
20
21
  class ListBlockStyle(str, Enum):
@@ -297,13 +298,36 @@ class PutBlockPayload(BaseAlbertModel):
297
298
 
298
299
 
299
300
  class NotebookCopyACL(BaseResource):
301
+ """
302
+ Access settings applied to a copied notebook.
303
+
304
+ Warning
305
+ -----
306
+ Deprecated and will be removed in 2.0. Use ``ACLContainer`` instead.
307
+
308
+ Attributes
309
+ ----------
310
+ fgclist : list[ACL]
311
+ Specific access rules for users or teams.
312
+ acl_class : str
313
+ Default access class (for example, "restricted" or "confidential").
314
+ """
315
+
300
316
  fgclist: list[ACL] = Field(default=None)
301
317
  acl_class: str = Field(alias="class")
302
318
 
319
+ def __init__(self, **data):
320
+ warnings.warn(
321
+ "NotebookCopyACL is deprecated and will be removed in 2.0; use ACLContainer instead.",
322
+ DeprecationWarning,
323
+ stacklevel=2,
324
+ )
325
+ super().__init__(**data)
326
+
303
327
 
304
328
  class NotebookCopyInfo(BaseAlbertModel):
305
329
  id: NotebookId
306
330
  parent_id: str = Field(alias="parentId")
307
331
  notebook_name: str | None = Field(default=None, alias="notebookName")
308
332
  name: str | None = Field(default=None)
309
- acl: NotebookCopyACL | None = Field(default=None)
333
+ acl: ACLContainer | NotebookCopyACL | None = Field(default=None)
@@ -2,7 +2,10 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import ast
6
+ import math
5
7
  import mimetypes
8
+ import operator
6
9
  import re
7
10
  import uuid
8
11
  from collections.abc import Callable
@@ -573,6 +576,63 @@ def get_all_columns_used_in_calculations(*, first_row_data_column: list):
573
576
  return used_columns
574
577
 
575
578
 
579
+ _ALLOWED_BINOPS = {
580
+ ast.Add: operator.add,
581
+ ast.Sub: operator.sub,
582
+ ast.Mult: operator.mul,
583
+ ast.Div: operator.truediv,
584
+ ast.Mod: operator.mod,
585
+ ast.Pow: operator.pow,
586
+ }
587
+ _ALLOWED_UNARYOPS = {
588
+ ast.UAdd: operator.pos,
589
+ ast.USub: operator.neg,
590
+ }
591
+ _ALLOWED_FUNCS: dict[str, tuple[Callable[..., float], int]] = {
592
+ "log10": (math.log10, 1),
593
+ "ln": (math.log, 1),
594
+ "sqrt": (math.sqrt, 1),
595
+ "pi": (lambda: math.pi, 0),
596
+ }
597
+ _ALLOWED_NAMES = {"pi": math.pi}
598
+
599
+
600
+ def _safe_eval_math(*, expression: str) -> float:
601
+ """Safely evaluate supported math expressions."""
602
+ parsed = ast.parse(expression, mode="eval")
603
+
604
+ def _eval(node: ast.AST) -> float:
605
+ if isinstance(node, ast.Expression):
606
+ return _eval(node.body)
607
+ if isinstance(node, ast.Constant) and isinstance(node.value, (int | float)):
608
+ return node.value
609
+ if isinstance(node, ast.BinOp) and type(node.op) in _ALLOWED_BINOPS:
610
+ return _ALLOWED_BINOPS[type(node.op)](_eval(node.left), _eval(node.right))
611
+ if isinstance(node, ast.UnaryOp) and type(node.op) in _ALLOWED_UNARYOPS:
612
+ return _ALLOWED_UNARYOPS[type(node.op)](_eval(node.operand))
613
+ if isinstance(node, ast.Call):
614
+ if not isinstance(node.func, ast.Name):
615
+ raise ValueError("Unsupported function call.")
616
+ func_name = node.func.id
617
+ if func_name not in _ALLOWED_FUNCS:
618
+ raise ValueError("Unsupported function.")
619
+ if node.keywords:
620
+ raise ValueError("Keyword arguments are not supported.")
621
+ func, arity = _ALLOWED_FUNCS[func_name]
622
+ if len(node.args) != arity:
623
+ raise ValueError("Unsupported function arity.")
624
+ if arity == 0:
625
+ return func()
626
+ return func(_eval(node.args[0]))
627
+ if isinstance(node, ast.Name):
628
+ if node.id in _ALLOWED_NAMES:
629
+ return _ALLOWED_NAMES[node.id]
630
+ raise ValueError("Unsupported name.")
631
+ raise ValueError("Unsupported expression.")
632
+
633
+ return _eval(parsed)
634
+
635
+
576
636
  def evaluate_calculation(*, calculation: str, column_values: dict) -> float | None:
577
637
  """Evaluate a calculation expression against column values."""
578
638
  calculation = calculation.lstrip("=")
@@ -589,7 +649,7 @@ def evaluate_calculation(*, calculation: str, column_values: dict) -> float | No
589
649
  calculation = pattern.sub(repl, calculation)
590
650
 
591
651
  calculation = calculation.replace("^", "**")
592
- return eval(calculation)
652
+ return _safe_eval_math(expression=calculation)
593
653
  except Exception as e:
594
654
  logger.info(
595
655
  "Error evaluating calculation '%s': %s. Likely do not have all values needed.",
@@ -0,0 +1,90 @@
1
+ from __future__ import annotations
2
+
3
+ from albert.resources.sheets import CellType, Sheet
4
+ from albert.resources.worksheets import Worksheet
5
+
6
+
7
+ def get_sheet_from_worksheet(*, sheet_name: str, worksheet: Worksheet) -> Sheet:
8
+ sheet = next((s for s in worksheet.sheets if s.name == sheet_name), None)
9
+ if not sheet:
10
+ raise ValueError(f"Sheet with name {sheet_name!r} not found in the Worksheet.")
11
+ return sheet
12
+
13
+
14
+ def get_columns_to_copy(
15
+ *,
16
+ sheet: Sheet,
17
+ copy_all_pinned_columns: bool,
18
+ copy_all_unpinned_columns: bool,
19
+ input_column_names: list[str] | None,
20
+ ) -> list[str]:
21
+ sheet_columns = sheet.columns
22
+ all_columns = {col.name: col.column_id for col in sheet_columns}
23
+
24
+ # If both flags are true, copy everything
25
+ if copy_all_pinned_columns and copy_all_unpinned_columns:
26
+ columns_to_copy: set[str] = {col.column_id for col in sheet_columns}
27
+ else:
28
+ columns_to_copy = set()
29
+ # Copy pinned columns
30
+ if copy_all_pinned_columns:
31
+ columns_to_copy.update(
32
+ col.column_id for col in sheet_columns if getattr(col, "pinned", False)
33
+ )
34
+
35
+ # Copy unpinned columns
36
+ if copy_all_unpinned_columns:
37
+ columns_to_copy.update(
38
+ col.column_id for col in sheet_columns if not getattr(col, "pinned", False)
39
+ )
40
+
41
+ # Add any explicitly specified columns
42
+ if input_column_names:
43
+ for name in input_column_names:
44
+ if name not in all_columns:
45
+ raise ValueError(f"Column name {name!r} not found in sheet {sheet.name!r}")
46
+ columns_to_copy.add(all_columns[name])
47
+
48
+ return list(columns_to_copy)
49
+
50
+
51
+ def get_task_rows_to_copy(*, sheet: Sheet, input_row_names: list[str] | None) -> list[str]:
52
+ task_rows = []
53
+
54
+ sheet_rows = sheet.rows
55
+ if not input_row_names:
56
+ # Copy all task rows if no input rows specified
57
+ for row in sheet_rows:
58
+ if row.type == CellType.TAS:
59
+ task_rows.append(row.row_id)
60
+ return task_rows
61
+
62
+ name_to_id = {row.name: row.row_id for row in sheet_rows if row.name}
63
+ for name in input_row_names:
64
+ row_id = name_to_id.get(name)
65
+ if row_id:
66
+ task_rows.append(row_id)
67
+ else:
68
+ raise ValueError(f"Task row name '{name}' not found in the grid.")
69
+ return task_rows
70
+
71
+
72
+ def get_prg_rows_to_copy(*, sheet: Sheet, input_row_names: list[str] | None) -> list[str]:
73
+ prg_rows = []
74
+
75
+ sheet_rows = sheet.rows
76
+ if not input_row_names:
77
+ # Copy all PRG rows if no input rows specified
78
+ for row in sheet_rows:
79
+ if row.type == CellType.PRG:
80
+ prg_rows.append(row.row_id)
81
+ return prg_rows
82
+
83
+ name_to_id = {row.name: row.row_id for row in sheet_rows if row.name}
84
+ for name in input_row_names:
85
+ row_id = name_to_id.get(name)
86
+ if row_id:
87
+ prg_rows.append(row_id)
88
+ else:
89
+ raise ValueError(f"PRG row name '{name}' not found in the grid.")
90
+ return prg_rows
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: albert
3
- Version: 1.13.0b2
3
+ Version: 1.14.0
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,9 +1,9 @@
1
- albert/__init__.py,sha256=PD2Bd-QFGbsWIU2l3LJov38x0waKKe_f7xmweh7YSLU,241
1
+ albert/__init__.py,sha256=XHvC39FCxseMBwu9xH2Vj24U9im2WtUiaBANFsmIiI0,239
2
2
  albert/client.py,sha256=9SUy9AJpnFEUlSfxvbP1gJI776WcOoZykgPHx0EcF8g,12038
3
3
  albert/exceptions.py,sha256=-oxOJGE0A__aPUhri3qqb5YQ5qanECcTqamS73vGajM,3172
4
4
  albert/collections/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  albert/collections/activities.py,sha256=vvV5-f9KH52HarVSzUNAL7o4hz9mKWEZiE1MreqiH1g,3373
6
- albert/collections/attachments.py,sha256=xhhdXxlVuQL_5K_rCKWlA-itr8yBMGfgkSaHrOtZfpw,9950
6
+ albert/collections/attachments.py,sha256=Lq5q2sMUKMBargfCM7xqFMbaZLRNRvyhjCUT5sWdhUk,10169
7
7
  albert/collections/base.py,sha256=MwBHndy5mXHvieozseaT1YF_RDevJWYu4IFGpLCBfww,8590
8
8
  albert/collections/batch_data.py,sha256=esMttDTCY8TqSEOfMFQQ6Um_77Mf-SY3g75t1b6THjQ,3388
9
9
  albert/collections/btdataset.py,sha256=rhjO4RtGSzAZj4hgA4M9BKG-eyhl111IDwZr_JW4t_E,4461
@@ -29,7 +29,7 @@ albert/collections/parameter_groups.py,sha256=8j5X3WJ7dcLFVLdtM7xg-iZzTijK2GCrWK
29
29
  albert/collections/parameters.py,sha256=buvW_2d2MvK0iadaFgaPt5ibJw3rGexXMO-AErlKgvs,6930
30
30
  albert/collections/pricings.py,sha256=hrlNOwt3rdmDvdfAMwepTem6iTpo5h6mZzyDlaYIsPE,6089
31
31
  albert/collections/product_design.py,sha256=hr3hijrNcE8OPFF5e-Ke2FB39KvyNz4CKoaN_UDj6s4,1855
32
- albert/collections/projects.py,sha256=uny5tLjyoXocauCpTPSknhztOwuLgyzqqg335oHvVyo,9703
32
+ albert/collections/projects.py,sha256=vKfE52laYijYsAN_igJJIrGEOK3M29SUoJHPIzP1OJI,9685
33
33
  albert/collections/property_data.py,sha256=tnP4jlOpKrvUkZUyXPITG27XOfvQZjmyToCqpbXQ2o0,39685
34
34
  albert/collections/report_templates.py,sha256=7Alsl-6zqbw_HQpDX89c-DjrpUk_3fb_jgAOMGk_8hU,2208
35
35
  albert/collections/reports.py,sha256=GzlXO_HH5EMXzzrFruHZVMhTMMQ8Lh55P_xf0yXb-xo,6278
@@ -44,26 +44,26 @@ albert/collections/un_numbers.py,sha256=C0RcmvUy0fOMZMe7s0cO1kAVHWArCvDNy3bPRyer
44
44
  albert/collections/units.py,sha256=3KKfZKBUavu5nTEhyN9rAM2ibFUucpZzqbnXkNoCUlg,7347
45
45
  albert/collections/users.py,sha256=A5LMsZkoeIZh8wqgmVQjRR38gCf1mCXxhW-WuXI3urc,8606
46
46
  albert/collections/workflows.py,sha256=dY5q3DdBUCo6BL0vCK7Ls40AvRdZMSrmF1hLc-qJVBA,6645
47
- albert/collections/worksheets.py,sha256=iZQ3KCMS7_HrFwdO3VVhovk5m1VkQiwOC-Vpgg3l_xE,3966
47
+ albert/collections/worksheets.py,sha256=kvYpmMXK7EFcmfzPf4rlQ2Un7KOclWfta1_td5Qyhj0,10997
48
48
  albert/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
49
  albert/core/base.py,sha256=vG7O665y4ck51baipoWO71qHHmAS1zxYyNocRf3KcNY,426
50
50
  albert/core/logging.py,sha256=sqNbIC3CZyaTyLnoV9mn0NCkxKH-jUNDJkAVMxgPSFY,820
51
51
  albert/core/pagination.py,sha256=aK8wUHknptP0lfLoNT5qsxlkR8UA0xXghZroFzgA2rY,4440
52
52
  albert/core/session.py,sha256=kCeTsjc4k-6bwqByc3R-tpG1ikvc17a_IRBKnflrCYY,3860
53
53
  albert/core/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
- albert/core/auth/_listener.py,sha256=Vinmz3vr2COjdN7tQHUz63J3COZZd9QAce9GBUJE2cM,2261
54
+ albert/core/auth/_listener.py,sha256=0RJnqofGrAQWkQ_UMWzk68_wGkzLmeX07Yn6NqWAeck,2293
55
55
  albert/core/auth/_manager.py,sha256=g4PUxADWJTTfwEP0-ob33ckjU_6mrFOBA4MWxaulsv4,1061
56
56
  albert/core/auth/credentials.py,sha256=uaN4RFIUsnCKPHgLnaZbNe9p-hhEDMOU6naE6vSrp8M,4234
57
57
  albert/core/auth/sso.py,sha256=4NEDN8wVT1fXvPEEgJNj20VCb5P5XZqG_6b8ovmloMI,7526
58
58
  albert/core/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
- albert/core/shared/enums.py,sha256=seNNmtKj3Mwh2ggJ1aTq85D6WwyS9D2if3tSU9jg1vk,468
59
+ albert/core/shared/enums.py,sha256=faIN6ps8Ug4T4gFH62hCw2H249P-vhQaPYj3R8GGAVY,498
60
60
  albert/core/shared/identifiers.py,sha256=f34UIXssyX-U3sCUda2Lxicc5zsz0PSoJoVmxoqadv0,8936
61
61
  albert/core/shared/types.py,sha256=iFlia55akIm_Wi4dhBrpJ2TquoOAqSGWh1slSS5FM_k,1462
62
62
  albert/core/shared/models/base.py,sha256=Jk7HkcSOTiVCRcdNp_7pJeZc_ZzIqNIpdfXGjL0yD1Y,2853
63
63
  albert/core/shared/models/patch.py,sha256=Y1sHc4-Zu32PQhF3361_2AdynZS9JQkG0RHxhw3IOqI,1787
64
64
  albert/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
65
65
  albert/resources/_mixins.py,sha256=rTHSzhAzJgj5nLQNdUTPAJt9abd7gwYavSF0Qs3H0cg,1026
66
- albert/resources/acls.py,sha256=T2EoO27WepPZNsZGlT_XoNWOpFNS86VUDEqG_iZmt0g,918
66
+ albert/resources/acls.py,sha256=OC-RA8EzOPO6-pWc16YFM1c7DlGjR4iFNA1hgcI4WF4,1627
67
67
  albert/resources/activities.py,sha256=eUmDoV-877Mm4JLLjI-s1lNKjgiNRycbJgyzWmlf_HA,1171
68
68
  albert/resources/attachments.py,sha256=uK9U54g6oUO4F4zx0A_rxdW0L2I35SFT81IEuAOe3-E,1597
69
69
  albert/resources/batch_data.py,sha256=CECWb7vzDb0Zq7NnXK-uBbz9J06oXzBDo3od3bgnVRs,3312
@@ -85,7 +85,7 @@ albert/resources/links.py,sha256=te7KIO7vfXrA-Qjngvb4JHy--SUVTG5M7GBFz8XdKto,103
85
85
  albert/resources/lists.py,sha256=UinEaBEEo80Q1Vf5Ys4ViPcNTSvAeCRaZiW41CHY4f0,1861
86
86
  albert/resources/locations.py,sha256=xqbSoO-IjLSyK74W1jSdsNL9kUN5jD6Wf9XNxw8czYA,821
87
87
  albert/resources/lots.py,sha256=8YC6fHv08uYvhh09ULzNtFmdyfLeM1z6Hkm9wcTvcX4,6839
88
- albert/resources/notebooks.py,sha256=PebrXEbR8J9afqwwTwGjFlEkYCdqRh3AtvVwYMypex8,9581
88
+ albert/resources/notebooks.py,sha256=gpVTlOmnHa6Dt9xXczkUDJgu6xXqIscxU6KhgS9LcuM,10246
89
89
  albert/resources/notes.py,sha256=QLB3D18H8g7VZoqvuv_EwQe4-YlMvBr86AExUNQuYxI,946
90
90
  albert/resources/parameter_groups.py,sha256=EMMuynGIl1fxLxM3QsGhqXWPdPkKp9iAzGXOBzQAxVQ,6344
91
91
  albert/resources/parameters.py,sha256=f4s2-IhkbzQ2frAwCbkAZNw8YAWqqJxBZvGX1_KwSeY,1162
@@ -115,9 +115,10 @@ albert/utils/_auth.py,sha256=YjzaGIzI9qP53nwdyE2Ezs-9UokzA38kgdE7Sxnyjd8,124
115
115
  albert/utils/_patch.py,sha256=e-bD2x6W3Wt4FaKFK477h3kZeyucn3DEB9m5DR7LzaA,24273
116
116
  albert/utils/data_template.py,sha256=AUwzfQ-I2HY-osq_Tme5KLwXfMzW2pJpiud7HAMh148,27874
117
117
  albert/utils/inventory.py,sha256=hhL1wCn2vtF2z5FGwlX-3XIla4paB26QbmbStkG3yTQ,7433
118
- albert/utils/property_data.py,sha256=US_5PYZu2Yv3CmAuWMQfYztjSIgNaRrCz9SsVzLOGcw,22906
118
+ albert/utils/property_data.py,sha256=xb0CZAh_0zFiN5yQwcw3Mjk4En8MxMrJF9DKQI19UhM,25074
119
119
  albert/utils/tasks.py,sha256=ejhXD9yQR_ReDKLJ3k8ZxWxkYl-Mta_4OPXrz_lQiBI,19878
120
- albert-1.13.0b2.dist-info/METADATA,sha256=StHvYaZ4wVlKiWm90CFSeI0dlKapLPydCjPolI7UlCA,15429
121
- albert-1.13.0b2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
122
- albert-1.13.0b2.dist-info/licenses/LICENSE,sha256=S7_vRdIhQmG7PmTlU8-BCCveuEcFZ6_3IUVdcoaJMuA,11348
123
- albert-1.13.0b2.dist-info/RECORD,,
120
+ albert/utils/worksheets.py,sha256=yJXoDBcjtT11fOz6q9D4gRR6NZECFJKwHyBpm27O5Ac,3040
121
+ albert-1.14.0.dist-info/METADATA,sha256=HdX4VmfEnqA_XjJiVPZli3eRGZRjbJV2ckW5egVNJ9c,15427
122
+ albert-1.14.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
123
+ albert-1.14.0.dist-info/licenses/LICENSE,sha256=S7_vRdIhQmG7PmTlU8-BCCveuEcFZ6_3IUVdcoaJMuA,11348
124
+ albert-1.14.0.dist-info/RECORD,,