strongbus 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- strongbus-0.1.0/.gitignore +14 -0
- strongbus-0.1.0/.python-version +1 -0
- strongbus-0.1.0/CLAUDE.md +54 -0
- strongbus-0.1.0/PKG-INFO +233 -0
- strongbus-0.1.0/README.md +221 -0
- strongbus-0.1.0/pyproject.toml +19 -0
- strongbus-0.1.0/src/strongbus/__init__.py +3 -0
- strongbus-0.1.0/src/strongbus/core.py +107 -0
- strongbus-0.1.0/src/strongbus/py.typed +0 -0
- strongbus-0.1.0/src/strongbus/tests.py +143 -0
- strongbus-0.1.0/tox.ini +21 -0
- strongbus-0.1.0/uv.lock +236 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
### Testing
|
|
8
|
+
```bash
|
|
9
|
+
python -m unittest src/strongbus/tests.py
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
### Build and Package
|
|
13
|
+
```bash
|
|
14
|
+
python -m build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### Install in Development Mode
|
|
18
|
+
```bash
|
|
19
|
+
pip install -e .
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
StrongBus is a type-safe event bus library for Python with these core components:
|
|
25
|
+
|
|
26
|
+
### Core Classes (`src/strongbus/core.py`)
|
|
27
|
+
- **Event**: Base class for all events. All event types must inherit from this class.
|
|
28
|
+
- **EventBus**: Central event dispatcher that manages subscriptions and publishing
|
|
29
|
+
- Uses weak references for method callbacks to prevent memory leaks
|
|
30
|
+
- Strong references for function callbacks
|
|
31
|
+
- Type-safe subscription with generic type constraints
|
|
32
|
+
- **Enrollment**: Abstract base class that provides subscription management with automatic cleanup
|
|
33
|
+
- Tracks all subscriptions for easy bulk unsubscription via `clear()`
|
|
34
|
+
- Delegates to EventBus for actual event handling
|
|
35
|
+
|
|
36
|
+
### Key Design Patterns
|
|
37
|
+
- **Type Safety**: Uses generics (`SpecificEvent = TypeVar("SpecificEvent", bound=Event)`) to ensure callbacks receive the correct event type
|
|
38
|
+
- **Memory Management**: Automatic cleanup of dead method references using `weakref.WeakMethod`
|
|
39
|
+
- **Inheritance Isolation**: Events don't propagate to parent/child types - each event type is handled independently
|
|
40
|
+
- **Subscription Tracking**: Enrollment pattern allows components to manage their own subscriptions lifecycle
|
|
41
|
+
|
|
42
|
+
### Event Flow
|
|
43
|
+
1. Create event classes by inheriting from `Event` (typically as frozen dataclasses)
|
|
44
|
+
2. Components inherit from `Enrollment` and subscribe to specific event types
|
|
45
|
+
3. Events are published through EventBus and delivered only to exact type matches
|
|
46
|
+
4. Cleanup handled automatically via weak references or explicit `clear()` calls
|
|
47
|
+
|
|
48
|
+
### Testing
|
|
49
|
+
Comprehensive test suite in `src/strongbus/tests.py` covers:
|
|
50
|
+
- Basic subscription/publishing
|
|
51
|
+
- Multiple subscribers
|
|
52
|
+
- Type isolation
|
|
53
|
+
- Memory management (weak references)
|
|
54
|
+
- Subscription cleanup
|
strongbus-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: strongbus
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A type-safe event bus library for Python that provides reliable publish-subscribe messaging with automatic memory management and full type safety.
|
|
5
|
+
Author-email: Marius Räsener <marius@raesener.de>
|
|
6
|
+
Keywords: event-bus,events,pubsub,type-safe,typed
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: build; extra == 'dev'
|
|
10
|
+
Requires-Dist: tox; extra == 'dev'
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# StrongBus
|
|
15
|
+
|
|
16
|
+
A type-safe event bus library for Python that provides reliable publish-subscribe messaging with automatic memory management and full type safety.
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- **Type Safety**: Full type checking with generics ensures callbacks receive the correct event types
|
|
21
|
+
- **Memory Management**: Automatic cleanup of dead references using weak references for methods
|
|
22
|
+
- **Subscription Management**: Easy subscription tracking and bulk cleanup via the Enrollment pattern
|
|
23
|
+
- **Event Isolation**: Events don't propagate to parent/child types - each event type is handled independently
|
|
24
|
+
- **Zero Dependencies**: Pure Python implementation with no external dependencies
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install strongbus
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
For development:
|
|
33
|
+
```bash
|
|
34
|
+
pip install -e .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from dataclasses import dataclass
|
|
41
|
+
from strongbus import Event, EventBus, Enrollment
|
|
42
|
+
|
|
43
|
+
# Define your events
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class UserLoginEvent(Event):
|
|
46
|
+
username: str
|
|
47
|
+
|
|
48
|
+
# Create subscribers using Enrollment
|
|
49
|
+
class NotificationService(Enrollment):
|
|
50
|
+
def __init__(self, event_bus: EventBus):
|
|
51
|
+
super().__init__(event_bus)
|
|
52
|
+
self.subscribe(UserLoginEvent, self.on_user_login)
|
|
53
|
+
|
|
54
|
+
def on_user_login(self, event: UserLoginEvent) -> None:
|
|
55
|
+
print(f"Welcome {event.username}!")
|
|
56
|
+
|
|
57
|
+
# Usage
|
|
58
|
+
event_bus = EventBus()
|
|
59
|
+
service = NotificationService(event_bus)
|
|
60
|
+
event_bus.publish(UserLoginEvent(username="Alice"))
|
|
61
|
+
# Output: Welcome Alice!
|
|
62
|
+
|
|
63
|
+
# Cleanup
|
|
64
|
+
service.clear() # Automatically unsubscribes from all events
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Core Concepts
|
|
68
|
+
|
|
69
|
+
### Events
|
|
70
|
+
|
|
71
|
+
Events are simple data classes that inherit from the `Event` base class:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
@dataclass(frozen=True)
|
|
75
|
+
class OrderCreatedEvent(Event):
|
|
76
|
+
order_id: str
|
|
77
|
+
customer_id: str
|
|
78
|
+
total: float
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### EventBus
|
|
82
|
+
|
|
83
|
+
The central hub for publishing and subscribing to events:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
event_bus = EventBus()
|
|
87
|
+
|
|
88
|
+
# Subscribe to events
|
|
89
|
+
event_bus.subscribe(OrderCreatedEvent, handle_order)
|
|
90
|
+
|
|
91
|
+
# Publish events
|
|
92
|
+
event_bus.publish(OrderCreatedEvent(
|
|
93
|
+
order_id="12345",
|
|
94
|
+
customer_id="user123",
|
|
95
|
+
total=99.99
|
|
96
|
+
))
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Enrollment
|
|
100
|
+
|
|
101
|
+
A base class that simplifies subscription management:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
class OrderProcessor(Enrollment):
|
|
105
|
+
def __init__(self, event_bus: EventBus):
|
|
106
|
+
super().__init__(event_bus)
|
|
107
|
+
self.subscribe(OrderCreatedEvent, self.process_order)
|
|
108
|
+
self.subscribe(PaymentReceivedEvent, self.confirm_payment)
|
|
109
|
+
|
|
110
|
+
def process_order(self, event: OrderCreatedEvent) -> None:
|
|
111
|
+
# Handle order processing
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
def confirm_payment(self, event: PaymentReceivedEvent) -> None:
|
|
115
|
+
# Handle payment confirmation
|
|
116
|
+
pass
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Memory Management
|
|
120
|
+
|
|
121
|
+
StrongBus automatically manages memory to prevent leaks:
|
|
122
|
+
|
|
123
|
+
- **Method callbacks** use weak references and are automatically cleaned up when the object is garbage collected
|
|
124
|
+
- **Function callbacks** use strong references and persist until explicitly unsubscribed
|
|
125
|
+
- **Enrollment pattern** provides easy bulk cleanup with `clear()`
|
|
126
|
+
|
|
127
|
+
## Testing
|
|
128
|
+
|
|
129
|
+
### Using tox (recommended)
|
|
130
|
+
|
|
131
|
+
Install tox with uv support:
|
|
132
|
+
```bash
|
|
133
|
+
uv tool install tox --with tox-uv
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Run all tests across multiple Python versions:
|
|
137
|
+
```bash
|
|
138
|
+
tox
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Manual testing
|
|
142
|
+
|
|
143
|
+
Run the test suite directly:
|
|
144
|
+
```bash
|
|
145
|
+
python -m unittest src/strongbus/tests.py
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Slightly larger example
|
|
149
|
+
```python
|
|
150
|
+
from dataclasses import dataclass
|
|
151
|
+
|
|
152
|
+
from strongbus import Event, EventBus, Enrollment
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@dataclass(frozen=True)
|
|
156
|
+
class UserLoginEvent(Event):
|
|
157
|
+
username: str
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
@dataclass(frozen=True)
|
|
161
|
+
class UserLogoutEvent(Event):
|
|
162
|
+
username: str
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass(frozen=True)
|
|
166
|
+
class DataUpdatedEvent(Event):
|
|
167
|
+
data_id: str
|
|
168
|
+
new_value: str
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@dataclass(frozen=True)
|
|
172
|
+
class TestEvent(Event):
|
|
173
|
+
message: str
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class PackageManager(Enrollment):
|
|
177
|
+
def __init__(self, event_bus: EventBus):
|
|
178
|
+
super().__init__(event_bus)
|
|
179
|
+
# Type-safe subscription - callback must accept UserLoginEvent
|
|
180
|
+
self.subscribe(UserLoginEvent, self.on_user_login)
|
|
181
|
+
self.subscribe(DataUpdatedEvent, self.on_data_updated)
|
|
182
|
+
|
|
183
|
+
def on_user_login(self, event: UserLoginEvent) -> None:
|
|
184
|
+
# Can access event.username with full type safety
|
|
185
|
+
print(f"PackageManager: User {event.username} logged in")
|
|
186
|
+
|
|
187
|
+
def on_data_updated(self, event: DataUpdatedEvent) -> None:
|
|
188
|
+
print(f"PackageManager: Data {event.data_id} updated to {event.new_value}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ContainerManager(Enrollment):
|
|
192
|
+
def __init__(self, event_bus: EventBus):
|
|
193
|
+
super().__init__(event_bus)
|
|
194
|
+
self.subscribe(UserLoginEvent, self.on_user_login)
|
|
195
|
+
self.subscribe(UserLogoutEvent, self.on_user_logout)
|
|
196
|
+
|
|
197
|
+
def on_user_login(self, event: UserLoginEvent) -> None:
|
|
198
|
+
print(f"ContainerManager: User {event.username} logged in")
|
|
199
|
+
|
|
200
|
+
def on_user_logout(self, event: UserLogoutEvent) -> None:
|
|
201
|
+
print(f"ContainerManager: User {event.username} logged out")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
# Usage
|
|
206
|
+
event_bus = EventBus()
|
|
207
|
+
manager0 = PackageManager(event_bus)
|
|
208
|
+
manager1 = ContainerManager(event_bus)
|
|
209
|
+
|
|
210
|
+
# Publish events - type-safe with proper event objects
|
|
211
|
+
event_bus.publish(UserLoginEvent(username="Alice"))
|
|
212
|
+
# Output:
|
|
213
|
+
# PackageManager: User Alice logged in
|
|
214
|
+
# ContainerManager: User Alice logged in
|
|
215
|
+
|
|
216
|
+
event_bus.publish(UserLogoutEvent(username="Alice"))
|
|
217
|
+
# Output:
|
|
218
|
+
# ContainerManager: User Alice logged out
|
|
219
|
+
|
|
220
|
+
event_bus.publish(DataUpdatedEvent(data_id="123", new_value="new data"))
|
|
221
|
+
# Output:
|
|
222
|
+
# PackageManager: Data 123 updated to new data
|
|
223
|
+
|
|
224
|
+
# List all available event types
|
|
225
|
+
print("\nAvailable event types:")
|
|
226
|
+
for event_class in Event.__subclasses__():
|
|
227
|
+
print(f" - {event_class.__name__}")
|
|
228
|
+
|
|
229
|
+
# Cleanup
|
|
230
|
+
manager0.clear()
|
|
231
|
+
manager1.clear()
|
|
232
|
+
|
|
233
|
+
```
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
|
|
2
|
+
# StrongBus
|
|
3
|
+
|
|
4
|
+
A type-safe event bus library for Python that provides reliable publish-subscribe messaging with automatic memory management and full type safety.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Type Safety**: Full type checking with generics ensures callbacks receive the correct event types
|
|
9
|
+
- **Memory Management**: Automatic cleanup of dead references using weak references for methods
|
|
10
|
+
- **Subscription Management**: Easy subscription tracking and bulk cleanup via the Enrollment pattern
|
|
11
|
+
- **Event Isolation**: Events don't propagate to parent/child types - each event type is handled independently
|
|
12
|
+
- **Zero Dependencies**: Pure Python implementation with no external dependencies
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install strongbus
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For development:
|
|
21
|
+
```bash
|
|
22
|
+
pip install -e .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from dataclasses import dataclass
|
|
29
|
+
from strongbus import Event, EventBus, Enrollment
|
|
30
|
+
|
|
31
|
+
# Define your events
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class UserLoginEvent(Event):
|
|
34
|
+
username: str
|
|
35
|
+
|
|
36
|
+
# Create subscribers using Enrollment
|
|
37
|
+
class NotificationService(Enrollment):
|
|
38
|
+
def __init__(self, event_bus: EventBus):
|
|
39
|
+
super().__init__(event_bus)
|
|
40
|
+
self.subscribe(UserLoginEvent, self.on_user_login)
|
|
41
|
+
|
|
42
|
+
def on_user_login(self, event: UserLoginEvent) -> None:
|
|
43
|
+
print(f"Welcome {event.username}!")
|
|
44
|
+
|
|
45
|
+
# Usage
|
|
46
|
+
event_bus = EventBus()
|
|
47
|
+
service = NotificationService(event_bus)
|
|
48
|
+
event_bus.publish(UserLoginEvent(username="Alice"))
|
|
49
|
+
# Output: Welcome Alice!
|
|
50
|
+
|
|
51
|
+
# Cleanup
|
|
52
|
+
service.clear() # Automatically unsubscribes from all events
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Core Concepts
|
|
56
|
+
|
|
57
|
+
### Events
|
|
58
|
+
|
|
59
|
+
Events are simple data classes that inherit from the `Event` base class:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class OrderCreatedEvent(Event):
|
|
64
|
+
order_id: str
|
|
65
|
+
customer_id: str
|
|
66
|
+
total: float
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### EventBus
|
|
70
|
+
|
|
71
|
+
The central hub for publishing and subscribing to events:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
event_bus = EventBus()
|
|
75
|
+
|
|
76
|
+
# Subscribe to events
|
|
77
|
+
event_bus.subscribe(OrderCreatedEvent, handle_order)
|
|
78
|
+
|
|
79
|
+
# Publish events
|
|
80
|
+
event_bus.publish(OrderCreatedEvent(
|
|
81
|
+
order_id="12345",
|
|
82
|
+
customer_id="user123",
|
|
83
|
+
total=99.99
|
|
84
|
+
))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Enrollment
|
|
88
|
+
|
|
89
|
+
A base class that simplifies subscription management:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
class OrderProcessor(Enrollment):
|
|
93
|
+
def __init__(self, event_bus: EventBus):
|
|
94
|
+
super().__init__(event_bus)
|
|
95
|
+
self.subscribe(OrderCreatedEvent, self.process_order)
|
|
96
|
+
self.subscribe(PaymentReceivedEvent, self.confirm_payment)
|
|
97
|
+
|
|
98
|
+
def process_order(self, event: OrderCreatedEvent) -> None:
|
|
99
|
+
# Handle order processing
|
|
100
|
+
pass
|
|
101
|
+
|
|
102
|
+
def confirm_payment(self, event: PaymentReceivedEvent) -> None:
|
|
103
|
+
# Handle payment confirmation
|
|
104
|
+
pass
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Memory Management
|
|
108
|
+
|
|
109
|
+
StrongBus automatically manages memory to prevent leaks:
|
|
110
|
+
|
|
111
|
+
- **Method callbacks** use weak references and are automatically cleaned up when the object is garbage collected
|
|
112
|
+
- **Function callbacks** use strong references and persist until explicitly unsubscribed
|
|
113
|
+
- **Enrollment pattern** provides easy bulk cleanup with `clear()`
|
|
114
|
+
|
|
115
|
+
## Testing
|
|
116
|
+
|
|
117
|
+
### Using tox (recommended)
|
|
118
|
+
|
|
119
|
+
Install tox with uv support:
|
|
120
|
+
```bash
|
|
121
|
+
uv tool install tox --with tox-uv
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Run all tests across multiple Python versions:
|
|
125
|
+
```bash
|
|
126
|
+
tox
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Manual testing
|
|
130
|
+
|
|
131
|
+
Run the test suite directly:
|
|
132
|
+
```bash
|
|
133
|
+
python -m unittest src/strongbus/tests.py
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Slightly larger example
|
|
137
|
+
```python
|
|
138
|
+
from dataclasses import dataclass
|
|
139
|
+
|
|
140
|
+
from strongbus import Event, EventBus, Enrollment
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
@dataclass(frozen=True)
|
|
144
|
+
class UserLoginEvent(Event):
|
|
145
|
+
username: str
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@dataclass(frozen=True)
|
|
149
|
+
class UserLogoutEvent(Event):
|
|
150
|
+
username: str
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass(frozen=True)
|
|
154
|
+
class DataUpdatedEvent(Event):
|
|
155
|
+
data_id: str
|
|
156
|
+
new_value: str
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@dataclass(frozen=True)
|
|
160
|
+
class TestEvent(Event):
|
|
161
|
+
message: str
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class PackageManager(Enrollment):
|
|
165
|
+
def __init__(self, event_bus: EventBus):
|
|
166
|
+
super().__init__(event_bus)
|
|
167
|
+
# Type-safe subscription - callback must accept UserLoginEvent
|
|
168
|
+
self.subscribe(UserLoginEvent, self.on_user_login)
|
|
169
|
+
self.subscribe(DataUpdatedEvent, self.on_data_updated)
|
|
170
|
+
|
|
171
|
+
def on_user_login(self, event: UserLoginEvent) -> None:
|
|
172
|
+
# Can access event.username with full type safety
|
|
173
|
+
print(f"PackageManager: User {event.username} logged in")
|
|
174
|
+
|
|
175
|
+
def on_data_updated(self, event: DataUpdatedEvent) -> None:
|
|
176
|
+
print(f"PackageManager: Data {event.data_id} updated to {event.new_value}")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class ContainerManager(Enrollment):
|
|
180
|
+
def __init__(self, event_bus: EventBus):
|
|
181
|
+
super().__init__(event_bus)
|
|
182
|
+
self.subscribe(UserLoginEvent, self.on_user_login)
|
|
183
|
+
self.subscribe(UserLogoutEvent, self.on_user_logout)
|
|
184
|
+
|
|
185
|
+
def on_user_login(self, event: UserLoginEvent) -> None:
|
|
186
|
+
print(f"ContainerManager: User {event.username} logged in")
|
|
187
|
+
|
|
188
|
+
def on_user_logout(self, event: UserLogoutEvent) -> None:
|
|
189
|
+
print(f"ContainerManager: User {event.username} logged out")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
# Usage
|
|
194
|
+
event_bus = EventBus()
|
|
195
|
+
manager0 = PackageManager(event_bus)
|
|
196
|
+
manager1 = ContainerManager(event_bus)
|
|
197
|
+
|
|
198
|
+
# Publish events - type-safe with proper event objects
|
|
199
|
+
event_bus.publish(UserLoginEvent(username="Alice"))
|
|
200
|
+
# Output:
|
|
201
|
+
# PackageManager: User Alice logged in
|
|
202
|
+
# ContainerManager: User Alice logged in
|
|
203
|
+
|
|
204
|
+
event_bus.publish(UserLogoutEvent(username="Alice"))
|
|
205
|
+
# Output:
|
|
206
|
+
# ContainerManager: User Alice logged out
|
|
207
|
+
|
|
208
|
+
event_bus.publish(DataUpdatedEvent(data_id="123", new_value="new data"))
|
|
209
|
+
# Output:
|
|
210
|
+
# PackageManager: Data 123 updated to new data
|
|
211
|
+
|
|
212
|
+
# List all available event types
|
|
213
|
+
print("\nAvailable event types:")
|
|
214
|
+
for event_class in Event.__subclasses__():
|
|
215
|
+
print(f" - {event_class.__name__}")
|
|
216
|
+
|
|
217
|
+
# Cleanup
|
|
218
|
+
manager0.clear()
|
|
219
|
+
manager1.clear()
|
|
220
|
+
|
|
221
|
+
```
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "strongbus"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A type-safe event bus library for Python that provides reliable publish-subscribe messaging with automatic memory management and full type safety."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{ name = "Marius Räsener", email = "marius@raesener.de" }]
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
dependencies = []
|
|
9
|
+
keywords = ["events", "event-bus", "typed", "type-safe", "pubsub"]
|
|
10
|
+
|
|
11
|
+
[project.optional-dependencies]
|
|
12
|
+
dev = ["tox", "build"]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
strongbus = "strongbus:main"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["hatchling"]
|
|
19
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
import weakref
|
|
3
|
+
from abc import ABC
|
|
4
|
+
from typing import Callable, Dict, List, Type, TypeVar, Union
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Event:
|
|
8
|
+
"""Base class for all events. Subclass for specific types."""
|
|
9
|
+
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
SpecificEvent = TypeVar("SpecificEvent", bound=Event)
|
|
14
|
+
|
|
15
|
+
EventHandler = Callable[[Event], None]
|
|
16
|
+
SubscriberType = Union[EventHandler, weakref.WeakMethod[EventHandler]]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EventBus:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self._subscribers: Dict[Type[Event], List[SubscriberType]] = {}
|
|
22
|
+
|
|
23
|
+
def subscribe(
|
|
24
|
+
self, event_type: Type[SpecificEvent], callback: Callable[[SpecificEvent], None]
|
|
25
|
+
) -> None:
|
|
26
|
+
"""Subscribe to a specific event type with a type-safe callback."""
|
|
27
|
+
if event_type not in self._subscribers:
|
|
28
|
+
self._subscribers[event_type] = []
|
|
29
|
+
|
|
30
|
+
if inspect.ismethod(callback):
|
|
31
|
+
weak_callback: SubscriberType = weakref.WeakMethod(callback) # type: ignore[arg-type]
|
|
32
|
+
else:
|
|
33
|
+
weak_callback: SubscriberType = callback # type: ignore[assignment]
|
|
34
|
+
|
|
35
|
+
self._subscribers[event_type].append(weak_callback)
|
|
36
|
+
|
|
37
|
+
def unsubscribe(
|
|
38
|
+
self, event_type: Type[SpecificEvent], callback: Callable[[SpecificEvent], None]
|
|
39
|
+
) -> None:
|
|
40
|
+
"""Unsubscribe a callback from a specific event type."""
|
|
41
|
+
if event_type in self._subscribers:
|
|
42
|
+
to_remove: List[SubscriberType] = []
|
|
43
|
+
for weak_cb in self._subscribers[event_type]:
|
|
44
|
+
if isinstance(weak_cb, weakref.WeakMethod):
|
|
45
|
+
cb: Callable[[Event], None] | None = weak_cb()
|
|
46
|
+
if cb is not None and cb == callback:
|
|
47
|
+
to_remove.append(weak_cb)
|
|
48
|
+
else:
|
|
49
|
+
if weak_cb == callback:
|
|
50
|
+
to_remove.append(weak_cb)
|
|
51
|
+
for r in to_remove:
|
|
52
|
+
self._subscribers[event_type].remove(r)
|
|
53
|
+
|
|
54
|
+
def publish(self, event: Event) -> None:
|
|
55
|
+
"""Publish an event to all subscribers of its type."""
|
|
56
|
+
event_type = type(event)
|
|
57
|
+
if event_type in self._subscribers:
|
|
58
|
+
subscribers = self._subscribers[event_type][
|
|
59
|
+
:
|
|
60
|
+
] # Copy to avoid modification during iteration
|
|
61
|
+
for weak_cb in subscribers:
|
|
62
|
+
if isinstance(weak_cb, weakref.WeakMethod):
|
|
63
|
+
cb: Callable[[Event], None] | None = weak_cb()
|
|
64
|
+
if cb is not None:
|
|
65
|
+
cb(event)
|
|
66
|
+
else:
|
|
67
|
+
self._subscribers[event_type].remove(weak_cb)
|
|
68
|
+
else:
|
|
69
|
+
weak_cb(event)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Enrollment(ABC):
|
|
73
|
+
def __init__(self, event_bus: EventBus):
|
|
74
|
+
self._event_bus = event_bus
|
|
75
|
+
self._subscriptions: Dict[Type[Event], List[Callable[[Event], None]]] = {}
|
|
76
|
+
|
|
77
|
+
def subscribe(
|
|
78
|
+
self, event_type: Type[SpecificEvent], callback: Callable[[SpecificEvent], None]
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Subscribe to an event type with automatic tracking."""
|
|
81
|
+
if event_type not in self._subscriptions:
|
|
82
|
+
self._subscriptions[event_type] = []
|
|
83
|
+
self._subscriptions[event_type].append(callback) # type: ignore
|
|
84
|
+
self._event_bus.subscribe(event_type, callback)
|
|
85
|
+
|
|
86
|
+
def unsubscribe(
|
|
87
|
+
self, event_type: Type[SpecificEvent], callback: Callable[[SpecificEvent], None]
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Unsubscribe from an event type."""
|
|
90
|
+
if event_type in self._subscriptions:
|
|
91
|
+
self._subscriptions[event_type] = [
|
|
92
|
+
cb for cb in self._subscriptions[event_type] if cb != callback
|
|
93
|
+
]
|
|
94
|
+
self._event_bus.unsubscribe(event_type, callback)
|
|
95
|
+
if not self._subscriptions[event_type]:
|
|
96
|
+
del self._subscriptions[event_type]
|
|
97
|
+
|
|
98
|
+
def publish(self, event: Event) -> None:
|
|
99
|
+
"""Publish an event through the event bus."""
|
|
100
|
+
self._event_bus.publish(event)
|
|
101
|
+
|
|
102
|
+
def clear(self) -> None:
|
|
103
|
+
"""Unsubscribe from all events."""
|
|
104
|
+
for event_type, callbacks in list(self._subscriptions.items()):
|
|
105
|
+
for callback in callbacks:
|
|
106
|
+
self._event_bus.unsubscribe(event_type, callback)
|
|
107
|
+
self._subscriptions.clear()
|
|
File without changes
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import gc
|
|
2
|
+
import unittest
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from unittest.mock import Mock
|
|
5
|
+
|
|
6
|
+
from strongbus import Event, EventBus
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class MyEvent(Event):
|
|
11
|
+
value: str
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class AnotherEvent(Event):
|
|
16
|
+
number: int
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class SubEvent(MyEvent):
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class TestEventBus(unittest.TestCase):
|
|
25
|
+
def setUp(self):
|
|
26
|
+
self.bus = EventBus()
|
|
27
|
+
|
|
28
|
+
def test_subscribe_and_publish(self):
|
|
29
|
+
callback = Mock()
|
|
30
|
+
self.bus.subscribe(MyEvent, callback)
|
|
31
|
+
event = MyEvent("test")
|
|
32
|
+
self.bus.publish(event)
|
|
33
|
+
callback.assert_called_once_with(event)
|
|
34
|
+
|
|
35
|
+
def test_no_subscribers(self):
|
|
36
|
+
event = MyEvent("test")
|
|
37
|
+
# Should not raise any error
|
|
38
|
+
self.bus.publish(event)
|
|
39
|
+
|
|
40
|
+
def test_multiple_subscribers(self):
|
|
41
|
+
callback1 = Mock()
|
|
42
|
+
callback2 = Mock()
|
|
43
|
+
self.bus.subscribe(MyEvent, callback1)
|
|
44
|
+
self.bus.subscribe(MyEvent, callback2)
|
|
45
|
+
event = MyEvent("test")
|
|
46
|
+
self.bus.publish(event)
|
|
47
|
+
callback1.assert_called_once_with(event)
|
|
48
|
+
callback2.assert_called_once_with(event)
|
|
49
|
+
|
|
50
|
+
def test_different_event_types(self):
|
|
51
|
+
callback_my = Mock()
|
|
52
|
+
callback_another = Mock()
|
|
53
|
+
self.bus.subscribe(MyEvent, callback_my)
|
|
54
|
+
self.bus.subscribe(AnotherEvent, callback_another)
|
|
55
|
+
event_my = MyEvent("test")
|
|
56
|
+
event_another = AnotherEvent(42)
|
|
57
|
+
self.bus.publish(event_my)
|
|
58
|
+
self.bus.publish(event_another)
|
|
59
|
+
callback_my.assert_called_once_with(event_my)
|
|
60
|
+
callback_another.assert_called_once_with(event_another)
|
|
61
|
+
|
|
62
|
+
def test_unsubscribe(self):
|
|
63
|
+
callback = Mock()
|
|
64
|
+
self.bus.subscribe(MyEvent, callback)
|
|
65
|
+
event = MyEvent("test")
|
|
66
|
+
self.bus.publish(event)
|
|
67
|
+
callback.assert_called_once_with(event)
|
|
68
|
+
self.bus.unsubscribe(MyEvent, callback)
|
|
69
|
+
callback.reset_mock()
|
|
70
|
+
self.bus.publish(event)
|
|
71
|
+
callback.assert_not_called()
|
|
72
|
+
|
|
73
|
+
def test_unsubscribe_non_existent(self):
|
|
74
|
+
callback = Mock()
|
|
75
|
+
# Should not raise error
|
|
76
|
+
self.bus.unsubscribe(MyEvent, callback)
|
|
77
|
+
event = MyEvent("test")
|
|
78
|
+
self.bus.publish(event)
|
|
79
|
+
callback.assert_not_called()
|
|
80
|
+
|
|
81
|
+
def test_inheritance_not_propagated(self):
|
|
82
|
+
callback_base = Mock()
|
|
83
|
+
callback_sub = Mock()
|
|
84
|
+
self.bus.subscribe(MyEvent, callback_base)
|
|
85
|
+
self.bus.subscribe(SubEvent, callback_sub)
|
|
86
|
+
event_base = MyEvent("base")
|
|
87
|
+
event_sub = SubEvent("sub")
|
|
88
|
+
self.bus.publish(event_base)
|
|
89
|
+
self.bus.publish(event_sub)
|
|
90
|
+
callback_base.assert_called_once_with(event_base)
|
|
91
|
+
callback_sub.assert_called_once_with(event_sub)
|
|
92
|
+
# Ensure base callback not called for sub event
|
|
93
|
+
self.assertEqual(callback_base.call_count, 1)
|
|
94
|
+
|
|
95
|
+
def test_weak_reference_for_methods(self):
|
|
96
|
+
class Subscriber:
|
|
97
|
+
def __init__(self):
|
|
98
|
+
self.called = 0
|
|
99
|
+
|
|
100
|
+
def on_event(self, event: MyEvent):
|
|
101
|
+
self.called += 1
|
|
102
|
+
|
|
103
|
+
sub = Subscriber()
|
|
104
|
+
self.bus.subscribe(MyEvent, sub.on_event)
|
|
105
|
+
self.assertEqual(len(self.bus._subscribers.get(MyEvent, [])), 1) # pyright: ignore[reportPrivateUsage]
|
|
106
|
+
|
|
107
|
+
event = MyEvent("test")
|
|
108
|
+
self.bus.publish(event)
|
|
109
|
+
self.assertEqual(sub.called, 1)
|
|
110
|
+
|
|
111
|
+
del sub
|
|
112
|
+
gc.collect()
|
|
113
|
+
|
|
114
|
+
self.bus.publish(event) # Should clean up dead weak ref
|
|
115
|
+
self.assertEqual(len(self.bus._subscribers.get(MyEvent, [])), 0) # pyright: ignore[reportPrivateUsage]
|
|
116
|
+
|
|
117
|
+
def test_strong_reference_for_functions(self):
|
|
118
|
+
def on_event(event: MyEvent):
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
self.bus.subscribe(MyEvent, on_event)
|
|
122
|
+
self.assertEqual(len(self.bus._subscribers.get(MyEvent, [])), 1) # pyright: ignore[reportPrivateUsage]
|
|
123
|
+
|
|
124
|
+
# Simulate GC, but since strong ref, should remain
|
|
125
|
+
gc.collect()
|
|
126
|
+
self.bus.publish(MyEvent("test"))
|
|
127
|
+
self.assertEqual(len(self.bus._subscribers.get(MyEvent, [])), 1) # pyright: ignore[reportPrivateUsage]
|
|
128
|
+
|
|
129
|
+
def test_unsubscribe_weak_method(self):
|
|
130
|
+
class Subscriber:
|
|
131
|
+
def on_event(self, event: MyEvent):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
sub = Subscriber()
|
|
135
|
+
self.bus.subscribe(MyEvent, sub.on_event)
|
|
136
|
+
self.assertEqual(len(self.bus._subscribers.get(MyEvent, [])), 1) # pyright: ignore[reportPrivateUsage]
|
|
137
|
+
|
|
138
|
+
self.bus.unsubscribe(MyEvent, sub.on_event)
|
|
139
|
+
self.assertEqual(len(self.bus._subscribers.get(MyEvent, [])), 0) # pyright: ignore[reportPrivateUsage]
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
unittest.main()
|
strongbus-0.1.0/tox.ini
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[tox]
|
|
2
|
+
envlist = py310, py311, py312, py313
|
|
3
|
+
isolated_build = true
|
|
4
|
+
|
|
5
|
+
[testenv]
|
|
6
|
+
deps =
|
|
7
|
+
build
|
|
8
|
+
commands =
|
|
9
|
+
python -m unittest src/strongbus/tests.py
|
|
10
|
+
|
|
11
|
+
[testenv:build]
|
|
12
|
+
deps =
|
|
13
|
+
build
|
|
14
|
+
commands =
|
|
15
|
+
python -m build
|
|
16
|
+
|
|
17
|
+
[testenv:dev]
|
|
18
|
+
deps =
|
|
19
|
+
build
|
|
20
|
+
commands =
|
|
21
|
+
pip install -e .
|
strongbus-0.1.0/uv.lock
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 2
|
|
3
|
+
requires-python = ">=3.10"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "build"
|
|
7
|
+
version = "1.2.2.post1"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "colorama", marker = "os_name == 'nt'" },
|
|
11
|
+
{ name = "importlib-metadata", marker = "python_full_version < '3.10.2'" },
|
|
12
|
+
{ name = "packaging" },
|
|
13
|
+
{ name = "pyproject-hooks" },
|
|
14
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
15
|
+
]
|
|
16
|
+
sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" }
|
|
17
|
+
wheels = [
|
|
18
|
+
{ url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[[package]]
|
|
22
|
+
name = "cachetools"
|
|
23
|
+
version = "6.1.0"
|
|
24
|
+
source = { registry = "https://pypi.org/simple" }
|
|
25
|
+
sdist = { url = "https://files.pythonhosted.org/packages/8a/89/817ad5d0411f136c484d535952aef74af9b25e0d99e90cdffbe121e6d628/cachetools-6.1.0.tar.gz", hash = "sha256:b4c4f404392848db3ce7aac34950d17be4d864da4b8b66911008e430bc544587", size = 30714, upload-time = "2025-06-16T18:51:03.07Z" }
|
|
26
|
+
wheels = [
|
|
27
|
+
{ url = "https://files.pythonhosted.org/packages/00/f0/2ef431fe4141f5e334759d73e81120492b23b2824336883a91ac04ba710b/cachetools-6.1.0-py3-none-any.whl", hash = "sha256:1c7bb3cf9193deaf3508b7c5f2a79986c13ea38965c5adcff1f84519cf39163e", size = 11189, upload-time = "2025-06-16T18:51:01.514Z" },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[[package]]
|
|
31
|
+
name = "chardet"
|
|
32
|
+
version = "5.2.0"
|
|
33
|
+
source = { registry = "https://pypi.org/simple" }
|
|
34
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618, upload-time = "2023-08-01T19:23:02.662Z" }
|
|
35
|
+
wheels = [
|
|
36
|
+
{ url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385, upload-time = "2023-08-01T19:23:00.661Z" },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
[[package]]
|
|
40
|
+
name = "colorama"
|
|
41
|
+
version = "0.4.6"
|
|
42
|
+
source = { registry = "https://pypi.org/simple" }
|
|
43
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
44
|
+
wheels = [
|
|
45
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
[[package]]
|
|
49
|
+
name = "distlib"
|
|
50
|
+
version = "0.3.9"
|
|
51
|
+
source = { registry = "https://pypi.org/simple" }
|
|
52
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" }
|
|
53
|
+
wheels = [
|
|
54
|
+
{ url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" },
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[[package]]
|
|
58
|
+
name = "filelock"
|
|
59
|
+
version = "3.18.0"
|
|
60
|
+
source = { registry = "https://pypi.org/simple" }
|
|
61
|
+
sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" }
|
|
62
|
+
wheels = [
|
|
63
|
+
{ url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" },
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
[[package]]
|
|
67
|
+
name = "importlib-metadata"
|
|
68
|
+
version = "8.7.0"
|
|
69
|
+
source = { registry = "https://pypi.org/simple" }
|
|
70
|
+
dependencies = [
|
|
71
|
+
{ name = "zipp" },
|
|
72
|
+
]
|
|
73
|
+
sdist = { url = "https://files.pythonhosted.org/packages/76/66/650a33bd90f786193e4de4b3ad86ea60b53c89b669a5c7be931fac31cdb0/importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", size = 56641, upload-time = "2025-04-27T15:29:01.736Z" }
|
|
74
|
+
wheels = [
|
|
75
|
+
{ url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" },
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
[[package]]
|
|
79
|
+
name = "packaging"
|
|
80
|
+
version = "25.0"
|
|
81
|
+
source = { registry = "https://pypi.org/simple" }
|
|
82
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" }
|
|
83
|
+
wheels = [
|
|
84
|
+
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" },
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[[package]]
|
|
88
|
+
name = "platformdirs"
|
|
89
|
+
version = "4.3.8"
|
|
90
|
+
source = { registry = "https://pypi.org/simple" }
|
|
91
|
+
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362, upload-time = "2025-05-07T22:47:42.121Z" }
|
|
92
|
+
wheels = [
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567, upload-time = "2025-05-07T22:47:40.376Z" },
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[[package]]
|
|
97
|
+
name = "pluggy"
|
|
98
|
+
version = "1.6.0"
|
|
99
|
+
source = { registry = "https://pypi.org/simple" }
|
|
100
|
+
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" }
|
|
101
|
+
wheels = [
|
|
102
|
+
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" },
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[[package]]
|
|
106
|
+
name = "pyproject-api"
|
|
107
|
+
version = "1.9.1"
|
|
108
|
+
source = { registry = "https://pypi.org/simple" }
|
|
109
|
+
dependencies = [
|
|
110
|
+
{ name = "packaging" },
|
|
111
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
112
|
+
]
|
|
113
|
+
sdist = { url = "https://files.pythonhosted.org/packages/19/fd/437901c891f58a7b9096511750247535e891d2d5a5a6eefbc9386a2b41d5/pyproject_api-1.9.1.tar.gz", hash = "sha256:43c9918f49daab37e302038fc1aed54a8c7a91a9fa935d00b9a485f37e0f5335", size = 22710, upload-time = "2025-05-12T14:41:58.025Z" }
|
|
114
|
+
wheels = [
|
|
115
|
+
{ url = "https://files.pythonhosted.org/packages/ef/e6/c293c06695d4a3ab0260ef124a74ebadba5f4c511ce3a4259e976902c00b/pyproject_api-1.9.1-py3-none-any.whl", hash = "sha256:7d6238d92f8962773dd75b5f0c4a6a27cce092a14b623b811dba656f3b628948", size = 13158, upload-time = "2025-05-12T14:41:56.217Z" },
|
|
116
|
+
]
|
|
117
|
+
|
|
118
|
+
[[package]]
|
|
119
|
+
name = "pyproject-hooks"
|
|
120
|
+
version = "1.2.0"
|
|
121
|
+
source = { registry = "https://pypi.org/simple" }
|
|
122
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" }
|
|
123
|
+
wheels = [
|
|
124
|
+
{ url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" },
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[[package]]
|
|
128
|
+
name = "strongbus"
|
|
129
|
+
version = "0.1.0"
|
|
130
|
+
source = { editable = "." }
|
|
131
|
+
|
|
132
|
+
[package.optional-dependencies]
|
|
133
|
+
dev = [
|
|
134
|
+
{ name = "build" },
|
|
135
|
+
{ name = "tox" },
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
[package.metadata]
|
|
139
|
+
requires-dist = [
|
|
140
|
+
{ name = "build", marker = "extra == 'dev'" },
|
|
141
|
+
{ name = "tox", marker = "extra == 'dev'" },
|
|
142
|
+
]
|
|
143
|
+
provides-extras = ["dev"]
|
|
144
|
+
|
|
145
|
+
[[package]]
|
|
146
|
+
name = "tomli"
|
|
147
|
+
version = "2.2.1"
|
|
148
|
+
source = { registry = "https://pypi.org/simple" }
|
|
149
|
+
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
|
|
150
|
+
wheels = [
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
|
|
152
|
+
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
|
|
153
|
+
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
|
|
154
|
+
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
|
|
155
|
+
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
|
|
156
|
+
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
|
|
157
|
+
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
|
|
158
|
+
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
|
|
159
|
+
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
|
|
161
|
+
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
|
|
162
|
+
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
|
|
163
|
+
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
|
|
164
|
+
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
|
|
165
|
+
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
|
|
166
|
+
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
|
|
167
|
+
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
|
|
168
|
+
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
|
|
169
|
+
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
|
|
170
|
+
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
|
|
171
|
+
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
|
|
172
|
+
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
|
|
174
|
+
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
|
|
175
|
+
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
|
|
176
|
+
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
|
|
177
|
+
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
|
|
178
|
+
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
|
|
179
|
+
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
|
|
180
|
+
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
|
|
181
|
+
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
[[package]]
|
|
185
|
+
name = "tox"
|
|
186
|
+
version = "4.27.0"
|
|
187
|
+
source = { registry = "https://pypi.org/simple" }
|
|
188
|
+
dependencies = [
|
|
189
|
+
{ name = "cachetools" },
|
|
190
|
+
{ name = "chardet" },
|
|
191
|
+
{ name = "colorama" },
|
|
192
|
+
{ name = "filelock" },
|
|
193
|
+
{ name = "packaging" },
|
|
194
|
+
{ name = "platformdirs" },
|
|
195
|
+
{ name = "pluggy" },
|
|
196
|
+
{ name = "pyproject-api" },
|
|
197
|
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
|
198
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
|
199
|
+
{ name = "virtualenv" },
|
|
200
|
+
]
|
|
201
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a5/b7/19c01717747076f63c54d871ada081cd711a7c9a7572f2225675c3858b94/tox-4.27.0.tar.gz", hash = "sha256:b97d5ecc0c0d5755bcc5348387fef793e1bfa68eb33746412f4c60881d7f5f57", size = 198351, upload-time = "2025-06-17T15:17:50.585Z" }
|
|
202
|
+
wheels = [
|
|
203
|
+
{ url = "https://files.pythonhosted.org/packages/c1/3a/30889167f41ecaffb957ec4409e1cbc1d5d558a5bbbdfb734a5b9911930f/tox-4.27.0-py3-none-any.whl", hash = "sha256:2b8a7fb986b82aa2c830c0615082a490d134e0626dbc9189986da46a313c4f20", size = 173441, upload-time = "2025-06-17T15:17:48.689Z" },
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
[[package]]
|
|
207
|
+
name = "typing-extensions"
|
|
208
|
+
version = "4.14.1"
|
|
209
|
+
source = { registry = "https://pypi.org/simple" }
|
|
210
|
+
sdist = { url = "https://files.pythonhosted.org/packages/98/5a/da40306b885cc8c09109dc2e1abd358d5684b1425678151cdaed4731c822/typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", size = 107673, upload-time = "2025-07-04T13:28:34.16Z" }
|
|
211
|
+
wheels = [
|
|
212
|
+
{ url = "https://files.pythonhosted.org/packages/b5/00/d631e67a838026495268c2f6884f3711a15a9a2a96cd244fdaea53b823fb/typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76", size = 43906, upload-time = "2025-07-04T13:28:32.743Z" },
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
[[package]]
|
|
216
|
+
name = "virtualenv"
|
|
217
|
+
version = "20.31.2"
|
|
218
|
+
source = { registry = "https://pypi.org/simple" }
|
|
219
|
+
dependencies = [
|
|
220
|
+
{ name = "distlib" },
|
|
221
|
+
{ name = "filelock" },
|
|
222
|
+
{ name = "platformdirs" },
|
|
223
|
+
]
|
|
224
|
+
sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" }
|
|
225
|
+
wheels = [
|
|
226
|
+
{ url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" },
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
[[package]]
|
|
230
|
+
name = "zipp"
|
|
231
|
+
version = "3.23.0"
|
|
232
|
+
source = { registry = "https://pypi.org/simple" }
|
|
233
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" }
|
|
234
|
+
wheels = [
|
|
235
|
+
{ url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" },
|
|
236
|
+
]
|