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 +1 -1
- albert/collections/attachments.py +17 -11
- albert/core/auth/_listener.py +4 -2
- albert/utils/property_data.py +61 -1
- {albert-1.11.1.dist-info → albert-1.12.0.dist-info}/METADATA +1 -1
- {albert-1.11.1.dist-info → albert-1.12.0.dist-info}/RECORD +8 -8
- {albert-1.11.1.dist-info → albert-1.12.0.dist-info}/WHEEL +0 -0
- {albert-1.11.1.dist-info → albert-1.12.0.dist-info}/licenses/LICENSE +0 -0
albert/__init__.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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=
|
|
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
|
albert/core/auth/_listener.py
CHANGED
|
@@ -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()
|
albert/utils/property_data.py
CHANGED
|
@@ -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
|
|
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.
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
121
|
-
albert-1.
|
|
122
|
-
albert-1.
|
|
123
|
-
albert-1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|