feat: Add SignalType enum for type-safe signal definitions.#3056
feat: Add SignalType enum for type-safe signal definitions.#3056
Conversation
|
Performance benchmarks:
|
|
I know there was an idea of using an enum in the fixme, but this can also be handled via |
|
@quaquel Here's my comparison of both approaches: Literal[] Pros
Literal[] Cons
Enum Pros
Regarding the Broader FIXME
This actually favors enums because:
|
I don't think this is correct.
I mostly agree, although support for typing and, by extension, Literal is improving rapidly in most IDEs.
I am not entirely sure how subclassing Enums works, so I would need an example or some time to investigate this myself. However, ideally, it should be trivial to obtain an overview of all observables and, for each observable, the signals it can emit. |
|
Runtime validation: Extensibility and introspection: from enum import Enum
class BaseSignals(str, Enum):
CHANGED = "changed"
REMOVED = "removed"
class CellSignals(BaseSignals):
AGENT_ADDED = "agent_added"
AGENT_REMOVED = "agent_removed"
# Works with type checkers
def handle_signal(signal: CellSignals): ...
# Introspection
list(CellSignals) # All signals including inherited ones
BaseSignals.CHANGED in CellSignals # True |
|
Thanks. I'll try to take a quick look myself later today at your example as well as reacquaint myself with this code to see if enums indeed solve all issues I had in mind when I wrote the fixme. |
|
Sure got it. |
|
As a further exploration of this idea, could you also update observable_collections? Here, more signals exist, so it's a nice test to see if using enums works as intended. |
Sure I will update it. |
…fe signal definitions
c4c002a to
15a66ac
Compare
|
@quaquel I have updated the |
| ] | ||
|
|
||
|
|
||
| class ListSignalType(str, Enum): |
There was a problem hiding this comment.
why does this not extent SignalType?
There was a problem hiding this comment.
Python Enums can’t be cleanly subclassed, so inheriting SignalType isn’t viable. I added ListSignalType as (str, Enum) to preserve runtime string-equality (e.g. ListSignalType.INSERT == "insert" == SignalType.INSERT), keep full backward compatibility, and get IDE/type-safety for list-specific signals.
| self.data[index] = value | ||
| self.owner.notify(self.name, old_value, value, "replace", index=index) | ||
| self.owner.notify( | ||
| self.name, old_value, value, ListSignalType.REPLACE, index=index |
There was a problem hiding this comment.
I do like the clean code due to the enum here. So yes, enums seem like the way to go.
| """Initialize the ObservableList.""" | ||
| super().__init__() | ||
| self.signal_types: set = {"remove", "replace", "change", "insert", "append"} | ||
| self.signal_types: set = { |
There was a problem hiding this comment.
I guess this should be simpler, because it's just all fields of the ListSignalEnum
There was a problem hiding this comment.
I replaced the manual string set with an automatic set of enum members so it always reflects ListSignalType. Added a one-line comment explaining the choice.
4cac8b8 to
15c8167
Compare
mesa_signalsuses plain strings for signal types. Typos are only caught at runtime, there is no IDE autocomplete or discoverability (FIXME in source).Solution
Add
SignalType(str, Enum) (e.g., SignalType.CHANGE)and use it internally while preserving full backward compatibility with string signal names.Changes
mesa_signal.py- added SignalType, updated usages__init__.py- exported SignalTypeTo Reproduce
Testing