14
14
Iterable ,
15
15
Literal ,
16
16
Mapping ,
17
- Sequence ,
18
17
Type ,
19
18
TypeVar ,
20
19
cast ,
33
32
34
33
__all__ = ["is_evented" , "get_evented_namespace" , "EqOperator" , "SignalGroupDescriptor" ]
35
34
35
+ FieldAliasFunc = Callable [[str ], str | None ]
36
36
T = TypeVar ("T" , bound = Type )
37
37
S = TypeVar ("S" )
38
38
@@ -150,8 +150,7 @@ def _build_dataclass_signal_group(
150
150
cls : type ,
151
151
signal_group_class : type [SignalGroup ] = SignalGroup ,
152
152
equality_operators : Iterable [tuple [str , EqOperator ]] | None = None ,
153
- skip_signals : Sequence [str ] | None = None ,
154
- signal_aliases : Mapping [str , str | None ] | Callable [[str ], str ] = {},
153
+ signal_aliases : Mapping [str , str | None ] | FieldAliasFunc | None = None ,
155
154
) -> type [SignalGroup ]:
156
155
"""Build a SignalGroup with events for each field in a dataclass.
157
156
@@ -166,33 +165,33 @@ def _build_dataclass_signal_group(
166
165
If defined, a mapping of field name and equality operator to use to compare if
167
166
each field was modified after being set.
168
167
Default to None
169
- skip_signals : Sequence[str] | None, optional
170
- A list of field names for which the creation of an associated Signal is skipped.
168
+ signal_aliases: Mapping[str, str | None] | Callable[[str], str | None] | None
169
+ If defined, a mapping between field name and signal name. Field names that are
170
+ not `signal_aliases` keys are not aliased (the signal name is the field name).
171
+ If the dict value is None, do not create a signal associated with this field.
172
+ If a callable, the signal name is the output of the function applied to the
173
+ field name. If the output is None, no signal is created for this field.
174
+ If None, defaults to an empty dict, no aliases.
171
175
Default to None
172
- signal_aliases: Mapping[str, str | None]
173
- A mapping between field name and signal name. Field that are not defined as keys
174
- alias to the same name. If the value is None, the field does not emit a changed
175
- signal when mutated.
176
- Default to {}
177
176
178
177
"""
179
- if skip_signals is None :
180
- skip_signals = []
181
- group_name = f"{ cls .__name__ } _{ signal_group_class .__name__ } "
178
+ group_name = f"{ cls .__name__ } { signal_group_class .__name__ } "
179
+ # parse arguments
182
180
_equality_operators = dict (equality_operators ) if equality_operators else {}
181
+ eq_map = _get_eq_operator_map (cls )
183
182
if callable (signal_aliases ):
184
183
transform = signal_aliases
185
184
_signal_aliases = {}
186
185
else :
187
186
transform = _identity
188
187
_signal_aliases = dict (signal_aliases ) if signal_aliases else {}
188
+ signal_group_sig_names = list (getattr (signal_group_class , "_psygnal_signals" , {}))
189
+
189
190
signals = {}
190
- eq_map = _get_eq_operator_map (cls )
191
191
# create a Signal for each field in the dataclass
192
192
for f in iter_fields_with_options (cls ):
193
- # Convert and validate private fields
194
- if f .skip or f .name in skip_signals :
195
- # Skip this field
193
+ # skip this field
194
+ if f .skip :
196
195
continue
197
196
198
197
# Equality operator
@@ -208,25 +207,22 @@ def _build_dataclass_signal_group(
208
207
eq_map [f .name ] = _pick_equality_operator (f .type_ )
209
208
210
209
# Signal name
210
+ sig_name : str | None
211
211
if f .alias is not None :
212
212
sig_name = f .alias
213
- elif f .disable_setattr :
214
- sig_name = f .name
215
213
elif f .name in _signal_aliases :
216
- sig_name_or_none = _signal_aliases [f .name ]
217
- if sig_name_or_none is None :
218
- # original name, but it will be ignored when setattr
219
- sig_name = f .name
220
- else :
221
- sig_name = sig_name_or_none
214
+ sig_name = _signal_aliases [f .name ]
222
215
else :
223
216
sig_name = transform (f .name )
224
217
225
218
# Add the field and signal name to the table of signals, to emit with `setattr`
226
- if f .disable_setattr :
227
- _signal_aliases [f .name ] = None
228
- elif f .name not in _signal_aliases :
229
- _signal_aliases [f .name ] = sig_name
219
+ # If the field has `disable_setattr`, the signal will be created, but it will
220
+ # not be called as the alias is set to None.
221
+ _signal_aliases [f .name ] = sig_name if not f .disable_setattr else None
222
+
223
+ # Skip creating the signal
224
+ if sig_name is None :
225
+ continue
230
226
231
227
# Repeated signal
232
228
if sig_name in signals :
@@ -238,6 +234,13 @@ def _build_dataclass_signal_group(
238
234
stacklevel = 2 ,
239
235
)
240
236
continue
237
+ if sig_name in signal_group_sig_names :
238
+ warnings .warn (
239
+ f"Skip signal { sig_name } , was already defined by { signal_group_class } " ,
240
+ UserWarning ,
241
+ stacklevel = 2 ,
242
+ )
243
+ continue
241
244
242
245
# Create the Signal
243
246
field_type = object if f .type_ is None else f .type_
@@ -443,14 +446,14 @@ def __setattr__(self, name: str, value: Any) -> None:
443
446
events when fields change. If `False`, no `__setattr__` method will be
444
447
created. (This will prevent signal emission, and assumes you are using a
445
448
different mechanism to emit signals when fields change.)
446
- signal_aliases: Mapping[str, str | None] | None
447
- If defined, a mapping between field name and signal name. Field that are not
448
- defined as keys alias to the same name. If the value is None, the field does not
449
- emit a changed signal when mutated. If None, defaults to an empty dict.
449
+ signal_aliases: Mapping[str, str | None] | Callable[[str], str | None] | None
450
+ If defined, a mapping between field name and signal name. Field names that are
451
+ not `signal_aliases` keys are not aliased (the signal name is the field name).
452
+ If the dict value is None, do not create a signal associated with this field.
453
+ If a callable, the signal name is the output of the function applied to the
454
+ field name. If the output is None, no signal is created for this field.
455
+ If None, defaults to an empty dict, no aliases.
450
456
Default to None
451
- skip_signals : Sequence[str], optional
452
- A list of field names for which the creation of an associated Signal is skipped.
453
- Default to []
454
457
455
458
Examples
456
459
--------
@@ -482,8 +485,7 @@ def __init__(
482
485
patch_setattr : bool = True ,
483
486
signal_group_class : type [SignalGroup ] = SignalGroup ,
484
487
collect_fields : bool = True ,
485
- signal_aliases : Mapping [str , str | None ] | Callable [[str ], str ] | None = None ,
486
- skip_signals : Sequence [str ] | None = None ,
488
+ signal_aliases : Mapping [str , str | None ] | FieldAliasFunc | None = None ,
487
489
):
488
490
self ._name : str | None = None
489
491
self ._eqop = tuple (equality_operators .items ()) if equality_operators else None
@@ -492,12 +494,7 @@ def __init__(
492
494
self ._patch_setattr = patch_setattr
493
495
self ._signal_group_class = signal_group_class
494
496
self ._collect_fields = collect_fields
495
- self ._skip_signals = list (skip_signals ) if skip_signals else []
496
- self ._signal_aliases : dict [str , str | None ] | Callable [[str ], str ] = (
497
- signal_aliases
498
- if callable (signal_aliases )
499
- else (dict (signal_aliases ) if signal_aliases else {})
500
- )
497
+ self ._signal_aliases = signal_aliases
501
498
502
499
self ._signal_groups : dict [int , type [SignalGroup ]] = {}
503
500
@@ -582,11 +579,6 @@ def _create_group(self, owner: type) -> type[SignalGroup]:
582
579
"subclass of SignalGroup if collect_field_to_signals is False."
583
580
)
584
581
585
- # Remove skipped signals
586
- for sig in self ._skip_signals :
587
- if sig in Group ._psygnal_signals :
588
- del Group ._psygnal_signals [sig ] # type: ignore [attr-defined]
589
-
590
582
# Add aliases
591
583
if callable (self ._signal_aliases ):
592
584
warnings .warn (
@@ -596,16 +588,18 @@ def _create_group(self, owner: type) -> type[SignalGroup]:
596
588
stacklevel = 2 ,
597
589
)
598
590
Group ._psygnal_aliases = {}
599
- else :
600
- Group ._psygnal_aliases = {** self ._signal_aliases }
591
+ elif isinstance (self ._signal_aliases , dict ):
592
+ Group ._psygnal_aliases = {
593
+ ** Group ._psygnal_aliases ,
594
+ ** self ._signal_aliases ,
595
+ }
601
596
602
597
# Collect fields and create SignalGroup subclass
603
598
else :
604
599
Group = _build_dataclass_signal_group (
605
600
owner ,
606
601
signal_group_class = self ._signal_group_class ,
607
602
equality_operators = self ._eqop ,
608
- skip_signals = self ._skip_signals ,
609
603
signal_aliases = self ._signal_aliases ,
610
604
)
611
605
0 commit comments