https://github.com/pyapp-kit/psygnal
Python observer pattern (callback/event system). Modeled after Qt Signals & Slots (but independent of Qt)
https://github.com/pyapp-kit/psygnal
callbacks python signalslot
Last synced: 8 months ago
JSON representation
Python observer pattern (callback/event system). Modeled after Qt Signals & Slots (but independent of Qt)
- Host: GitHub
- URL: https://github.com/pyapp-kit/psygnal
- Owner: pyapp-kit
- License: bsd-3-clause
- Created: 2021-07-05T12:16:30.000Z (almost 5 years ago)
- Default Branch: main
- Last Pushed: 2025-09-24T11:32:32.000Z (9 months ago)
- Last Synced: 2025-09-24T13:22:15.067Z (9 months ago)
- Topics: callbacks, python, signalslot
- Language: Python
- Homepage: https://psygnal.readthedocs.io/
- Size: 939 KB
- Stars: 113
- Watchers: 4
- Forks: 21
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# psygnal
[](https://github.com/pyapp-kit/psygnal/raw/master/LICENSE)
[](https://pypi.org/project/psygnal)
[](https://github.com/conda-forge/psygnal-feedstock)
[](https://python.org)
[](https://github.com/pyapp-kit/psygnal/actions/workflows/test.yml)
[](https://codecov.io/gh/pyapp-kit/psygnal)
[](https://psygnal.readthedocs.io/en/latest/?badge=latest)
[](https://codspeed.io/pyapp-kit/psygnal)
Psygnal (pronounced "signal") is a pure python implementation of the [observer
pattern](https://en.wikipedia.org/wiki/Observer_pattern), with the API of
[Qt-style Signals](https://doc.qt.io/qt-5/signalsandslots.html) with (optional)
signature and type checking, and support for threading. It has no dependencies.
> This library does ***not*** require or use Qt in any way, It simply implements
> a similar observer pattern API.
## Documentation
https://psygnal.readthedocs.io/
### Install
```sh
pip install psygnal
```
```sh
conda install -c conda-forge psygnal
```
## Usage
The [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern) is a software design pattern in which an object maintains a list of its dependents ("**observers**"), and notifies them of any state changes – usually by calling a **callback function** provided by the observer.
Here is a simple example of using psygnal:
```python
from psygnal import Signal
class MyObject:
# define one or more signals as class attributes
value_changed = Signal(str)
# create an instance
my_obj = MyObject()
# You (or others) can connect callbacks to your signals
@my_obj.value_changed.connect
def on_change(new_value: str):
print(f"The value changed to {new_value}!")
# The object may now emit signals when appropriate,
# (for example in a setter method)
my_obj.value_changed.emit('hi') # prints "The value changed to hi!"
```
Much more detail available in the [documentation](https://psygnal.readthedocs.io/)!
### Evented Dataclasses
A particularly nice usage of the signal pattern is to emit signals whenever a
field of a dataclass changes. Psygnal provides an `@evented` decorator that will
emit a signal whenever a field changes. It is compatible with `dataclasses`
from [the standard library](https://docs.python.org/3/library/dataclasses.html),
as well as [attrs](https://www.attrs.org/en/stable/), and
[pydantic](https://pydantic-docs.helpmanual.io):
```python
from psygnal import evented
from dataclasses import dataclass
@evented
@dataclass
class Person:
name: str
age: int = 0
person = Person('John', age=30)
# connect callbacks
@person.events.age.connect
def _on_age_change(new_age: str):
print(f"Age changed to {new_age}")
person.age = 31 # prints: Age changed to 31
```
See the [dataclass documentation](https://psygnal.readthedocs.io/en/latest/dataclasses/) for more details.
### Evented Containers
`psygnal.containers` provides evented versions of mutable data structures
(`dict`, `list`, `set`), for cases when you need to monitor mutation:
```python
from psygnal.containers import EventedList
my_list = EventedList([1, 2, 3, 4, 5])
my_list.events.inserted.connect(lambda i, val: print(f"Inserted {val} at index {i}"))
my_list.events.removed.connect(lambda i, val: print(f"Removed {val} at index {i}"))
my_list.append(6) # Output: Inserted 6 at index 5
my_list.pop() # Output: Removed 6 at index 5
```
See the
[evented containers documentation](https://psygnal.readthedocs.io/en/latest/API/containers/)
for more details.
## Benchmark history
https://pyapp-kit.github.io/psygnal/
and
https://codspeed.io/pyapp-kit/psygnal
## Developers
### Setup
This project uses PEP 735 dependency groups.
After cloning, setup your env with `uv sync` or `pip install -e . --group dev`
### Compiling
While `psygnal` is a pure python package, it is compiled with mypyc to increase
performance. To test the compiled version locally, you can run:
```bash
HATCH_BUILD_HOOKS_ENABLE=1 uv sync --force-reinstall
```
(which is also available as `make build` if you have make installed)
### Debugging
To disable all compiled files and run the pure python version,
you may run:
```bash
python -c "import psygnal.utils; psygnal.utils.decompile()"
```
To return the compiled version, run:
```bash
python -c "import psygnal.utils; psygnal.utils.recompile()"
```
The `psygnal._compiled` variable will tell you if you're using the compiled
version or not.