Skip to content

feat: Add weak reference and one-shot connection support #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2024-12-21

### Added
- **Weak Reference Support**: Introduced `weak=True` for signal connections to allow automatic disconnection when the receiver is garbage-collected.
- **One-Shot Connections**: Added `one_shot=True` in `connect(...)` to enable automatically disconnecting a slot after its first successful emission call.
- Extended integration tests to cover new `weak` and `one_shot` functionality.

### Improved
- **Thread Safety**: Strengthened internal locking and concurrency patterns to reduce race conditions in high-load or multi-threaded environments.
- **Documentation**: Updated `readme.md`, `api.md`, and example code sections to explain weak references, one-shot usage, and improved thread-safety details.

## [0.3.0] - 2024-12-19

### Changed
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ TSignal is a lightweight, pure-Python signal/slot library that provides thread-s
- **Flexible Connection Types**: Direct or queued connections, automatically chosen based on the caller and callee threads.
- **Worker Thread Pattern**: Simplify background task execution with a built-in worker pattern that provides an event loop and task queue in a dedicated thread.
- **Familiar Decorators**: Inspired by Qt’s pattern, `@t_with_signals`, `@t_signal`, and `@t_slot` let you define signals and slots declaratively.
- **Weak Reference**:
- By setting `weak=True` when connecting a slot, the library holds a weak reference to the receiver object. This allows the receiver to be garbage-collected if there are no other strong references to it. Once garbage-collected, the connection is automatically removed, preventing stale references.

## Why TSignal?

Expand Down Expand Up @@ -100,6 +102,7 @@ asyncio.run(main())
### Thread Safety and Connection Types
TSignal automatically detects whether the signal emission and slot execution occur in the same thread or different threads:

- **Auto Connection**: When connection_type is AUTO_CONNECTION (default), TSignal checks whether the slot is a coroutine function or whether the caller and callee share the same thread affinity. If they are the same thread and slot is synchronous, it uses direct connection. Otherwise, it uses queued connection.
- **Direct Connection**: If signal and slot share the same thread affinity, the slot is invoked directly.
- **Queued Connection**: If they differ, the call is queued to the slot’s thread/event loop, ensuring thread safety.

Expand Down
32 changes: 27 additions & 5 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,12 @@ Represents a signal. Signals are created by `@t_signal` and accessed as class at
Connects the signal to a slot.

- **Parameters:**
- receiver_or_slot: Either the receiver object and slot method, or just a callable (function/lambda) if slot is None.
- slot: The method in the receiver if a receiver object is provided.
- connection_type: DIRECT_CONNECTION, QUEUED_CONNECTION, or AUTO_CONNECTION.
- AUTO_CONNECTION (default): Determines connection type automatically based on thread affinity and slot type.
- **receiver_or_slot:** Either the receiver object and slot method, or just a callable (function/lambda) if slot is None.
- **slot:** The method in the receiver if a receiver object is provided.
- **connection_type:** DIRECT_CONNECTION, QUEUED_CONNECTION, or AUTO_CONNECTION.
- **AUTO_CONNECTION (default):** Determines connection type automatically based on thread affinity and slot type.
- **weak:** If `True`, the receiver is kept via a weak reference so it can be garbage collected once there are no strong references. The signal automatically removes the connection if the receiver is collected.
- **one_shot:** If `True`, the connection is automatically disconnected after the first successful emit call. This is useful for events that should only notify a slot once.

**Examples:**

Expand All @@ -151,9 +153,29 @@ signal.connect(print)

Disconnects a previously connected slot. Returns the number of disconnected connections.

- **Parameters:**
- receiver: The object whose slot is connected. If receiver is None, all receivers are considered.
- slot: The specific slot to disconnect from the signal. If slot is None, all slots for the given receiver (or all connections if receiver is also None) are disconnected.
- **Returns:** The number of connections that were disconnected.-

**Examples:**
```python
# Disconnect all connections
signal.disconnect()

# Disconnect all slots from a specific receiver
signal.disconnect(receiver=my_receiver)

# Disconnect a specific slot from a specific receiver
signal.disconnect(receiver=my_receiver, slot=my_receiver.some_slot)

# Disconnect a standalone function
signal.disconnect(slot=my_function)
```

`emit(*args, **kwargs) -> None`

Emits the signal, invoking all connected slots either directly or via the event loop of the slot’s associated thread.
Emits the signal, invoking all connected slots either directly or via the event loop of the slot’s associated thread, depending on the connection type. If a connection is marked one_shot, it is automatically removed right after invocation.

`TConnectionType`

Expand Down
3 changes: 2 additions & 1 deletion docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ tests/
│ ├── test_property.py
│ ├── test_signal.py
│ ├── test_slot.py
│ └── test_utils.py
│ ├── test_utils.py
│ └── test_weak.py
├── integration/ # Integration tests
│ ├── __init__.py
│ ├── test_async.py
Expand Down
Loading
Loading