Skip to content

Commit f6cebcb

Browse files
getzzepre-commit-ci[bot]tlambert03
authored
feat: add signal aliases on SignalGroup (#299)
* add signal_aliases * add test signal_aliases * add test signal_aliases * add coverage * style(pre-commit.ci): auto fixes [...] * clean * compat py38 * cover group.signals * more tests * try speedup evented setattr * use type alias * go back to optional str * simplify with_aliases * revert with_aliases * remove from public --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Talley Lambert <[email protected]>
1 parent 33a6f20 commit f6cebcb

File tree

7 files changed

+613
-51
lines changed

7 files changed

+613
-51
lines changed

src/psygnal/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,7 @@
5151
from ._evented_decorator import evented
5252
from ._exceptions import EmitLoopError
5353
from ._group import EmissionInfo, SignalGroup
54-
from ._group_descriptor import (
55-
SignalGroupDescriptor,
56-
get_evented_namespace,
57-
is_evented,
58-
)
54+
from ._group_descriptor import SignalGroupDescriptor, get_evented_namespace, is_evented
5955
from ._queue import emit_queued
6056
from ._signal import Signal, SignalInstance, _compiled
6157
from ._throttler import debounced, throttled

src/psygnal/_evented_decorator.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
11
from __future__ import annotations
22

3-
from typing import (
4-
Any,
5-
Callable,
6-
Literal,
7-
TypeVar,
8-
overload,
9-
)
3+
from typing import TYPE_CHECKING, Callable, Literal, Mapping, TypeVar, overload
104

115
from psygnal._group_descriptor import SignalGroupDescriptor
126

7+
if TYPE_CHECKING:
8+
from psygnal._group_descriptor import EqOperator, FieldAliasFunc
9+
1310
__all__ = ["evented"]
1411

1512
T = TypeVar("T", bound=type)
1613

17-
EqOperator = Callable[[Any, Any], bool]
18-
1914

2015
@overload
2116
def evented(
@@ -25,6 +20,7 @@ def evented(
2520
equality_operators: dict[str, EqOperator] | None = None,
2621
warn_on_no_fields: bool = ...,
2722
cache_on_instance: bool = ...,
23+
signal_aliases: Mapping[str, str | None] | FieldAliasFunc | None = ...,
2824
) -> T: ...
2925

3026

@@ -36,6 +32,7 @@ def evented(
3632
equality_operators: dict[str, EqOperator] | None = None,
3733
warn_on_no_fields: bool = ...,
3834
cache_on_instance: bool = ...,
35+
signal_aliases: Mapping[str, str | None] | FieldAliasFunc | None = ...,
3936
) -> Callable[[T], T]: ...
4037

4138

@@ -46,6 +43,7 @@ def evented(
4643
equality_operators: dict[str, EqOperator] | None = None,
4744
warn_on_no_fields: bool = True,
4845
cache_on_instance: bool = True,
46+
signal_aliases: Mapping[str, str | None] | FieldAliasFunc | None = None,
4947
) -> Callable[[T], T] | T:
5048
"""A decorator to add events to a dataclass.
5149
@@ -85,6 +83,14 @@ def evented(
8583
access, but means that the owner instance will no longer be pickleable. If
8684
`False`, the SignalGroup instance will *still* be cached, but not on the
8785
instance itself.
86+
signal_aliases: Mapping[str, str | None] | Callable[[str], str | None] | None
87+
If defined, a mapping between field name and signal name. Field names that are
88+
not `signal_aliases` keys are not aliased (the signal name is the field name).
89+
If the dict value is None, do not create a signal associated with this field.
90+
If a callable, the signal name is the output of the function applied to the
91+
field name. If the output is None, no signal is created for this field.
92+
If None, defaults to an empty dict, no aliases.
93+
Default to None
8894
8995
Returns
9096
-------
@@ -122,6 +128,7 @@ def _decorate(cls: T) -> T:
122128
equality_operators=equality_operators,
123129
warn_on_no_fields=warn_on_no_fields,
124130
cache_on_instance=cache_on_instance,
131+
signal_aliases=signal_aliases,
125132
)
126133
# as a decorator, this will have already been called
127134
descriptor.__set_name__(cls, events_namespace)

src/psygnal/_group.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ class MySignals(SignalGroup):
260260
_psygnal_signals: ClassVar[Mapping[str, Signal]]
261261
_psygnal_uniform: ClassVar[bool] = False
262262
_psygnal_name_conflicts: ClassVar[set[str]]
263+
_psygnal_aliases: ClassVar[dict[str, str | None]]
263264

264265
_psygnal_instances: dict[str, SignalInstance]
265266

@@ -280,7 +281,11 @@ def __init__(self, instance: Any = None) -> None:
280281
}
281282
self._psygnal_relay = SignalRelay(self._psygnal_instances, instance)
282283

283-
def __init_subclass__(cls, strict: bool = False) -> None:
284+
def __init_subclass__(
285+
cls,
286+
strict: bool = False,
287+
signal_aliases: Mapping[str, str | None] = {},
288+
) -> None:
284289
"""Collects all Signal instances on the class under `cls._psygnal_signals`."""
285290
# Collect Signals and remove from class attributes
286291
# Use dir(cls) instead of cls.__dict__ to get attributes from super()
@@ -328,6 +333,8 @@ def __init_subclass__(cls, strict: bool = False) -> None:
328333
stacklevel=2,
329334
)
330335

336+
aliases = getattr(cls, "_psygnal_aliases", {})
337+
cls._psygnal_aliases = {**aliases, **signal_aliases}
331338
cls._psygnal_uniform = _is_uniform(cls._psygnal_signals.values())
332339
if strict and not cls._psygnal_uniform:
333340
raise TypeError(

0 commit comments

Comments
 (0)