labkey 3.0.0__py3-none-any.whl → 3.2.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.
labkey/__init__.py CHANGED
@@ -14,6 +14,6 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __title__ = "labkey"
17
- __version__ = "3.0.0"
17
+ __version__ = "3.2.0"
18
18
  __author__ = "LabKey"
19
19
  __license__ = "Apache License 2.0"
labkey/api_wrapper.py CHANGED
@@ -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)
labkey/exceptions.py CHANGED
@@ -47,7 +47,22 @@ class RequestError(exceptions.RequestException):
47
47
  self.message = "No response received"
48
48
 
49
49
  def __str__(self):
50
- return repr(self.message)
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):
labkey/experiment.py CHANGED
@@ -218,6 +218,70 @@ def save_batches(
218
218
  return None
219
219
 
220
220
 
221
+ def lineage(
222
+ server_context: ServerContext,
223
+ lsids: List[str],
224
+ children: bool = None,
225
+ container_path: str = None,
226
+ cpas_type: str = None,
227
+ depth: int = None,
228
+ exp_type: str = None,
229
+ include_inputs_and_outputs: bool = None,
230
+ include_properties: bool = None,
231
+ include_run_steps: bool = None,
232
+ parents: bool = None,
233
+ run_protocol_lsid: str = None,
234
+ ):
235
+ """
236
+ :param server_context: A LabKey server context. See utils.create_server_context.
237
+ :param lsids: Array of LSIDs for the seed ExpData, ExpMaterials, or ExpRun
238
+ :param children: Include children in the lineage response. Defaults to true.
239
+ :param container_path: labkey container path if not already set in context
240
+ :param cpas_type: Optional LSID of a SampleSet or DataClass to filter the response. Defaults to include all.
241
+ :param depth: An optional depth argument. Defaults to include all.
242
+ :param exp_type: Optional experiment type to filter response -- either "Data", "Material", or "ExperimentRun".
243
+ Defaults to include all.
244
+ :param include_inputs_and_outputs: Include inputs and outputs in the lineage response.
245
+ :param include_properties: Include properties in the lineage response.
246
+ :param include_run_steps: Include run steps in the lineage response.
247
+ :param parents: Include parents in the lineage response. Defaults to true.
248
+ :param run_protocol_lsid: Optional Exp Run Protocol Lsid to filter response. Defaults to include all.
249
+ """
250
+ lineage_url = server_context.build_url(
251
+ "experiment", "lineage.api", container_path=container_path
252
+ )
253
+ payload = {"lsids": lsids}
254
+
255
+ if children is not None:
256
+ payload["children"] = children
257
+
258
+ if cpas_type is not None:
259
+ payload["cpasType"] = cpas_type
260
+
261
+ if depth is not None:
262
+ payload["depth"] = depth
263
+
264
+ if exp_type is not None:
265
+ payload["expType"] = exp_type
266
+
267
+ if include_inputs_and_outputs is not None:
268
+ payload["includeInputsAndOutputs"] = include_inputs_and_outputs
269
+
270
+ if include_properties is not None:
271
+ payload["includeProperties"] = include_properties
272
+
273
+ if include_run_steps is not None:
274
+ payload["includeRunSteps"] = include_run_steps
275
+
276
+ if parents is not None:
277
+ payload["parents"] = parents
278
+
279
+ if run_protocol_lsid is not None:
280
+ payload["runProtocolLsid"] = run_protocol_lsid
281
+
282
+ return server_context.make_request(lineage_url, payload=payload, method="POST")
283
+
284
+
221
285
  class ExperimentWrapper:
222
286
  """
223
287
  Wrapper for all of the API methods exposed in the experiment module. Used by the APIWrapper class.
@@ -237,3 +301,33 @@ class ExperimentWrapper:
237
301
  @functools.wraps(save_batches)
238
302
  def save_batches(self, assay_id: int, batches: List[Batch]) -> Optional[List[Batch]]:
239
303
  return save_batches(self.server_context, assay_id, batches)
304
+
305
+ @functools.wraps(lineage)
306
+ def lineage(
307
+ self,
308
+ lsids: List[str],
309
+ children: bool = None,
310
+ container_path: str = None,
311
+ cpas_type: str = None,
312
+ exp_type: str = None,
313
+ depth: int = None,
314
+ include_properties: bool = None,
315
+ include_inputs_and_outputs: bool = None,
316
+ include_run_steps: bool = None,
317
+ parents: bool = None,
318
+ run_protocol_lsid: str = None,
319
+ ):
320
+ return lineage(
321
+ self.server_context,
322
+ lsids,
323
+ children,
324
+ container_path,
325
+ cpas_type,
326
+ depth,
327
+ exp_type,
328
+ parents,
329
+ include_inputs_and_outputs,
330
+ include_properties,
331
+ include_run_steps,
332
+ run_protocol_lsid,
333
+ )
labkey/query.py CHANGED
@@ -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 = {"targetContainerPath": target_container_path, "schemaName": schema_name, "queryName": query_name, "rows": rows}
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
  )
labkey/security.py CHANGED
@@ -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
labkey/server_context.py CHANGED
@@ -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(url, params=payload, headers=headers, timeout=timeout)
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(url, data=data, headers=headers_, timeout=timeout)
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, data=payload, headers=headers, timeout=timeout
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: labkey
3
- Version: 3.0.0
3
+ Version: 3.2.0
4
4
  Summary: Python client API for LabKey Server
5
5
  Home-page: https://github.com/LabKey/labkey-api-python
6
6
  Author: LabKey
@@ -21,6 +21,17 @@ Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Topic :: Scientific/Engineering
22
22
  License-File: LICENSE.txt
23
23
  Requires-Dist: requests
24
+ Provides-Extra: build
25
+ Requires-Dist: setuptools ; extra == 'build'
26
+ Requires-Dist: build ; extra == 'build'
27
+ Requires-Dist: twine ; extra == 'build'
28
+ Requires-Dist: wheel ; extra == 'build'
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest ; extra == 'dev'
31
+ Requires-Dist: requests ; extra == 'dev'
32
+ Requires-Dist: mock ; extra == 'dev'
33
+ Requires-Dist: pytest-cov ; extra == 'dev'
34
+ Requires-Dist: black ; extra == 'dev'
24
35
  Provides-Extra: test
25
36
  Requires-Dist: pytest ; extra == 'test'
26
37
  Requires-Dist: requests ; extra == 'test'
@@ -0,0 +1,16 @@
1
+ labkey/__init__.py,sha256=BxplFb91voofYbNkCpovsh5LfOdvBLWqCmHPjhy2Lj0,695
2
+ labkey/api_wrapper.py,sha256=OxnV6_5jONWiwsi24TOXTKP5inXtAfABGqXAPua3eCM,1427
3
+ labkey/container.py,sha256=DXmLhGsNnN_QLXa_tMCM0Xf_Kkz7B8KjRFhJT5I4FtY,5497
4
+ labkey/domain.py,sha256=SABG7BfSqQGJr8DPjUsvhi8C7xP2MTpkq24sdtAu4J8,22486
5
+ labkey/exceptions.py,sha256=00x-4oP_2d0SfZKxsGIs8QGvXiNq2y7C5hAzuzT3Gkk,3619
6
+ labkey/experiment.py,sha256=6U4BXnYHNXCUF_mee0maK_CZDVm_lbMjC82ywx9YCTE,12062
7
+ labkey/query.py,sha256=w6vYDwiwnLalrDQ8pGY-c9DvoPQuMXaFXnZLRUiezuk,24041
8
+ labkey/security.py,sha256=eMa2b_22FWB2rVvVpxTeWrK0VgGTszhRchsApbDR6sA,15306
9
+ labkey/server_context.py,sha256=0YcA7BRijLMs172qTrTB12MlnIcqz4X9dkkCwSxxzaA,7741
10
+ labkey/storage.py,sha256=WpgpTWQzgig_qJXMGszFcFjtf8oMd2-6TBehSkpJPUg,6144
11
+ labkey/utils.py,sha256=oL6qmHkpzLTm_9c7gSEzumv_MVh5s8fj6bOrbuslrV0,3233
12
+ labkey-3.2.0.dist-info/LICENSE.txt,sha256=xllut76FgcGL5zbIRvuRc7aezPbvlMUTWJPsVr2Sugg,11358
13
+ labkey-3.2.0.dist-info/METADATA,sha256=0xOPpsSbHkT9ow_bUQeILdYVPAcFIwmr_KG1tlHHbxQ,1481
14
+ labkey-3.2.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
15
+ labkey-3.2.0.dist-info/top_level.txt,sha256=DQIk1fQNg7NxxBsEt_K7k-aopiH87jmnU98zwDp0n04,7
16
+ labkey-3.2.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.42.0)
2
+ Generator: setuptools (74.1.2)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,16 +0,0 @@
1
- labkey/__init__.py,sha256=q1xx8gL3h06R1DGXoOOInvVNbmuIOmuCexBPMyEApxM,695
2
- labkey/api_wrapper.py,sha256=ks6q5qwcGkutg2m5sMdTvryhfK42yltfPCV5iLPi22k,1351
3
- labkey/container.py,sha256=DXmLhGsNnN_QLXa_tMCM0Xf_Kkz7B8KjRFhJT5I4FtY,5497
4
- labkey/domain.py,sha256=SABG7BfSqQGJr8DPjUsvhi8C7xP2MTpkq24sdtAu4J8,22486
5
- labkey/exceptions.py,sha256=VtuKphEczSsfHAXgWRv-2HRzxSiObBnOEC_sgEPPR3c,2929
6
- labkey/experiment.py,sha256=Wtuz52bhuvDWk_o2AbTw7SJnwEji2iD6Q04ct0PWSxE,8804
7
- labkey/query.py,sha256=0Hp8P-mFuqurGlVbF71BN3Woz6tZfdweRKW0s4GFsh4,23995
8
- labkey/security.py,sha256=cVrlYbb7Yogjo9L1exGKgpscRFEljBpzFITQZWHW2X8,15284
9
- labkey/server_context.py,sha256=UGf4fJ9Cw_YIM_DwdPVP8WgKORt5aO_4bFCz14rjsrg,6950
10
- labkey/storage.py,sha256=WpgpTWQzgig_qJXMGszFcFjtf8oMd2-6TBehSkpJPUg,6144
11
- labkey/utils.py,sha256=oL6qmHkpzLTm_9c7gSEzumv_MVh5s8fj6bOrbuslrV0,3233
12
- labkey-3.0.0.dist-info/LICENSE.txt,sha256=xllut76FgcGL5zbIRvuRc7aezPbvlMUTWJPsVr2Sugg,11358
13
- labkey-3.0.0.dist-info/METADATA,sha256=BtwRKk5F9ja7CD37QvyCljOi0_R9Dq-10uUS0OH3BHQ,1076
14
- labkey-3.0.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
- labkey-3.0.0.dist-info/top_level.txt,sha256=DQIk1fQNg7NxxBsEt_K7k-aopiH87jmnU98zwDp0n04,7
16
- labkey-3.0.0.dist-info/RECORD,,