labkey 3.3.0__tar.gz → 4.0.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.
@@ -2,6 +2,26 @@
2
2
  LabKey Python Client API News
3
3
  +++++++++++
4
4
 
5
+ What's New in the LabKey 4.0.0 package
6
+ ==============================
7
+
8
+ *Release date: 09/16/2025*
9
+ - Add save_rows API to query module
10
+ - Accessible via API wrappers e.g. api.query.save_rows
11
+ - Update the minimum required version of Python to 3.10
12
+ - Add "derivationDataScope" to PropertyDescriptor class. Thanks @eso-xyme!
13
+
14
+ What's New in the LabKey 3.4.0 package
15
+ ==============================
16
+
17
+ *Release date: 04/22/2025*
18
+ - Add "valueExpression" to PropertyDescriptor class
19
+ - needed for creating/adding domain calculated fields
20
+ - update Domain class to append calculatedFields to the domain's fields
21
+ - Fix Issue 52904
22
+ - UnexpectedRedirectError is not wrapped with ServerContextError
23
+ - Allow redirects when deactivating users
24
+
5
25
  What's New in the LabKey 3.3.0 package
6
26
  ==============================
7
27
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: labkey
3
- Version: 3.3.0
3
+ Version: 4.0.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
@@ -19,6 +19,7 @@ Classifier: Operating System :: Microsoft
19
19
  Classifier: Operating System :: POSIX
20
20
  Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Topic :: Scientific/Engineering
22
+ Requires-Python: >=3.10
22
23
  License-File: LICENSE.txt
23
24
  Requires-Dist: requests
24
25
  Provides-Extra: test
@@ -37,5 +38,20 @@ Requires-Dist: setuptools; extra == "build"
37
38
  Requires-Dist: build; extra == "build"
38
39
  Requires-Dist: twine; extra == "build"
39
40
  Requires-Dist: wheel; extra == "build"
41
+ Requires-Dist: packaging; extra == "build"
42
+ Dynamic: author
43
+ Dynamic: author-email
44
+ Dynamic: classifier
45
+ Dynamic: description
46
+ Dynamic: home-page
47
+ Dynamic: keywords
48
+ Dynamic: license
49
+ Dynamic: license-file
50
+ Dynamic: maintainer
51
+ Dynamic: maintainer-email
52
+ Dynamic: provides-extra
53
+ Dynamic: requires-dist
54
+ Dynamic: requires-python
55
+ Dynamic: summary
40
56
 
41
57
  Python client API for LabKey Server. Supports query and experiment APIs.
@@ -113,7 +113,7 @@ else:
113
113
  ```
114
114
 
115
115
  ## Supported Versions
116
- Python 3.7+ is fully supported.
116
+ Python 3.10+ is fully supported. <!-- Note: update setup.py python_requires if you change this -->
117
117
  LabKey Server v15.1 and later.
118
118
 
119
119
  ## Contributing
@@ -14,6 +14,6 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __title__ = "labkey"
17
- __version__ = "3.3.0"
17
+ __version__ = "4.0.0"
18
18
  __author__ = "LabKey"
19
19
  __license__ = "Apache License 2.0"
@@ -48,6 +48,9 @@ class PropertyDescriptor:
48
48
  self.default_value_type = kwargs.pop(
49
49
  "default_value_type", kwargs.pop("defaultValueType", None)
50
50
  )
51
+ self.derivation_data_scope = kwargs.pop(
52
+ "derivation_data_scope", kwargs.pop("derivationDataScope", None)
53
+ )
51
54
  self.description = kwargs.pop("description", None)
52
55
  self.dimension = kwargs.pop("dimension", None)
53
56
  self.disable_editing = kwargs.pop("disable_editing", kwargs.pop("disableEditing", None))
@@ -109,6 +112,7 @@ class PropertyDescriptor:
109
112
  )
110
113
  self.type_editable = kwargs.pop("type_editable", kwargs.pop("typeEditable", None))
111
114
  self.url = kwargs.pop("url", None)
115
+ self.value_expression = kwargs.pop("value_expression", kwargs.pop("valueExpression", None))
112
116
 
113
117
  def to_json(self, strip_none=True):
114
118
  # TODO: Likely only want to include those that are not None
@@ -119,6 +123,7 @@ class PropertyDescriptor:
119
123
  "defaultScale": self.default_scale,
120
124
  "defaultValue": self.default_value,
121
125
  "defaultValueType": self.default_value_type,
126
+ "derivationDataScope": self.derivation_data_scope,
122
127
  "description": self.description,
123
128
  "dimension": self.dimension,
124
129
  "disableEditing": self.disable_editing,
@@ -155,6 +160,7 @@ class PropertyDescriptor:
155
160
  "shownInUpdateView": self.shown_in_update_view,
156
161
  "typeEditable": self.type_editable,
157
162
  "url": self.url,
163
+ "valueExpression": self.value_expression,
158
164
  }
159
165
 
160
166
  json_formats = []
@@ -232,9 +238,11 @@ class Domain:
232
238
  "template_description", kwargs.pop("templateDescription", None)
233
239
  )
234
240
 
235
- fields = kwargs.pop("fields", [])
236
241
  fields_instances = []
237
-
242
+ fields = kwargs.pop("fields", [])
243
+ for field in fields:
244
+ fields_instances.append(PropertyDescriptor(**field))
245
+ fields = kwargs.pop("calculated_fields", kwargs.pop("calculatedFields", []))
238
246
  for field in fields:
239
247
  fields_instances.append(PropertyDescriptor(**field))
240
248
 
@@ -15,15 +15,15 @@
15
15
  #
16
16
  """
17
17
  ############################################################################
18
- NAME:
19
- LabKey Query API
18
+ NAME:
19
+ LabKey Query API
20
20
 
21
- SUMMARY:
21
+ SUMMARY:
22
22
  This module provides functions for interacting with data on a LabKey Server.
23
23
 
24
24
  DESCRIPTION:
25
- This module is designed to simplify querying and manipulating data in LabKey Server.
26
- Its APIs are modeled after the LabKey Server JavaScript APIs of the same names.
25
+ This module is designed to simplify querying and manipulating data in LabKey Server.
26
+ Its APIs are modeled after the LabKey Server JavaScript APIs of the same names.
27
27
 
28
28
  Installation and Setup for the LabKey Python API:
29
29
  https://github.com/LabKey/labkey-api-python/blob/master/README.md
@@ -41,7 +41,7 @@ https://www.labkey.org/home/developer/forum/project-start.view
41
41
  ############################################################################
42
42
  """
43
43
  import functools
44
- from typing import List, TextIO
44
+ from typing import List, Literal, NotRequired, TextIO, TypedDict
45
45
 
46
46
  from .server_context import ServerContext
47
47
  from .utils import waf_encode
@@ -196,7 +196,7 @@ def delete_rows(
196
196
  :param transacted: whether all of the updates should be done in a single transaction
197
197
  :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
198
198
  :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
199
- :param timeout: timeout of request in seconds (defaults to 30s)
199
+ :param timeout: timeout of request in seconds (defaults to 300s)
200
200
  :return:
201
201
  """
202
202
  url = server_context.build_url("query", "deleteRows.api", container_path=container_path)
@@ -232,7 +232,7 @@ def truncate_table(
232
232
  :param schema_name: schema of table
233
233
  :param query_name: table name to delete from
234
234
  :param container_path: labkey container path if not already set in context
235
- :param timeout: timeout of request in seconds (defaults to 30s)
235
+ :param timeout: timeout of request in seconds (defaults to 300s)
236
236
  :return:
237
237
  """
238
238
  url = server_context.build_url("query", "truncateTable.api", container_path=container_path)
@@ -275,7 +275,7 @@ def execute_sql(
275
275
  :param save_in_session: save query result as a named view to the session
276
276
  :param parameters: parameter values to pass through to a parameterized query
277
277
  :param required_version: Api version of response
278
- :param timeout: timeout of request in seconds (defaults to 30s)
278
+ :param timeout: timeout of request in seconds (defaults to 300s)
279
279
  :param waf_encode_sql: WAF encode sql in request (defaults to True)
280
280
  :return:
281
281
  """
@@ -331,7 +331,7 @@ def insert_rows(
331
331
  :param transacted: whether all of the updates should be done in a single transaction
332
332
  :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
333
333
  :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
334
- :param timeout: timeout of request in seconds (defaults to 30s)
334
+ :param timeout: timeout of request in seconds (defaults to 300s)
335
335
  :return:
336
336
  """
337
337
  url = server_context.build_url("query", "insertRows.api", container_path=container_path)
@@ -407,6 +407,93 @@ def import_rows(
407
407
  return server_context.make_request(url, payload, method="POST", file_payload=file_payload)
408
408
 
409
409
 
410
+ class Command(TypedDict):
411
+ """
412
+ TypedDict representing a command for saveRows API.
413
+ """
414
+
415
+ audit_behavior: NotRequired[AuditBehavior]
416
+ audit_user_comment: NotRequired[str]
417
+ command: Literal["insert", "update", "delete"]
418
+ container_path: NotRequired[str]
419
+ extra_context: NotRequired[dict]
420
+ query_name: str
421
+ rows: List[any]
422
+ schema_name: str
423
+ skip_reselect_rows: NotRequired[bool]
424
+
425
+
426
+ def save_rows(
427
+ server_context: ServerContext,
428
+ commands: List[Command],
429
+ api_version: float = None,
430
+ container_path: str = None,
431
+ extra_context: dict = None,
432
+ timeout: int = _default_timeout,
433
+ transacted: bool = None,
434
+ validate_only: bool = None,
435
+ ):
436
+ """
437
+ Save inserts, updates, and/or deletes to potentially multiple tables with a single request.
438
+ :param server_context: A LabKey server context. See utils.create_server_context.
439
+ :param commands: A List of the update/insert/delete operations to be performed.
440
+ :param api_version: decimal value that indicates the response version of the api. If this is 13.2 or higher, a
441
+ request that fails validation will be returned as a successful response. Use the 'errorCount' and 'committed'
442
+ properties in the response to tell if it committed or not.
443
+ :param container_path: folder path if not already part of server_context
444
+ :param extra_context: Extra context object passed into the transformation/validation script environment.
445
+ :param timeout: Request timeout in seconds (defaults to 300s)
446
+ :param transacted: Whether all the commands should be done in a single transaction, so they all succeed or all
447
+ fail. Defaults to true.
448
+ :param validate_only: Whether the server should attempt to proceed through all the commands but not commit them to
449
+ the database. Useful for scenarios like giving incremental validation feedback as a user fills out a UI form but
450
+ does not save anything until they explicitly request a save.
451
+ """
452
+ url = server_context.build_url("query", "saveRows.api", container_path=container_path)
453
+
454
+ json_commands = []
455
+ for command in commands:
456
+ json_command = {
457
+ "command": command["command"],
458
+ "queryName": command["query_name"],
459
+ "schemaName": command["schema_name"],
460
+ "rows": command["rows"],
461
+ }
462
+
463
+ if command.get("audit_behavior") is not None:
464
+ json_command["auditBehavior"] = command["audit_behavior"]
465
+
466
+ if command.get("audit_user_comment") is not None:
467
+ json_command["auditUserComment"] = command["audit_user_comment"]
468
+
469
+ if command.get("container_path") is not None:
470
+ json_command["containerPath"] = command["container_path"]
471
+
472
+ if command.get("extra_context") is not None:
473
+ json_command["extraContext"] = command["extra_context"]
474
+
475
+ if command.get("skip_reselect_rows") is not None:
476
+ json_command["skipReselectRows"] = command["skip_reselect_rows"]
477
+
478
+ json_commands.append(json_command)
479
+
480
+ payload = {"commands": json_commands}
481
+
482
+ if api_version is not None:
483
+ payload["apiVersion"] = api_version
484
+
485
+ if extra_context is not None:
486
+ payload["extraContext"] = extra_context
487
+
488
+ if transacted is not None:
489
+ payload["transacted"] = transacted
490
+
491
+ if validate_only is not None:
492
+ payload["validateOnly"] = validate_only
493
+
494
+ return server_context.make_request(url, json=payload, timeout=timeout)
495
+
496
+
410
497
  def select_rows(
411
498
  server_context: ServerContext,
412
499
  schema_name: str,
@@ -450,7 +537,7 @@ def select_rows(
450
537
  :param include_update_column: Boolean value that indicates whether to include an Update link column in results
451
538
  :param selection_key:
452
539
  :param required_version: decimal value that indicates the response version of the api
453
- :param timeout: Request timeout in seconds (defaults to 30s)
540
+ :param timeout: Request timeout in seconds (defaults to 300s)
454
541
  :param ignore_filter: Boolean, if true, the command will ignore any filter that may be part of the chosen view.
455
542
  :return:
456
543
  """
@@ -534,7 +621,7 @@ def update_rows(
534
621
  :param transacted: whether all of the updates should be done in a single transaction
535
622
  :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
536
623
  :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
537
- :param timeout: timeout of request in seconds (defaults to 30s)
624
+ :param timeout: timeout of request in seconds (defaults to 300s)
538
625
  :return:
539
626
  """
540
627
  url = server_context.build_url("query", "updateRows.api", container_path=container_path)
@@ -580,7 +667,7 @@ def move_rows(
580
667
  :param transacted: whether all of the updates should be done in a single transaction
581
668
  :param audit_behavior: used to override the audit behavior for the update. See class query.AuditBehavior
582
669
  :param audit_user_comment: used to provide a comment that will be attached to certain detailed audit log records
583
- :param timeout: timeout of request in seconds (defaults to 30s)
670
+ :param timeout: timeout of request in seconds (defaults to 300s)
584
671
  :return:
585
672
  """
586
673
  url = server_context.build_url("query", "moveRows.api", container_path=container_path)
@@ -726,6 +813,28 @@ class QueryWrapper:
726
813
  import_lookup_by_alternate_key,
727
814
  )
728
815
 
816
+ @functools.wraps(save_rows)
817
+ def save_rows(
818
+ self,
819
+ commands: List[Command],
820
+ api_version: float = None,
821
+ container_path: str = None,
822
+ extra_context: dict = None,
823
+ timeout: int = _default_timeout,
824
+ transacted: bool = None,
825
+ validate_only: bool = None,
826
+ ):
827
+ return save_rows(
828
+ self.server_context,
829
+ commands,
830
+ api_version,
831
+ container_path,
832
+ extra_context,
833
+ timeout,
834
+ transacted,
835
+ validate_only,
836
+ )
837
+
729
838
  @functools.wraps(select_rows)
730
839
  def select_rows(
731
840
  self,
@@ -121,6 +121,7 @@ def deactivate_users(
121
121
  target_ids=target_ids,
122
122
  api="deactivateUsers.view",
123
123
  container_path=container_path,
124
+ allow_redirects=True,
124
125
  )
125
126
  if response is not None and response["status_code"] == 200:
126
127
  return dict(success=True)
@@ -360,7 +361,11 @@ def __make_security_role_api_request(
360
361
 
361
362
 
362
363
  def __make_user_api_request(
363
- server_context: ServerContext, target_ids: List[int], api: str, container_path: str = None
364
+ server_context: ServerContext,
365
+ target_ids: List[int],
366
+ api: str,
367
+ container_path: str = None,
368
+ allow_redirects: bool = False,
364
369
  ):
365
370
  """
366
371
  Make a request to the LabKey User Controller
@@ -372,7 +377,7 @@ def __make_user_api_request(
372
377
  """
373
378
  url = server_context.build_url(USER_CONTROLLER, api, container_path)
374
379
 
375
- return server_context.make_request(url, {"userId": target_ids})
380
+ return server_context.make_request(url, {"userId": target_ids}, allow_redirects=allow_redirects)
376
381
 
377
382
 
378
383
  class SecurityWrapper:
@@ -164,7 +164,12 @@ class ServerContext:
164
164
  return client
165
165
 
166
166
  def handle_request_exception(self, exception):
167
- if type(exception) in [RequestAuthorizationError, QueryNotFoundError, ServerNotFoundError]:
167
+ if type(exception) in [
168
+ RequestAuthorizationError,
169
+ QueryNotFoundError,
170
+ ServerNotFoundError,
171
+ UnexpectedRedirectError,
172
+ ]:
168
173
  raise exception
169
174
 
170
175
  raise ServerContextError(self, exception)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: labkey
3
- Version: 3.3.0
3
+ Version: 4.0.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
@@ -19,6 +19,7 @@ Classifier: Operating System :: Microsoft
19
19
  Classifier: Operating System :: POSIX
20
20
  Classifier: Programming Language :: Python :: 3
21
21
  Classifier: Topic :: Scientific/Engineering
22
+ Requires-Python: >=3.10
22
23
  License-File: LICENSE.txt
23
24
  Requires-Dist: requests
24
25
  Provides-Extra: test
@@ -37,5 +38,20 @@ Requires-Dist: setuptools; extra == "build"
37
38
  Requires-Dist: build; extra == "build"
38
39
  Requires-Dist: twine; extra == "build"
39
40
  Requires-Dist: wheel; extra == "build"
41
+ Requires-Dist: packaging; extra == "build"
42
+ Dynamic: author
43
+ Dynamic: author-email
44
+ Dynamic: classifier
45
+ Dynamic: description
46
+ Dynamic: home-page
47
+ Dynamic: keywords
48
+ Dynamic: license
49
+ Dynamic: license-file
50
+ Dynamic: maintainer
51
+ Dynamic: maintainer-email
52
+ Dynamic: provides-extra
53
+ Dynamic: requires-dist
54
+ Dynamic: requires-python
55
+ Dynamic: summary
40
56
 
41
57
  Python client API for LabKey Server. Supports query and experiment APIs.
@@ -2,7 +2,6 @@ CHANGE.txt
2
2
  LICENSE.txt
3
3
  MANIFEST.in
4
4
  README.md
5
- debug.txt
6
5
  pyproject.toml
7
6
  pytest.ini
8
7
  setup.cfg
@@ -5,6 +5,7 @@ setuptools
5
5
  build
6
6
  twine
7
7
  wheel
8
+ packaging
8
9
 
9
10
  [dev]
10
11
  pytest
@@ -34,7 +34,7 @@ long_desc = "Python client API for LabKey Server. Supports query and experiment
34
34
 
35
35
  tests_require = ["pytest", "requests", "mock", "pytest-cov"]
36
36
  dev_require = ["pytest", "requests", "mock", "pytest-cov", "black"]
37
- build_require = ["setuptools", "build", "twine", "wheel"]
37
+ build_require = ["setuptools", "build", "twine", "wheel", "packaging"]
38
38
 
39
39
  setup(
40
40
  name="labkey",
@@ -49,6 +49,7 @@ setup(
49
49
  url="https://github.com/LabKey/labkey-api-python",
50
50
  packages=packages,
51
51
  package_data={},
52
+ python_requires=">=3.10", # Note: update README.md supported versions if you change this
52
53
  install_requires=["requests"],
53
54
  extras_require={"test": tests_require, "dev": dev_require, "build": build_require},
54
55
  keywords="labkey api client",
labkey-3.3.0/debug.txt DELETED
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