Dataclass Descriptor#
These objects are used to turn dataclasses into "evented" objects, with a signal emitted whenever a field is changed.
psygnal.SignalGroupDescriptor
#
Create a psygnal.SignalGroup
on first instance attribute access.
This descriptor is designed to be used as a class attribute on a dataclass-like class (e.g. a dataclass
, a pydantic.BaseModel
, an attrs class, a msgspec.Struct
) On first access of the descriptor on an instance, it will create a SignalGroup
bound to the instance, with a SignalInstance
for each field in the dataclass.
Important
Using this descriptor will patch the class's __setattr__
method to emit events when fields change. (That patching occurs on first access of the descriptor name on an instance). To prevent this patching, you can set patch_setattr=False
when creating the descriptor, but then you will need to manually call emit
on the appropriate SignalInstance
when you want to emit an event. Or you can use evented_setattr
yourself
from psygnal._group_descriptor import evented_setattr
from psygnal import SignalGroupDescriptor
from dataclasses import dataclass
from typing import ClassVar
@dataclass
class Foo:
x: int
_events: ClassVar = SignalGroupDescriptor(patch_setattr=False)
@evented_setattr("_events") # pass the name of your SignalGroup
def __setattr__(self, name: str, value: Any) -> None:
super().__setattr__(name, value)
This currently requires a private import, please open an issue if you would like to depend on this functionality.
Parameters:
- equality_operators (
dict[str, Callable[[Any, Any], bool]], optional
) –A dictionary mapping field names to custom equality operators, where an equality operator is a callable that accepts two arguments and returns True if the two objects are equal. This will be used when comparing the old and new values of a field to determine whether to emit an event. If not provided, the default equality operator is
operator.eq
, except for numpy arrays, wherenp.array_equal
is used. - warn_on_no_fields (
bool, optional
) –If
True
(the default), a warning will be emitted if no mutable dataclass-like fields are found on the object. - cache_on_instance (
bool, optional
) –If
True
(the default), a newly-created SignalGroup instance will be cached on the instance itself, so that subsequent accesses to the descriptor will return the same SignalGroup instance. This makes for slightly faster subsequent access, but means that the owner instance will no longer be pickleable. IfFalse
, the SignalGroup instance will still be cached, but not on the instance itself. - patch_setattr (
bool, optional
) –If
True
(the default), a new__setattr__
method will be created that emits events when fields change. IfFalse
, no__setattr__
method will be created. (This will prevent signal emission, and assumes you are using a different mechanism to emit signals when fields change.) - signal_group_class (
type[SignalGroup] | None, optional
) –A custom SignalGroup class to use, SignalGroup if None, by default None
- collect_fields (
bool, optional
) –Create a signal for each field in the dataclass. If True, the
SignalGroup
instance will be a subclass ofsignal_group_class
(SignalGroup if it is None). If False, a deepcopy ofsignal_group_class
will be used. Default to True - signal_aliases (
Mapping[str, str | None] | FieldAliasFunc | None
) –If defined, a mapping between field name and signal name. Field names that are not
signal_aliases
keys are not aliased (the signal name is the field name). If the dict value is None, do not create a signal associated with this field. If a callable, the signal name is the output of the function applied to the field name. If the output is None, no signal is created for this field. If None, defaults to an empty dict, no aliases. Default to None
Examples:
from typing import ClassVar
from dataclasses import dataclass
from psygnal import SignalGroupDescriptor
@dataclass
class Person:
name: str
age: int = 0
events: ClassVar[SignalGroupDescriptor] = SignalGroupDescriptor()
john = Person("John", 40)
john.events.age.connect(print)
john.age += 1 # prints 41
Source code in psygnal/_group_descriptor.py
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 |
|
psygnal.evented(cls=None, *, events_namespace='events', equality_operators=None, warn_on_no_fields=True, cache_on_instance=True, signal_aliases=None)
#
A decorator to add events to a dataclass.
See also the documentation for SignalGroupDescriptor
. This decorator is equivalent setting a class variable named events
to a new SignalGroupDescriptor
instance.
Note that this decorator will modify cls
in place, as well as return it.
Tip
It is recommended to use the SignalGroupDescriptor
descriptor rather than the decorator, as it it is more explicit and provides for easier static type inference.
Parameters:
- cls (
type
) –The class to decorate.
- events_namespace (
str
) –The name of the namespace to add the events to, by default
"events"
- equality_operators (
dict[str, Callable] | None
) –A dictionary mapping field names to equality operators (a function that takes two values and returns
True
if they are equal). These will be used to determine if a field has changed when setting a new value. By default, this will use the__eq__
method of the field type, or np.array_equal, for numpy arrays. But you can provide your own if you want to customize how equality is checked. Alternatively, if the class has an__eq_operators__
class attribute, it will be used. - warn_on_no_fields (
bool
) –If
True
(the default), a warning will be emitted if no mutable dataclass-like fields are found on the object. - cache_on_instance (
bool, optional
) –If
True
(the default), a newly-created SignalGroup instance will be cached on the instance itself, so that subsequent accesses to the descriptor will return the same SignalGroup instance. This makes for slightly faster subsequent access, but means that the owner instance will no longer be pickleable. IfFalse
, the SignalGroup instance will still be cached, but not on the instance itself. - signal_aliases (
Mapping[str, str | None] | FieldAliasFunc | None
) –If defined, a mapping between field name and signal name. Field names that are not
signal_aliases
keys are not aliased (the signal name is the field name). If the dict value is None, do not create a signal associated with this field. If a callable, the signal name is the output of the function applied to the field name. If the output is None, no signal is created for this field. If None, defaults to an empty dict, no aliases. Default to None
Returns:
-
type
–The decorated class, which gains a new SignalGroup instance at the
events_namespace
attribute (by default,events
).
Raises:
-
TypeError
–If the class is frozen or is not a class.
Examples:
from psygnal import evented
from dataclasses import dataclass
@evented
@dataclass
class Person:
name: str
age: int = 0
Source code in psygnal/_evented_decorator.py
41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
|