Skip to content

Commit f5bcd73

Browse files
authored
Fix issues 43337, 43611 (#48)
- Add get_domain_details API to domain module - Fix Issue 43337 - Fix ConditionalFormat.to_json to match what LabKey Server response - domain.save: support optional "options"
1 parent 7835d36 commit f5bcd73

File tree

6 files changed

+116
-16
lines changed

6 files changed

+116
-16
lines changed

CHANGE.txt

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
LabKey Python Client API News
33
+++++++++++
44

5+
What's New in the LabKey 2.2.0 package
6+
==============================
7+
- Add `domain.get_domain_details` API to domain module.
8+
- Support saving domain options via `domain.save`.
9+
- Fix `ConditionalFormat.to_json()` to match server response.
10+
511
What's New in the LabKey 2.1.1 package
612
==============================
713

labkey/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
from labkey import domain, query, experiment, security, utils
1717

1818
__title__ = "labkey"
19-
__version__ = "2.1.1"
19+
__version__ = "2.2.0"
2020
__author__ = "LabKey"
2121
__license__ = "Apache License 2.0"

labkey/domain.py

+71-9
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
# limitations under the License.
1515
#
1616
import functools
17-
from typing import Union, List
17+
from typing import Dict, List, Union, Tuple
1818

1919
from .server_context import ServerContext
2020
from labkey.query import QueryFilter
@@ -207,12 +207,12 @@ def __init__(self, **kwargs):
207207

208208
def to_json(self):
209209
data = {
210-
"backgroundColor": self.background_color,
210+
"backgroundcolor": self.background_color,
211211
"bold": self.bold,
212212
"filter": self.filter,
213213
"italic": self.italic,
214214
"strikethrough": self.strike_through,
215-
"textColor": self.text_color,
215+
"textcolor": self.text_color,
216216
}
217217

218218
return data
@@ -392,12 +392,12 @@ def create(
392392
domain = None
393393

394394
# domainDesign is not required when creating a domain from a template
395-
if domain_definition["domainDesign"] is not None:
395+
if domain_definition.get("domainDesign", None) is not None:
396396
domain_fields = domain_definition["domainDesign"]["fields"]
397397
domain_definition["domainDesign"]["fields"] = list(
398398
map(__format_conditional_filters, domain_fields)
399399
)
400-
400+
401401
raw_domain = server_context.make_request(url, json=domain_definition)
402402

403403
if raw_domain is not None:
@@ -436,13 +436,50 @@ def get(
436436
"""
437437
url = server_context.build_url("property", "getDomain.api", container_path=container_path)
438438
payload = {"schemaName": schema_name, "queryName": query_name}
439-
domain = None
440439
raw_domain = server_context.make_request(url, payload, method="GET")
441440

441+
if raw_domain is not None:
442+
return Domain(**raw_domain)
443+
444+
return None
445+
446+
447+
def get_domain_details(
448+
server_context: ServerContext,
449+
schema_name: str = None,
450+
query_name: str = None,
451+
domain_id: int = None,
452+
domain_kind: str = None,
453+
container_path: str = None,
454+
) -> Tuple[Domain, Dict]:
455+
"""
456+
Gets a domain design and its associated options.
457+
:param server_context: A LabKey server context. See utils.create_server_context.
458+
:param schema_name: schema of table
459+
:param query_name: table name of domain to get
460+
:param domain_id: id of domain to get
461+
:param domain_kind: domainKind of domain to get
462+
:param container_path: labkey container path if not already set in context
463+
:return: Domain, Dict
464+
"""
465+
url = server_context.build_url(
466+
"property", "getDomainDetails.api", container_path=container_path
467+
)
468+
payload = {
469+
"schemaName": schema_name,
470+
"queryName": query_name,
471+
"domainId": domain_id,
472+
"domainKind": domain_kind,
473+
}
474+
response = server_context.make_request(url, payload, method="GET")
475+
raw_domain = response.get("domainDesign", None)
476+
domain = None
477+
options = response.get("options", None)
478+
442479
if raw_domain is not None:
443480
domain = Domain(**raw_domain)
444481

445-
return domain
482+
return domain, options
446483

447484

448485
def infer_fields(
@@ -473,6 +510,7 @@ def save(
473510
query_name: str,
474511
domain: Domain,
475512
container_path: str = None,
513+
options: Dict = None,
476514
) -> any:
477515
"""
478516
Saves the provided domain design
@@ -481,6 +519,7 @@ def save(
481519
:param query_name: query name of domain
482520
:param domain: Domain to save
483521
:param container_path: labkey container path if not already set in context
522+
:param options: associated domain options to be saved
484523
:return:
485524
"""
486525
url = server_context.build_url("property", "saveDomain.api", container_path=container_path)
@@ -490,6 +529,9 @@ def save(
490529
"schemaName": schema_name,
491530
}
492531

532+
if options is not None:
533+
payload["options"] = options
534+
493535
return server_context.make_request(url, json=payload)
494536

495537

@@ -513,10 +555,30 @@ def drop(self, schema_name: str, query_name: str, container_path: str = None):
513555
def get(self, schema_name: str, query_name: str, container_path: str = None):
514556
return get(self.server_context, schema_name, query_name, container_path)
515557

558+
@functools.wraps(get_domain_details)
559+
def get_domain_details(
560+
self,
561+
schema_name: str = None,
562+
query_name: str = None,
563+
domain_id: int = None,
564+
domain_kind: str = None,
565+
container_path: str = None,
566+
):
567+
return get_domain_details(
568+
self.server_context, schema_name, query_name, domain_id, domain_kind, container_path
569+
)
570+
516571
@functools.wraps(infer_fields)
517572
def infer_fields(self, data_file: any, container_path: str = None):
518573
return infer_fields(self.server_context, data_file, container_path)
519574

520575
@functools.wraps(save)
521-
def save(self, schema_name: str, query_name: str, domain: Domain, container_path: str = None):
522-
return save(self.server_context, schema_name, query_name, domain, container_path)
576+
def save(
577+
self,
578+
schema_name: str,
579+
query_name: str,
580+
domain: Domain,
581+
container_path: str = None,
582+
options: Dict = None,
583+
):
584+
return save(self.server_context, schema_name, query_name, domain, container_path, options)

samples/domain_example.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@
6565
dataset_domain = api.domain.create(dataset_domain_definition)
6666

6767
###################
68-
# Get a domain
68+
# Get a domain and it's associated options
6969
###################
70-
list_domain = api.domain.get("lists", "BloodTypes")
70+
list_domain, list_domain_options = api.domain.get_domain_details("lists", "BloodTypes")
7171

7272
# examine different from the domain
7373
print(list_domain.name)
@@ -82,10 +82,13 @@
8282
fields_file = open("data/infer.tsv", "rb")
8383
inferred_fields = api.domain.infer_fields(fields_file)
8484

85+
# Update the description via domain options
86+
list_domain_options["description"] = "Contains all known human blood types"
87+
8588
for field in inferred_fields:
8689
list_domain.add_field(field)
8790

88-
api.domain.save("lists", "BloodTypes", list_domain)
91+
api.domain.save("lists", "BloodTypes", list_domain, options=list_domain_options)
8992

9093
###################
9194
# Drop a domain

test/integration/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,6 @@ def project(server_context_vars):
6161
# The project may not exist, and that is ok.
6262
pass
6363

64-
project_ = container.create(context, PROJECT_NAME, folder_type="study")
64+
project_ = container.create(context, PROJECT_NAME, folder_type="Study")
6565
yield project_
6666
container.delete(context, PROJECT_NAME)

test/integration/test_domain.py

+31-2
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@ def test_remove_conditional_format(api: APIWrapper, list_fixture):
166166

167167

168168
def test_update_conditional_format_serialize_filter(api: APIWrapper, list_fixture):
169-
from labkey.query import QueryFilter
170-
171169
new_filter = QueryFilter("formatted", 15, QueryFilter.Types.GREATER_THAN_OR_EQUAL)
172170
cf = conditional_format(new_filter, text_color="ff00ff")
173171

@@ -232,3 +230,34 @@ def test_create_list_with_conditional_formatted_field(api: APIWrapper):
232230
assert len(field.conditional_formats) == 2
233231

234232
api.domain.drop(LISTS_SCHEMA, "composed_list_name")
233+
234+
235+
def test_get_domain_details(api: APIWrapper, list_fixture):
236+
# test retrieving domain by specifying schema/query
237+
domain, options = api.domain.get_domain_details(LISTS_SCHEMA, LIST_NAME)
238+
assert domain.name == LIST_NAME
239+
assert len(domain.fields) == 2
240+
assert domain.fields[0].name == "rowId"
241+
assert domain.fields[1].name == "formatted"
242+
assert domain.fields[1].conditional_formats[0].to_json() == CONDITIONAL_FORMAT[0]
243+
assert options["keyName"] == "rowId"
244+
assert options["name"] == LIST_NAME
245+
246+
# test retrieving domain by specifying domainId
247+
assert domain.domain_id is not None
248+
domain_from_id, domain_from_id_options = api.domain.get_domain_details(
249+
domain_id=domain.domain_id
250+
)
251+
assert domain_from_id.name == LIST_NAME
252+
253+
254+
def test_domain_save_options(api: APIWrapper, list_fixture):
255+
domain, options = api.domain.get_domain_details(LISTS_SCHEMA, LIST_NAME)
256+
expected_description = "updated description from test"
257+
assert options.get("description") != expected_description
258+
259+
options["description"] = expected_description
260+
api.domain.save(LISTS_SCHEMA, LIST_NAME, domain, options=options)
261+
262+
updated_domain, updated_options = api.domain.get_domain_details(LISTS_SCHEMA, LIST_NAME)
263+
assert updated_options.get("description") == expected_description

0 commit comments

Comments
 (0)