From 2fe851c4754aab40739516ca627cdd902dac14bb Mon Sep 17 00:00:00 2001 From: XingY Date: Thu, 18 Jun 2026 19:18:19 -0700 Subject: [PATCH 1/2] Allow require reason on storage changes --- CHANGE.txt | 6 ++++++ labkey/storage.py | 27 ++++++++++++++++++--------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/CHANGE.txt b/CHANGE.txt index 0349142..3d77e71 100644 --- a/CHANGE.txt +++ b/CHANGE.txt @@ -2,6 +2,12 @@ LabKey Python Client API News +++++++++++ +What's New in the LabKey 4.3.0 package +============================== + +*Release date: XX/2026* +- Freezer Manager API - add `auditUserComment` to create/update/delete_storage_item + What's New in the LabKey 4.2.0 package ============================== diff --git a/labkey/storage.py b/labkey/storage.py index b597b24..ab5c1e3 100644 --- a/labkey/storage.py +++ b/labkey/storage.py @@ -58,7 +58,7 @@ def create_storage_item( - server_context: ServerContext, type: str, props: dict, container_path: str = None + server_context: ServerContext, type: str, props: dict, container_path: str = None, audit_user_comment: str = None ): """ Create a new LabKey Freezer Manager storage item that can be used in the creation of a storage hierarchy. @@ -66,16 +66,19 @@ def create_storage_item( :param type: :param props: :param container_path: + :param audit_user_comment: optional comment that will be attached to the audit log record for this storage change. :return: """ url = server_context.build_url(STORAGE_CONTROLLER, "create.api", container_path) payload = {"type": type, "props": props} + if audit_user_comment is not None: + payload["auditUserComment"] = audit_user_comment return server_context.make_request(url, json=payload) def update_storage_item( - server_context: ServerContext, type: str, props: dict, container_path: str = None + server_context: ServerContext, type: str, props: dict, container_path: str = None, audit_user_comment: str = None ): """ Update an existing LabKey Freezer Manager storage item to change its properties or location within the storage hierarchy. @@ -84,16 +87,19 @@ def update_storage_item( :param type: :param props: :param container_path: + :param audit_user_comment: optional comment that will be attached to the audit log record for this storage change. :return: """ url = server_context.build_url(STORAGE_CONTROLLER, "update.api", container_path) payload = {"type": type, "props": props} + if audit_user_comment is not None: + payload["auditUserComment"] = audit_user_comment return server_context.make_request(url, json=payload) def delete_storage_item( - server_context: ServerContext, type: str, row_id: int, container_path: str = None + server_context: ServerContext, type: str, row_id: int, container_path: str = None, audit_user_comment: str = None ): """ Delete an existing LabKey Freezer Manager storage item. Note that deletion of freezers, primary storage, or locations @@ -103,10 +109,13 @@ def delete_storage_item( :param type: :param row_id: :param container_path: + :param audit_user_comment: optional comment that will be attached to the audit log record for this storage change. :return: """ url = server_context.build_url(STORAGE_CONTROLLER, "delete.api", container_path) payload = {"type": type, "props": {"rowId": row_id}} + if audit_user_comment is not None: + payload["auditUserComment"] = audit_user_comment return server_context.make_request(url, json=payload) @@ -120,13 +129,13 @@ def __init__(self, server_context: ServerContext): self.server_context = server_context @functools.wraps(create_storage_item) - def create_storage_item(self, type: str, props: dict, container_path: str = None): - return create_storage_item(self.server_context, type, props, container_path) + def create_storage_item(self, type: str, props: dict, container_path: str = None, audit_user_comment: str = None): + return create_storage_item(self.server_context, type, props, container_path, audit_user_comment) @functools.wraps(update_storage_item) - def update_storage_item(self, type: str, props: dict, container_path: str = None): - return update_storage_item(self.server_context, type, props, container_path) + def update_storage_item(self, type: str, props: dict, container_path: str = None, audit_user_comment: str = None): + return update_storage_item(self.server_context, type, props, container_path, audit_user_comment) @functools.wraps(delete_storage_item) - def delete_storage_item(self, type: str, row_id: int, container_path: str = None): - return delete_storage_item(self.server_context, type, row_id, container_path) + def delete_storage_item(self, type: str, row_id: int, container_path: str = None, audit_user_comment: str = None): + return delete_storage_item(self.server_context, type, row_id, container_path, audit_user_comment) From 8d43dc470f901b884bba6a13db2bb72532fad490 Mon Sep 17 00:00:00 2001 From: XingY Date: Mon, 22 Jun 2026 14:43:23 -0700 Subject: [PATCH 2/2] Update apis --- docs/storage.md | 20 ++++++++++++++++---- labkey/storage.py | 31 ++++++++++++++----------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/docs/storage.md b/docs/storage.md index 8fe6d0b..67c56ca 100644 --- a/docs/storage.md +++ b/docs/storage.md @@ -110,10 +110,17 @@ else: box_id = result["data"]["rowId"] ############### -# Update the location of a box in the freezer +# Update the location of a box in the freezer. An optional "auditUserComment" +# property may be included in the props dict; it is recorded as the "Reason" +# on the resulting audit event. ############### result = api.storage.update_storage_item( - "Terminal Storage Location", {"rowId": box_id, "locationId": shelf2_row_id} + "Terminal Storage Location", + { + "rowId": box_id, + "locationId": shelf2_row_id, + "auditUserComment": "Relocated to make room for incoming samples from Lab B.", + }, ) if result is not None: print(result) @@ -122,9 +129,14 @@ else: exit() ############### -# Delete the freezer, which will delete the full hierarchy of non-terminal and terminal storage locations +# Delete the freezer, which will delete the full hierarchy of non-terminal and terminal +# storage locations. An optional audit_user_comment is supplied here for the audit log. ############### -result = api.storage.delete_storage_item("Freezer", freezer_row_id) +result = api.storage.delete_storage_item( + "Freezer", + freezer_row_id, + audit_user_comment="Decommissioning Freezer #1 per facilities request.", +) if result is not None: print(result) else: diff --git a/labkey/storage.py b/labkey/storage.py index ab5c1e3..cd58978 100644 --- a/labkey/storage.py +++ b/labkey/storage.py @@ -58,42 +58,38 @@ def create_storage_item( - server_context: ServerContext, type: str, props: dict, container_path: str = None, audit_user_comment: str = None + server_context: ServerContext, type: str, props: dict, container_path: str = None ): """ Create a new LabKey Freezer Manager storage item that can be used in the creation of a storage hierarchy. :param server_context: A LabKey server context. See utils.create_server_context. :param type: - :param props: + :param props: a dict of property values for the storage item. Any storage item type also accepts an optional + "auditUserComment" (string) entry, which is recorded as the "Reason" on the resulting audit event. :param container_path: - :param audit_user_comment: optional comment that will be attached to the audit log record for this storage change. :return: """ url = server_context.build_url(STORAGE_CONTROLLER, "create.api", container_path) payload = {"type": type, "props": props} - if audit_user_comment is not None: - payload["auditUserComment"] = audit_user_comment return server_context.make_request(url, json=payload) def update_storage_item( - server_context: ServerContext, type: str, props: dict, container_path: str = None, audit_user_comment: str = None + server_context: ServerContext, type: str, props: dict, container_path: str = None ): """ Update an existing LabKey Freezer Manager storage item to change its properties or location within the storage hierarchy. For update_storage_item, the "rowId" primary key value is required to be set within the props. :param server_context: A LabKey server context. See utils.create_server_context. :param type: - :param props: + :param props: a dict of property values for the storage item. Any storage item type also accepts an optional + "auditUserComment" (string) entry, which is recorded as the "Reason" on the resulting audit event. :param container_path: - :param audit_user_comment: optional comment that will be attached to the audit log record for this storage change. :return: """ url = server_context.build_url(STORAGE_CONTROLLER, "update.api", container_path) payload = {"type": type, "props": props} - if audit_user_comment is not None: - payload["auditUserComment"] = audit_user_comment return server_context.make_request(url, json=payload) @@ -109,13 +105,14 @@ def delete_storage_item( :param type: :param row_id: :param container_path: - :param audit_user_comment: optional comment that will be attached to the audit log record for this storage change. + :param audit_user_comment: optional reason text recorded as the "Reason" on the resulting audit event. :return: """ url = server_context.build_url(STORAGE_CONTROLLER, "delete.api", container_path) - payload = {"type": type, "props": {"rowId": row_id}} + props = {"rowId": row_id} if audit_user_comment is not None: - payload["auditUserComment"] = audit_user_comment + props["auditUserComment"] = audit_user_comment + payload = {"type": type, "props": props} return server_context.make_request(url, json=payload) @@ -129,12 +126,12 @@ def __init__(self, server_context: ServerContext): self.server_context = server_context @functools.wraps(create_storage_item) - def create_storage_item(self, type: str, props: dict, container_path: str = None, audit_user_comment: str = None): - return create_storage_item(self.server_context, type, props, container_path, audit_user_comment) + def create_storage_item(self, type: str, props: dict, container_path: str = None): + return create_storage_item(self.server_context, type, props, container_path) @functools.wraps(update_storage_item) - def update_storage_item(self, type: str, props: dict, container_path: str = None, audit_user_comment: str = None): - return update_storage_item(self.server_context, type, props, container_path, audit_user_comment) + def update_storage_item(self, type: str, props: dict, container_path: str = None): + return update_storage_item(self.server_context, type, props, container_path) @functools.wraps(delete_storage_item) def delete_storage_item(self, type: str, row_id: int, container_path: str = None, audit_user_comment: str = None):