rwlocker 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.
- rwlocker-1.0/LICENSE +21 -0
- rwlocker-1.0/MANIFEST.in +2 -0
- rwlocker-1.0/PKG-INFO +365 -0
- rwlocker-1.0/README-pypi.md +336 -0
- rwlocker-1.0/pyproject.toml +52 -0
- rwlocker-1.0/rwlocker/__init__.py +52 -0
- rwlocker-1.0/rwlocker/async_rwlock.py +560 -0
- rwlocker-1.0/rwlocker/thread_rwlock.py +574 -0
- rwlocker-1.0/rwlocker.egg-info/PKG-INFO +365 -0
- rwlocker-1.0/rwlocker.egg-info/SOURCES.txt +11 -0
- rwlocker-1.0/rwlocker.egg-info/dependency_links.txt +1 -0
- rwlocker-1.0/rwlocker.egg-info/top_level.txt +1 -0
- rwlocker-1.0/setup.cfg +4 -0
rwlocker-1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 TahsinCr - tahsincrs@gmail.com
|
|
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.
|
rwlocker-1.0/MANIFEST.in
ADDED
rwlocker-1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rwlocker
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Advanced, High-Performance, and State-Machine Based Synchronous/Asynchronous Read-Write Locks.
|
|
5
|
+
Author-email: TahsinCr <TahsinCrs@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/TahsinCr/python-rwlocker
|
|
8
|
+
Project-URL: Repository, https://github.com/TahsinCr/python-rwlocker
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/TahsinCr/python-rwlocker/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/TahsinCr/python-rwlocker/blob/master/CHANGELOG.md
|
|
11
|
+
Keywords: rwlock,read-write-lock,thread,concurrency,lock,thread-safe,async,thundering-herd
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
[![Contributors][contributors-shield]][contributors-url]
|
|
31
|
+
[![Forks][forks-shield]][forks-url]
|
|
32
|
+
[![Stargazers][stars-shield]][stars-url]
|
|
33
|
+
[![Issues][issues-shield]][issues-url]
|
|
34
|
+
[![MIT License][license-shield]][license-url]
|
|
35
|
+
[![LinkedIn][linkedin-shield]][linkedin-url]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
<!-- About -->
|
|
40
|
+
<div align="center">
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
<h3 align="center">Python RWLocker</h3>
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
|
|
47
|
+
Advanced, High-Performance, and State-Machine Based Synchronous/Asynchronous Read-Write Locks.
|
|
48
|
+
|
|
49
|
+
[Changelog][changelog-url] ยท [Report Bug][issues-url] ยท [Request Feature][issues-url]
|
|
50
|
+
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<br/>
|
|
56
|
+
|
|
57
|
+
## ๐ About the Project
|
|
58
|
+
|
|
59
|
+
### ๐ Why RWLocker?
|
|
60
|
+
|
|
61
|
+
Standard locks in Python (`Lock`, `RLock`) are **Exclusive** locks. Even if 100 readers (e.g., threads fetching data from a database) arrive at the gate, they are forced to execute these operations sequentially, one by one.
|
|
62
|
+
|
|
63
|
+
`rwlocker`, on the other hand, is based on a **Shared** reading logic. While writer locks are exclusive, reader locks allow thousands of threads or tasks to access data simultaneously without blocking each other. It unleashes the true potential of the system, especially during I/O (Network/Disk/Database) operations where the GIL (Global Interpreter Lock) is released.
|
|
64
|
+
|
|
65
|
+
### โจ Key Features
|
|
66
|
+
|
|
67
|
+
* **Both Thread and Asyncio Support:** You can manage both standard OS threads (`rwlocker.thread_rwlock`) and event-loop based tasks (`rwlocker.async_rwlock`) using the exact same API logic.
|
|
68
|
+
* **Smart Proxy Architecture:** Intuitive usage of `with` and `async with` context managers via `.read` and `.write` proxies.
|
|
69
|
+
* **Atomic Downgrading:** The ability to instantly downgrade a Write lock to a Read lock (`downgrade()`) without completely releasing the lock, preventing other writers from slipping in.
|
|
70
|
+
* **Safe Reentrancy:** O(1) memory pointer tracking allowing the same thread or task to repeatedly acquire a write lock without causing a Deadlock.
|
|
71
|
+
* **Cancellation Safety:** Full resilience against task cancellations (`CancelledError`) in the `asyncio` environment. Cancelled tasks do not corrupt the system state and safely wake up waiting tasks.
|
|
72
|
+
|
|
73
|
+
### ๐ก๏ธ Lock Strategies
|
|
74
|
+
|
|
75
|
+
You can select the right lock strategy based on your system's bottleneck profile. Each strategy has a `SafeWriter` variant that allows for reentrancy.
|
|
76
|
+
|
|
77
|
+
| Strategy Type | Class Name (Thread / Async) | Description | When to Use? |
|
|
78
|
+
| --- | --- | --- | --- |
|
|
79
|
+
| **Writer-Preferring** | `RWLockWrite` / `AsyncRWLockWrite` | Forbids new readers from entering if there is a waiting writer. Prevents writer starvation. | To prevent writers from being overwhelmed in read-heavy systems. |
|
|
80
|
+
| **Reader-Preferring** | `RWLockRead` / `AsyncRWLockRead` | Continuously allows new readers in, even if writers are waiting. Provides maximum parallelism. | In cache structures where write operations are very rare or non-critical. |
|
|
81
|
+
| **Fair (FIFO)** | `RWLockFIFO` / `AsyncRWLockFIFO` | Grants access alternately between readers and writers (interleaving). Prevents starvation for both sides. | In high-frequency, bidirectional traffic (MAVLink, WebSockets, etc.). |
|
|
82
|
+
<br/>
|
|
83
|
+
|
|
84
|
+
## โ๏ธ Architectural Limitations
|
|
85
|
+
|
|
86
|
+
Engineering facts developers need to know when using this library:
|
|
87
|
+
|
|
88
|
+
1. **The CPU-Bound vs I/O-Bound Reality:**
|
|
89
|
+
`rwlocker` derives its power from the moments when Python's GIL (Global Interpreter Lock) is released (Network requests, Database queries, File I/O, etc.). If you are looking for a lock for purely heavy mathematical computations (CPU-Bound) that do not involve I/O yields like `time.sleep()`, you will not achieve true parallelism due to the GIL, and the C-based standard `threading.Lock` will be slightly faster. **RWLock's true battlefield is I/O operations.**
|
|
90
|
+
2. **Circular References:**
|
|
91
|
+
Lock classes establish a circular reference graph (Lock -> Proxy -> Lock) when creating smart proxy objects (`.read` and `.write`). This design is intentional. Memory cleanup (Garbage Collection) is safely handled by Python's Cyclic GC engine, not by `__del__`.
|
|
92
|
+
3. **Strict Nested Write Locks:**
|
|
93
|
+
In `SafeWriter` variants, only "Write" locks can be nested. If a writer wants to acquire a reader lock, it cannot do so implicitly; it must explicitly call the `.downgrade()` method. This is a strict architectural decision made to prevent deadlocks at the structural level.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
<br/>
|
|
97
|
+
|
|
98
|
+
## ๐ Performance and Benchmark Results
|
|
99
|
+
|
|
100
|
+
`rwlocker` unlocks the system's true potential during Network and Database I/O operations where Python's GIL (Global Interpreter Lock) is released. In aggressive scenario tests conducted with zero OS sleep interference, it overwhelmingly outperformed standard locks.
|
|
101
|
+
|
|
102
|
+
**Summary of Performance Outputs:**
|
|
103
|
+
|
|
104
|
+
* **๐ Read-Heavy Scenario (100 Readers, 2 Writers):**
|
|
105
|
+
While standard locks queue readers single-file and choke the system, `rwlocker` allows readers to access the data simultaneously. This achieves **~37x FASTER** speed and throughput in **Threading** and **~30x FASTER** in **Asyncio**.
|
|
106
|
+
* **โ๏ธ Balanced Scenario (50 Readers, 50 Writers):**
|
|
107
|
+
Thanks to the Fair (FIFO) state machine, read operations are squeezed in parallel between write queues. It increases performance by **2x** compared to standard locks without creating a system bottleneck.
|
|
108
|
+
* **๐ก๏ธ Write-Heavy Scenario (2 Readers, 100 Writers):**
|
|
109
|
+
Even though write operations inherently cannot be executed concurrently (in parallel), thanks to `rwlocker`'s zero-allocation smart proxy architecture, it runs **7-8% faster** than standard `C`-based locks. Even the O(1) cost "SafeWriter" (reentrancy) feature adds almost no overhead to performance.
|
|
110
|
+
|
|
111
|
+
*(Note: All lock classes have passed 135 different unit tests covering reentrancy, deadlock, timeout, and cancellation safety scenarios with 0 errors.)*
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
<br/>
|
|
115
|
+
|
|
116
|
+
## ๐ Getting Started
|
|
117
|
+
|
|
118
|
+
### ๐ ๏ธ Dependencies
|
|
119
|
+
|
|
120
|
+
* No external dependencies.
|
|
121
|
+
* Only Python Standard Library (`threading`, `asyncio`, `typing`).
|
|
122
|
+
* Fully compatible with Python 3.9+.
|
|
123
|
+
|
|
124
|
+
### ๐ฆ Installation
|
|
125
|
+
|
|
126
|
+
The library has zero external dependencies and works directly with Python's core libraries.
|
|
127
|
+
|
|
128
|
+
1. Clone the repository
|
|
129
|
+
```sh
|
|
130
|
+
git clone https://github.com/TahsinCr/python-rwlocker.git
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
2. Install via PIP
|
|
134
|
+
```sh
|
|
135
|
+
pip install rwlocker
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
<br/>
|
|
139
|
+
|
|
140
|
+
### ๐ป Usage Examples
|
|
141
|
+
|
|
142
|
+
#### 1. High-Concurrency In-Memory Cache (Read-Heavy)
|
|
143
|
+
|
|
144
|
+
Prevents readers from waiting for each other in a web server handling thousands of requests.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
import threading
|
|
148
|
+
import time
|
|
149
|
+
from typing import Any, Dict, Optional
|
|
150
|
+
from rwlocker.thread_rwlock import RWLockRead
|
|
151
|
+
|
|
152
|
+
class InMemoryCache:
|
|
153
|
+
def __init__(self):
|
|
154
|
+
self._lock = RWLockRead()
|
|
155
|
+
self._cache: Dict[str, Any] = {}
|
|
156
|
+
|
|
157
|
+
def get(self, key: str) -> Optional[Any]:
|
|
158
|
+
# Readers NEVER block each other, maximizing throughput!
|
|
159
|
+
with self._lock.read:
|
|
160
|
+
time.sleep(0.01) # Network or Serialization (I/O) simulation
|
|
161
|
+
return self._cache.get(key)
|
|
162
|
+
|
|
163
|
+
def set(self, key: str, value: Any) -> None:
|
|
164
|
+
# Acquires an exclusive write lock. Safely pauses new readers.
|
|
165
|
+
with self._lock.write:
|
|
166
|
+
self._cache[key] = value
|
|
167
|
+
|
|
168
|
+
# USAGE
|
|
169
|
+
cache = InMemoryCache()
|
|
170
|
+
cache.set("status", "ONLINE")
|
|
171
|
+
|
|
172
|
+
# These 50 threads can read simultaneously without waiting.
|
|
173
|
+
threads = [threading.Thread(target=cache.get, args=("status",)) for _ in range(50)]
|
|
174
|
+
for t in threads: t.start()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### 2. Atomic State Downgrading in Financial Ledgers
|
|
180
|
+
|
|
181
|
+
Perfect for updating data (Write) and immediately reading/auditing the same data (Read) without letting another writer slip in between.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
import uuid
|
|
185
|
+
from rwlocker.thread_rwlock import RWLockWriteSafeWriter
|
|
186
|
+
|
|
187
|
+
class TransactionLedger:
|
|
188
|
+
def __init__(self):
|
|
189
|
+
self._lock = RWLockWriteSafeWriter()
|
|
190
|
+
self._balance = 1000.0
|
|
191
|
+
|
|
192
|
+
def process_payment(self, amount: float):
|
|
193
|
+
self._lock.write.acquire()
|
|
194
|
+
try:
|
|
195
|
+
# PHASE 1: Exclusive Write (Update balance)
|
|
196
|
+
self._balance += amount
|
|
197
|
+
|
|
198
|
+
# ATOMIC DOWNGRADE: Write Lock is downgraded to Read Lock.
|
|
199
|
+
# Waiting readers are allowed in, but other WRITERS are strictly blocked.
|
|
200
|
+
self._lock.write.downgrade()
|
|
201
|
+
|
|
202
|
+
# PHASE 2: Shared Read (Broadcast to other services over network)
|
|
203
|
+
self._dispatch_audit_event(self._balance)
|
|
204
|
+
|
|
205
|
+
finally:
|
|
206
|
+
# Since we downgraded, we must now release the READ lock.
|
|
207
|
+
self._lock.read.release()
|
|
208
|
+
|
|
209
|
+
def _dispatch_audit_event(self, balance: float):
|
|
210
|
+
print(f"Audit Report Dispatched. New Balance: {balance}")
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### 3. JWT Token Refresh (Thundering Herd Solution)
|
|
216
|
+
|
|
217
|
+
Prevents hundreds of tasks waking up simultaneously to refresh an expired token (Thundering Herd stampede) from crashing the auth server.
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
import asyncio
|
|
221
|
+
from rwlocker.async_rwlock import AsyncRWLockWrite
|
|
222
|
+
|
|
223
|
+
class AuthTokenManager:
|
|
224
|
+
def __init__(self):
|
|
225
|
+
self._lock = AsyncRWLockWrite()
|
|
226
|
+
self._token = "valid_token"
|
|
227
|
+
self._is_expired = False
|
|
228
|
+
|
|
229
|
+
async def get_valid_token(self) -> str:
|
|
230
|
+
# Fast Path: If the token is valid, 500 tasks pass through here concurrently without waiting.
|
|
231
|
+
async with self._lock.read:
|
|
232
|
+
if not self._is_expired:
|
|
233
|
+
return self._token
|
|
234
|
+
|
|
235
|
+
# Slow Path: Token expired. Acquire write lock.
|
|
236
|
+
async with self._lock.write:
|
|
237
|
+
# Double-checked locking: While we were waiting for the lock,
|
|
238
|
+
# another task might have entered and refreshed the token.
|
|
239
|
+
if self._is_expired:
|
|
240
|
+
print("Refreshing token...")
|
|
241
|
+
await asyncio.sleep(0.5) # API Request
|
|
242
|
+
self._token = "new_valid_token"
|
|
243
|
+
self._is_expired = False
|
|
244
|
+
|
|
245
|
+
return self._token
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### 4. High-Frequency Telemetry (Fair FIFO Distribution)
|
|
251
|
+
|
|
252
|
+
Data arrives from a sensor 100 times per second (Write), and 200 WebSockets read this data (Read). The FIFO architecture prevents both sides from starving.
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
import asyncio
|
|
256
|
+
from typing import Dict
|
|
257
|
+
from rwlocker.async_rwlock import AsyncRWLockFIFO
|
|
258
|
+
|
|
259
|
+
class TelemetryDispatcher:
|
|
260
|
+
def __init__(self):
|
|
261
|
+
# FIFO (Fair Lock) prevents read and write intensities from choking each other.
|
|
262
|
+
self._lock = AsyncRWLockFIFO()
|
|
263
|
+
self._state = {"alt": 0.0, "lat": 0.0, "lon": 0.0}
|
|
264
|
+
|
|
265
|
+
async def ingest_sensor_data(self, new_data: Dict[str, float]):
|
|
266
|
+
"""Writes incoming data from high-frequency UDP stream."""
|
|
267
|
+
async with self._lock.write:
|
|
268
|
+
self._state.update(new_data)
|
|
269
|
+
await asyncio.sleep(0.001)
|
|
270
|
+
|
|
271
|
+
async def broadcast_to_clients(self):
|
|
272
|
+
"""Reads data concurrently for dozens of websocket clients."""
|
|
273
|
+
async with self._lock.read:
|
|
274
|
+
# Safely copy the state quickly to minimize lock holding time
|
|
275
|
+
current_state = self._state.copy()
|
|
276
|
+
|
|
277
|
+
# Perform slow network I/O operations while the lock is released
|
|
278
|
+
await self._network_send(current_state)
|
|
279
|
+
|
|
280
|
+
async def _network_send(self, data):
|
|
281
|
+
await asyncio.sleep(0.05) # Network latency simulation
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
*For more examples, please check the [examples][examples-url] directory.*
|
|
287
|
+
|
|
288
|
+
See the [open issues][issues-url] for a full list of proposed features (and known issues).
|
|
289
|
+
|
|
290
|
+
<br/>
|
|
291
|
+
|
|
292
|
+
## ๐ Acknowledgments and License
|
|
293
|
+
|
|
294
|
+
This project is fully open-source under the **MIT License** ([License][license-url]).
|
|
295
|
+
|
|
296
|
+
Thanks to the entire Python open-source community for helping us face the deepest realities of the Python C-API during the development of testing and benchmark architectures.
|
|
297
|
+
|
|
298
|
+
* **PyPI:** [RWLocker on PyPI][pypi-project-url]
|
|
299
|
+
* **Source Code:** [Tahsincr/python-rwlocker][project-url]
|
|
300
|
+
|
|
301
|
+
If you find any bugs or want to make an architectural contribution, feel free to open an Issue or submit a Pull Request on GitHub!
|
|
302
|
+
|
|
303
|
+
<br/>
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
## ๐ซ Contact
|
|
307
|
+
|
|
308
|
+
X: [@TahsinCrs][x-url]
|
|
309
|
+
|
|
310
|
+
Linkedin: [@TahsinCr][linkedin-url]
|
|
311
|
+
|
|
312
|
+
Email: TahsinCrs@gmail.com
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
<!-- IMAGES URL -->
|
|
316
|
+
|
|
317
|
+
[contributors-shield]: https://img.shields.io/github/contributors/TahsinCr/python-rwlocker.svg?style=for-the-badge
|
|
318
|
+
|
|
319
|
+
[forks-shield]: https://img.shields.io/github/forks/TahsinCr/python-rwlocker.svg?style=for-the-badge
|
|
320
|
+
|
|
321
|
+
[stars-shield]: https://img.shields.io/github/stars/TahsinCr/python-rwlocker.svg?style=for-the-badge
|
|
322
|
+
|
|
323
|
+
[issues-shield]: https://img.shields.io/github/issues/TahsinCr/python-rwlocker.svg?style=for-the-badge
|
|
324
|
+
|
|
325
|
+
[license-shield]: https://img.shields.io/github/license/TahsinCr/python-rwlocker.svg?style=for-the-badge
|
|
326
|
+
|
|
327
|
+
[linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
<!-- Github Project URL -->
|
|
332
|
+
|
|
333
|
+
[project-url]: https://github.com/TahsinCr/python-rwlocker
|
|
334
|
+
|
|
335
|
+
[pypi-project-url]: https://pypi.org/project/rwlocker
|
|
336
|
+
|
|
337
|
+
[contributors-url]: https://github.com/TahsinCr/python-rwlocker/graphs/contributors
|
|
338
|
+
|
|
339
|
+
[stars-url]: https://github.com/TahsinCr/python-rwlocker/stargazers
|
|
340
|
+
|
|
341
|
+
[forks-url]: https://github.com/TahsinCr/python-rwlocker/network/members
|
|
342
|
+
|
|
343
|
+
[issues-url]: https://github.com/TahsinCr/python-rwlocker/issues
|
|
344
|
+
|
|
345
|
+
[examples-url]: https://github.com/TahsinCr/python-rwlocker/wiki
|
|
346
|
+
|
|
347
|
+
[license-url]: https://github.com/TahsinCr/python-rwlocker/blob/master/LICENSE
|
|
348
|
+
|
|
349
|
+
[changelog-url]:https://github.com/TahsinCr/python-rwlocker/blob/master/CHANGELOG.md
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
<!-- Contacts URL -->
|
|
354
|
+
|
|
355
|
+
[linkedin-url]: https://linkedin.com/in/TahsinCr
|
|
356
|
+
|
|
357
|
+
[x-url]: https://twitter.com/TahsinCrs
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
<!-- File URL -->
|
|
362
|
+
|
|
363
|
+
[lang-tr-url]: https://github.com/TahsinCr/python-rwlocker/blob/master/README_TR.md
|
|
364
|
+
|
|
365
|
+
[lang-en-url]: https://github.com/TahsinCr/python-rwlocker/blob/master/README.md
|