{"id":3748,"library":"psygnal","title":"psygnal","description":"Psygnal is a pure Python implementation of the observer pattern, providing a fast callback and event system modeled after Qt Signals & Slots. It offers optional signature and type checking for connected slots and supports threading, all without requiring or using Qt. The current version is 0.15.1, and the library is actively maintained with regular releases.","status":"active","version":"0.15.1","language":"en","source_language":"en","source_url":"https://github.com/pyapp-kit/psygnal","tags":["signals","events","callbacks","observer-pattern","qt-style","threading","dataclasses","pydantic"],"install":[{"cmd":"pip install psygnal","lang":"bash","label":"Install with pip"}],"dependencies":[],"imports":[{"symbol":"Signal","correct":"from psygnal import Signal"},{"note":"Decorator for creating evented dataclasses or Pydantic models.","symbol":"evented","correct":"from psygnal import evented"},{"note":"Also EventedDict, EventedSet for mutable data structures.","symbol":"EventedList","correct":"from psygnal.containers import EventedList"},{"note":"A Pydantic BaseModel that emits signals on field changes.","symbol":"EventedModel","correct":"from psygnal import EventedModel"},{"note":"Decorator to debounce function calls.","symbol":"debounced","correct":"from psygnal import debounced"},{"note":"Decorator to throttle function calls.","symbol":"throttled","correct":"from psygnal import throttled"}],"quickstart":{"code":"from psygnal import Signal\n\nclass MyObject:\n    \"\"\"A simple object that emits a signal when its value changes.\"\"\"\n    value_changed = Signal(str)\n\n    def __init__(self, initial_value: str = \"\"):\n        self._value = initial_value\n\n    def set_value(self, new_value: str):\n        if new_value != self._value:\n            self._value = new_value\n            self.value_changed.emit(self._value)\n\n# Create an instance of the object\nmy_obj = MyObject(\"start\")\n\n# Connect a callback function using the .connect() method\ndef on_value_change_method(new_value: str):\n    print(f\"Callback 1 (method): The value changed to '{new_value}'!\")\n\nmy_obj.value_changed.connect(on_value_change_method)\n\n# Connect another callback function using the @.connect decorator\n@my_obj.value_changed.connect\ndef on_value_change_decorator(new_value: str):\n    print(f\"Callback 2 (decorator): I also received: '{new_value}'!\")\n\nprint(\"Initial value set, no emission yet.\")\n\n# Emit signals by changing the value\nprint(\"\\nSetting value to 'hello':\")\nmy_obj.set_value(\"hello\")\n\nprint(\"\\nSetting value to 'world':\")\nmy_obj.set_value(\"world\")\n\nprint(\"\\nSetting value to 'world' again (should not emit):\")\nmy_obj.set_value(\"world\")\n\n# Disconnect a callback\nmy_obj.value_changed.disconnect(on_value_change_method)\nprint(\"\\nDisconnected 'Callback 1'. Setting value to 'psygnal':\")\nmy_obj.set_value(\"psygnal\")","lang":"python","description":"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."},"warnings":[{"fix":"Ensure `psygnal.emit_queued()` is called regularly in the target thread, often integrated with an event loop (e.g., using `QTimer` for Qt applications).","message":"Cross-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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Call `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.","message":"When 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.","severity":"breaking","affected_versions":"All versions"},{"fix":"Enable 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.","message":"By 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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Always import `Signal` from `psygnal` (`from psygnal import Signal`) and refer to `psygnal`'s documentation for its API.","message":"Users 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.","severity":"deprecated","affected_versions":"Users of PySignal (an external, deprecated library)"}],"env_vars":null,"last_verified":"2026-04-11T00:00:00.000Z","next_check":"2026-07-10T00:00:00.000Z"}