Skip to content

Commit 17635b3

Browse files
committed
only argument
1 parent f7250cf commit 17635b3

File tree

4 files changed

+116
-86
lines changed

4 files changed

+116
-86
lines changed

src/psygnal/_evented_decorator.py

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,11 @@
44
Callable,
55
Literal,
66
Mapping,
7-
Sequence,
87
TypeVar,
98
overload,
109
)
1110

12-
from psygnal._group_descriptor import EqOperator, SignalGroupDescriptor
11+
from psygnal._group_descriptor import EqOperator, FieldAliasFunc, SignalGroupDescriptor
1312

1413
__all__ = ["evented"]
1514

@@ -24,8 +23,7 @@ def evented(
2423
equality_operators: dict[str, EqOperator] | None = None,
2524
warn_on_no_fields: bool = ...,
2625
cache_on_instance: bool = ...,
27-
signal_aliases: Mapping[str, str | None] | Callable[[str], str] | None = ...,
28-
skip_signals: Sequence[str] | None = ...,
26+
signal_aliases: Mapping[str, str | None] | FieldAliasFunc | None = ...,
2927
) -> T: ...
3028

3129

@@ -37,8 +35,7 @@ def evented(
3735
equality_operators: dict[str, EqOperator] | None = None,
3836
warn_on_no_fields: bool = ...,
3937
cache_on_instance: bool = ...,
40-
signal_aliases: Mapping[str, str | None] | Callable[[str], str] | None = ...,
41-
skip_signals: Sequence[str] | None = ...,
38+
signal_aliases: Mapping[str, str | None] | FieldAliasFunc | None = ...,
4239
) -> Callable[[T], T]: ...
4340

4441

@@ -49,8 +46,7 @@ def evented(
4946
equality_operators: dict[str, EqOperator] | None = None,
5047
warn_on_no_fields: bool = True,
5148
cache_on_instance: bool = True,
52-
signal_aliases: Mapping[str, str | None] | Callable[[str], str] | None = None,
53-
skip_signals: Sequence[str] | None = None,
49+
signal_aliases: Mapping[str, str | None] | FieldAliasFunc | None = None,
5450
) -> Callable[[T], T] | T:
5551
"""A decorator to add events to a dataclass.
5652
@@ -90,14 +86,14 @@ def evented(
9086
access, but means that the owner instance will no longer be pickleable. If
9187
`False`, the SignalGroup instance will *still* be cached, but not on the
9288
instance itself.
93-
signal_aliases: Mapping[str, str | None] | None
94-
If defined, a mapping between field name and signal name. Field that are not
95-
defined as keys alias to the same name. If the value is None, the field does not
96-
emit a changed signal when mutated. If None, defaults to an empty dict.
89+
signal_aliases: Mapping[str, str | None] | Callable[[str], str | None] | None
90+
If defined, a mapping between field name and signal name. Field names that are
91+
not `signal_aliases` keys are not aliased (the signal name is the field name).
92+
If the dict value is None, do not create a signal associated with this field.
93+
If a callable, the signal name is the output of the function applied to the
94+
field name. If the output is None, no signal is created for this field.
95+
If None, defaults to an empty dict, no aliases.
9796
Default to None
98-
skip_signals : Sequence[str], optional
99-
A list of field names for which the creation of an associated Signal is skipped.
100-
Default to []
10197
10298
Returns
10399
-------
@@ -136,7 +132,6 @@ def _decorate(cls: T) -> T:
136132
warn_on_no_fields=warn_on_no_fields,
137133
cache_on_instance=cache_on_instance,
138134
signal_aliases=signal_aliases,
139-
skip_signals=skip_signals,
140135
)
141136
# as a decorator, this will have already been called
142137
descriptor.__set_name__(cls, events_namespace)

src/psygnal/_group.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,8 @@ def __init_subclass__(
335335
stacklevel=2,
336336
)
337337

338-
cls._psygnal_aliases = {**signal_aliases}
338+
aliases = getattr(cls, "_psygnal_aliases", {})
339+
cls._psygnal_aliases = {**aliases, **signal_aliases}
339340

340341
cls._psygnal_uniform = _is_uniform(cls._psygnal_signals.values())
341342
if strict and not cls._psygnal_uniform:

src/psygnal/_group_descriptor.py

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
Iterable,
1515
Literal,
1616
Mapping,
17-
Sequence,
1817
Type,
1918
TypeVar,
2019
cast,
@@ -33,6 +32,7 @@
3332

3433
__all__ = ["is_evented", "get_evented_namespace", "EqOperator", "SignalGroupDescriptor"]
3534

35+
FieldAliasFunc = Callable[[str], str | None]
3636
T = TypeVar("T", bound=Type)
3737
S = TypeVar("S")
3838

@@ -150,8 +150,7 @@ def _build_dataclass_signal_group(
150150
cls: type,
151151
signal_group_class: type[SignalGroup] = SignalGroup,
152152
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,
155154
) -> type[SignalGroup]:
156155
"""Build a SignalGroup with events for each field in a dataclass.
157156
@@ -166,33 +165,33 @@ def _build_dataclass_signal_group(
166165
If defined, a mapping of field name and equality operator to use to compare if
167166
each field was modified after being set.
168167
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.
171175
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 {}
177176
178177
"""
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
182180
_equality_operators = dict(equality_operators) if equality_operators else {}
181+
eq_map = _get_eq_operator_map(cls)
183182
if callable(signal_aliases):
184183
transform = signal_aliases
185184
_signal_aliases = {}
186185
else:
187186
transform = _identity
188187
_signal_aliases = dict(signal_aliases) if signal_aliases else {}
188+
signal_group_sig_names = list(getattr(signal_group_class, "_psygnal_signals", {}))
189+
189190
signals = {}
190-
eq_map = _get_eq_operator_map(cls)
191191
# create a Signal for each field in the dataclass
192192
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:
196195
continue
197196

198197
# Equality operator
@@ -208,25 +207,22 @@ def _build_dataclass_signal_group(
208207
eq_map[f.name] = _pick_equality_operator(f.type_)
209208

210209
# Signal name
210+
sig_name: str | None
211211
if f.alias is not None:
212212
sig_name = f.alias
213-
elif f.disable_setattr:
214-
sig_name = f.name
215213
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]
222215
else:
223216
sig_name = transform(f.name)
224217

225218
# 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
230226

231227
# Repeated signal
232228
if sig_name in signals:
@@ -238,6 +234,13 @@ def _build_dataclass_signal_group(
238234
stacklevel=2,
239235
)
240236
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
241244

242245
# Create the Signal
243246
field_type = object if f.type_ is None else f.type_
@@ -443,14 +446,14 @@ def __setattr__(self, name: str, value: Any) -> None:
443446
events when fields change. If `False`, no `__setattr__` method will be
444447
created. (This will prevent signal emission, and assumes you are using a
445448
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.
450456
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 []
454457
455458
Examples
456459
--------
@@ -482,8 +485,7 @@ def __init__(
482485
patch_setattr: bool = True,
483486
signal_group_class: type[SignalGroup] = SignalGroup,
484487
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,
487489
):
488490
self._name: str | None = None
489491
self._eqop = tuple(equality_operators.items()) if equality_operators else None
@@ -492,12 +494,7 @@ def __init__(
492494
self._patch_setattr = patch_setattr
493495
self._signal_group_class = signal_group_class
494496
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
501498

502499
self._signal_groups: dict[int, type[SignalGroup]] = {}
503500

@@ -582,11 +579,6 @@ def _create_group(self, owner: type) -> type[SignalGroup]:
582579
"subclass of SignalGroup if collect_field_to_signals is False."
583580
)
584581

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-
590582
# Add aliases
591583
if callable(self._signal_aliases):
592584
warnings.warn(
@@ -596,16 +588,18 @@ def _create_group(self, owner: type) -> type[SignalGroup]:
596588
stacklevel=2,
597589
)
598590
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+
}
601596

602597
# Collect fields and create SignalGroup subclass
603598
else:
604599
Group = _build_dataclass_signal_group(
605600
owner,
606601
signal_group_class=self._signal_group_class,
607602
equality_operators=self._eqop,
608-
skip_signals=self._skip_signals,
609603
signal_aliases=self._signal_aliases,
610604
)
611605

0 commit comments

Comments
 (0)