labkey 2.6.1__py3-none-any.whl → 3.1.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__ = "2.6.1"
17
+ __version__ = "3.1.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/query.py CHANGED
@@ -44,6 +44,7 @@ import functools
44
44
  from typing import List
45
45
 
46
46
  from .server_context import ServerContext
47
+ from .utils import waf_encode
47
48
 
48
49
  _default_timeout = 60 * 5 # 5 minutes
49
50
 
@@ -164,12 +165,25 @@ class QueryFilter:
164
165
  return "<QueryFilter [{} {} {}]>".format(self.column_name, self.filter_type, self.value)
165
166
 
166
167
 
168
+ class AuditBehavior:
169
+ """
170
+ Enum of different auditing levels
171
+ """
172
+
173
+ DETAILED = "DETAILED"
174
+ NONE = "NONE"
175
+ SUMMARY = "SUMMARY"
176
+
177
+
167
178
  def delete_rows(
168
179
  server_context: ServerContext,
169
180
  schema_name: str,
170
181
  query_name: str,
171
182
  rows: any,
172
183
  container_path: str = None,
184
+ transacted: bool = True,
185
+ audit_behavior: AuditBehavior = None,
186
+ audit_user_comment: str = None,
173
187
  timeout: int = _default_timeout,
174
188
  ):
175
189
  """
@@ -179,12 +193,25 @@ def delete_rows(
179
193
  :param query_name: table name to delete from
180
194
  :param rows: Set of rows to delete
181
195
  :param container_path: labkey container path if not already set in context
196
+ :param transacted: whether all of the updates should be done in a single transaction
197
+ :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
198
+ :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
182
199
  :param timeout: timeout of request in seconds (defaults to 30s)
183
200
  :return:
184
201
  """
185
202
  url = server_context.build_url("query", "deleteRows.api", container_path=container_path)
203
+
186
204
  payload = {"schemaName": schema_name, "queryName": query_name, "rows": rows}
187
205
 
206
+ if transacted is False:
207
+ payload["transacted"] = transacted
208
+
209
+ if audit_behavior is not None:
210
+ payload["auditBehavior"] = audit_behavior
211
+
212
+ if audit_user_comment is not None:
213
+ payload["auditUserComment"] = audit_user_comment
214
+
188
215
  return server_context.make_request(
189
216
  url,
190
217
  json=payload,
@@ -231,6 +258,7 @@ def execute_sql(
231
258
  parameters: dict = None,
232
259
  required_version: float = None,
233
260
  timeout: int = _default_timeout,
261
+ waf_encode_sql: bool = True,
234
262
  ):
235
263
  """
236
264
  Execute sql query against a LabKey server.
@@ -248,11 +276,12 @@ def execute_sql(
248
276
  :param parameters: parameter values to pass through to a parameterized query
249
277
  :param required_version: Api version of response
250
278
  :param timeout: timeout of request in seconds (defaults to 30s)
279
+ :param waf_encode_sql: WAF encode sql in request (defaults to True)
251
280
  :return:
252
281
  """
253
282
  url = server_context.build_url("query", "executeSql.api", container_path=container_path)
254
283
 
255
- payload = {"schemaName": schema_name, "sql": sql}
284
+ payload = {"schemaName": schema_name, "sql": waf_encode(sql) if waf_encode_sql else sql}
256
285
 
257
286
  if container_filter is not None:
258
287
  payload["containerFilter"] = container_filter
@@ -285,6 +314,10 @@ def insert_rows(
285
314
  query_name: str,
286
315
  rows: List[any],
287
316
  container_path: str = None,
317
+ skip_reselect_rows: bool = False,
318
+ transacted: bool = True,
319
+ audit_behavior: AuditBehavior = None,
320
+ audit_user_comment: str = None,
288
321
  timeout: int = _default_timeout,
289
322
  ):
290
323
  """
@@ -294,6 +327,10 @@ def insert_rows(
294
327
  :param query_name: table name to insert into
295
328
  :param rows: set of rows to insert
296
329
  :param container_path: labkey container path if not already set in context
330
+ :param skip_reselect_rows: whether the full detailed response for the insert can be skipped
331
+ :param transacted: whether all of the updates should be done in a single transaction
332
+ :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
333
+ :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
297
334
  :param timeout: timeout of request in seconds (defaults to 30s)
298
335
  :return:
299
336
  """
@@ -301,6 +338,18 @@ def insert_rows(
301
338
 
302
339
  payload = {"schemaName": schema_name, "queryName": query_name, "rows": rows}
303
340
 
341
+ if skip_reselect_rows is True:
342
+ payload["skipReselectRows"] = skip_reselect_rows
343
+
344
+ if transacted is False:
345
+ payload["transacted"] = transacted
346
+
347
+ if audit_behavior is not None:
348
+ payload["auditBehavior"] = audit_behavior
349
+
350
+ if audit_user_comment is not None:
351
+ payload["auditUserComment"] = audit_user_comment
352
+
304
353
  return server_context.make_request(
305
354
  url,
306
355
  json=payload,
@@ -419,6 +468,9 @@ def update_rows(
419
468
  query_name: str,
420
469
  rows: List[any],
421
470
  container_path: str = None,
471
+ transacted: bool = True,
472
+ audit_behavior: AuditBehavior = None,
473
+ audit_user_comment: str = None,
422
474
  timeout: int = _default_timeout,
423
475
  ):
424
476
  """
@@ -429,6 +481,9 @@ def update_rows(
429
481
  :param query_name: table name to update
430
482
  :param rows: Set of rows to update
431
483
  :param container_path: labkey container path if not already set in context
484
+ :param transacted: whether all of the updates should be done in a single transaction
485
+ :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
486
+ :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
432
487
  :param timeout: timeout of request in seconds (defaults to 30s)
433
488
  :return:
434
489
  """
@@ -436,6 +491,66 @@ def update_rows(
436
491
 
437
492
  payload = {"schemaName": schema_name, "queryName": query_name, "rows": rows}
438
493
 
494
+ if transacted is False:
495
+ payload["transacted"] = transacted
496
+
497
+ if audit_behavior is not None:
498
+ payload["auditBehavior"] = audit_behavior
499
+
500
+ if audit_user_comment is not None:
501
+ payload["auditUserComment"] = audit_user_comment
502
+
503
+ return server_context.make_request(
504
+ url,
505
+ json=payload,
506
+ timeout=timeout,
507
+ )
508
+
509
+
510
+ def move_rows(
511
+ server_context: ServerContext,
512
+ target_container_path: str,
513
+ schema_name: str,
514
+ query_name: str,
515
+ rows: any,
516
+ container_path: str = None,
517
+ transacted: bool = True,
518
+ audit_behavior: AuditBehavior = None,
519
+ audit_user_comment: str = None,
520
+ timeout: int = _default_timeout,
521
+ ):
522
+ """
523
+ Move a set of rows from the schema.query
524
+ :param server_context: A LabKey server context. See utils.create_server_context.
525
+ :param target_container_path: target labkey container path for the move
526
+ :param schema_name: schema of table
527
+ :param query_name: table name to move from
528
+ :param rows: Set of rows to move
529
+ :param container_path: source labkey container path if not already set in context
530
+ :param transacted: whether all of the updates should be done in a single transaction
531
+ :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
532
+ :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
533
+ :param timeout: timeout of request in seconds (defaults to 30s)
534
+ :return:
535
+ """
536
+ url = server_context.build_url("query", "moveRows.api", container_path=container_path)
537
+
538
+ payload = {
539
+ "targetContainerPath": target_container_path,
540
+ "schemaName": schema_name,
541
+ "queryName": query_name,
542
+ "rows": rows,
543
+ }
544
+
545
+ if transacted is False:
546
+ payload["transacted"] = transacted
547
+
548
+ if audit_behavior is not None:
549
+ payload["auditBehavior"] = audit_behavior
550
+
551
+ if audit_user_comment is not None:
552
+ payload["auditUserComment"] = audit_user_comment
553
+
439
554
  return server_context.make_request(
440
555
  url,
441
556
  json=payload,
@@ -458,10 +573,21 @@ class QueryWrapper:
458
573
  query_name: str,
459
574
  rows: any,
460
575
  container_path: str = None,
576
+ transacted: bool = True,
577
+ audit_behavior: AuditBehavior = None,
578
+ audit_user_comment: str = None,
461
579
  timeout: int = _default_timeout,
462
580
  ):
463
581
  return delete_rows(
464
- self.server_context, schema_name, query_name, rows, container_path, timeout
582
+ self.server_context,
583
+ schema_name,
584
+ query_name,
585
+ rows,
586
+ container_path,
587
+ transacted,
588
+ audit_behavior,
589
+ audit_user_comment,
590
+ timeout,
465
591
  )
466
592
 
467
593
  @functools.wraps(truncate_table)
@@ -484,6 +610,7 @@ class QueryWrapper:
484
610
  parameters: dict = None,
485
611
  required_version: float = None,
486
612
  timeout: int = _default_timeout,
613
+ waf_encode_sql: bool = True,
487
614
  ):
488
615
  return execute_sql(
489
616
  self.server_context,
@@ -498,6 +625,7 @@ class QueryWrapper:
498
625
  parameters,
499
626
  required_version,
500
627
  timeout,
628
+ waf_encode_sql,
501
629
  )
502
630
 
503
631
  @functools.wraps(insert_rows)
@@ -507,10 +635,23 @@ class QueryWrapper:
507
635
  query_name: str,
508
636
  rows: List[any],
509
637
  container_path: str = None,
638
+ skip_reselect_rows: bool = False,
639
+ transacted: bool = True,
640
+ audit_behavior: AuditBehavior = None,
641
+ audit_user_comment: str = None,
510
642
  timeout: int = _default_timeout,
511
643
  ):
512
644
  return insert_rows(
513
- self.server_context, schema_name, query_name, rows, container_path, timeout
645
+ self.server_context,
646
+ schema_name,
647
+ query_name,
648
+ rows,
649
+ container_path,
650
+ skip_reselect_rows,
651
+ transacted,
652
+ audit_behavior,
653
+ audit_user_comment,
654
+ timeout,
514
655
  )
515
656
 
516
657
  @functools.wraps(select_rows)
@@ -566,8 +707,45 @@ class QueryWrapper:
566
707
  query_name: str,
567
708
  rows: List[any],
568
709
  container_path: str = None,
710
+ transacted: bool = True,
711
+ audit_behavior: AuditBehavior = None,
712
+ audit_user_comment: str = None,
569
713
  timeout: int = _default_timeout,
570
714
  ):
571
715
  return update_rows(
572
- self.server_context, schema_name, query_name, rows, container_path, timeout
716
+ self.server_context,
717
+ schema_name,
718
+ query_name,
719
+ rows,
720
+ container_path,
721
+ transacted,
722
+ audit_behavior,
723
+ audit_user_comment,
724
+ timeout,
725
+ )
726
+
727
+ @functools.wraps(move_rows)
728
+ def move_rows(
729
+ self,
730
+ target_container_path: str,
731
+ schema_name: str,
732
+ query_name: str,
733
+ rows: any,
734
+ container_path: str = None,
735
+ transacted: bool = True,
736
+ audit_behavior: AuditBehavior = None,
737
+ audit_user_comment: str = None,
738
+ timeout: int = _default_timeout,
739
+ ):
740
+ return move_rows(
741
+ self.server_context,
742
+ target_container_path,
743
+ schema_name,
744
+ query_name,
745
+ rows,
746
+ container_path,
747
+ transacted,
748
+ audit_behavior,
749
+ audit_user_comment,
750
+ timeout,
573
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:
labkey/utils.py CHANGED
@@ -16,6 +16,8 @@
16
16
  import json
17
17
  from functools import wraps
18
18
  from datetime import date, datetime
19
+ from base64 import b64encode
20
+ from urllib import parse
19
21
 
20
22
 
21
23
  # Issue #14: json.dumps on datetime throws TypeError
@@ -71,3 +73,21 @@ def transform_helper(user_transform_func, file_path_run_properties):
71
73
  row = [str(el).strip() for el in row]
72
74
  row = "\t".join(row)
73
75
  file_out.write(row + "\n")
76
+
77
+
78
+ def btoa(value: str) -> str:
79
+ if not value:
80
+ return value
81
+ binary = value.encode("utf-8")
82
+ return b64encode(binary).decode()
83
+
84
+
85
+ def encode_uri_component(value: str) -> str:
86
+ # https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
87
+ return parse.quote(value, encoding="utf-8", safe="-_.!~*'()")
88
+
89
+
90
+ def waf_encode(value: str) -> str:
91
+ if value:
92
+ return "/*{{base64/x-www-form-urlencoded/wafText}}*/" + btoa(encode_uri_component(value))
93
+ return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: labkey
3
- Version: 2.6.1
3
+ Version: 3.1.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
@@ -9,7 +9,6 @@ Maintainer: Alan Vezina
9
9
  Maintainer-email: alanv@labkey.com
10
10
  License: Apache License 2.0
11
11
  Keywords: labkey api client
12
- Platform: UNKNOWN
13
12
  Classifier: Development Status :: 4 - Beta
14
13
  Classifier: Environment :: Console
15
14
  Classifier: Intended Audience :: Science/Research
@@ -20,6 +19,7 @@ Classifier: Operating System :: Microsoft
20
19
  Classifier: Operating System :: POSIX
21
20
  Classifier: Programming Language :: Python :: 3
22
21
  Classifier: Topic :: Scientific/Engineering
22
+ License-File: LICENSE.txt
23
23
  Requires-Dist: requests
24
24
  Provides-Extra: test
25
25
  Requires-Dist: pytest ; extra == 'test'
@@ -28,5 +28,3 @@ Requires-Dist: mock ; extra == 'test'
28
28
  Requires-Dist: pytest-cov ; extra == 'test'
29
29
 
30
30
  Python client API for LabKey Server. Supports query and experiment APIs.
31
-
32
-
@@ -0,0 +1,16 @@
1
+ labkey/__init__.py,sha256=SjBskVArzIGzH9aIlUwSK246XoguCK7iCvEm0fXQAhQ,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=Wtuz52bhuvDWk_o2AbTw7SJnwEji2iD6Q04ct0PWSxE,8804
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.1.0.dist-info/LICENSE.txt,sha256=xllut76FgcGL5zbIRvuRc7aezPbvlMUTWJPsVr2Sugg,11358
13
+ labkey-3.1.0.dist-info/METADATA,sha256=8O6aEerFp6Qn5B18KBaa7GGKApoNCwDDWDpaB9atXwM,1076
14
+ labkey-3.1.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
15
+ labkey-3.1.0.dist-info/top_level.txt,sha256=DQIk1fQNg7NxxBsEt_K7k-aopiH87jmnU98zwDp0n04,7
16
+ labkey-3.1.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: bdist_wheel (0.36.2)
2
+ Generator: bdist_wheel (0.42.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,16 +0,0 @@
1
- labkey/__init__.py,sha256=kC19-fjUIjUmWaNbRLugYAwZn-jtYgZt-dd65OLQ6hw,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=ZUQLdfZOFJ07om1L0ARPlcLMXGIskxgGMJwuo5Y_L6Q,18083
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=rfZM1LT5iPOgVbKcOLUuWh59LqXYV-g3zoQUKwKcz-I,2651
12
- labkey-2.6.1.dist-info/LICENSE.txt,sha256=xllut76FgcGL5zbIRvuRc7aezPbvlMUTWJPsVr2Sugg,11358
13
- labkey-2.6.1.dist-info/METADATA,sha256=YZi0E0SrIagngSWsUND3mTI4D7jJzz7YZiYI_b1Rmbc,1070
14
- labkey-2.6.1.dist-info/WHEEL,sha256=OqRkF0eY5GHssMorFjlbTIq072vpHpF60fIQA6lS9xA,92
15
- labkey-2.6.1.dist-info/top_level.txt,sha256=DQIk1fQNg7NxxBsEt_K7k-aopiH87jmnU98zwDp0n04,7
16
- labkey-2.6.1.dist-info/RECORD,,