Pure python implementation of Qt Signals
Project description
psygnal
Pure python implementation of Qt-style Signals, with (optional) signature and type checking, and support for threading.
Usage
Install
pip install psygnal
Basic usage
If you are familiar with the Qt Signals &
Slots API as implemented in
PySide and
PyQt5,
then you should be good to go! psygnal
aims to be a superset of those APIs
(some functions do accept additional arguments, like
check_nargs
and
check_types
).
Note: the name "Signal
" is used here instead of pyqtSignal
, following the
qtpy
and PySide
convention.
from psygnal import Signal
# create an object with class attribute Signals
class MyObj:
# this signal will emit a single string
value_changed = Signal(str)
def __init__(self, value=0):
self._value = value
def set_value(self, value):
if value != self._value:
self._value = str(value)
# emit the signal
self.value_changed.emit(self._value)
def on_value_changed(new_value):
print(f"The new value is {new_value!r}")
# instantiate the object with Signals
obj = MyObj()
# connect one or more callbacks with `connect`
obj.value_changed.connect(on_value_changed)
# callbacks are called when value changes
obj.set_value('hello!') # prints: 'The new value is 'hello!'
# disconnect callbacks with `disconnect`
obj.value_changed.disconnect(on_value_changed)
connect
as a decorator
.connect()
returns the object that it is passed, and so
can be used as a decorator.
@obj.value_changed.connect
def some_other_callback(value):
print(f"I also received: {value!r}")
obj.set_value('world!')
# prints:
# I also received: 'world!'
Connection safety (number of arguments)
psygnal
prevents you from connecting a callback function that is guaranteed
to fail due to an incompatible number of positional arguments. For example,
the following callback has too many arguments for our Signal (which we declared
above as emitting a single argument: Signal(str)
)
def i_require_two_arguments(first, second):
print(first, second)
obj.value_changed.connect(i_require_two_arguments)
raises:
ValueError: Cannot connect slot 'i_require_two_arguments' with signature: (first, second):
- Slot requires at least 2 positional arguments, but spec only provides 1
Accepted signature: (p0: str, /)
Note: Positional argument checking can be disabled with connect(..., check_nargs=False)
Extra positional arguments ignored
While a callback may not require more positional arguments than the signature
of the Signal
to which it is connecting, it may accept less. Extra
arguments will be discarded when emitting the signal (so it
isn't necessary to create a lambda
to swallow unnecessary arguments):
obj = MyObj()
def no_args_please():
print(locals())
obj.value_changed.connect(no_args_please)
# otherwise one might need
# obj.value_changed.connect(lambda a: no_args_please())
obj.value_changed.emit('hi') # prints: "{}"
Connection safety (types)
For type safety when connecting slots, use check_types=True
when connecting a
callback. Recall that our signal was declared as accepting a string
Signal(str)
. The following function has an incompatible type annotation: x: int
.
# this would fail because you cannot concatenate a string and int
def i_expect_an_integer(x: int):
print(f'{x} + 4 = {x + 4}')
# psygnal won't let you connect it
obj.value_changed.connect(i_expect_an_integer, check_types=True)
raises:
ValueError: Cannot connect slot 'i_expect_an_integer' with signature: (x: int):
- Slot types (x: int) do not match types in signal.
Accepted signature: (p0: str, /)
Note: unlike Qt, psygnal
does not perform any type coercion when emitting
a value.
Query the sender
Similar to Qt's QObject.sender()
method, a callback can query the sender using the Signal.sender()
class
method. (The implementation is of course different than Qt, since the receiver
is not a QObject
.)
obj = MyObj()
def curious():
print("Sent by", Signal.sender())
assert Signal.sender() == obj
obj.value_changed.connect(curious)
obj.value_changed.emit(10)
# prints (and does not raise):
# Sent by <__main__.MyObj object at 0x1046a30d0>
If you want the actual signal instance that is emitting the signal
(obj.value_changed
in the above example), use Signal.current_emitter()
.
Emitting signals asynchronously (threading)
There is experimental support for calling all connected slots in another thread,
using emit(..., asynchronous=True)
obj = MyObj()
def slow_callback(arg):
import time
time.sleep(0.5)
print(f"Hi {arg!r}, from another thread")
obj.value_changed.connect(slow_callback)
This one is called synchronously (note the order of print statements):
obj.value_changed.emit('friend')
print("Hi, from main thread.")
# after 0.5 seconds, prints:
# Hi 'friend', from another thread
# Hi, from main thread.
This one is called asynchronously, and immediately returns to the caller.
A threading.Thread
object is returned.
thread = obj.value_changed.emit('friend', asynchronous=True)
print("Hi, from main thread.")
# immediately prints
# Hi, from main thread.
# then after 0.5 seconds this will print:
# Hi 'friend', from another thread
Note: The user is responsible for joining
and managing the
threading.Thread
instance returned when calling .emit(..., asynchronous=True)
.
Experimental! While thread-safety is the goal,
(RLocks
are
used during important state mutations) it is not guaranteed. Please use at your
own risk. Issues/PRs welcome.
Other similar libraries
There are other libraries that implement similar event-based signals, they may server your purposes better depending on what you are doing.
PySignal (deprecated)
This package borrows inspiration from – and is most similar to – the now
deprecated PySignal project, with a few
notable new features in psygnal
regarding signature and type checking, sender
querying, and threading.
similarities with PySignal
- still a "Qt-style" signal implementation that doesn't depend on Qt
- supports class methods, functions, lambdas and partials
differences with PySignal
- the class attribute
pysignal.ClassSignal
is called simplySignal
inpsygnal
(to more closely match the PyQt/Pyside syntax). Correspondinglypysignal.Signal
is similar topsygnal.SignalInstance
. - Whereas
PySignal
refrained from doing any signature and/or type checking either at slot-connection time, or at signal emission time,psygnal
offers signature declaration similar to Qt with , for example,Signal(int, int)
. along with opt-in signature compatibility (withcheck_nargs=True
) and type checking (withcheck_types=True
)..connect(..., check_nargs=True)
in particular ensures that any slot to connected to a signal will at least be compatible with the emitted arguments. - You can query the sender in
psygnal
by using theSignal.sender()
orSignal.current_emitter()
class methods. (The former returns the instance emitting the signal, similar to Qt'sQObject.sender()
method, whereas the latter returns the currently emittingSignalInstance
.) - There is basic threading support (calling all slots in another thread), using
emit(..., asynchronous=True)
. This is experimental, and while thread-safety is the goal, it is not guaranteed. - There are no
SignalFactory
classes here.
The following two libraries implement django-inspired signals, they do not attempt to mimic the Qt API.
Blinker
Blinker provides a fast dispatching system that allows any number of interested parties to subscribe to events, or "signals".
SmokeSignal
(This appears to be unmaintained)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for psygnal-0.1.1-py2.py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6beedc04021e8e6fc6bd7cea3a4d0c50edbdd4db5ed7ea6a605f0ffae25ee52e |
|
MD5 | 0210804560654ce685bf9fd141337c1f |
|
BLAKE2b-256 | 0d000ee627ecb1d73d00f7d9c0975040f2cd07c77ea21cde11a89362d66b4323 |
Hashes for psygnal-0.1.1-cp39-cp39-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4ba78c7e9bc459400af75c3886dd43aef4996d21c1235f5d1607ba23b51eeb66 |
|
MD5 | 4ebf0acbc433241da46958ddae0928da |
|
BLAKE2b-256 | ba6cf7e54424f29d37e5e46461f495f8471b86d1fdd3a8bd9ea2b8c589bb9e1a |
Hashes for psygnal-0.1.1-cp39-cp39-manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | cfadca46cfb026b4dfa10060151dd8d2f23202a664b18f85f9b55eba28dc3100 |
|
MD5 | eb2fc775e3e82c47ef824062d6ec3803 |
|
BLAKE2b-256 | 771a43d53fe07fb0e98e9772ee1ae39c6e0bc8adf145dde11e6864048198ab19 |
Hashes for psygnal-0.1.1-cp39-cp39-manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5251328d6312779888278a420e022bdce89469657119fd058dd03cb384e95ad1 |
|
MD5 | e5e8efa110607729720150bedeeffe04 |
|
BLAKE2b-256 | b4bd47dad8edc291dc5bf774366b0d8b9cd771c54fa6d23ca87e4aa7189ba6dc |
Hashes for psygnal-0.1.1-cp39-cp39-manylinux1_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0ddc5184e089b906c85e18699332b8a839d2b6a15fbd843e0c3f964924bcb48e |
|
MD5 | 6c60343c41c7832b7e96a590eaa427f1 |
|
BLAKE2b-256 | 6451d8e2e11254f1bed7f2a094c41fae57646da00d477ceba614c6b3bd3b3f0a |
Hashes for psygnal-0.1.1-cp39-cp39-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fc0757bf4c035d7f43a1beb9f3891c8444815b19c74020ab8fac5b77f265a0a9 |
|
MD5 | 3c899dea6ca4e53e13c3c0c659af6194 |
|
BLAKE2b-256 | 07d4a484726e118e59b48bcee9ff994ee8360b65bb37a9c3e604ae6078a7fb17 |
Hashes for psygnal-0.1.1-cp38-cp38-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a885783e7ee919a3e332451e6e70f1ff371669e41c1f262a4f0c4101c7c07090 |
|
MD5 | 1ebd7056b99a5c53afb8b68df9bf15d9 |
|
BLAKE2b-256 | a16a64fd8e224e8ce14f978679ef652c3a27c0ddff2ad3ac5296d850f6e0a671 |
Hashes for psygnal-0.1.1-cp38-cp38-manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 225a3de4c2288710cf77876909f8541b95a250b390a2f9df136846cc3f541ae7 |
|
MD5 | f48c4e3ca272f57fdfe2507dd21ea7f2 |
|
BLAKE2b-256 | cf1022311ad5c0c60b5dd68987643a548700f7effd804bd8c9ec088b9edf6bac |
Hashes for psygnal-0.1.1-cp38-cp38-manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 16a8c489465333d1d42afac6f23405ff4908770719ec7ed582d104e19306fd6c |
|
MD5 | f8b26f3aaf17e9407aaafb4770173707 |
|
BLAKE2b-256 | 667f90da865838a74f2fc59316da3a768c7975ab5d709aaf232e989eecfb0ee5 |
Hashes for psygnal-0.1.1-cp38-cp38-manylinux1_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 398714c9392d0bd16e1b9904ff1d33673ac1c39b420f820c60b95d7565c56270 |
|
MD5 | c9bbe2c85ee65b0dbdd6d4af9251ff48 |
|
BLAKE2b-256 | baf242b0ff17a52beedbf6ea106ea3774572f078dec5bbd346b6386c5bebd25d |
Hashes for psygnal-0.1.1-cp38-cp38-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4b5e570adc3b17eaf92d4bd237b6e28c12bcccc5e43e09cc8f55c0fa815c3c4f |
|
MD5 | d13268d892cd42b22e012566edb56842 |
|
BLAKE2b-256 | 10534cb5481a9c4d3b779ff8624f8bdcd861bf3a3eb320d306000028570bff76 |
Hashes for psygnal-0.1.1-cp37-cp37m-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8ea3cc12fe0b082b245201ffe5a27416d89d9bb5c879f5ca2b6e834d0e78d25c |
|
MD5 | 287a27b89b73c0e8092273ea0084aeb3 |
|
BLAKE2b-256 | da4f7ebd0377d3a0912ffe18a1f6ddd5bfea2ff5ef18e92f4906c1198a2995b9 |
Hashes for psygnal-0.1.1-cp37-cp37m-manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 54bb344398e26a026e4d80fdf4794f006ee78da6c6c455a526a65d50db3996a9 |
|
MD5 | 63f7581ae2a5f112bce1c7661faa571b |
|
BLAKE2b-256 | dc04a5f69166495d52260f6dfebba91cb9dfb3a4247b10823143fb9ecc6b0ccc |
Hashes for psygnal-0.1.1-cp37-cp37m-manylinux2014_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1a1121f1fa9a12c6b6363e46c407c77e55b94cb638455abf91b7275326fdace9 |
|
MD5 | e17f6dcc412a4cf4a3c86419739224c9 |
|
BLAKE2b-256 | 29af910d07f67f8d0904a438f34dd3fd284ac988db15d496c43c476560a0e6f3 |
Hashes for psygnal-0.1.1-cp37-cp37m-manylinux1_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d5fd3de50133b2263042615c130a95d0a5ec3e993b56cf4232272671a04a8b7b |
|
MD5 | 071d2a12dee8ada396788f487eca6539 |
|
BLAKE2b-256 | 682b8e26ea241141e2f43024b13f0f0513136740ef51a6d96ef6870fdaa7258f |
Hashes for psygnal-0.1.1-cp37-cp37m-macosx_10_9_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5f9c2b87935b74c3c8d6a66ae9180a5df2b466ab637fc9e6b17c9f83d7acfa8a |
|
MD5 | 47055c57a7efbb42a8221765e64f99c9 |
|
BLAKE2b-256 | 676e2b857853dd775dcbf4a6ef3073a21096abf703e34977bdb23d3c61b26e35 |