Skip to content

Commit 120d78c

Browse files
author
labkey-ians
committed
Spec 24062: Support Python 3
- added some mocking for unittests - added additional documentation - added execeptions to handle_response, since it would otherwise just swallow anything not 200
1 parent 75cac39 commit 120d78c

16 files changed

+911
-47
lines changed

labkey/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@
1414
# limitations under the License.
1515
#
1616
from labkey import query, experiment # wiki, messageboard
17+
from pkg_resources import get_distribution
1718

18-
__title__ = 'labkey'
19-
__version__ = '0.3.0'
19+
__title__ = get_distribution('labkey').project_name
20+
__version__ = get_distribution('labkey').version
2021
__author__ = 'LabKey Software'
21-
__license__ = 'Apache 2.0'
22+
__license__ = 'Apache License 2.0'

labkey/experiment.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from requests.exceptions import SSLError
2020
from labkey.utils import build_url, handle_response
21+
from labkey.exceptions import ServerContextError
2122

2223

2324
# EXAMPLE
@@ -49,8 +50,8 @@
4950
# --------
5051
# /EXAMPLE
5152

52-
# TODO Incorporate logging
5353

54+
# TODO Incorporate logging
5455
def load_batch(server_context, assay_id, batch_id):
5556
"""
5657
Loads a batch from the server.
@@ -74,12 +75,12 @@ def load_batch(server_context, assay_id, batch_id):
7475
}
7576

7677
try:
77-
response = session.post(load_batch_url, data=json.dumps(payload), headers=headers)
78+
response = session.post(load_batch_url, data=json.dumps(payload, sort_keys=True), headers=headers)
7879
json_body = handle_response(response)
7980
if json_body is not None:
8081
loaded_batch = Batch.from_data(json_body['batch'])
8182
except SSLError as e:
82-
raise Exception("Failed to match server SSL configuration. Failed to load batch.")
83+
raise ServerContextError(e)
8384

8485
return loaded_batch
8586

@@ -132,13 +133,13 @@ def save_batches(server_context, assay_id, batches):
132133

133134
try:
134135
# print(payload)
135-
response = session.post(save_batch_url, data=json.dumps(payload), headers=headers)
136+
response = session.post(save_batch_url, data=json.dumps(payload, sort_keys=True), headers=headers)
136137
json_body = handle_response(response)
137138
if json_body is not None:
138139
resp_batches = json_body['batches']
139140
return [Batch.from_data(resp_batch) for resp_batch in resp_batches]
140141
except SSLError as e:
141-
raise Exception("Failed to match server SSL configuration. Failed to save batch.")
142+
raise ServerContextError(e)
142143

143144
return None
144145

labkey/query.py

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949

5050
from requests.exceptions import SSLError
5151
from labkey.utils import build_url, handle_response
52+
from labkey.exceptions import ServerContextError
5253

5354

5455
_query_headers = {
@@ -59,14 +60,27 @@
5960

6061

6162
class Pagination:
63+
"""
64+
Enum of paging styles
65+
"""
6266
PAGINATED = 'paginated'
6367
SELECTED = 'selected'
6468
UNSELECTED = 'unselected'
6569
ALL = 'all'
6670
NONE = 'none'
6771

6872

69-
def delete_rows(server_context, schema_name, query_name, rows, container_path=None, transacted=None, timeout=_default_timeout):
73+
def delete_rows(server_context, schema_name, query_name, rows, container_path=None, timeout=_default_timeout):
74+
"""
75+
Delete a set of rows from the schema.query
76+
:param server_context: A LabKey server context. See utils.create_server_context.
77+
:param schema_name: schema of table
78+
:param query_name: table name to delete from
79+
:param rows: Set of rows to delete
80+
:param container_path: labkey container path if not already set in context
81+
:param timeout: timeout of request in seconds (defaults to 30s)
82+
:return:
83+
"""
7084
url = build_url(server_context, 'query', 'deleteRows.api', container_path=container_path)
7185

7286
payload = {
@@ -76,7 +90,8 @@ def delete_rows(server_context, schema_name, query_name, rows, container_path=No
7690
}
7791

7892
# explicit json payload and headers required for form generation
79-
delete_rows_response = _make_request(server_context, url, json.dumps(payload), headers=_query_headers, timeout=timeout)
93+
delete_rows_response = _make_request(server_context, url, json.dumps(payload, sort_keys=True),
94+
headers=_query_headers, timeout=timeout)
8095
return delete_rows_response
8196

8297

@@ -89,6 +104,24 @@ def execute_sql(server_context, schema_name, sql, container_path=None,
89104
parameters=None,
90105
required_version=None,
91106
timeout=_default_timeout):
107+
"""
108+
Execute a string a labkey sql against a LabKey server.
109+
110+
:param server_context: A LabKey server context. See utils.create_server_context.
111+
:param schema_name: schema of table
112+
:param sql: String of labkey sql to execute
113+
:param container_path: labkey container path if not already set in context
114+
:param max_rows: max number of rows to return
115+
:param sort: comma separated list of column names to sort by
116+
:param offset: number of rows to offset results by
117+
:param container_filter: enumeration of the various container filters available. See:
118+
https://www.labkey.org/download/clientapi_docs/javascript-api/symbols/LABKEY.Query.html#.containerFilter
119+
:param save_in_session: save query result as a named view to the session
120+
:param parameters: parameter values to pass through to a parameterized query
121+
:param required_version: Api version of response
122+
:param timeout: timeout of request in seconds (defaults to 30s)
123+
:return:
124+
"""
92125
url = build_url(server_context, 'query', 'executeSql.api', container_path=container_path)
93126

94127
payload = {
@@ -122,6 +155,16 @@ def execute_sql(server_context, schema_name, sql, container_path=None,
122155

123156

124157
def insert_rows(server_context, schema_name, query_name, rows, container_path=None, timeout=_default_timeout):
158+
"""
159+
Insert row(s) into table
160+
:param server_context: A LabKey server context. See utils.create_server_context.
161+
:param schema_name: schema of table
162+
:param query_name: table name to insert into
163+
:param rows: set of rows to insert
164+
:param container_path: labkey container path if not already set in context
165+
:param timeout: timeout of request in seconds (defaults to 30s)
166+
:return:
167+
"""
125168
url = build_url(server_context, 'query', 'insertRows.api', container_path=container_path)
126169

127170
payload = {
@@ -131,7 +174,8 @@ def insert_rows(server_context, schema_name, query_name, rows, container_path=No
131174
}
132175

133176
# explicit json payload and headers required for form generation
134-
insert_rows_response = _make_request(server_context, url, json.dumps(payload), headers=_query_headers, timeout=timeout)
177+
insert_rows_response = _make_request(server_context, url, json.dumps(payload, sort_keys=True),
178+
headers=_query_headers, timeout=timeout)
135179
return insert_rows_response
136180

137181

@@ -152,6 +196,30 @@ def select_rows(server_context, schema_name, query_name, view_name=None,
152196
required_version=None,
153197
timeout=_default_timeout
154198
):
199+
"""
200+
Query data from a labkey server
201+
:param server_context: A LabKey server context. See utils.create_server_context.
202+
:param schema_name: schema of table
203+
:param query_name: table name to select from
204+
:param view_name: pre-existing named view
205+
:param filter_array: set of filter objects to apply
206+
:param container_path: folder path if not already part of server_context
207+
:param columns: set of columns to retrieve
208+
:param max_rows: max number of rows to retrieve
209+
:param sort: comma separated list of column names to sort by, prefix a column with '-' to sort descending
210+
:param offset: number of rows to offset results by
211+
:param container_filter: enumeration of the various container filters available. See:
212+
https://www.labkey.org/download/clientapi_docs/javascript-api/symbols/LABKEY.Query.html#.containerFilter
213+
:param parameters: Set of parameters to pass along to a parameterized query
214+
:param show_rows: An enumeration of various paging styles
215+
:param include_total_count: Boolean value that indicates whether to include a total count value in response
216+
:param include_details_column: Boolean value that indicates whether to include a Details link column in results
217+
:param include_update_column: Boolean value that indicates whether to include an Update link column in results
218+
:param selection_key:
219+
:param required_version: decimal value that indicates the response version of the api
220+
:param timeout: Request timeout in seconds (defaults to 30s)
221+
:return:
222+
"""
155223
# TODO: Support data_region_name
156224
url = build_url(server_context, 'query', 'getQuery.api', container_path=container_path)
157225

@@ -210,6 +278,17 @@ def select_rows(server_context, schema_name, query_name, view_name=None,
210278

211279

212280
def update_rows(server_context, schema_name, query_name, rows, container_path=None, timeout=_default_timeout):
281+
"""
282+
Update a set of rows
283+
284+
:param server_context: A LabKey server context. See utils.create_server_context.
285+
:param schema_name: schema of table
286+
:param query_name: table name to update
287+
:param rows: Set of rows to update
288+
:param container_path: labkey container path if not already set in context
289+
:param timeout: timeout of request in seconds (defaults to 30s)
290+
:return:
291+
"""
213292
url = build_url(server_context, 'query', 'updateRows.api', container_path=container_path)
214293

215294
payload = {
@@ -219,7 +298,8 @@ def update_rows(server_context, schema_name, query_name, rows, container_path=No
219298
}
220299

221300
# explicit json payload and headers required for form generation
222-
update_rows_response = _make_request(server_context, url, json.dumps(payload), headers=_query_headers, timeout=timeout)
301+
update_rows_response = _make_request(server_context, url, json.dumps(payload, sort_keys=True),
302+
headers=_query_headers, timeout=timeout)
223303
return update_rows_response
224304

225305

@@ -229,7 +309,7 @@ def _make_request(server_context, url, payload, headers=None, timeout=_default_t
229309
raw_response = session.post(url, data=payload, headers=headers, timeout=timeout)
230310
return handle_response(raw_response)
231311
except SSLError as e:
232-
raise Exception('Failed to match server SSL configuration. Ensure the server_context is configured correctly.')
312+
raise ServerContextError(e)
233313

234314

235315
# TODO: Provide filter generators.
@@ -240,8 +320,13 @@ def _make_request(server_context, url, payload, headers=None, timeout=_default_t
240320
#
241321
# https://www.labkey.org/download/clientapi_docs/javascript-api/symbols/LABKEY.Filter.html
242322
class QueryFilter:
243-
323+
"""
324+
Filter object to simplify generation of query filters
325+
"""
244326
class Types:
327+
"""
328+
Enumeration of acceptable filter types
329+
"""
245330
HAS_ANY_VALUE = '',
246331

247332
EQUAL = 'eq',

labkey/utils.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
from requests.adapters import HTTPAdapter
2222
from requests.packages.urllib3.poolmanager import PoolManager
23+
from labkey.exceptions import RequestError, RequestAuthorizationError, QueryNotFoundError, ServerNotFoundError
2324

2425

2526
# _ssl.c:504: error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure
@@ -100,25 +101,16 @@ def handle_response(response):
100101
if sc == 200 or sc == 207:
101102
return response.json()
102103
elif sc == 401:
103-
print(str(sc) + ": Authorization failed.")
104+
raise RequestAuthorizationError(response)
104105
elif sc == 404:
105-
msg = str(sc) + ": "
106-
decoded = response.json()
107-
if 'exception' in decoded:
108-
msg += decoded['exception']
109-
else:
110-
msg += 'Not found.'
111-
print(msg)
112-
elif sc == 500:
113-
msg = str(sc) + ": "
114-
decoded = response.json()
115-
if 'exception' in decoded:
116-
msg += decoded['exception']
117-
else:
118-
msg += 'Internal Server Error.'
119-
print(msg)
106+
try:
107+
response.json() # attempt to decode response
108+
raise QueryNotFoundError(response)
109+
except ValueError:
110+
# could not decode response
111+
raise ServerNotFoundError(response)
120112
else:
121-
print(str(sc))
122-
print(response.json())
113+
raise RequestError(response)
123114

124115
return None
116+

samples/load_batch_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
server_context = create_server_context('localhost:8080', project_name, 'labkey', use_ssl=False)
99

1010
print("Load an Assay batch from the server")
11-
assay_id = 1168 # provide one from your server
12-
batch_id = 95 # provide one from your server
11+
assay_id = 2809 # provide one from your server
12+
batch_id = 120 # provide one from your server
1313
run_group = load_batch(server_context, assay_id, batch_id)
1414

1515
if run_group is not None:

samples/query_examples.py

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from __future__ import unicode_literals
1111
from labkey.utils import create_server_context
12+
from labkey.exceptions import RequestError, QueryNotFoundError, ServerContextError, ServerNotFoundError
1213
from labkey.query import select_rows, update_rows, Pagination, QueryFilter, \
1314
insert_rows, delete_rows, execute_sql
1415
from requests.exceptions import Timeout
@@ -26,21 +27,74 @@
2627
column1 = 'Group Assignment'
2728
column2 = 'Participant ID'
2829

30+
2931
###################
3032
# Test basic select_rows
3133
###################
3234
result = select_rows(server_context, schema, table)
3335
if result is not None:
36+
print(result['rows'][0])
3437
print("select_rows: There are " + str(result['rowCount']) + " rows.")
3538
else:
3639
print('select_rows: Failed to load results from ' + schema + '.' + table)
3740

41+
42+
###################
43+
# Test error handling
44+
###################
45+
# catch base error
46+
try:
47+
result = select_rows(server_context, schema, 'badtable')
48+
print(result)
49+
except RequestError:
50+
print('Caught base error')
51+
52+
# catch table not found error
53+
try:
54+
result = select_rows(server_context, schema, 'badtable')
55+
print(result)
56+
except QueryNotFoundError:
57+
print('Caught bad table')
58+
59+
# catch schema error
60+
try:
61+
result = select_rows(server_context, 'badSchema', table)
62+
print(result)
63+
except QueryNotFoundError:
64+
print('Caught bad schema')
65+
66+
# catch SSL error
67+
ssl_server_context = create_server_context(labkey_server, project_name, contextPath, use_ssl=True)
68+
try:
69+
result = select_rows(ssl_server_context, schema, table)
70+
print(result)
71+
except ServerContextError:
72+
print('Caught SSL Error')
73+
74+
75+
# catch bad context path
76+
bad_server_context = create_server_context(labkey_server, project_name, '', use_ssl=False)
77+
try:
78+
result = select_rows(bad_server_context, schema, table)
79+
print(result)
80+
except ServerNotFoundError:
81+
print('Caught context path')
82+
83+
# catch bad folder path error
84+
bad_server_context = create_server_context(labkey_server, 'bad_project_name', contextPath, use_ssl=False)
85+
try:
86+
result = select_rows(bad_server_context, schema, table)
87+
print(result)
88+
except ServerNotFoundError:
89+
print('Caught bad folder name')
90+
91+
3892
###################
3993
# Test some parameters of select_rows
4094
###################
4195
result = select_rows(server_context, schema, table,
4296
max_rows=5, offset=10, include_total_count=True, include_details_column=True,
43-
include_update_column=True, required_version=12.2)
97+
include_update_column=True) # , required_version=12.2)
4498
if result is not None:
4599
print('select_rows: There are ' + str(len(result['rows'])) + ' rows.')
46100
print('select_rows: There are ' + str(result['rowCount']) + ' total rows.')
@@ -146,7 +200,6 @@
146200
print('Delete Rows: after row count [ ' + str(all_rows['rowCount']) + ' ]')
147201

148202

149-
150203
###################
151204
# Test execute_sql
152205
###################

0 commit comments

Comments
 (0)