seatlock 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.
- seatlock-0.1.0/License +21 -0
- seatlock-0.1.0/PKG-INFO +331 -0
- seatlock-0.1.0/README.md +314 -0
- seatlock-0.1.0/pyproject.toml +32 -0
- seatlock-0.1.0/seatlock/__init__.py +2 -0
- seatlock-0.1.0/seatlock/manager.py +177 -0
- seatlock-0.1.0/seatlock/seat.py +20 -0
- seatlock-0.1.0/seatlock/states.py +3 -0
- seatlock-0.1.0/seatlock.egg-info/PKG-INFO +331 -0
- seatlock-0.1.0/seatlock.egg-info/SOURCES.txt +11 -0
- seatlock-0.1.0/seatlock.egg-info/dependency_links.txt +1 -0
- seatlock-0.1.0/seatlock.egg-info/top_level.txt +1 -0
- seatlock-0.1.0/setup.cfg +4 -0
seatlock-0.1.0/License
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2026] [AdityaKatyal]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
seatlock-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seatlock
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A concurrency-safe seat allocation and booking engine
|
|
5
|
+
Author: Aditya
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: License
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Seat Allocation Engine – Working Cycle
|
|
19
|
+
|
|
20
|
+
This document explains **how the Seat Allocation Engine works internally**, step by step. It focuses on the **lifecycle of a seat**, **locking rules**, **bulk operations**, and **cleanup mechanics**. No UI, API, or framework concepts are involved here—this is purely the domain engine.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Core Purpose
|
|
25
|
+
|
|
26
|
+
The engine is designed to solve one problem **correctly**:
|
|
27
|
+
|
|
28
|
+
> Allocate seats to users in a safe, fair, and deterministic way.
|
|
29
|
+
|
|
30
|
+
It guarantees:
|
|
31
|
+
- No double booking
|
|
32
|
+
- No permanent locks
|
|
33
|
+
- No partial group bookings
|
|
34
|
+
- No lock stealing
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Core Concepts
|
|
39
|
+
|
|
40
|
+
### 2.1 Seat as a Resource
|
|
41
|
+
Each seat is treated as an **independent resource**.
|
|
42
|
+
|
|
43
|
+
A seat has exactly one of three states:
|
|
44
|
+
- `AVAILABLE`
|
|
45
|
+
- `LOCKED`
|
|
46
|
+
- `BOOKED`
|
|
47
|
+
|
|
48
|
+
Once a seat is `BOOKED`, it is **terminal** and cannot change state.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### 2.2 Seat Ownership
|
|
53
|
+
|
|
54
|
+
When a seat is locked:
|
|
55
|
+
- It is owned by exactly **one user**
|
|
56
|
+
- Only that user can book it
|
|
57
|
+
- Other users are rejected
|
|
58
|
+
|
|
59
|
+
Ownership is enforced strictly at all times.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Seat Lifecycle
|
|
64
|
+
|
|
65
|
+
The lifecycle of a seat follows this strict state machine:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
AVAILABLE → LOCKED → BOOKED
|
|
69
|
+
↑ |
|
|
70
|
+
└────────┘ (lock expiry)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Invalid transitions are never allowed.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 4. Locking Mechanism
|
|
78
|
+
|
|
79
|
+
### 4.1 Single Seat Locking
|
|
80
|
+
|
|
81
|
+
When a user requests to lock a seat:
|
|
82
|
+
|
|
83
|
+
1. The engine checks if the seat exists
|
|
84
|
+
2. If the seat is `BOOKED` → reject
|
|
85
|
+
3. If the seat is `LOCKED` → reject
|
|
86
|
+
4. If the seat is `AVAILABLE` → lock it
|
|
87
|
+
|
|
88
|
+
A successful lock records:
|
|
89
|
+
- `locked_by` (user id)
|
|
90
|
+
- `lock_time` (timestamp)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### 4.2 Bulk Seat Locking (Group Selection)
|
|
95
|
+
|
|
96
|
+
Bulk locking is **atomic**.
|
|
97
|
+
|
|
98
|
+
This means:
|
|
99
|
+
> Either all seats are locked, or none are.
|
|
100
|
+
|
|
101
|
+
#### Working cycle:
|
|
102
|
+
|
|
103
|
+
**Phase 1 – Validation (read-only)**
|
|
104
|
+
- All seats must exist
|
|
105
|
+
- All seats must be `AVAILABLE`
|
|
106
|
+
- If any seat fails → abort immediately
|
|
107
|
+
|
|
108
|
+
**Phase 2 – Commit (write)**
|
|
109
|
+
- All seats are locked together
|
|
110
|
+
- Same user
|
|
111
|
+
- Same lock timestamp
|
|
112
|
+
|
|
113
|
+
No partial locking is ever possible.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 5. Booking Mechanism
|
|
118
|
+
|
|
119
|
+
### 5.1 Single Seat Booking
|
|
120
|
+
|
|
121
|
+
To book a seat:
|
|
122
|
+
|
|
123
|
+
1. Seat must exist
|
|
124
|
+
2. Seat must be `LOCKED`
|
|
125
|
+
3. Seat must be locked by the same user
|
|
126
|
+
|
|
127
|
+
If all checks pass:
|
|
128
|
+
- Seat transitions to `BOOKED`
|
|
129
|
+
- Lock metadata is cleared
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### 5.2 Bulk Seat Booking
|
|
134
|
+
|
|
135
|
+
Bulk booking follows the same atomic principle as bulk locking.
|
|
136
|
+
|
|
137
|
+
**Validation phase:**
|
|
138
|
+
- All seats must exist
|
|
139
|
+
- All seats must be `LOCKED`
|
|
140
|
+
- All seats must be locked by the same user
|
|
141
|
+
|
|
142
|
+
**Commit phase:**
|
|
143
|
+
- All seats transition to `BOOKED`
|
|
144
|
+
|
|
145
|
+
If any seat fails validation → **no seat is booked**.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 6. Lock Expiry (Auto Release)
|
|
150
|
+
|
|
151
|
+
Locks are **temporary by design**.
|
|
152
|
+
|
|
153
|
+
### 6.1 Timeout Rule
|
|
154
|
+
|
|
155
|
+
- Each lock has a maximum lifetime (`LOCK_TIMEOUT`)
|
|
156
|
+
- Default: 10 seconds
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### 6.2 Cleanup Mechanism
|
|
161
|
+
|
|
162
|
+
Lock expiry is handled by a **system-level cleanup function**:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
cleanup_expired_locks()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This function:
|
|
169
|
+
- Iterates over all seats
|
|
170
|
+
- Releases locks that exceeded the timeout
|
|
171
|
+
- Never depends on user actions
|
|
172
|
+
|
|
173
|
+
Important rule:
|
|
174
|
+
|
|
175
|
+
> A lock may expire, but it is never stolen by another user.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 7. Concurrency Safety
|
|
180
|
+
|
|
181
|
+
All operations run inside a **global lock**.
|
|
182
|
+
|
|
183
|
+
This guarantees:
|
|
184
|
+
- Atomic operations
|
|
185
|
+
- No race conditions
|
|
186
|
+
- Deterministic behavior
|
|
187
|
+
|
|
188
|
+
The engine prioritizes **correctness over performance**.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 8. Separation of Responsibilities
|
|
193
|
+
|
|
194
|
+
| Layer | Responsibility |
|
|
195
|
+
|-----|--------------|
|
|
196
|
+
| Seat | State + truth checks |
|
|
197
|
+
| SeatManager | Transitions + rules |
|
|
198
|
+
| Cleanup | Time-based expiry |
|
|
199
|
+
| UI / API | Input & presentation only |
|
|
200
|
+
|
|
201
|
+
The engine does **not** know about:
|
|
202
|
+
- Clicks
|
|
203
|
+
- HTTP
|
|
204
|
+
- UI state
|
|
205
|
+
- Databases
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 9. What This Engine Guarantees
|
|
210
|
+
|
|
211
|
+
✔ No double booking
|
|
212
|
+
✔ No permanent locks
|
|
213
|
+
✔ No partial group bookings
|
|
214
|
+
✔ Strong ownership enforcement
|
|
215
|
+
✔ Deterministic outcomes
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 10. What This Engine Intentionally Does NOT Do
|
|
220
|
+
|
|
221
|
+
- No UI rendering
|
|
222
|
+
- No HTTP / REST handling
|
|
223
|
+
- No persistence
|
|
224
|
+
- No background threads
|
|
225
|
+
|
|
226
|
+
These are integration concerns and belong outside the engine.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 11. Intended Usage
|
|
231
|
+
|
|
232
|
+
This engine is designed to be:
|
|
233
|
+
- Packaged as a reusable library
|
|
234
|
+
- Called from event-driven systems
|
|
235
|
+
- Used under web, desktop, or CLI interfaces
|
|
236
|
+
|
|
237
|
+
The engine remains unchanged while integrations evolve.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 12. Final Note
|
|
242
|
+
|
|
243
|
+
This is a **domain-correct seat allocation engine**.
|
|
244
|
+
|
|
245
|
+
All future work (UI, APIs, persistence, scaling) should be built **on top of this logic**, not mixed into it.
|
|
246
|
+
|
|
247
|
+
This separation is intentional and fundamental.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
# Public API Reference
|
|
254
|
+
|
|
255
|
+
This section lists **all public methods exposed by the SeatLock engine**, with a one-line explanation of what each does. These are the only methods consumers should rely on.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Importing the Engine
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from seatlock import Seat, SeatManager
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Core Classes
|
|
268
|
+
|
|
269
|
+
### `Seat`
|
|
270
|
+
Represents a single seat as an independent resource.
|
|
271
|
+
|
|
272
|
+
- `Seat(seat_id)` → Create a new seat with a unique identifier
|
|
273
|
+
- `is_available()` → Returns `True` if the seat is free
|
|
274
|
+
- `is_locked()` → Returns `True` if the seat is temporarily reserved
|
|
275
|
+
- `is_booked()` → Returns `True` if the seat is permanently booked
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### `SeatManager`
|
|
280
|
+
Central engine that enforces all locking, booking, cancellation, and cleanup rules.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Locking Methods
|
|
285
|
+
|
|
286
|
+
- `lock_seat(seat_id, user_id)`
|
|
287
|
+
Locks a single available seat for a user
|
|
288
|
+
|
|
289
|
+
- `lock_seats_bulk(seat_ids, user_id)`
|
|
290
|
+
Atomically locks multiple seats for a user (all-or-nothing)
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Booking Methods
|
|
295
|
+
|
|
296
|
+
- `book_a_seat(seat_id, user_id)`
|
|
297
|
+
Permanently books a single seat previously locked by the same user
|
|
298
|
+
|
|
299
|
+
- `book_seats_bulk(seat_ids, user_id)`
|
|
300
|
+
Atomically books multiple seats previously locked by the same user
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Cancellation Methods
|
|
305
|
+
|
|
306
|
+
- `cancel_lock(seat_id, user_id)`
|
|
307
|
+
Releases a lock held by the user on a single seat
|
|
308
|
+
|
|
309
|
+
- `cancel_locks_bulk(seat_ids, user_id)`
|
|
310
|
+
Atomically releases locks held by the user on multiple seats
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Cleanup / System Methods
|
|
315
|
+
|
|
316
|
+
- `cleanup_expired_locks()`
|
|
317
|
+
System-level method that releases all locks exceeding the configured timeout
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Notes on Usage
|
|
322
|
+
|
|
323
|
+
- All methods are **thread-safe**
|
|
324
|
+
- All bulk operations are **atomic**
|
|
325
|
+
- Only the lock owner may book or cancel seats
|
|
326
|
+
- Booked seats are **final and immutable**
|
|
327
|
+
- Time-based lock expiry is handled **only** by `cleanup_expired_locks`
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
This API is intentionally minimal and stable. All UI, event handling, persistence, and networking should be built **on top of these methods**, not mixe
|
seatlock-0.1.0/README.md
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
# Seat Allocation Engine – Working Cycle
|
|
2
|
+
|
|
3
|
+
This document explains **how the Seat Allocation Engine works internally**, step by step. It focuses on the **lifecycle of a seat**, **locking rules**, **bulk operations**, and **cleanup mechanics**. No UI, API, or framework concepts are involved here—this is purely the domain engine.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Core Purpose
|
|
8
|
+
|
|
9
|
+
The engine is designed to solve one problem **correctly**:
|
|
10
|
+
|
|
11
|
+
> Allocate seats to users in a safe, fair, and deterministic way.
|
|
12
|
+
|
|
13
|
+
It guarantees:
|
|
14
|
+
- No double booking
|
|
15
|
+
- No permanent locks
|
|
16
|
+
- No partial group bookings
|
|
17
|
+
- No lock stealing
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. Core Concepts
|
|
22
|
+
|
|
23
|
+
### 2.1 Seat as a Resource
|
|
24
|
+
Each seat is treated as an **independent resource**.
|
|
25
|
+
|
|
26
|
+
A seat has exactly one of three states:
|
|
27
|
+
- `AVAILABLE`
|
|
28
|
+
- `LOCKED`
|
|
29
|
+
- `BOOKED`
|
|
30
|
+
|
|
31
|
+
Once a seat is `BOOKED`, it is **terminal** and cannot change state.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
### 2.2 Seat Ownership
|
|
36
|
+
|
|
37
|
+
When a seat is locked:
|
|
38
|
+
- It is owned by exactly **one user**
|
|
39
|
+
- Only that user can book it
|
|
40
|
+
- Other users are rejected
|
|
41
|
+
|
|
42
|
+
Ownership is enforced strictly at all times.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 3. Seat Lifecycle
|
|
47
|
+
|
|
48
|
+
The lifecycle of a seat follows this strict state machine:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
AVAILABLE → LOCKED → BOOKED
|
|
52
|
+
↑ |
|
|
53
|
+
└────────┘ (lock expiry)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Invalid transitions are never allowed.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 4. Locking Mechanism
|
|
61
|
+
|
|
62
|
+
### 4.1 Single Seat Locking
|
|
63
|
+
|
|
64
|
+
When a user requests to lock a seat:
|
|
65
|
+
|
|
66
|
+
1. The engine checks if the seat exists
|
|
67
|
+
2. If the seat is `BOOKED` → reject
|
|
68
|
+
3. If the seat is `LOCKED` → reject
|
|
69
|
+
4. If the seat is `AVAILABLE` → lock it
|
|
70
|
+
|
|
71
|
+
A successful lock records:
|
|
72
|
+
- `locked_by` (user id)
|
|
73
|
+
- `lock_time` (timestamp)
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
### 4.2 Bulk Seat Locking (Group Selection)
|
|
78
|
+
|
|
79
|
+
Bulk locking is **atomic**.
|
|
80
|
+
|
|
81
|
+
This means:
|
|
82
|
+
> Either all seats are locked, or none are.
|
|
83
|
+
|
|
84
|
+
#### Working cycle:
|
|
85
|
+
|
|
86
|
+
**Phase 1 – Validation (read-only)**
|
|
87
|
+
- All seats must exist
|
|
88
|
+
- All seats must be `AVAILABLE`
|
|
89
|
+
- If any seat fails → abort immediately
|
|
90
|
+
|
|
91
|
+
**Phase 2 – Commit (write)**
|
|
92
|
+
- All seats are locked together
|
|
93
|
+
- Same user
|
|
94
|
+
- Same lock timestamp
|
|
95
|
+
|
|
96
|
+
No partial locking is ever possible.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 5. Booking Mechanism
|
|
101
|
+
|
|
102
|
+
### 5.1 Single Seat Booking
|
|
103
|
+
|
|
104
|
+
To book a seat:
|
|
105
|
+
|
|
106
|
+
1. Seat must exist
|
|
107
|
+
2. Seat must be `LOCKED`
|
|
108
|
+
3. Seat must be locked by the same user
|
|
109
|
+
|
|
110
|
+
If all checks pass:
|
|
111
|
+
- Seat transitions to `BOOKED`
|
|
112
|
+
- Lock metadata is cleared
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### 5.2 Bulk Seat Booking
|
|
117
|
+
|
|
118
|
+
Bulk booking follows the same atomic principle as bulk locking.
|
|
119
|
+
|
|
120
|
+
**Validation phase:**
|
|
121
|
+
- All seats must exist
|
|
122
|
+
- All seats must be `LOCKED`
|
|
123
|
+
- All seats must be locked by the same user
|
|
124
|
+
|
|
125
|
+
**Commit phase:**
|
|
126
|
+
- All seats transition to `BOOKED`
|
|
127
|
+
|
|
128
|
+
If any seat fails validation → **no seat is booked**.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## 6. Lock Expiry (Auto Release)
|
|
133
|
+
|
|
134
|
+
Locks are **temporary by design**.
|
|
135
|
+
|
|
136
|
+
### 6.1 Timeout Rule
|
|
137
|
+
|
|
138
|
+
- Each lock has a maximum lifetime (`LOCK_TIMEOUT`)
|
|
139
|
+
- Default: 10 seconds
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### 6.2 Cleanup Mechanism
|
|
144
|
+
|
|
145
|
+
Lock expiry is handled by a **system-level cleanup function**:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
cleanup_expired_locks()
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This function:
|
|
152
|
+
- Iterates over all seats
|
|
153
|
+
- Releases locks that exceeded the timeout
|
|
154
|
+
- Never depends on user actions
|
|
155
|
+
|
|
156
|
+
Important rule:
|
|
157
|
+
|
|
158
|
+
> A lock may expire, but it is never stolen by another user.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 7. Concurrency Safety
|
|
163
|
+
|
|
164
|
+
All operations run inside a **global lock**.
|
|
165
|
+
|
|
166
|
+
This guarantees:
|
|
167
|
+
- Atomic operations
|
|
168
|
+
- No race conditions
|
|
169
|
+
- Deterministic behavior
|
|
170
|
+
|
|
171
|
+
The engine prioritizes **correctness over performance**.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 8. Separation of Responsibilities
|
|
176
|
+
|
|
177
|
+
| Layer | Responsibility |
|
|
178
|
+
|-----|--------------|
|
|
179
|
+
| Seat | State + truth checks |
|
|
180
|
+
| SeatManager | Transitions + rules |
|
|
181
|
+
| Cleanup | Time-based expiry |
|
|
182
|
+
| UI / API | Input & presentation only |
|
|
183
|
+
|
|
184
|
+
The engine does **not** know about:
|
|
185
|
+
- Clicks
|
|
186
|
+
- HTTP
|
|
187
|
+
- UI state
|
|
188
|
+
- Databases
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 9. What This Engine Guarantees
|
|
193
|
+
|
|
194
|
+
✔ No double booking
|
|
195
|
+
✔ No permanent locks
|
|
196
|
+
✔ No partial group bookings
|
|
197
|
+
✔ Strong ownership enforcement
|
|
198
|
+
✔ Deterministic outcomes
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 10. What This Engine Intentionally Does NOT Do
|
|
203
|
+
|
|
204
|
+
- No UI rendering
|
|
205
|
+
- No HTTP / REST handling
|
|
206
|
+
- No persistence
|
|
207
|
+
- No background threads
|
|
208
|
+
|
|
209
|
+
These are integration concerns and belong outside the engine.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 11. Intended Usage
|
|
214
|
+
|
|
215
|
+
This engine is designed to be:
|
|
216
|
+
- Packaged as a reusable library
|
|
217
|
+
- Called from event-driven systems
|
|
218
|
+
- Used under web, desktop, or CLI interfaces
|
|
219
|
+
|
|
220
|
+
The engine remains unchanged while integrations evolve.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 12. Final Note
|
|
225
|
+
|
|
226
|
+
This is a **domain-correct seat allocation engine**.
|
|
227
|
+
|
|
228
|
+
All future work (UI, APIs, persistence, scaling) should be built **on top of this logic**, not mixed into it.
|
|
229
|
+
|
|
230
|
+
This separation is intentional and fundamental.
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
# Public API Reference
|
|
237
|
+
|
|
238
|
+
This section lists **all public methods exposed by the SeatLock engine**, with a one-line explanation of what each does. These are the only methods consumers should rely on.
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Importing the Engine
|
|
243
|
+
|
|
244
|
+
```python
|
|
245
|
+
from seatlock import Seat, SeatManager
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Core Classes
|
|
251
|
+
|
|
252
|
+
### `Seat`
|
|
253
|
+
Represents a single seat as an independent resource.
|
|
254
|
+
|
|
255
|
+
- `Seat(seat_id)` → Create a new seat with a unique identifier
|
|
256
|
+
- `is_available()` → Returns `True` if the seat is free
|
|
257
|
+
- `is_locked()` → Returns `True` if the seat is temporarily reserved
|
|
258
|
+
- `is_booked()` → Returns `True` if the seat is permanently booked
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
### `SeatManager`
|
|
263
|
+
Central engine that enforces all locking, booking, cancellation, and cleanup rules.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Locking Methods
|
|
268
|
+
|
|
269
|
+
- `lock_seat(seat_id, user_id)`
|
|
270
|
+
Locks a single available seat for a user
|
|
271
|
+
|
|
272
|
+
- `lock_seats_bulk(seat_ids, user_id)`
|
|
273
|
+
Atomically locks multiple seats for a user (all-or-nothing)
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
## Booking Methods
|
|
278
|
+
|
|
279
|
+
- `book_a_seat(seat_id, user_id)`
|
|
280
|
+
Permanently books a single seat previously locked by the same user
|
|
281
|
+
|
|
282
|
+
- `book_seats_bulk(seat_ids, user_id)`
|
|
283
|
+
Atomically books multiple seats previously locked by the same user
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Cancellation Methods
|
|
288
|
+
|
|
289
|
+
- `cancel_lock(seat_id, user_id)`
|
|
290
|
+
Releases a lock held by the user on a single seat
|
|
291
|
+
|
|
292
|
+
- `cancel_locks_bulk(seat_ids, user_id)`
|
|
293
|
+
Atomically releases locks held by the user on multiple seats
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## Cleanup / System Methods
|
|
298
|
+
|
|
299
|
+
- `cleanup_expired_locks()`
|
|
300
|
+
System-level method that releases all locks exceeding the configured timeout
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Notes on Usage
|
|
305
|
+
|
|
306
|
+
- All methods are **thread-safe**
|
|
307
|
+
- All bulk operations are **atomic**
|
|
308
|
+
- Only the lock owner may book or cancel seats
|
|
309
|
+
- Booked seats are **final and immutable**
|
|
310
|
+
- Time-based lock expiry is handled **only** by `cleanup_expired_locks`
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
This API is intentionally minimal and stable. All UI, event handling, persistence, and networking should be built **on top of these methods**, not mixe
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "seatlock"
|
|
8
|
+
version = "0.1.0"
|
|
9
|
+
description = "A concurrency-safe seat allocation and booking engine"
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Aditya" }
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
license = { text = "MIT" }
|
|
18
|
+
|
|
19
|
+
classifiers = [
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Topic :: Software Development :: Libraries"
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dependencies = []
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
packages = ["seatlock"]
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import threading, time
|
|
2
|
+
from .states import AVAILABLE, BOOKED, LOCKED
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SeatManager:
|
|
6
|
+
LOCK_TIMEOUT = 10 # seconds
|
|
7
|
+
|
|
8
|
+
def __init__(self, seats):
|
|
9
|
+
self.seats = {seat.seat_id: seat for seat in seats}
|
|
10
|
+
self.global_lock = threading.Lock()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def cleanup_expired_locks(self):
|
|
14
|
+
with self.global_lock:
|
|
15
|
+
now = time.time()
|
|
16
|
+
|
|
17
|
+
for seat in self.seats.values():
|
|
18
|
+
if seat.is_locked():
|
|
19
|
+
if now - seat.lock_time > self.LOCK_TIMEOUT:
|
|
20
|
+
self._release_lock(seat)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def lock_seat(self, seat_id, user_id):
|
|
24
|
+
with self.global_lock:
|
|
25
|
+
seat = self.seats.get(seat_id)
|
|
26
|
+
|
|
27
|
+
if not seat:
|
|
28
|
+
return f"Seat {seat_id} does not exist"
|
|
29
|
+
|
|
30
|
+
# Expire old lock if timeout passed
|
|
31
|
+
if seat.is_locked():
|
|
32
|
+
if time.time() - seat.lock_time > self.LOCK_TIMEOUT:
|
|
33
|
+
self._release_lock(seat)
|
|
34
|
+
|
|
35
|
+
if seat.is_booked():
|
|
36
|
+
return f"Seat {seat_id} is already booked"
|
|
37
|
+
|
|
38
|
+
if seat.is_locked():
|
|
39
|
+
return f"Seat {seat_id} is currently locked by {seat.locked_by}"
|
|
40
|
+
|
|
41
|
+
seat.status = LOCKED
|
|
42
|
+
seat.locked_by = user_id
|
|
43
|
+
seat.lock_time = time.time()
|
|
44
|
+
|
|
45
|
+
return f"Seat {seat_id} is locked by {user_id}"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def lock_seats_bulk(self, seat_ids, user_id):
|
|
49
|
+
with self.global_lock:
|
|
50
|
+
seats_to_lock = []
|
|
51
|
+
|
|
52
|
+
# Phase 1: validation (READ-ONLY)
|
|
53
|
+
for seat_id in seat_ids:
|
|
54
|
+
seat = self.seats.get(seat_id)
|
|
55
|
+
|
|
56
|
+
if not seat:
|
|
57
|
+
return f"Seat {seat_id} does not exist"
|
|
58
|
+
|
|
59
|
+
if seat.is_booked():
|
|
60
|
+
return f"Seat {seat_id} is already booked"
|
|
61
|
+
|
|
62
|
+
if seat.is_locked():
|
|
63
|
+
# If locked by someone else, reject
|
|
64
|
+
return f"Seat {seat_id} is locked by {seat.locked_by}"
|
|
65
|
+
|
|
66
|
+
seats_to_lock.append(seat)
|
|
67
|
+
|
|
68
|
+
# Phase 2: commit (WRITE)
|
|
69
|
+
lock_time = time.time()
|
|
70
|
+
for seat in seats_to_lock:
|
|
71
|
+
seat.status = LOCKED
|
|
72
|
+
seat.locked_by = user_id
|
|
73
|
+
seat.lock_time = lock_time
|
|
74
|
+
|
|
75
|
+
return f"Seats {seat_ids} locked by {user_id}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def book_a_seat(self, seat_id, user_id):
|
|
79
|
+
with self.global_lock:
|
|
80
|
+
seat = self.seats.get(seat_id)
|
|
81
|
+
|
|
82
|
+
if not seat:
|
|
83
|
+
return f"Seat {seat_id} does not exist"
|
|
84
|
+
|
|
85
|
+
if not seat.is_locked():
|
|
86
|
+
return f"Seat {seat_id} is not locked"
|
|
87
|
+
|
|
88
|
+
if seat.locked_by != user_id:
|
|
89
|
+
return f"Seat {seat_id} is locked by another user"
|
|
90
|
+
|
|
91
|
+
seat.status = BOOKED
|
|
92
|
+
seat.locked_by = None
|
|
93
|
+
seat.lock_time = None
|
|
94
|
+
|
|
95
|
+
return f"Seat {seat_id} is now booked by {user_id}"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def book_seats_bulk(self, seat_ids, user_id):
|
|
99
|
+
with self.global_lock:
|
|
100
|
+
seats_to_book = []
|
|
101
|
+
|
|
102
|
+
# Validation
|
|
103
|
+
for seat_id in seat_ids:
|
|
104
|
+
seat = self.seats.get(seat_id)
|
|
105
|
+
|
|
106
|
+
if not seat:
|
|
107
|
+
return f"Seat {seat_id} does not exist"
|
|
108
|
+
|
|
109
|
+
if not seat.is_locked():
|
|
110
|
+
return f"Seat {seat_id} is not locked"
|
|
111
|
+
|
|
112
|
+
if seat.locked_by != user_id:
|
|
113
|
+
return f"Seat {seat_id} is locked by another user"
|
|
114
|
+
|
|
115
|
+
seats_to_book.append(seat)
|
|
116
|
+
|
|
117
|
+
# Commit booking
|
|
118
|
+
for seat in seats_to_book:
|
|
119
|
+
seat.status = BOOKED
|
|
120
|
+
seat.locked_by = None
|
|
121
|
+
seat.lock_time = None
|
|
122
|
+
|
|
123
|
+
return f"Seats {seat_ids} successfully booked by {user_id}"
|
|
124
|
+
def cancel_lock(self, seat_id, user_id):
|
|
125
|
+
with self.global_lock:
|
|
126
|
+
seat = self.seats.get(seat_id)
|
|
127
|
+
|
|
128
|
+
if not seat:
|
|
129
|
+
return f"Seat {seat_id} does not exist"
|
|
130
|
+
|
|
131
|
+
if seat.is_booked():
|
|
132
|
+
return f"Seat {seat_id} is already booked and cannot be cancelled"
|
|
133
|
+
|
|
134
|
+
if not seat.is_locked():
|
|
135
|
+
return f"Seat {seat_id} is not locked"
|
|
136
|
+
|
|
137
|
+
if seat.locked_by != user_id:
|
|
138
|
+
return f"Seat {seat_id} is locked by another user"
|
|
139
|
+
|
|
140
|
+
self._release_lock(seat)
|
|
141
|
+
return f"Seat {seat_id} lock cancelled by {user_id}"
|
|
142
|
+
|
|
143
|
+
def cancel_locks_bulk(self, seat_ids, user_id):
|
|
144
|
+
with self.global_lock:
|
|
145
|
+
seats_to_cancel = []
|
|
146
|
+
|
|
147
|
+
# Validation phase
|
|
148
|
+
for seat_id in seat_ids:
|
|
149
|
+
seat = self.seats.get(seat_id)
|
|
150
|
+
|
|
151
|
+
if not seat:
|
|
152
|
+
return f"Seat {seat_id} does not exist"
|
|
153
|
+
|
|
154
|
+
if seat.is_booked():
|
|
155
|
+
return f"Seat {seat_id} is already booked and cannot be cancelled"
|
|
156
|
+
|
|
157
|
+
if not seat.is_locked():
|
|
158
|
+
return f"Seat {seat_id} is not locked"
|
|
159
|
+
|
|
160
|
+
if seat.locked_by != user_id:
|
|
161
|
+
return f"Seat {seat_id} is locked by another user"
|
|
162
|
+
|
|
163
|
+
seats_to_cancel.append(seat)
|
|
164
|
+
|
|
165
|
+
# Commit phase
|
|
166
|
+
for seat in seats_to_cancel:
|
|
167
|
+
self._release_lock(seat)
|
|
168
|
+
|
|
169
|
+
return f"Locks for seats {seat_ids} cancelled by {user_id}"
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _release_lock(self, seat):
|
|
175
|
+
seat.status = AVAILABLE
|
|
176
|
+
seat.locked_by = None
|
|
177
|
+
seat.lock_time = None
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
from .states import AVAILABLE, LOCKED, BOOKED
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Seat:
|
|
6
|
+
def __init__(self, seat_id):
|
|
7
|
+
self.seat_id = seat_id
|
|
8
|
+
self.status = AVAILABLE
|
|
9
|
+
self.locked_by = None
|
|
10
|
+
self.lock_time = None
|
|
11
|
+
|
|
12
|
+
def is_locked(self):
|
|
13
|
+
return self.status == LOCKED
|
|
14
|
+
|
|
15
|
+
def is_available(self):
|
|
16
|
+
return self.status == AVAILABLE
|
|
17
|
+
|
|
18
|
+
def is_booked(self):
|
|
19
|
+
return self.status == BOOKED
|
|
20
|
+
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: seatlock
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A concurrency-safe seat allocation and booking engine
|
|
5
|
+
Author: Aditya
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: License
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# Seat Allocation Engine – Working Cycle
|
|
19
|
+
|
|
20
|
+
This document explains **how the Seat Allocation Engine works internally**, step by step. It focuses on the **lifecycle of a seat**, **locking rules**, **bulk operations**, and **cleanup mechanics**. No UI, API, or framework concepts are involved here—this is purely the domain engine.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Core Purpose
|
|
25
|
+
|
|
26
|
+
The engine is designed to solve one problem **correctly**:
|
|
27
|
+
|
|
28
|
+
> Allocate seats to users in a safe, fair, and deterministic way.
|
|
29
|
+
|
|
30
|
+
It guarantees:
|
|
31
|
+
- No double booking
|
|
32
|
+
- No permanent locks
|
|
33
|
+
- No partial group bookings
|
|
34
|
+
- No lock stealing
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 2. Core Concepts
|
|
39
|
+
|
|
40
|
+
### 2.1 Seat as a Resource
|
|
41
|
+
Each seat is treated as an **independent resource**.
|
|
42
|
+
|
|
43
|
+
A seat has exactly one of three states:
|
|
44
|
+
- `AVAILABLE`
|
|
45
|
+
- `LOCKED`
|
|
46
|
+
- `BOOKED`
|
|
47
|
+
|
|
48
|
+
Once a seat is `BOOKED`, it is **terminal** and cannot change state.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
### 2.2 Seat Ownership
|
|
53
|
+
|
|
54
|
+
When a seat is locked:
|
|
55
|
+
- It is owned by exactly **one user**
|
|
56
|
+
- Only that user can book it
|
|
57
|
+
- Other users are rejected
|
|
58
|
+
|
|
59
|
+
Ownership is enforced strictly at all times.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Seat Lifecycle
|
|
64
|
+
|
|
65
|
+
The lifecycle of a seat follows this strict state machine:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
AVAILABLE → LOCKED → BOOKED
|
|
69
|
+
↑ |
|
|
70
|
+
└────────┘ (lock expiry)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Invalid transitions are never allowed.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 4. Locking Mechanism
|
|
78
|
+
|
|
79
|
+
### 4.1 Single Seat Locking
|
|
80
|
+
|
|
81
|
+
When a user requests to lock a seat:
|
|
82
|
+
|
|
83
|
+
1. The engine checks if the seat exists
|
|
84
|
+
2. If the seat is `BOOKED` → reject
|
|
85
|
+
3. If the seat is `LOCKED` → reject
|
|
86
|
+
4. If the seat is `AVAILABLE` → lock it
|
|
87
|
+
|
|
88
|
+
A successful lock records:
|
|
89
|
+
- `locked_by` (user id)
|
|
90
|
+
- `lock_time` (timestamp)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### 4.2 Bulk Seat Locking (Group Selection)
|
|
95
|
+
|
|
96
|
+
Bulk locking is **atomic**.
|
|
97
|
+
|
|
98
|
+
This means:
|
|
99
|
+
> Either all seats are locked, or none are.
|
|
100
|
+
|
|
101
|
+
#### Working cycle:
|
|
102
|
+
|
|
103
|
+
**Phase 1 – Validation (read-only)**
|
|
104
|
+
- All seats must exist
|
|
105
|
+
- All seats must be `AVAILABLE`
|
|
106
|
+
- If any seat fails → abort immediately
|
|
107
|
+
|
|
108
|
+
**Phase 2 – Commit (write)**
|
|
109
|
+
- All seats are locked together
|
|
110
|
+
- Same user
|
|
111
|
+
- Same lock timestamp
|
|
112
|
+
|
|
113
|
+
No partial locking is ever possible.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## 5. Booking Mechanism
|
|
118
|
+
|
|
119
|
+
### 5.1 Single Seat Booking
|
|
120
|
+
|
|
121
|
+
To book a seat:
|
|
122
|
+
|
|
123
|
+
1. Seat must exist
|
|
124
|
+
2. Seat must be `LOCKED`
|
|
125
|
+
3. Seat must be locked by the same user
|
|
126
|
+
|
|
127
|
+
If all checks pass:
|
|
128
|
+
- Seat transitions to `BOOKED`
|
|
129
|
+
- Lock metadata is cleared
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
### 5.2 Bulk Seat Booking
|
|
134
|
+
|
|
135
|
+
Bulk booking follows the same atomic principle as bulk locking.
|
|
136
|
+
|
|
137
|
+
**Validation phase:**
|
|
138
|
+
- All seats must exist
|
|
139
|
+
- All seats must be `LOCKED`
|
|
140
|
+
- All seats must be locked by the same user
|
|
141
|
+
|
|
142
|
+
**Commit phase:**
|
|
143
|
+
- All seats transition to `BOOKED`
|
|
144
|
+
|
|
145
|
+
If any seat fails validation → **no seat is booked**.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 6. Lock Expiry (Auto Release)
|
|
150
|
+
|
|
151
|
+
Locks are **temporary by design**.
|
|
152
|
+
|
|
153
|
+
### 6.1 Timeout Rule
|
|
154
|
+
|
|
155
|
+
- Each lock has a maximum lifetime (`LOCK_TIMEOUT`)
|
|
156
|
+
- Default: 10 seconds
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### 6.2 Cleanup Mechanism
|
|
161
|
+
|
|
162
|
+
Lock expiry is handled by a **system-level cleanup function**:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
cleanup_expired_locks()
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
This function:
|
|
169
|
+
- Iterates over all seats
|
|
170
|
+
- Releases locks that exceeded the timeout
|
|
171
|
+
- Never depends on user actions
|
|
172
|
+
|
|
173
|
+
Important rule:
|
|
174
|
+
|
|
175
|
+
> A lock may expire, but it is never stolen by another user.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 7. Concurrency Safety
|
|
180
|
+
|
|
181
|
+
All operations run inside a **global lock**.
|
|
182
|
+
|
|
183
|
+
This guarantees:
|
|
184
|
+
- Atomic operations
|
|
185
|
+
- No race conditions
|
|
186
|
+
- Deterministic behavior
|
|
187
|
+
|
|
188
|
+
The engine prioritizes **correctness over performance**.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 8. Separation of Responsibilities
|
|
193
|
+
|
|
194
|
+
| Layer | Responsibility |
|
|
195
|
+
|-----|--------------|
|
|
196
|
+
| Seat | State + truth checks |
|
|
197
|
+
| SeatManager | Transitions + rules |
|
|
198
|
+
| Cleanup | Time-based expiry |
|
|
199
|
+
| UI / API | Input & presentation only |
|
|
200
|
+
|
|
201
|
+
The engine does **not** know about:
|
|
202
|
+
- Clicks
|
|
203
|
+
- HTTP
|
|
204
|
+
- UI state
|
|
205
|
+
- Databases
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 9. What This Engine Guarantees
|
|
210
|
+
|
|
211
|
+
✔ No double booking
|
|
212
|
+
✔ No permanent locks
|
|
213
|
+
✔ No partial group bookings
|
|
214
|
+
✔ Strong ownership enforcement
|
|
215
|
+
✔ Deterministic outcomes
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 10. What This Engine Intentionally Does NOT Do
|
|
220
|
+
|
|
221
|
+
- No UI rendering
|
|
222
|
+
- No HTTP / REST handling
|
|
223
|
+
- No persistence
|
|
224
|
+
- No background threads
|
|
225
|
+
|
|
226
|
+
These are integration concerns and belong outside the engine.
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 11. Intended Usage
|
|
231
|
+
|
|
232
|
+
This engine is designed to be:
|
|
233
|
+
- Packaged as a reusable library
|
|
234
|
+
- Called from event-driven systems
|
|
235
|
+
- Used under web, desktop, or CLI interfaces
|
|
236
|
+
|
|
237
|
+
The engine remains unchanged while integrations evolve.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 12. Final Note
|
|
242
|
+
|
|
243
|
+
This is a **domain-correct seat allocation engine**.
|
|
244
|
+
|
|
245
|
+
All future work (UI, APIs, persistence, scaling) should be built **on top of this logic**, not mixed into it.
|
|
246
|
+
|
|
247
|
+
This separation is intentional and fundamental.
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
# Public API Reference
|
|
254
|
+
|
|
255
|
+
This section lists **all public methods exposed by the SeatLock engine**, with a one-line explanation of what each does. These are the only methods consumers should rely on.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## Importing the Engine
|
|
260
|
+
|
|
261
|
+
```python
|
|
262
|
+
from seatlock import Seat, SeatManager
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Core Classes
|
|
268
|
+
|
|
269
|
+
### `Seat`
|
|
270
|
+
Represents a single seat as an independent resource.
|
|
271
|
+
|
|
272
|
+
- `Seat(seat_id)` → Create a new seat with a unique identifier
|
|
273
|
+
- `is_available()` → Returns `True` if the seat is free
|
|
274
|
+
- `is_locked()` → Returns `True` if the seat is temporarily reserved
|
|
275
|
+
- `is_booked()` → Returns `True` if the seat is permanently booked
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### `SeatManager`
|
|
280
|
+
Central engine that enforces all locking, booking, cancellation, and cleanup rules.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Locking Methods
|
|
285
|
+
|
|
286
|
+
- `lock_seat(seat_id, user_id)`
|
|
287
|
+
Locks a single available seat for a user
|
|
288
|
+
|
|
289
|
+
- `lock_seats_bulk(seat_ids, user_id)`
|
|
290
|
+
Atomically locks multiple seats for a user (all-or-nothing)
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Booking Methods
|
|
295
|
+
|
|
296
|
+
- `book_a_seat(seat_id, user_id)`
|
|
297
|
+
Permanently books a single seat previously locked by the same user
|
|
298
|
+
|
|
299
|
+
- `book_seats_bulk(seat_ids, user_id)`
|
|
300
|
+
Atomically books multiple seats previously locked by the same user
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Cancellation Methods
|
|
305
|
+
|
|
306
|
+
- `cancel_lock(seat_id, user_id)`
|
|
307
|
+
Releases a lock held by the user on a single seat
|
|
308
|
+
|
|
309
|
+
- `cancel_locks_bulk(seat_ids, user_id)`
|
|
310
|
+
Atomically releases locks held by the user on multiple seats
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Cleanup / System Methods
|
|
315
|
+
|
|
316
|
+
- `cleanup_expired_locks()`
|
|
317
|
+
System-level method that releases all locks exceeding the configured timeout
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Notes on Usage
|
|
322
|
+
|
|
323
|
+
- All methods are **thread-safe**
|
|
324
|
+
- All bulk operations are **atomic**
|
|
325
|
+
- Only the lock owner may book or cancel seats
|
|
326
|
+
- Booked seats are **final and immutable**
|
|
327
|
+
- Time-based lock expiry is handled **only** by `cleanup_expired_locks`
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
This API is intentionally minimal and stable. All UI, event handling, persistence, and networking should be built **on top of these methods**, not mixe
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
License
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
seatlock/__init__.py
|
|
5
|
+
seatlock/manager.py
|
|
6
|
+
seatlock/seat.py
|
|
7
|
+
seatlock/states.py
|
|
8
|
+
seatlock.egg-info/PKG-INFO
|
|
9
|
+
seatlock.egg-info/SOURCES.txt
|
|
10
|
+
seatlock.egg-info/dependency_links.txt
|
|
11
|
+
seatlock.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
seatlock
|
seatlock-0.1.0/setup.cfg
ADDED