Skip to content

Commit d9d8834

Browse files
authored
Add support for creating Freezer Manager freezer hierarchies via StorageController APIs (#52)
* StorageWrapper: create_storage_item, update_storage_item, delete_storage_item * docs/storage.md with info and examples
1 parent 7644bd6 commit d9d8834

File tree

6 files changed

+278
-3
lines changed

6 files changed

+278
-3
lines changed

CHANGE.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
+++++++++++
22
LabKey Python Client API News
33
+++++++++++
4+
What's New in the LabKey 2.4.0 package
5+
==============================
6+
7+
*Release date: 09/21/2022*
8+
- Add support for creating Freezer Manager freezer hierarchies via StorageController APIs
9+
- earliest compatible LabKey Server version: 22.10.0
10+
- StorageWrapper: create_storage_item, update_storage_item, delete_storage_item
11+
412
What's New in the LabKey 2.3.0 package
513
==============================
614

README.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,13 @@ Security API - [sample code](samples/security_example.py)
3636

3737
- Available for administrating and configuring user accounts and permissions.
3838

39-
WebDav
39+
Storage API - [docs](docs/storage.md)
4040

41-
- Documentation and example code can be found [here](docs/webdav.md).
41+
- Create, update, or delete a LabKey Freezer Manager storage item.
42+
43+
WebDav - [docs](docs/webdav.md)
44+
45+
- Convenience methods for creating "webdavclient3" clients and building webdav file paths.
4246

4347
## Installation
4448
To install, simply use `pip`:

docs/storage.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# LabKey Storage API Support
2+
3+
Create, update, or delete a LabKey Freezer Manager storage item.
4+
5+
Storage items can be used in the creation of a freezer hierarchy. Freezer hierarchies consist of a top level Freezer,
6+
which can have any combination of child non-terminal storage locations (i.e. those that do not directly contain samples
7+
but can contain other units) and terminal storage locations (i.e. units in the freezer that directly contain samples
8+
and cannot contain other units).
9+
10+
Storage items can be of the following types: Physical Location, Freezer, Shelf, Rack, Canister, Storage Unit Type, or
11+
Terminal Storage Location.
12+
13+
The specific set of props will differ for each storage item type:
14+
- Physical Location: name, description, locationId (rowId of the parent Physical Location)
15+
- Freezer: name, description, locationId (rowId of the parent Physical Location), manufacturer, freezerModel, temperature, temperatureUnits, serialNumber, sensorName, lossRate, status
16+
- Shelf/Rack/Canister: name, description, locationId (rowId of the parent freezer or Shelf/Rack/Canister)
17+
- Storage Unit Type: name, description, unitType (one of the following: "Box", "Plate", "Bag", "Cane", "Tube Rack"), rows, cols (required if positionFormat is not "Num"), positionFormat (one of the following: "Num", "AlphaNum", "AlphaAlpha", "NumAlpha", "NumNum"), positionOrder (one of the following: "RowColumn", "ColumnRow")
18+
- Terminal Storage Location: name, description, typeId (rowId of the Storage Unit Type), locationId (rowId of the parent freezer or Shelf/Rack/Canister)
19+
20+
### Installation and Setup for the LabKey Python API:
21+
- https://github.com/LabKey/labkey-api-python/blob/master/README.md
22+
23+
### Additional details from Labkey Documentation:
24+
- https://www.labkey.org/SampleManagerHelp/wiki-page.view?name=createFreezer
25+
- https://www.labkey.org/SampleManagerHelp/wiki-page.view?name=freezerLocation
26+
27+
### Examples
28+
29+
```python
30+
from labkey.api_wrapper import APIWrapper
31+
32+
labkey_server = "localhost:8080"
33+
project_name = "FM API Test" # Project folder name
34+
contextPath = "labkey"
35+
api = APIWrapper(labkey_server, project_name, contextPath, use_ssl=False)
36+
37+
38+
###############
39+
# Create a freezer with two shelves
40+
###############
41+
result = api.storage.create_storage_item(
42+
"Freezer",
43+
{
44+
"name": "Freezer #1",
45+
"description": "Test freezer from API",
46+
"serialNumber": "ABC123",
47+
"status": "Active",
48+
},
49+
)
50+
if result is not None:
51+
print(result)
52+
else:
53+
print("Create freezer: no results returned")
54+
exit()
55+
freezer_row_id = result["data"]["rowId"]
56+
57+
result = api.storage.create_storage_item(
58+
"Shelf",
59+
{
60+
"name": "Shelf #1",
61+
"description": "This shelf is for samples from Lab A.",
62+
"locationId": freezer_row_id,
63+
},
64+
)
65+
if result is not None:
66+
print(result)
67+
else:
68+
print("Create shelf: no results returned")
69+
exit()
70+
shelf1_row_id = result["data"]["rowId"]
71+
72+
result = api.storage.create_storage_item(
73+
"Shelf",
74+
{
75+
"name": "Shelf #2",
76+
"description": "This shelf is for samples from Lab B.",
77+
"locationId": freezer_row_id,
78+
},
79+
)
80+
if result is not None:
81+
print(result)
82+
else:
83+
print("Create shelf: no results returned")
84+
exit()
85+
shelf2_row_id = result["data"]["rowId"]
86+
87+
###############
88+
# Create a terminal storage location in the freezer
89+
###############
90+
result = api.storage.create_storage_item(
91+
"Storage Unit Type", {"name": "10 X 10 Box", "unitType": "Box", "rows": 10, "cols": 10}
92+
)
93+
if result is not None:
94+
print(result)
95+
else:
96+
print("Create storage unit type: no results returned")
97+
exit()
98+
box_type_id = result["data"]["rowId"]
99+
100+
result = api.storage.create_storage_item(
101+
"Terminal Storage Location",
102+
{"name": "Box #1", "typeId": box_type_id, "locationId": shelf1_row_id},
103+
)
104+
if result is not None:
105+
print(result)
106+
else:
107+
print("Create box: no results returned")
108+
exit()
109+
box_id = result["data"]["rowId"]
110+
111+
###############
112+
# Update the location of a box in the freezer
113+
###############
114+
result = api.storage.update_storage_item(
115+
"Terminal Storage Location", {"rowId": box_id, "locationId": shelf2_row_id}
116+
)
117+
if result is not None:
118+
print(result)
119+
else:
120+
print("Update box: no results returned")
121+
exit()
122+
123+
###############
124+
# Delete the freezer, which will delete the full hierarchy of non-terminal and terminal storage locations
125+
###############
126+
result = api.storage.delete_storage_item("Freezer", freezer_row_id)
127+
if result is not None:
128+
print(result)
129+
else:
130+
print("Delete freezer: no results returned")
131+
exit()
132+
```

labkey/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
from labkey import domain, query, experiment, security, utils
1717

1818
__title__ = "labkey"
19-
__version__ = "2.3.0"
19+
__version__ = "2.4.0"
2020
__author__ = "LabKey"
2121
__license__ = "Apache License 2.0"

labkey/api_wrapper.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from .experiment import ExperimentWrapper
44
from .query import QueryWrapper
55
from .security import SecurityWrapper
6+
from .storage import StorageWrapper
67
from .server_context import ServerContext
78

89

@@ -36,3 +37,4 @@ def __init__(
3637
self.experiment = ExperimentWrapper(self.server_context)
3738
self.query = QueryWrapper(self.server_context)
3839
self.security = SecurityWrapper(self.server_context)
40+
self.storage = StorageWrapper(self.server_context)

labkey/storage.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#
2+
# Copyright (c) 2017-2018 LabKey Corporation
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
"""
17+
############################################################################
18+
NAME:
19+
LabKey Storage API
20+
21+
SUMMARY:
22+
This module provides functions for interacting with storage items on a LabKey Server.
23+
24+
DESCRIPTION:
25+
Create, update, or delete a LabKey Freezer Manager storage item. Storage items can be used in the creation of a
26+
freezer hierarchy. Freezer hierarchies consist of a top level Freezer, which can have any combination of child
27+
non-terminal storage locations (i.e. those that do not directly contain samples but can contain other units) and
28+
terminal storage locations (i.e. units in the freezer that directly contain samples and cannot contain other units).
29+
30+
Storage items can be of the following types: Physical Location, Freezer, Shelf, Rack, Canister, Storage Unit Type, or Terminal Storage Location.
31+
The specific set of props will differ for each storage item type:
32+
- Physical Location: name, description, locationId (rowId of the parent Physical Location)
33+
- Freezer: name, description, locationId (rowId of the parent Physical Location), manufacturer, freezerModel, temperature, temperatureUnits, serialNumber, sensorName, lossRate, status
34+
- Shelf/Rack/Canister: name, description, locationId (rowId of the parent freezer or Shelf/Rack/Canister)
35+
- Storage Unit Type: name, description, unitType (one of the following: "Box", "Plate", "Bag", "Cane", "Tube Rack"), rows, cols (required if positionFormat is not "Num"), positionFormat (one of the following: "Num", "AlphaNum", "AlphaAlpha", "NumAlpha", "NumNum"), positionOrder (one of the following: "RowColumn", "ColumnRow")
36+
- Terminal Storage Location: name, description, typeId (rowId of the Storage Unit Type), locationId (rowId of the parent freezer or Shelf/Rack/Canister)
37+
38+
Installation and Setup for the LabKey Python API:
39+
https://github.com/LabKey/labkey-api-python/blob/master/README.md
40+
41+
Additional details from Labkey Documentation:
42+
https://www.labkey.org/SampleManagerHelp/wiki-page.view?name=createFreezer
43+
https://www.labkey.org/SampleManagerHelp/wiki-page.view?name=freezerLocation
44+
45+
############################################################################
46+
"""
47+
import functools
48+
from dataclasses import dataclass
49+
50+
from typing import Union, List
51+
52+
from labkey.server_context import ServerContext
53+
54+
STORAGE_CONTROLLER = "storage"
55+
56+
57+
def create_storage_item(
58+
server_context: ServerContext, type: str, props: dict, container_path: str = None
59+
):
60+
"""
61+
Create a new LabKey Freezer Manager storage item that can be used in the creation of a freezer hierarchy.
62+
:param server_context: A LabKey server context. See utils.create_server_context.
63+
:param type:
64+
:param props:
65+
:param container_path:
66+
:return:
67+
"""
68+
url = server_context.build_url(STORAGE_CONTROLLER, "create.api", container_path)
69+
payload = {"type": type, "props": props}
70+
71+
return server_context.make_request(url, json=payload)
72+
73+
74+
def update_storage_item(
75+
server_context: ServerContext, type: str, props: dict, container_path: str = None
76+
):
77+
"""
78+
Update an existing LabKey Freezer Manager storage item to change its properties or location within the freezer hierarchy.
79+
For update_storage_item, the "rowId" primary key value is required to be set within the props.
80+
:param server_context: A LabKey server context. See utils.create_server_context.
81+
:param type:
82+
:param props:
83+
:param container_path:
84+
:return:
85+
"""
86+
url = server_context.build_url(STORAGE_CONTROLLER, "update.api", container_path)
87+
payload = {"type": type, "props": props}
88+
89+
return server_context.make_request(url, json=payload)
90+
91+
92+
def delete_storage_item(
93+
server_context: ServerContext, type: str, row_id: int, container_path: str = None
94+
):
95+
"""
96+
Delete an existing LabKey Freezer Manager storage item. Note that deletion of freezers or locations within the
97+
freezer hierarchy will cascade the delete down the hierarchy to remove child locations and terminal storage locations.
98+
Samples in the deleted freezer location(s) will not be deleted but will be removed from storage.
99+
:param server_context: A LabKey server context. See utils.create_server_context.
100+
:param type:
101+
:param row_id:
102+
:param container_path:
103+
:return:
104+
"""
105+
url = server_context.build_url(STORAGE_CONTROLLER, "delete.api", container_path)
106+
payload = {"type": type, "props": {"rowId": row_id}}
107+
108+
return server_context.make_request(url, json=payload)
109+
110+
111+
class StorageWrapper:
112+
"""
113+
Wrapper for all of the API methods exposed in the storage module. Used by the APIWrapper class.
114+
"""
115+
116+
def __init__(self, server_context: ServerContext):
117+
self.server_context = server_context
118+
119+
@functools.wraps(create_storage_item)
120+
def create_storage_item(self, type: str, props: dict, container_path: str = None):
121+
return create_storage_item(self.server_context, type, props, container_path)
122+
123+
@functools.wraps(update_storage_item)
124+
def update_storage_item(self, type: str, props: dict, container_path: str = None):
125+
return update_storage_item(self.server_context, type, props, container_path)
126+
127+
@functools.wraps(delete_storage_item)
128+
def delete_storage_item(self, type: str, row_id: int, container_path: str = None):
129+
return delete_storage_item(self.server_context, type, row_id, container_path)

0 commit comments

Comments
 (0)