55allowing other components to react to changes in the collection's contents.
66
77The module provides:
8+ - ListSignalType: Enum defining signal types for list collections
89- ObservableList: A list descriptor that emits signals on modifications
910- SignalingList: The underlying list implementation that manages signal emission
1011
1314"""
1415
1516from collections .abc import Iterable , MutableSequence
17+ from enum import Enum
1618from typing import Any
1719
1820from .mesa_signal import BaseObservable , HasObservables
1921
2022__all__ = [
23+ "ListSignalType" ,
2124 "ObservableList" ,
2225]
2326
2427
28+ class ListSignalType (str , Enum ):
29+ """Enumeration of signal types that observable lists can emit.
30+
31+ Provides list-specific signal types with IDE autocomplete and type safety.
32+ Inherits from str for backward compatibility with existing string-based code.
33+ Includes all list-specific signals (INSERT, APPEND, REMOVE, REPLACE) plus
34+ the base CHANGE signal inherited from the observable protocol.
35+
36+ Note on Design:
37+ This enum does NOT extend SignalType because Python Enums cannot be extended
38+ once they have members defined. Instead, we include CHANGE as a member here
39+ to maintain compatibility. The string inheritance provides value equality:
40+ ListSignalType.CHANGE == SignalType.CHANGE == "change" (all True).
41+
42+ Attributes:
43+ CHANGE: Emitted when the list itself is replaced/assigned.
44+ INSERT: Emitted when an item is inserted into the list.
45+ APPEND: Emitted when an item is appended to the list.
46+ REMOVE: Emitted when an item is removed from the list.
47+ REPLACE: Emitted when an item is replaced/modified in the list.
48+
49+ Examples:
50+ >>> from mesa.experimental.mesa_signals import ObservableList, HasObservables, ListSignalType
51+ >>> class MyModel(HasObservables):
52+ ... items = ObservableList()
53+ ... def __init__(self):
54+ ... super().__init__()
55+ ... self.items = []
56+ >>> model = MyModel()
57+ >>> model.observe("items", ListSignalType.INSERT, lambda s: print(f"Inserted {s.new}"))
58+ >>> model.items.insert(0, "first")
59+ Inserted first
60+
61+ Note:
62+ String-based signal types are still supported for backward compatibility:
63+ >>> model.observe("items", "insert", handler) # Still works
64+ Also compatible with SignalType.CHANGE since both equal "change" as strings.
65+ """
66+
67+ CHANGE = "change"
68+ INSERT = "insert"
69+ APPEND = "append"
70+ REMOVE = "remove"
71+ REPLACE = "replace"
72+
73+ def __str__ (self ):
74+ """Return the string value of the signal type."""
75+ return self .value
76+
77+
2578class ObservableList (BaseObservable ):
2679 """An ObservableList that emits signals on changes to the underlying list."""
2780
2881 def __init__ (self ):
2982 """Initialize the ObservableList."""
3083 super ().__init__ ()
31- self .signal_types : set = {"remove" , "replace" , "change" , "insert" , "append" }
84+ # Use all members of ListSignalType enum
85+ self .signal_types : set = set (ListSignalType )
3286 self .fallback_value = []
3387
3488 def __set__ (self , instance : "HasObservables" , value : Iterable ):
@@ -75,7 +129,9 @@ def __setitem__(self, index: int, value: Any) -> None:
75129 """
76130 old_value = self .data [index ]
77131 self .data [index ] = value
78- self .owner .notify (self .name , old_value , value , "replace" , index = index )
132+ self .owner .notify (
133+ self .name , old_value , value , ListSignalType .REPLACE , index = index
134+ )
79135
80136 def __delitem__ (self , index : int ) -> None :
81137 """Delete item at index.
@@ -86,7 +142,9 @@ def __delitem__(self, index: int) -> None:
86142 """
87143 old_value = self .data
88144 del self .data [index ]
89- self .owner .notify (self .name , old_value , None , "remove" , index = index )
145+ self .owner .notify (
146+ self .name , old_value , None , ListSignalType .REMOVE , index = index
147+ )
90148
91149 def __getitem__ (self , index ) -> Any :
92150 """Get item at index.
@@ -112,7 +170,7 @@ def insert(self, index, value):
112170
113171 """
114172 self .data .insert (index , value )
115- self .owner .notify (self .name , None , value , "insert" , index = index )
173+ self .owner .notify (self .name , None , value , ListSignalType . INSERT , index = index )
116174
117175 def append (self , value ):
118176 """Insert value at index.
@@ -124,7 +182,7 @@ def append(self, value):
124182 """
125183 index = len (self .data )
126184 self .data .append (value )
127- self .owner .notify (self .name , None , value , "append" , index = index )
185+ self .owner .notify (self .name , None , value , ListSignalType . APPEND , index = index )
128186
129187 def __str__ (self ):
130188 return self .data .__str__ ()
0 commit comments