albert 1.14.0__py3-none-any.whl → 1.16.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 +45 -1
- albert/collections/btinsight.py +9 -8
- albert/collections/cas.py +1 -1
- albert/collections/companies.py +3 -3
- albert/collections/custom_templates.py +315 -26
- albert/collections/data_columns.py +4 -3
- albert/collections/data_templates.py +1 -1
- albert/collections/entity_types.py +2 -2
- albert/collections/inventory.py +8 -8
- albert/collections/lists.py +1 -1
- albert/collections/locations.py +3 -3
- albert/collections/lots.py +11 -28
- albert/collections/notes.py +1 -1
- albert/collections/parameter_groups.py +3 -2
- albert/collections/parameters.py +4 -4
- albert/collections/projects.py +44 -3
- albert/collections/property_data.py +4 -7
- albert/collections/storage_locations.py +3 -3
- albert/collections/tags.py +3 -2
- albert/collections/tasks.py +6 -3
- albert/collections/units.py +4 -3
- albert/collections/users.py +2 -2
- albert/core/utils.py +20 -0
- albert/resources/custom_templates.py +32 -10
- albert/resources/projects.py +16 -1
- albert/resources/tags.py +1 -41
- {albert-1.14.0.dist-info → albert-1.16.0.dist-info}/METADATA +1 -1
- {albert-1.14.0.dist-info → albert-1.16.0.dist-info}/RECORD +31 -30
- {albert-1.14.0.dist-info → albert-1.16.0.dist-info}/WHEEL +0 -0
- {albert-1.14.0.dist-info → albert-1.16.0.dist-info}/licenses/LICENSE +0 -0
albert/__init__.py
CHANGED
|
@@ -9,7 +9,7 @@ from pydantic import validate_call
|
|
|
9
9
|
from albert.collections.base import BaseCollection
|
|
10
10
|
from albert.collections.files import FileCollection
|
|
11
11
|
from albert.collections.notes import NotesCollection
|
|
12
|
-
from albert.core.shared.identifiers import AttachmentId, DataColumnId, InventoryId
|
|
12
|
+
from albert.core.shared.identifiers import AttachmentId, DataColumnId, InventoryId, ProjectId
|
|
13
13
|
from albert.core.shared.types import MetadataItem
|
|
14
14
|
from albert.resources.attachments import Attachment, AttachmentCategory
|
|
15
15
|
from albert.resources.files import FileCategory, FileNamespace
|
|
@@ -288,3 +288,47 @@ class AttachmentCollection(BaseCollection):
|
|
|
288
288
|
|
|
289
289
|
response = self.session.post(self.base_path, json=payload)
|
|
290
290
|
return Attachment(**response.json())
|
|
291
|
+
|
|
292
|
+
@validate_call
|
|
293
|
+
def upload_and_attach_document_to_project(
|
|
294
|
+
self,
|
|
295
|
+
*,
|
|
296
|
+
project_id: ProjectId,
|
|
297
|
+
file_path: Path,
|
|
298
|
+
) -> Attachment:
|
|
299
|
+
"""Upload a file and attach it as a document to a project.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
project_id: The Albert ID of the project (e.g. "PRO770").
|
|
303
|
+
file_path: Local path to the file to upload.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
The created Attachment record.
|
|
307
|
+
"""
|
|
308
|
+
resolved_path = file_path.expanduser()
|
|
309
|
+
if not resolved_path.is_file():
|
|
310
|
+
raise FileNotFoundError(f"File not found at '{resolved_path}'")
|
|
311
|
+
|
|
312
|
+
content_type = mimetypes.guess_type(resolved_path.name)[0] or "application/octet-stream"
|
|
313
|
+
encoded_file_name = quote(resolved_path.name)
|
|
314
|
+
file_key = f"{project_id}/documents/original/{encoded_file_name}"
|
|
315
|
+
|
|
316
|
+
file_collection = self._get_file_collection()
|
|
317
|
+
with resolved_path.open("rb") as file_handle:
|
|
318
|
+
file_collection.sign_and_upload_file(
|
|
319
|
+
data=file_handle,
|
|
320
|
+
name=file_key,
|
|
321
|
+
namespace=FileNamespace.RESULT,
|
|
322
|
+
content_type=content_type,
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
payload = {
|
|
326
|
+
"parentId": project_id,
|
|
327
|
+
"category": AttachmentCategory.OTHER.value,
|
|
328
|
+
"name": encoded_file_name,
|
|
329
|
+
"key": file_key,
|
|
330
|
+
"nameSpace": FileNamespace.RESULT.value,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
response = self.session.post(self.base_path, json=payload)
|
|
334
|
+
return Attachment(**response.json())
|
albert/collections/btinsight.py
CHANGED
|
@@ -7,6 +7,7 @@ from albert.core.pagination import AlbertPaginator
|
|
|
7
7
|
from albert.core.session import AlbertSession
|
|
8
8
|
from albert.core.shared.enums import OrderBy, PaginationMode
|
|
9
9
|
from albert.core.shared.identifiers import BTInsightId
|
|
10
|
+
from albert.core.utils import ensure_list
|
|
10
11
|
from albert.resources.btinsight import BTInsight, BTInsightCategory, BTInsightState
|
|
11
12
|
|
|
12
13
|
|
|
@@ -132,17 +133,17 @@ class BTInsightCollection(BaseCollection):
|
|
|
132
133
|
"""
|
|
133
134
|
params = {
|
|
134
135
|
"offset": offset,
|
|
135
|
-
"order":
|
|
136
|
+
"order": order_by,
|
|
136
137
|
"sortBy": sort_by,
|
|
137
138
|
"text": text,
|
|
138
|
-
"name": name,
|
|
139
|
+
"name": ensure_list(name),
|
|
139
140
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
141
|
+
|
|
142
|
+
state_values = ensure_list(state)
|
|
143
|
+
params["state"] = state_values if state_values else None
|
|
144
|
+
|
|
145
|
+
category_values = ensure_list(category)
|
|
146
|
+
params["category"] = category_values if category_values else None
|
|
146
147
|
|
|
147
148
|
return AlbertPaginator(
|
|
148
149
|
mode=PaginationMode.OFFSET,
|
albert/collections/cas.py
CHANGED
|
@@ -106,7 +106,7 @@ class CasCollection(BaseCollection):
|
|
|
106
106
|
An iterator over Cas entities.
|
|
107
107
|
"""
|
|
108
108
|
|
|
109
|
-
params: dict[str, Any] = {"orderBy": order_by
|
|
109
|
+
params: dict[str, Any] = {"orderBy": order_by}
|
|
110
110
|
if id is not None:
|
|
111
111
|
yield self.get_by_id(id=id)
|
|
112
112
|
return
|
albert/collections/companies.py
CHANGED
|
@@ -7,6 +7,7 @@ from albert.core.logging import logger
|
|
|
7
7
|
from albert.core.pagination import AlbertPaginator, PaginationMode
|
|
8
8
|
from albert.core.session import AlbertSession
|
|
9
9
|
from albert.core.shared.identifiers import CompanyId
|
|
10
|
+
from albert.core.utils import ensure_list
|
|
10
11
|
from albert.exceptions import AlbertException
|
|
11
12
|
from albert.resources.companies import Company
|
|
12
13
|
|
|
@@ -62,9 +63,8 @@ class CompanyCollection(BaseCollection):
|
|
|
62
63
|
"dupDetection": "false",
|
|
63
64
|
"startKey": start_key,
|
|
64
65
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
params["exactMatch"] = str(exact_match).lower()
|
|
66
|
+
params["name"] = ensure_list(name)
|
|
67
|
+
params["exactMatch"] = str(exact_match).lower()
|
|
68
68
|
|
|
69
69
|
return AlbertPaginator(
|
|
70
70
|
mode=PaginationMode.KEY,
|
|
@@ -3,13 +3,20 @@ from collections.abc import Iterator
|
|
|
3
3
|
from pydantic import validate_call
|
|
4
4
|
|
|
5
5
|
from albert.collections.base import BaseCollection
|
|
6
|
+
from albert.collections.tags import TagCollection
|
|
6
7
|
from albert.core.logging import logger
|
|
7
8
|
from albert.core.pagination import AlbertPaginator
|
|
8
9
|
from albert.core.session import AlbertSession
|
|
9
|
-
from albert.core.shared.enums import PaginationMode
|
|
10
|
+
from albert.core.shared.enums import OrderBy, PaginationMode, Status
|
|
10
11
|
from albert.core.shared.identifiers import CustomTemplateId
|
|
11
|
-
from albert.
|
|
12
|
-
from albert.
|
|
12
|
+
from albert.core.shared.models.patch import PatchOperation
|
|
13
|
+
from albert.core.utils import ensure_list
|
|
14
|
+
from albert.resources.acls import ACL
|
|
15
|
+
from albert.resources.custom_templates import (
|
|
16
|
+
CustomTemplate,
|
|
17
|
+
CustomTemplateSearchItem,
|
|
18
|
+
TemplateCategory,
|
|
19
|
+
)
|
|
13
20
|
|
|
14
21
|
|
|
15
22
|
class CustomTemplatesCollection(BaseCollection):
|
|
@@ -31,6 +38,102 @@ class CustomTemplatesCollection(BaseCollection):
|
|
|
31
38
|
self.base_path = f"/api/{CustomTemplatesCollection._api_version}/customtemplates"
|
|
32
39
|
|
|
33
40
|
@validate_call
|
|
41
|
+
def create(
|
|
42
|
+
self,
|
|
43
|
+
*,
|
|
44
|
+
custom_template: CustomTemplate | list[CustomTemplate],
|
|
45
|
+
) -> list[CustomTemplate]:
|
|
46
|
+
"""
|
|
47
|
+
Creates one or more custom templates.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
custom_template : CustomTemplate | list[CustomTemplate]
|
|
52
|
+
The template entities to create.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
list[CustomTemplate]
|
|
57
|
+
The created CustomTemplate entities.
|
|
58
|
+
"""
|
|
59
|
+
templates = ensure_list(custom_template) or []
|
|
60
|
+
if len(templates) == 0:
|
|
61
|
+
raise ValueError("At least one CustomTemplate must be provided.")
|
|
62
|
+
if len(templates) > 10:
|
|
63
|
+
raise ValueError("A maximum of 10 CustomTemplates can be created at once.")
|
|
64
|
+
|
|
65
|
+
payload = [
|
|
66
|
+
template.model_dump(
|
|
67
|
+
mode="json",
|
|
68
|
+
by_alias=True,
|
|
69
|
+
exclude_none=True,
|
|
70
|
+
exclude_unset=True,
|
|
71
|
+
)
|
|
72
|
+
for template in templates
|
|
73
|
+
]
|
|
74
|
+
response = self.session.post(url=self.base_path, json=payload)
|
|
75
|
+
response_data = response.json()
|
|
76
|
+
created_payloads = (
|
|
77
|
+
(response_data or {}).get("CreatedItems")
|
|
78
|
+
if response.status_code == 206
|
|
79
|
+
else response_data
|
|
80
|
+
) or []
|
|
81
|
+
|
|
82
|
+
tag_collection = TagCollection(session=self.session)
|
|
83
|
+
|
|
84
|
+
def resolve_tag(tag_id: str | None) -> dict[str, str] | None:
|
|
85
|
+
if not tag_id:
|
|
86
|
+
return None
|
|
87
|
+
tag = tag_collection.get_by_id(id=tag_id)
|
|
88
|
+
return {"albertId": tag.id or tag_id, "name": tag.tag}
|
|
89
|
+
|
|
90
|
+
def populate_tag_names(section: dict | None) -> None:
|
|
91
|
+
if not isinstance(section, dict):
|
|
92
|
+
return
|
|
93
|
+
tags = section.get("Tags")
|
|
94
|
+
if not tags:
|
|
95
|
+
return
|
|
96
|
+
resolved_tags = []
|
|
97
|
+
for tag in tags:
|
|
98
|
+
if isinstance(tag, dict):
|
|
99
|
+
tag_id = tag.get("id") or tag.get("albertId")
|
|
100
|
+
elif isinstance(tag, str):
|
|
101
|
+
tag_id = tag
|
|
102
|
+
else:
|
|
103
|
+
tag_id = None
|
|
104
|
+
|
|
105
|
+
resolved_tag = resolve_tag(tag_id)
|
|
106
|
+
if resolved_tag:
|
|
107
|
+
resolved_tags.append(resolved_tag)
|
|
108
|
+
section["Tags"] = resolved_tags
|
|
109
|
+
|
|
110
|
+
for payload in created_payloads:
|
|
111
|
+
if not isinstance(payload, dict):
|
|
112
|
+
continue
|
|
113
|
+
populate_tag_names(payload.get("Data"))
|
|
114
|
+
|
|
115
|
+
if response.status_code == 206:
|
|
116
|
+
failed_items = response_data.get("FailedItems") or []
|
|
117
|
+
if failed_items:
|
|
118
|
+
error_messages = []
|
|
119
|
+
for failed in failed_items:
|
|
120
|
+
errors = failed.get("errors") or []
|
|
121
|
+
if errors:
|
|
122
|
+
error_messages.extend(err.get("msg", "Unknown error") for err in errors)
|
|
123
|
+
joined = " | ".join(error_messages) if error_messages else "Unknown error"
|
|
124
|
+
logger.warning(
|
|
125
|
+
"Custom template creation partially succeeded. Errors: %s",
|
|
126
|
+
joined,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
hydrated_templates = []
|
|
130
|
+
for payload in created_payloads:
|
|
131
|
+
template = CustomTemplate(**payload)
|
|
132
|
+
hydrated = self.get_by_id(id=template.id)
|
|
133
|
+
hydrated_templates.append(hydrated)
|
|
134
|
+
|
|
135
|
+
return hydrated_templates
|
|
136
|
+
|
|
34
137
|
def get_by_id(self, *, id: CustomTemplateId) -> CustomTemplate:
|
|
35
138
|
"""Get a Custom Template by ID
|
|
36
139
|
|
|
@@ -52,8 +155,20 @@ class CustomTemplatesCollection(BaseCollection):
|
|
|
52
155
|
self,
|
|
53
156
|
*,
|
|
54
157
|
text: str | None = None,
|
|
55
|
-
max_items: int | None = None,
|
|
56
158
|
offset: int | None = 0,
|
|
159
|
+
sort_by: str | None = None,
|
|
160
|
+
order_by: OrderBy | None = None,
|
|
161
|
+
status: Status | None = None,
|
|
162
|
+
created_by: str | None = None,
|
|
163
|
+
category: TemplateCategory | list[TemplateCategory] | None = None,
|
|
164
|
+
created_by_name: str | list[str] | None = None,
|
|
165
|
+
collaborator: str | list[str] | None = None,
|
|
166
|
+
facet_text: str | None = None,
|
|
167
|
+
facet_field: str | None = None,
|
|
168
|
+
contains_field: str | list[str] | None = None,
|
|
169
|
+
contains_text: str | list[str] | None = None,
|
|
170
|
+
my_role: str | list[str] | None = None,
|
|
171
|
+
max_items: int | None = None,
|
|
57
172
|
) -> Iterator[CustomTemplateSearchItem]:
|
|
58
173
|
"""
|
|
59
174
|
Search for CustomTemplate matching the provided criteria.
|
|
@@ -64,20 +179,57 @@ class CustomTemplatesCollection(BaseCollection):
|
|
|
64
179
|
Parameters
|
|
65
180
|
----------
|
|
66
181
|
text : str, optional
|
|
67
|
-
|
|
68
|
-
max_items : int, optional
|
|
69
|
-
Maximum number of items to return in total. If None, fetches all available items.
|
|
182
|
+
Free text search term.
|
|
70
183
|
offset : int, optional
|
|
71
|
-
|
|
184
|
+
Starting offset for pagination.
|
|
185
|
+
sort_by : str, optional
|
|
186
|
+
Field to sort on.
|
|
187
|
+
order_by : OrderBy, optional
|
|
188
|
+
Sort direction for `sort_by`.
|
|
189
|
+
status : Status | str, optional
|
|
190
|
+
Filter results by template status.
|
|
191
|
+
created_by : str, optional
|
|
192
|
+
Filter by creator id.
|
|
193
|
+
category : TemplateCategory | list[TemplateCategory], optional
|
|
194
|
+
Filter by template categories.
|
|
195
|
+
created_by_name : str | list[str], optional
|
|
196
|
+
Filter by creator display name(s).
|
|
197
|
+
collaborator : str | list[str], optional
|
|
198
|
+
Filter by collaborator ids.
|
|
199
|
+
facet_text : str, optional
|
|
200
|
+
Filter text within a facet.
|
|
201
|
+
facet_field : str, optional
|
|
202
|
+
Facet field to search inside.
|
|
203
|
+
contains_field : str | list[str], optional
|
|
204
|
+
Fields to apply contains search to.
|
|
205
|
+
contains_text : str | list[str], optional
|
|
206
|
+
Text values for contains search.
|
|
207
|
+
my_role : str | list[str], optional
|
|
208
|
+
Restrict templates to roles held by the calling user.
|
|
209
|
+
max_items : int, optional
|
|
210
|
+
Maximum number of items to yield client-side.
|
|
72
211
|
|
|
73
212
|
Returns
|
|
74
213
|
-------
|
|
75
214
|
Iterator[CustomTemplateSearchItem]
|
|
76
215
|
An iterator of CustomTemplateSearchItem items.
|
|
77
216
|
"""
|
|
217
|
+
|
|
78
218
|
params = {
|
|
79
219
|
"text": text,
|
|
80
220
|
"offset": offset,
|
|
221
|
+
"sortBy": sort_by,
|
|
222
|
+
"order": order_by,
|
|
223
|
+
"status": status,
|
|
224
|
+
"createdBy": created_by,
|
|
225
|
+
"category": ensure_list(category),
|
|
226
|
+
"createdByName": ensure_list(created_by_name),
|
|
227
|
+
"collaborator": ensure_list(collaborator),
|
|
228
|
+
"facetText": facet_text,
|
|
229
|
+
"facetField": facet_field,
|
|
230
|
+
"containsField": ensure_list(contains_field),
|
|
231
|
+
"containsText": ensure_list(contains_text),
|
|
232
|
+
"myRole": ensure_list(my_role),
|
|
81
233
|
}
|
|
82
234
|
|
|
83
235
|
return AlbertPaginator(
|
|
@@ -94,32 +246,169 @@ class CustomTemplatesCollection(BaseCollection):
|
|
|
94
246
|
def get_all(
|
|
95
247
|
self,
|
|
96
248
|
*,
|
|
97
|
-
|
|
249
|
+
name: str | list[str] | None = None,
|
|
250
|
+
created_by: str | None = None,
|
|
251
|
+
category: TemplateCategory | None = None,
|
|
252
|
+
start_key: str | None = None,
|
|
98
253
|
max_items: int | None = None,
|
|
99
|
-
offset: int | None = 0,
|
|
100
254
|
) -> Iterator[CustomTemplate]:
|
|
101
|
-
"""
|
|
102
|
-
Retrieve fully hydrated CustomTemplate entities with optional filters.
|
|
103
|
-
|
|
104
|
-
This method returns complete entity data using `get_by_id`.
|
|
105
|
-
Use :meth:`search` for faster retrieval when you only need lightweight, partial (unhydrated) entities.
|
|
255
|
+
"""Iterate over CustomTemplate entities with optional filters.
|
|
106
256
|
|
|
107
257
|
Parameters
|
|
108
258
|
----------
|
|
109
|
-
|
|
110
|
-
|
|
259
|
+
name : str | list[str], optional
|
|
260
|
+
Filter by template name(s).
|
|
261
|
+
created_by : str, optional
|
|
262
|
+
Filter by creator id.
|
|
263
|
+
category : TemplateCategory, optional
|
|
264
|
+
Filter by category.
|
|
265
|
+
start_key : str, optional
|
|
266
|
+
Provide the `lastKey` from a previous request to resume pagination.
|
|
111
267
|
max_items : int, optional
|
|
112
|
-
Maximum number of items to return
|
|
113
|
-
offset : int, optional
|
|
114
|
-
Offset for search pagination.
|
|
268
|
+
Maximum number of items to return.
|
|
115
269
|
|
|
116
270
|
Returns
|
|
117
271
|
-------
|
|
118
272
|
Iterator[CustomTemplate]
|
|
119
|
-
An iterator of
|
|
273
|
+
An iterator of CustomTemplates.
|
|
120
274
|
"""
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
275
|
+
params = {
|
|
276
|
+
"startKey": start_key,
|
|
277
|
+
"createdBy": created_by,
|
|
278
|
+
"category": category,
|
|
279
|
+
}
|
|
280
|
+
params["name"] = ensure_list(name)
|
|
281
|
+
|
|
282
|
+
return AlbertPaginator(
|
|
283
|
+
mode=PaginationMode.KEY,
|
|
284
|
+
path=self.base_path,
|
|
285
|
+
session=self.session,
|
|
286
|
+
params=params,
|
|
287
|
+
max_items=max_items,
|
|
288
|
+
deserialize=lambda items: [CustomTemplate(**item) for item in items],
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
@validate_call
|
|
292
|
+
def delete(self, *, id: CustomTemplateId) -> None:
|
|
293
|
+
"""
|
|
294
|
+
Delete a custom template by id.
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
id : CustomTemplateId
|
|
299
|
+
The id of the custom template to delete.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
None
|
|
304
|
+
"""
|
|
305
|
+
|
|
306
|
+
url = f"{self.base_path}/{id}"
|
|
307
|
+
self.session.delete(url)
|
|
308
|
+
|
|
309
|
+
@validate_call
|
|
310
|
+
def update_acl(
|
|
311
|
+
self,
|
|
312
|
+
*,
|
|
313
|
+
custom_template_id: CustomTemplateId,
|
|
314
|
+
acl_class: str | None = None,
|
|
315
|
+
acls: list[ACL] | None = None,
|
|
316
|
+
) -> CustomTemplate:
|
|
317
|
+
"""
|
|
318
|
+
Replace a template's ACL class and/or entries with the provided values.
|
|
319
|
+
|
|
320
|
+
Parameters
|
|
321
|
+
----------
|
|
322
|
+
custom_template_id : CustomTemplateId
|
|
323
|
+
The id of the custom template to update.
|
|
324
|
+
acl_class : str | None, optional
|
|
325
|
+
The ACL class to set (if provided).
|
|
326
|
+
acls : list[ACL] | None, optional
|
|
327
|
+
The ACL entries to replace on the template.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
CustomTemplate
|
|
332
|
+
The updated CustomTemplate.
|
|
333
|
+
"""
|
|
334
|
+
|
|
335
|
+
if acl_class is None and acls is None:
|
|
336
|
+
raise ValueError("Provide an ACL class and/or ACL entries to update.")
|
|
337
|
+
|
|
338
|
+
data = []
|
|
339
|
+
current_template: CustomTemplate | None = None
|
|
340
|
+
|
|
341
|
+
if acl_class is not None:
|
|
342
|
+
acl_class_value = getattr(acl_class, "value", acl_class)
|
|
343
|
+
data.append(
|
|
344
|
+
{
|
|
345
|
+
"operation": PatchOperation.UPDATE.value,
|
|
346
|
+
"attribute": "class",
|
|
347
|
+
"newValue": acl_class_value,
|
|
348
|
+
}
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if acls is not None:
|
|
352
|
+
current_template = self.get_by_id(id=custom_template_id)
|
|
353
|
+
current_acl = (
|
|
354
|
+
current_template.acl.fgclist
|
|
355
|
+
if current_template.acl and current_template.acl.fgclist
|
|
356
|
+
else []
|
|
357
|
+
)
|
|
358
|
+
current_entries = {
|
|
359
|
+
entry.id: getattr(entry.fgc, "value", entry.fgc) for entry in current_acl
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
desired_entries = {entry.id: getattr(entry.fgc, "value", entry.fgc) for entry in acls}
|
|
363
|
+
|
|
364
|
+
to_add = []
|
|
365
|
+
to_delete = []
|
|
366
|
+
to_update = []
|
|
367
|
+
|
|
368
|
+
for entry_id, new_fgc in desired_entries.items():
|
|
369
|
+
if entry_id not in current_entries:
|
|
370
|
+
payload = {"id": entry_id}
|
|
371
|
+
if new_fgc is not None:
|
|
372
|
+
payload["fgc"] = new_fgc
|
|
373
|
+
to_add.append(payload)
|
|
374
|
+
else:
|
|
375
|
+
old_fgc = current_entries[entry_id]
|
|
376
|
+
if new_fgc is not None and old_fgc != new_fgc:
|
|
377
|
+
to_update.append(
|
|
378
|
+
{
|
|
379
|
+
"operation": PatchOperation.UPDATE.value,
|
|
380
|
+
"attribute": "fgc",
|
|
381
|
+
"id": entry_id,
|
|
382
|
+
"oldValue": old_fgc,
|
|
383
|
+
"newValue": new_fgc,
|
|
384
|
+
}
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
for entry_id in current_entries:
|
|
388
|
+
if entry_id not in desired_entries:
|
|
389
|
+
to_delete.append({"id": entry_id})
|
|
390
|
+
|
|
391
|
+
if to_add:
|
|
392
|
+
data.append(
|
|
393
|
+
{
|
|
394
|
+
"operation": PatchOperation.ADD.value,
|
|
395
|
+
"attribute": "ACL",
|
|
396
|
+
"newValue": to_add,
|
|
397
|
+
}
|
|
398
|
+
)
|
|
399
|
+
if to_delete:
|
|
400
|
+
data.append(
|
|
401
|
+
{
|
|
402
|
+
"operation": PatchOperation.DELETE.value,
|
|
403
|
+
"attribute": "ACL",
|
|
404
|
+
"oldValue": to_delete,
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
data.extend(to_update)
|
|
408
|
+
|
|
409
|
+
if not data:
|
|
410
|
+
return current_template or self.get_by_id(id=custom_template_id)
|
|
411
|
+
|
|
412
|
+
url = f"{self.base_path}/{custom_template_id}/acl"
|
|
413
|
+
self.session.patch(url, json={"data": data})
|
|
414
|
+
return self.get_by_id(id=custom_template_id)
|
|
@@ -7,6 +7,7 @@ from albert.core.pagination import AlbertPaginator
|
|
|
7
7
|
from albert.core.session import AlbertSession
|
|
8
8
|
from albert.core.shared.enums import OrderBy, PaginationMode
|
|
9
9
|
from albert.core.shared.identifiers import DataColumnId
|
|
10
|
+
from albert.core.utils import ensure_list
|
|
10
11
|
from albert.resources.data_columns import DataColumn
|
|
11
12
|
|
|
12
13
|
|
|
@@ -102,12 +103,12 @@ class DataColumnCollection(BaseCollection):
|
|
|
102
103
|
yield from (DataColumn(**item) for item in items)
|
|
103
104
|
|
|
104
105
|
params = {
|
|
105
|
-
"orderBy": order_by
|
|
106
|
+
"orderBy": order_by,
|
|
106
107
|
"startKey": start_key,
|
|
107
|
-
"name":
|
|
108
|
+
"name": ensure_list(name),
|
|
108
109
|
"exactMatch": exact_match,
|
|
109
110
|
"default": default,
|
|
110
|
-
"dataColumns":
|
|
111
|
+
"dataColumns": ensure_list(ids),
|
|
111
112
|
}
|
|
112
113
|
|
|
113
114
|
return AlbertPaginator(
|
|
@@ -282,10 +282,10 @@ class EntityTypeCollection(BaseCollection):
|
|
|
282
282
|
Returns an iterator of EntityType items matching the search criteria.
|
|
283
283
|
"""
|
|
284
284
|
params = {
|
|
285
|
-
"service": service
|
|
285
|
+
"service": service,
|
|
286
286
|
"limit": max_items,
|
|
287
287
|
"startKey": start_key,
|
|
288
|
-
"orderBy": order
|
|
288
|
+
"orderBy": order,
|
|
289
289
|
}
|
|
290
290
|
return AlbertPaginator(
|
|
291
291
|
mode=PaginationMode.KEY,
|
albert/collections/inventory.py
CHANGED
|
@@ -16,6 +16,7 @@ from albert.core.shared.identifiers import (
|
|
|
16
16
|
SearchProjectId,
|
|
17
17
|
WorksheetId,
|
|
18
18
|
)
|
|
19
|
+
from albert.core.utils import ensure_list
|
|
19
20
|
from albert.resources.facet import FacetItem
|
|
20
21
|
from albert.resources.inventory import (
|
|
21
22
|
ALL_MERGE_MODULES,
|
|
@@ -88,10 +89,10 @@ class InventoryCollection(BaseCollection):
|
|
|
88
89
|
# define merge endpoint
|
|
89
90
|
url = f"{self.base_path}/merge"
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
92
|
+
child_ids = ensure_list(child_id) or []
|
|
93
|
+
if not child_ids:
|
|
94
|
+
raise ValueError("At least one child inventory id is required for merge operations.")
|
|
95
|
+
child_inventories = [{"id": i} for i in child_ids]
|
|
95
96
|
|
|
96
97
|
# define payload using the class
|
|
97
98
|
payload = MergeInventory(
|
|
@@ -360,9 +361,9 @@ class InventoryCollection(BaseCollection):
|
|
|
360
361
|
|
|
361
362
|
params = {
|
|
362
363
|
"text": text,
|
|
363
|
-
"order": order
|
|
364
|
+
"order": order,
|
|
364
365
|
"sortBy": sort_by if sort_by is not None else None,
|
|
365
|
-
"category":
|
|
366
|
+
"category": category,
|
|
366
367
|
"tags": tags,
|
|
367
368
|
"manufacturer": [c.name for c in company] if company is not None else None,
|
|
368
369
|
"cas": [c.number for c in cas] if cas is not None else None,
|
|
@@ -447,8 +448,7 @@ class InventoryCollection(BaseCollection):
|
|
|
447
448
|
This can be used for example to fetch all remaining tags as part of an iterative
|
|
448
449
|
refinement of a search.
|
|
449
450
|
"""
|
|
450
|
-
|
|
451
|
-
name = [name]
|
|
451
|
+
name = ensure_list(name) or []
|
|
452
452
|
|
|
453
453
|
facets = self.get_all_facets(
|
|
454
454
|
text=text,
|
albert/collections/lists.py
CHANGED
|
@@ -92,7 +92,7 @@ class ListsCollection(BaseCollection):
|
|
|
92
92
|
params = {
|
|
93
93
|
"startKey": start_key,
|
|
94
94
|
"name": names,
|
|
95
|
-
"category": category
|
|
95
|
+
"category": category,
|
|
96
96
|
"listType": list_type,
|
|
97
97
|
"orderBy": order_by,
|
|
98
98
|
}
|
albert/collections/locations.py
CHANGED
|
@@ -4,6 +4,7 @@ from albert.collections.base import BaseCollection
|
|
|
4
4
|
from albert.core.pagination import AlbertPaginator
|
|
5
5
|
from albert.core.session import AlbertSession
|
|
6
6
|
from albert.core.shared.enums import PaginationMode
|
|
7
|
+
from albert.core.utils import ensure_list
|
|
7
8
|
from albert.resources.locations import Location
|
|
8
9
|
|
|
9
10
|
|
|
@@ -64,9 +65,8 @@ class LocationCollection(BaseCollection):
|
|
|
64
65
|
}
|
|
65
66
|
if ids:
|
|
66
67
|
params["id"] = ids
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
params["exactMatch"] = exact_match
|
|
68
|
+
params["name"] = ensure_list(name)
|
|
69
|
+
params["exactMatch"] = exact_match
|
|
70
70
|
|
|
71
71
|
return AlbertPaginator(
|
|
72
72
|
mode=PaginationMode.KEY,
|