Background
When the server returns an Enum from a parameter get, it serializes it as {"value": member.value, "_class_type": "pkg.mod.EnumClass"} (_convert_enum_to_dict, added in #140). The client rebuilds the member in _convert_dict_to_obj. JSON has no tuple type, so any tuple inside value reaches the client as a list.
#160 fixed this for namedtuple-valued enums (the QDAC1 Mode enum): when value is a list, it takes the value type of the first enum member and rebuilds it with ValueType(*value). That works for namedtuples, but some cases are still broken, and one used to work and no longer does.
Remaining problems
| Enum values |
Before #160 |
After #160 |
namedtuple, e.g. X = P(1, 2) |
ValueError |
works |
plain tuple, e.g. X = (1, 2) |
ValueError |
TypeError: tuple expected at most 1 argument, got 2 |
list, e.g. X = [1, 2] |
works |
TypeError: list expected at most 1 argument, got 2 (regression) |
nested tuple, e.g. X = ((1, 2), 3) |
ValueError |
fails, inner list never converted |
| mixed value types across members |
ValueError for list values |
may rebuild with the wrong type, since it uses the first member's type |
List-valued enums are rare (list values can't be hashed), so the regression probably doesn't hit anyone in practice. None of the enums in this repo (Operation, ParameterTypes, LogLevels, the test StatusFlag) have tuple or list values.
Proposed fix
Look up the value as it arrives first, and only if that fails, convert lists to tuples recursively and look it up again. A namedtuple compares and hashes equal to a plain tuple with the same items, so a plain-tuple lookup finds namedtuple members without knowing their type.
def _lists_to_tuples(v):
if isinstance(v, list):
return tuple(_lists_to_tuples(x) for x in v)
return v
if isinstance(cls, type) and issubclass(cls, Enum):
value = item_dict["value"]
try:
return cls(value)
except ValueError:
if isinstance(value, list):
return cls(_lists_to_tuples(value))
raise
With this, list-valued enums go back to working, plain-tuple and nested-tuple enums start working, and the code no longer guesses the value type from the first member.
Tests
#160 added no tests. Add round-trip cases (serialize a ServerResponse whose message is the enum member, then deserialize_obj) to test/pytest/test_enum_serialization.py for enums with:
Background
When the server returns an
Enumfrom a parameter get, it serializes it as{"value": member.value, "_class_type": "pkg.mod.EnumClass"}(_convert_enum_to_dict, added in #140). The client rebuilds the member in_convert_dict_to_obj. JSON has no tuple type, so any tuple insidevaluereaches the client as a list.#160 fixed this for namedtuple-valued enums (the QDAC1
Modeenum): whenvalueis a list, it takes the value type of the first enum member and rebuilds it withValueType(*value). That works for namedtuples, but some cases are still broken, and one used to work and no longer does.Remaining problems
X = P(1, 2)ValueErrorX = (1, 2)ValueErrorTypeError: tuple expected at most 1 argument, got 2X = [1, 2]TypeError: list expected at most 1 argument, got 2(regression)X = ((1, 2), 3)ValueErrorValueErrorfor list valuesList-valued enums are rare (list values can't be hashed), so the regression probably doesn't hit anyone in practice. None of the enums in this repo (
Operation,ParameterTypes,LogLevels, the testStatusFlag) have tuple or list values.Proposed fix
Look up the value as it arrives first, and only if that fails, convert lists to tuples recursively and look it up again. A namedtuple compares and hashes equal to a plain tuple with the same items, so a plain-tuple lookup finds namedtuple members without knowing their type.
With this, list-valued enums go back to working, plain-tuple and nested-tuple enums start working, and the code no longer guesses the value type from the first member.
Tests
#160 added no tests. Add round-trip cases (serialize a
ServerResponsewhose message is the enum member, thendeserialize_obj) totest/pytest/test_enum_serialization.pyfor enums with: