Install & Compatibility
Where this runs
tested against v0.15.1 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.146s · 20.7MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 1.7s · import 0.124s · 21MB
19MB installed
● package 19MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
Signal
✓ from psygnal import Signal
evented
✓ from psygnal import evented
Decorator for creating evented dataclasses or Pydantic models.
EventedList
✓ from psygnal.containers import EventedList
Also EventedDict, EventedSet for mutable data structures.
EventedModel
✓ from psygnal import EventedModel
A Pydantic BaseModel that emits signals on field changes.
debounced
✓ from psygnal import debounced
Decorator to debounce function calls.
throttled
✓ from psygnal import throttled
Decorator to throttle function calls.
This example demonstrates how to define a signal, connect multiple callbacks (both directly and with a decorator), emit a signal, and disconnect a callback. Note that a signal is only emitted if the value truly changes.
from psygnal import Signal
class MyObject:
"""A simple object that emits a signal when its value changes."""
value_changed = Signal(str)
def __init__(self, initial_value: str = ""):
self._value = initial_value
def set_value(self, new_value: str):
if new_value != self._value:
self._value = new_value
self.value_changed.emit(self._value)
# Create an instance of the object
my_obj = MyObject("start")
# Connect a callback function using the .connect() method
def on_value_change_method(new_value: str):
print(f"Callback 1 (method): The value changed to '{new_value}'!")
my_obj.value_changed.connect(on_value_change_method)
# Connect another callback function using the @.connect decorator
@my_obj.value_changed.connect
def on_value_change_decorator(new_value: str):
print(f"Callback 2 (decorator): I also received: '{new_value}'!")
print("Initial value set, no emission yet.")
# Emit signals by changing the value
print("\nSetting value to 'hello':")
my_obj.set_value("hello")
print("\nSetting value to 'world':")
my_obj.set_value("world")
print("\nSetting value to 'world' again (should not emit):")
my_obj.set_value("world")
# Disconnect a callback
my_obj.value_changed.disconnect(on_value_change_method)
print("\nDisconnected 'Callback 1'. Setting value to 'psygnal':")
my_obj.set_value("psygnal")
Debug
Known issues
gotchaCross-thread signal emission requires manual queue processing. If connecting a slot to run in a different thread (`connect(thread=...)`), the `psygnal.emit_queued()` function *must* be periodically called in the target thread's event loop to process the queued callbacks. Without this, callbacks will not be invoked across threads.fixEnsure `psygnal.emit_queued()` is called regularly in the target thread, often integrated with an event loop (e.g., using `QTimer` for Qt applications).
affects: All versions
breakingWhen using asynchronous callbacks (`async def` functions), the async backend (`psygnal.set_async_backend()`) must be configured *before* connecting any async callbacks. Failure to do so will result in a `RuntimeError` or `RuntimeWarning` and the callback not being called.fixCall `psygnal.set_async_backend('asyncio')` (or 'anyio', 'trio') at the start of your application, and ensure the chosen backend's event loop is running and ready before connecting async slots. affects: All versions
gotchaBy default, `psygnal` does not strictly check the number of arguments (nargs) or types of connected slots against the signal's signature. This can lead to runtime `TypeError` exceptions when the signal is emitted if the slot's signature is incompatible.fixEnable stricter checking by connecting with `signal.connect(slot_func, check_nargs=True, check_types=True)`. This will raise an error at connection time if signatures are incompatible.
affects: All versions
deprecatedUsers migrating from the older `PySignal` library might be confused by the `Signal` class naming. `psygnal`'s primary signal class is `psygnal.Signal`, while `PySignal` used `PySignal.ClassSignal` and `PySignal.Signal` (which is similar to `psygnal.SignalInstance`). The `PySignal` library itself is deprecated and unmaintained.fixAlways import `Signal` from `psygnal` (`from psygnal import Signal`) and refer to `psygnal`'s documentation for its API.
affects: Users of PySignal (an external, deprecated library)
Errors
Common errors & fixes
ValueError: Cannot connect slot 'your_slot_function' with signature: (x: int): - Slot types (x: int) do not match types in signal. Accepted signature: (p0: str, /).
This error occurs when a slot function is connected to a signal with `check_types=True` (or `check_nargs=True`), and the slot's signature (number or types of arguments) does not match the signal's declared signature.
fixEnsure the slot function's arguments match the types declared in the `Signal()` constructor. If the signal emits `Signal(str)`, the slot should accept a string argument. Set `check_types=False` on connect to disable type checking if the mismatch is intentional and handled by the slot, or adjust the slot's signature.
AttributeError: 'Signal' object has no attribute 'emit'
This error happens when you try to call `.emit()` (or `.connect()`) on the `Signal` class itself rather than on an instance of the signal, which is typically a class attribute of an object. `Signal` defines the emitter, but `SignalInstance` (the bound signal on an object) is what you connect to and emit from.
fixYou must create an instance of the class containing the signal, then call `.emit()` or `.connect()` on that instance's signal attribute.
```python
from psygnal import Signal
class MyObject:
value_changed = Signal(str) # Defines the signal
my_obj = MyObject() # Create an instance of MyObject
def on_value_changed(new_value: str):
print(f"Value changed to: {new_value}")
my_obj.value_changed.connect(on_value_changed) # Connect to the instance's signal
my_obj.value_changed.emit("new_value") # Emit from the instance's signal
``` EmitLoopError: Exception occurred during callback
This exception is raised by `psygnal` when a connected callback (slot) itself raises an unhandled exception during the signal emission process. `EmitLoopError` wraps the original exception, which can be found in its `__cause__` attribute.
fixCatch and handle the exception within the callback function (slot) to prevent it from propagating up through the signal emission. Alternatively, use `contextlib.suppress(EmitLoopError)` around the `.emit()` call if you wish to ignore exceptions in callbacks.
```python
from psygnal import Signal
class MyEmitter:
sig = Signal()
def bad_callback():
raise ValueError("Something went wrong in the slot!")
emitter = MyEmitter()
emitter.sig.connect(bad_callback)
# To handle the error in the callback:
try:
emitter.sig.emit()
except Exception as e:
print(f"Caught: {e}") # This will be EmitLoopError
# Or, to suppress it (not recommended for general use):
from contextlib import suppress
with suppress(EmitLoopError):
emitter.sig.emit()
``` ModuleNotFoundError: No module named 'psygnal'
The `psygnal` library is not installed in the Python environment where you are trying to import it.
fixInstall `psygnal` using pip or conda.
```bash
pip install psygnal
# or for conda users
conda install -c conda-forge psygnal
```
Upgrade
Version history
0.15.1latest on PyPI · released Jan 4, 2026
Audit
Dependencies
No dependency data recorded yet.