From 723f73a0c667b9c1d6f62f92ba1362cc1c2133a8 Mon Sep 17 00:00:00 2001 From: lindsay stevens Date: Wed, 29 Nov 2023 21:38:18 +1100 Subject: [PATCH] fix: preserve order of columns when building secondary instance - Seems like main reason for sorting in the first place was to help satisfy string-based matching for XML output tests. Since there are XPath test facilities now, it's not necessary. - fix: remove explicit sorting in survey.py and utils.py - fix: remove order randomisation in xls2json.py via conversion of keys to set objects - use dicts instead so that order is preserved. - fix: tests broken by the above changes - fix: add retry loop for shutting down server between test runs. - add: test asserting column order for additional choices columns --- pyxform/survey.py | 2 +- pyxform/utils.py | 6 ++---- pyxform/xls2json.py | 11 +++++++---- tests/fixtures/strings.ini | 2 -- tests/j2x_question_tests.py | 33 +++++++++++++++++++++----------- tests/test_choices_sheet.py | 29 ++++++++++++++++++++++++++++ tests/test_external_instances.py | 18 ++++------------- tests/test_whitespace.py | 19 ++++++++++++------ tests/validators/server.py | 12 +++++++++--- 9 files changed, 87 insertions(+), 45 deletions(-) diff --git a/pyxform/survey.py b/pyxform/survey.py index 242fbcebd..e0061c173 100644 --- a/pyxform/survey.py +++ b/pyxform/survey.py @@ -296,7 +296,7 @@ def _generate_static_instances(list_name, choice_list) -> InstanceInfo: itext_id = "-".join([list_name, str(idx)]) choice_element_list.append(node("itextId", itext_id)) - for name, value in sorted(choice.items()): + for name, value in choice.items(): if isinstance(value, str) and name != "label": choice_element_list.append(node(name, str(value))) if ( diff --git a/pyxform/utils.py b/pyxform/utils.py index 7786cb1cb..a5d8c926b 100644 --- a/pyxform/utils.py +++ b/pyxform/utils.py @@ -80,10 +80,8 @@ def node(*args, **kwargs) -> DetachableElement: assert len(unicode_args) <= 1 parsed_string = False - # Convert the kwargs xml attribute dictionary to a xml.dom.minidom.Element. Sort the - # attributes to guarantee a consistent order across Python versions. - # See pyxform_test_case.reorder_attributes for details. - for k, v in iter(sorted(kwargs.items())): + # Convert the kwargs xml attribute dictionary to a xml.dom.minidom.Element. + for k, v in iter(kwargs.items()): if k in blocked_attributes: continue if k == "toParseString": diff --git a/pyxform/xls2json.py b/pyxform/xls2json.py index d21f3644e..21add16f5 100644 --- a/pyxform/xls2json.py +++ b/pyxform/xls2json.py @@ -58,19 +58,22 @@ def merge_dicts(dict_a, dict_b, default_key="default"): if dict_b is None or dict_b == {}: return dict_a - if type(dict_a) is not dict: + if not isinstance(dict_a, dict): if default_key in dict_b: return dict_b dict_a = {default_key: dict_a} - if type(dict_b) is not dict: + if not isinstance(dict_b, dict): if default_key in dict_a: return dict_a dict_b = {default_key: dict_b} - all_keys = set(dict_a.keys()).union(set(dict_b.keys())) + # Union keys but retain order (as opposed to set()), preferencing dict_a then dict_b. + # E.g. {"a": 1, "b": 2} + {"c": 3, "a": 4} -> {"a": None, "b": None, "c": None} + all_keys = {k: None for k in dict_a.keys()} + all_keys.update({k: None for k in dict_b.keys()}) out_dict = dict() - for key in all_keys: + for key in all_keys.keys(): out_dict[key] = merge_dicts(dict_a.get(key), dict_b.get(key), default_key) return out_dict diff --git a/tests/fixtures/strings.ini b/tests/fixtures/strings.ini index a46e9cded..90dfedd34 100644 --- a/tests/fixtures/strings.ini +++ b/tests/fixtures/strings.ini @@ -11,8 +11,6 @@ test_simple_integer_question_type_multilingual_control = test_simple_date_question_type_multilingual_control =