Home > Blockchain >  Translation of message dict into msg enum
Translation of message dict into msg enum

Time:08-18

I'm dealing with refactoring code which extensively uses dicts in a circumstance where enums could be used. Unfortunately, to reduce typing the dict keys were abbreviated in a cryptic fashion. In order to have more meaningful code and fewer string literals as well as a more advanced interface I translated the message dictionary based code into an Enum based code using the same messages.

The message dictionaries looked like the following:

MsgDictionary = {'none': None,
                 'STJ': 'start_job',
                 'RPS': 'report_status',
                 'KLJ': 'kill_job'}

ExecStates = {'none': None,
              'JCNS': 'job_could_not_start',
              'JSS': 'job_successfully_started',
              'JSF': 'job_successfully_finished'}

This, unfortunately lead to cluttered code:

...
self.send_message(id = MsgDictionary["stj"], some_data)

...

msg = self.receive_msg()
if msg.id in (MsgDictionary['STJ'], MsgDictionary['KLJ']):
    self.toggle_job()
...

I would merely like to get rid of the string accesses, the cryptic names and the low level interface, like in the following. This send_message should send the str typed value of the Enum not the Enum instance itself.

...
self.send_message(id = MessagesEnum.START_JOB, some_data)

...

msg = self.receive_msg()
if msg.id in (MessagesEnum.START_JOB, MessagesEnum.KILL_JOB):
    self.toggle_job()
...

But as in the original case, undefined execution states should still be allowed. This does currently not work. The reason is to not break existing code:

e = ExecStates(None)
-> ValueError: None is not a valid ExecutionStates

And I would like to be able to compare enum instances, e.g.:

e = ExecState[START_JOB]
if e == ExecState[START_JOB]:
    pass

if e == ExecState[KILL_JOB]:
    pass

Using the following definitions, I believe I'm almost there:

import enum


class _BaseEnum(str, enum.Enum):
    @classmethod
    def values(cls) -> DictValues:
        return cls.__members__.values()

    def _generate_next_value_(name: str, *args: object) -> str:
        return name.lower()

    def __str__(self):
        return str(self.value)  # Use stringification to cover the None value case


class MessageEnum(_BaseEnum):
    NONE = None
    START_JOB = enum.auto()
    REPORT_STATUS = enum.auto()
    KILL_JOB = enum.auto()


class ExecutionState(_BaseEnum):
    NONE = None
    JOB_COULD_NOT_START = enum.auto()
    JOB_SUCCESSFULLY_STARTED = enum.auto()
    JOB_SUCCESSFULLY_FINISHED = enum.auto()

However, one problem still remains. How can I deal with None value as well as strings in the enumerations? In my case, all enum items gets mapped to the lowercase of the enum item name. Which is the intended functionality. However, None gets unintendedly mapped to 'None'. This in effect leads to problems at other spots in the existing code which initializes an ExecutionState instance with None. I would like to also cover this case to not break existing code.

When I add a __new__ method to the _BaseEnum,

def __new__(cls, value):
    obj = str.__new__(cls)
    obj._value_ = value

    return obj

I loose the possibility to compare the enumeration instances as all instances compare equal to ``.

My question is, in order to solve my problem, if I can corner case the None either in the _generate_next_value_ or the __new__ method or maybe using a proxy pattern ?

CodePudding user response:

Two things that should help:

  • in your __new__, the creation line should read obj = str.__new__(cls, value) -- that way each instance will compare equal to its lower-cased name

  • export your enum members to the global namespace, and use is:

    START_JOB, REPORT_STATUS, KILL_JOB = MessageEnum
    ...
    if e is START_JOB: ...
    ...
    if msg.id in (START_JOB, KILL_JOB): ...
  • Related