albert 1.11.1__py3-none-any.whl → 1.12.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.11.1"
7
+ __version__ = "1.12.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
@@ -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()
@@ -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.",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: albert
3
- Version: 1.11.1
3
+ Version: 1.12.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=XThh5N2xtwjr3MAIRabVZNb-albZ35TFqJUFVdg7js4,239
1
+ albert/__init__.py,sha256=IxZK2r9lTXpOpps67inCsmcTp1mEgdAXXsoIgn-EO2c,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
@@ -51,7 +51,7 @@ 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
@@ -115,9 +115,9 @@ 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=ViHxb62DVxniEJqOfD7Gf8HltLeCbq21y4rS1xkVRnY,5349
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.11.1.dist-info/METADATA,sha256=zKrAC_yzs9_KXp7OBa74BBLLIwkl1Zw9fKlbAZmyoMw,15427
121
- albert-1.11.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
122
- albert-1.11.1.dist-info/licenses/LICENSE,sha256=S7_vRdIhQmG7PmTlU8-BCCveuEcFZ6_3IUVdcoaJMuA,11348
123
- albert-1.11.1.dist-info/RECORD,,
120
+ albert-1.12.0.dist-info/METADATA,sha256=fPc1wDNv8rwCKP4RMRMyrmEx9-tShO2r8UuRPMr7yw0,15427
121
+ albert-1.12.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
122
+ albert-1.12.0.dist-info/licenses/LICENSE,sha256=S7_vRdIhQmG7PmTlU8-BCCveuEcFZ6_3IUVdcoaJMuA,11348
123
+ albert-1.12.0.dist-info/RECORD,,