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 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.
@@ -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,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,2 @@
1
+ from .seat import Seat
2
+ from .manager import SeatManager
@@ -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,3 @@
1
+ AVAILABLE = "AVAILABLE"
2
+ LOCKED = "LOCKED"
3
+ BOOKED = "BOOKED"
@@ -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
+ seatlock
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+