labkey 3.0.0__tar.gz → 3.1.0__tar.gz
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.
- {labkey-3.0.0 → labkey-3.1.0}/CHANGE.txt +11 -0
- {labkey-3.0.0/labkey.egg-info → labkey-3.1.0}/PKG-INFO +1 -1
- {labkey-3.0.0 → labkey-3.1.0}/labkey/__init__.py +1 -1
- {labkey-3.0.0 → labkey-3.1.0}/labkey/api_wrapper.py +2 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey/exceptions.py +16 -1
- {labkey-3.0.0 → labkey-3.1.0}/labkey/query.py +13 -8
- {labkey-3.0.0 → labkey-3.1.0}/labkey/security.py +1 -1
- {labkey-3.0.0 → labkey-3.1.0}/labkey/server_context.py +27 -4
- {labkey-3.0.0 → labkey-3.1.0/labkey.egg-info}/PKG-INFO +1 -1
- {labkey-3.0.0 → labkey-3.1.0}/LICENSE.txt +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/MANIFEST.in +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/README.md +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey/container.py +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey/domain.py +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey/experiment.py +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey/storage.py +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey/utils.py +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey.egg-info/SOURCES.txt +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey.egg-info/dependency_links.txt +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey.egg-info/requires.txt +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/labkey.egg-info/top_level.txt +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/pyproject.toml +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/pytest.ini +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/setup.cfg +0 -0
- {labkey-3.0.0 → labkey-3.1.0}/setup.py +0 -0
@@ -2,6 +2,17 @@
|
|
2
2
|
LabKey Python Client API News
|
3
3
|
+++++++++++
|
4
4
|
|
5
|
+
What's New in the LabKey 3.1.0 package
|
6
|
+
==============================
|
7
|
+
|
8
|
+
*Release date: 04/03/2024*
|
9
|
+
- ServerContext
|
10
|
+
- Add allow_redirects flag (defaults to False) to constructor
|
11
|
+
- Add allow_redirects flag to make_request
|
12
|
+
- APIWrapper: Add allow_redirects flag (defaults to False)
|
13
|
+
- Add UnexpectedRedirectError
|
14
|
+
- thrown when allow_redirects is False and the server issues a redirect
|
15
|
+
|
5
16
|
What's New in the LabKey 3.0.0 package
|
6
17
|
==============================
|
7
18
|
|
@@ -22,6 +22,7 @@ class APIWrapper:
|
|
22
22
|
verify_ssl=True,
|
23
23
|
api_key=None,
|
24
24
|
disable_csrf=False,
|
25
|
+
allow_redirects=False,
|
25
26
|
):
|
26
27
|
self.server_context = ServerContext(
|
27
28
|
domain=domain,
|
@@ -31,6 +32,7 @@ class APIWrapper:
|
|
31
32
|
verify_ssl=verify_ssl,
|
32
33
|
api_key=api_key,
|
33
34
|
disable_csrf=disable_csrf,
|
35
|
+
allow_redirects=allow_redirects,
|
34
36
|
)
|
35
37
|
self.container = ContainerWrapper(self.server_context)
|
36
38
|
self.domain = DomainWrapper(self.server_context)
|
@@ -47,7 +47,22 @@ class RequestError(exceptions.RequestException):
|
|
47
47
|
self.message = "No response received"
|
48
48
|
|
49
49
|
def __str__(self):
|
50
|
-
return
|
50
|
+
return str(self.message)
|
51
|
+
|
52
|
+
|
53
|
+
class UnexpectedRedirectError(RequestError):
|
54
|
+
default_msg = "Unexpected redirect occurred"
|
55
|
+
|
56
|
+
def __init__(self, server_response, **kwargs):
|
57
|
+
super().__init__(server_response, **kwargs)
|
58
|
+
|
59
|
+
location = server_response.headers.get("Location", "")
|
60
|
+
|
61
|
+
# If the server is redirecting from http to https the user probably has a misconfigured ServerContext with use_ssl=False
|
62
|
+
if server_response.url.startswith("http://") and location.startswith("https://"):
|
63
|
+
self.message = "Redirected from http to https, set use_ssl=True in your APIWrapper or ServerContext"
|
64
|
+
elif location != "":
|
65
|
+
self.message = f"Unexpected redirect to: {location}"
|
51
66
|
|
52
67
|
|
53
68
|
class QueryNotFoundError(RequestError):
|
@@ -258,7 +258,7 @@ def execute_sql(
|
|
258
258
|
parameters: dict = None,
|
259
259
|
required_version: float = None,
|
260
260
|
timeout: int = _default_timeout,
|
261
|
-
waf_encode_sql: bool = True
|
261
|
+
waf_encode_sql: bool = True,
|
262
262
|
):
|
263
263
|
"""
|
264
264
|
Execute sql query against a LabKey server.
|
@@ -535,7 +535,12 @@ def move_rows(
|
|
535
535
|
"""
|
536
536
|
url = server_context.build_url("query", "moveRows.api", container_path=container_path)
|
537
537
|
|
538
|
-
payload = {
|
538
|
+
payload = {
|
539
|
+
"targetContainerPath": target_container_path,
|
540
|
+
"schemaName": schema_name,
|
541
|
+
"queryName": query_name,
|
542
|
+
"rows": rows,
|
543
|
+
}
|
539
544
|
|
540
545
|
if transacted is False:
|
541
546
|
payload["transacted"] = transacted
|
@@ -582,7 +587,7 @@ class QueryWrapper:
|
|
582
587
|
transacted,
|
583
588
|
audit_behavior,
|
584
589
|
audit_user_comment,
|
585
|
-
timeout
|
590
|
+
timeout,
|
586
591
|
)
|
587
592
|
|
588
593
|
@functools.wraps(truncate_table)
|
@@ -605,7 +610,7 @@ class QueryWrapper:
|
|
605
610
|
parameters: dict = None,
|
606
611
|
required_version: float = None,
|
607
612
|
timeout: int = _default_timeout,
|
608
|
-
waf_encode_sql: bool = True
|
613
|
+
waf_encode_sql: bool = True,
|
609
614
|
):
|
610
615
|
return execute_sql(
|
611
616
|
self.server_context,
|
@@ -620,7 +625,7 @@ class QueryWrapper:
|
|
620
625
|
parameters,
|
621
626
|
required_version,
|
622
627
|
timeout,
|
623
|
-
waf_encode_sql
|
628
|
+
waf_encode_sql,
|
624
629
|
)
|
625
630
|
|
626
631
|
@functools.wraps(insert_rows)
|
@@ -646,7 +651,7 @@ class QueryWrapper:
|
|
646
651
|
transacted,
|
647
652
|
audit_behavior,
|
648
653
|
audit_user_comment,
|
649
|
-
timeout
|
654
|
+
timeout,
|
650
655
|
)
|
651
656
|
|
652
657
|
@functools.wraps(select_rows)
|
@@ -716,7 +721,7 @@ class QueryWrapper:
|
|
716
721
|
transacted,
|
717
722
|
audit_behavior,
|
718
723
|
audit_user_comment,
|
719
|
-
timeout
|
724
|
+
timeout,
|
720
725
|
)
|
721
726
|
|
722
727
|
@functools.wraps(move_rows)
|
@@ -742,5 +747,5 @@ class QueryWrapper:
|
|
742
747
|
transacted,
|
743
748
|
audit_behavior,
|
744
749
|
audit_user_comment,
|
745
|
-
timeout
|
750
|
+
timeout,
|
746
751
|
)
|
@@ -279,7 +279,7 @@ def stop_impersonating(server_context: ServerContext):
|
|
279
279
|
Stop impersonating a user while keeping the original user logged in.
|
280
280
|
"""
|
281
281
|
url = server_context.build_url(LOGIN_CONTROLLER, "stopImpersonating.api")
|
282
|
-
return server_context.make_request(url)
|
282
|
+
return server_context.make_request(url, allow_redirects=True)
|
283
283
|
|
284
284
|
|
285
285
|
@dataclass
|
@@ -8,6 +8,7 @@ from labkey.exceptions import (
|
|
8
8
|
QueryNotFoundError,
|
9
9
|
ServerContextError,
|
10
10
|
ServerNotFoundError,
|
11
|
+
UnexpectedRedirectError,
|
11
12
|
)
|
12
13
|
|
13
14
|
API_KEY_TOKEN = "apikey"
|
@@ -29,7 +30,8 @@ def handle_response(response, non_json_response=False):
|
|
29
30
|
content=response.content,
|
30
31
|
)
|
31
32
|
return result
|
32
|
-
|
33
|
+
elif sc == 302:
|
34
|
+
raise UnexpectedRedirectError(response)
|
33
35
|
elif sc == 401:
|
34
36
|
raise RequestAuthorizationError(response)
|
35
37
|
elif sc == 404:
|
@@ -62,6 +64,7 @@ class ServerContext:
|
|
62
64
|
verify_ssl=True,
|
63
65
|
api_key=None,
|
64
66
|
disable_csrf=False,
|
67
|
+
allow_redirects=False,
|
65
68
|
):
|
66
69
|
self._container_path = container_path
|
67
70
|
self._context_path = context_path
|
@@ -70,6 +73,7 @@ class ServerContext:
|
|
70
73
|
self._verify_ssl = verify_ssl
|
71
74
|
self._api_key = api_key
|
72
75
|
self._disable_csrf = disable_csrf
|
76
|
+
self.allow_redirects = allow_redirects
|
73
77
|
self._session = requests.Session()
|
74
78
|
self._session.headers.update({"User-Agent": f"LabKey Python API/{__version__}"})
|
75
79
|
|
@@ -174,7 +178,9 @@ class ServerContext:
|
|
174
178
|
non_json_response: bool = False,
|
175
179
|
file_payload: any = None,
|
176
180
|
json: dict = None,
|
181
|
+
allow_redirects=False,
|
177
182
|
) -> any:
|
183
|
+
allow_redirects_ = allow_redirects or self.allow_redirects
|
178
184
|
if self._api_key is not None:
|
179
185
|
if self._session.headers.get(API_KEY_TOKEN) is not self._api_key:
|
180
186
|
self._session.headers.update({API_KEY_TOKEN: self._api_key})
|
@@ -189,7 +195,13 @@ class ServerContext:
|
|
189
195
|
|
190
196
|
try:
|
191
197
|
if method == "GET":
|
192
|
-
response = self._session.get(
|
198
|
+
response = self._session.get(
|
199
|
+
url,
|
200
|
+
params=payload,
|
201
|
+
headers=headers,
|
202
|
+
timeout=timeout,
|
203
|
+
allow_redirects=allow_redirects_,
|
204
|
+
)
|
193
205
|
else:
|
194
206
|
if file_payload is not None:
|
195
207
|
response = self._session.post(
|
@@ -198,6 +210,7 @@ class ServerContext:
|
|
198
210
|
files=file_payload,
|
199
211
|
headers=headers,
|
200
212
|
timeout=timeout,
|
213
|
+
allow_redirects=allow_redirects_,
|
201
214
|
)
|
202
215
|
elif json is not None:
|
203
216
|
if headers is None:
|
@@ -206,10 +219,20 @@ class ServerContext:
|
|
206
219
|
headers_ = {**headers, "Content-Type": "application/json"}
|
207
220
|
# sort_keys is a hack to make unit tests work
|
208
221
|
data = json_dumps(json, sort_keys=True)
|
209
|
-
response = self._session.post(
|
222
|
+
response = self._session.post(
|
223
|
+
url,
|
224
|
+
data=data,
|
225
|
+
headers=headers_,
|
226
|
+
timeout=timeout,
|
227
|
+
allow_redirects=allow_redirects_,
|
228
|
+
)
|
210
229
|
else:
|
211
230
|
response = self._session.post(
|
212
|
-
url,
|
231
|
+
url,
|
232
|
+
data=payload,
|
233
|
+
headers=headers,
|
234
|
+
timeout=timeout,
|
235
|
+
allow_redirects=allow_redirects_,
|
213
236
|
)
|
214
237
|
return handle_response(response, non_json_response)
|
215
238
|
except RequestException as e:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|