Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/lockfile/__init__.py: 51%

97 statements  

« prev     ^ index     » next       coverage.py v7.0.1, created at 2022-12-25 06:11 +0000

1# -*- coding: utf-8 -*- 

2 

3""" 

4lockfile.py - Platform-independent advisory file locks. 

5 

6Requires Python 2.5 unless you apply 2.4.diff 

7Locking is done on a per-thread basis instead of a per-process basis. 

8 

9Usage: 

10 

11>>> lock = LockFile('somefile') 

12>>> try: 

13... lock.acquire() 

14... except AlreadyLocked: 

15... print 'somefile', 'is locked already.' 

16... except LockFailed: 

17... print 'somefile', 'can\\'t be locked.' 

18... else: 

19... print 'got lock' 

20got lock 

21>>> print lock.is_locked() 

22True 

23>>> lock.release() 

24 

25>>> lock = LockFile('somefile') 

26>>> print lock.is_locked() 

27False 

28>>> with lock: 

29... print lock.is_locked() 

30True 

31>>> print lock.is_locked() 

32False 

33 

34>>> lock = LockFile('somefile') 

35>>> # It is okay to lock twice from the same thread... 

36>>> with lock: 

37... lock.acquire() 

38... 

39>>> # Though no counter is kept, so you can't unlock multiple times... 

40>>> print lock.is_locked() 

41False 

42 

43Exceptions: 

44 

45 Error - base class for other exceptions 

46 LockError - base class for all locking exceptions 

47 AlreadyLocked - Another thread or process already holds the lock 

48 LockFailed - Lock failed for some other reason 

49 UnlockError - base class for all unlocking exceptions 

50 AlreadyUnlocked - File was not locked. 

51 NotMyLock - File was locked but not by the current thread/process 

52""" 

53 

54from __future__ import absolute_import 

55 

56import functools 

57import os 

58import socket 

59import threading 

60import warnings 

61 

62# Work with PEP8 and non-PEP8 versions of threading module. 

63if not hasattr(threading, "current_thread"): 

64 threading.current_thread = threading.currentThread 

65if not hasattr(threading.Thread, "get_name"): 

66 threading.Thread.get_name = threading.Thread.getName 

67 

68__all__ = ['Error', 'LockError', 'LockTimeout', 'AlreadyLocked', 

69 'LockFailed', 'UnlockError', 'NotLocked', 'NotMyLock', 

70 'LinkFileLock', 'MkdirFileLock', 'SQLiteFileLock', 

71 'LockBase', 'locked'] 

72 

73 

74class Error(Exception): 

75 """ 

76 Base class for other exceptions. 

77 

78 >>> try: 

79 ... raise Error 

80 ... except Exception: 

81 ... pass 

82 """ 

83 pass 

84 

85 

86class LockError(Error): 

87 """ 

88 Base class for error arising from attempts to acquire the lock. 

89 

90 >>> try: 

91 ... raise LockError 

92 ... except Error: 

93 ... pass 

94 """ 

95 pass 

96 

97 

98class LockTimeout(LockError): 

99 """Raised when lock creation fails within a user-defined period of time. 

100 

101 >>> try: 

102 ... raise LockTimeout 

103 ... except LockError: 

104 ... pass 

105 """ 

106 pass 

107 

108 

109class AlreadyLocked(LockError): 

110 """Some other thread/process is locking the file. 

111 

112 >>> try: 

113 ... raise AlreadyLocked 

114 ... except LockError: 

115 ... pass 

116 """ 

117 pass 

118 

119 

120class LockFailed(LockError): 

121 """Lock file creation failed for some other reason. 

122 

123 >>> try: 

124 ... raise LockFailed 

125 ... except LockError: 

126 ... pass 

127 """ 

128 pass 

129 

130 

131class UnlockError(Error): 

132 """ 

133 Base class for errors arising from attempts to release the lock. 

134 

135 >>> try: 

136 ... raise UnlockError 

137 ... except Error: 

138 ... pass 

139 """ 

140 pass 

141 

142 

143class NotLocked(UnlockError): 

144 """Raised when an attempt is made to unlock an unlocked file. 

145 

146 >>> try: 

147 ... raise NotLocked 

148 ... except UnlockError: 

149 ... pass 

150 """ 

151 pass 

152 

153 

154class NotMyLock(UnlockError): 

155 """Raised when an attempt is made to unlock a file someone else locked. 

156 

157 >>> try: 

158 ... raise NotMyLock 

159 ... except UnlockError: 

160 ... pass 

161 """ 

162 pass 

163 

164 

165class _SharedBase(object): 

166 def __init__(self, path): 

167 self.path = path 

168 

169 def acquire(self, timeout=None): 

170 """ 

171 Acquire the lock. 

172 

173 * If timeout is omitted (or None), wait forever trying to lock the 

174 file. 

175 

176 * If timeout > 0, try to acquire the lock for that many seconds. If 

177 the lock period expires and the file is still locked, raise 

178 LockTimeout. 

179 

180 * If timeout <= 0, raise AlreadyLocked immediately if the file is 

181 already locked. 

182 """ 

183 raise NotImplemented("implement in subclass") 

184 

185 def release(self): 

186 """ 

187 Release the lock. 

188 

189 If the file is not locked, raise NotLocked. 

190 """ 

191 raise NotImplemented("implement in subclass") 

192 

193 def __enter__(self): 

194 """ 

195 Context manager support. 

196 """ 

197 self.acquire() 

198 return self 

199 

200 def __exit__(self, *_exc): 

201 """ 

202 Context manager support. 

203 """ 

204 self.release() 

205 

206 def __repr__(self): 

207 return "<%s: %r>" % (self.__class__.__name__, self.path) 

208 

209 

210class LockBase(_SharedBase): 

211 """Base class for platform-specific lock classes.""" 

212 def __init__(self, path, threaded=True, timeout=None): 

213 """ 

214 >>> lock = LockBase('somefile') 

215 >>> lock = LockBase('somefile', threaded=False) 

216 """ 

217 super(LockBase, self).__init__(path) 

218 self.lock_file = os.path.abspath(path) + ".lock" 

219 self.hostname = socket.gethostname() 

220 self.pid = os.getpid() 

221 if threaded: 

222 t = threading.current_thread() 

223 # Thread objects in Python 2.4 and earlier do not have ident 

224 # attrs. Worm around that. 

225 ident = getattr(t, "ident", hash(t)) 

226 self.tname = "-%x" % (ident & 0xffffffff) 

227 else: 

228 self.tname = "" 

229 dirname = os.path.dirname(self.lock_file) 

230 

231 # unique name is mostly about the current process, but must 

232 # also contain the path -- otherwise, two adjacent locked 

233 # files conflict (one file gets locked, creating lock-file and 

234 # unique file, the other one gets locked, creating lock-file 

235 # and overwriting the already existing lock-file, then one 

236 # gets unlocked, deleting both lock-file and unique file, 

237 # finally the last lock errors out upon releasing. 

238 self.unique_name = os.path.join(dirname, 

239 "%s%s.%s%s" % (self.hostname, 

240 self.tname, 

241 self.pid, 

242 hash(self.path))) 

243 self.timeout = timeout 

244 

245 def is_locked(self): 

246 """ 

247 Tell whether or not the file is locked. 

248 """ 

249 raise NotImplemented("implement in subclass") 

250 

251 def i_am_locking(self): 

252 """ 

253 Return True if this object is locking the file. 

254 """ 

255 raise NotImplemented("implement in subclass") 

256 

257 def break_lock(self): 

258 """ 

259 Remove a lock. Useful if a locking thread failed to unlock. 

260 """ 

261 raise NotImplemented("implement in subclass") 

262 

263 def __repr__(self): 

264 return "<%s: %r -- %r>" % (self.__class__.__name__, self.unique_name, 

265 self.path) 

266 

267 

268def _fl_helper(cls, mod, *args, **kwds): 

269 warnings.warn("Import from %s module instead of lockfile package" % mod, 

270 DeprecationWarning, stacklevel=2) 

271 # This is a bit funky, but it's only for awhile. The way the unit tests 

272 # are constructed this function winds up as an unbound method, so it 

273 # actually takes three args, not two. We want to toss out self. 

274 if not isinstance(args[0], str): 

275 # We are testing, avoid the first arg 

276 args = args[1:] 

277 if len(args) == 1 and not kwds: 

278 kwds["threaded"] = True 

279 return cls(*args, **kwds) 

280 

281 

282def LinkFileLock(*args, **kwds): 

283 """Factory function provided for backwards compatibility. 

284 

285 Do not use in new code. Instead, import LinkLockFile from the 

286 lockfile.linklockfile module. 

287 """ 

288 from . import linklockfile 

289 return _fl_helper(linklockfile.LinkLockFile, "lockfile.linklockfile", 

290 *args, **kwds) 

291 

292 

293def MkdirFileLock(*args, **kwds): 

294 """Factory function provided for backwards compatibility. 

295 

296 Do not use in new code. Instead, import MkdirLockFile from the 

297 lockfile.mkdirlockfile module. 

298 """ 

299 from . import mkdirlockfile 

300 return _fl_helper(mkdirlockfile.MkdirLockFile, "lockfile.mkdirlockfile", 

301 *args, **kwds) 

302 

303 

304def SQLiteFileLock(*args, **kwds): 

305 """Factory function provided for backwards compatibility. 

306 

307 Do not use in new code. Instead, import SQLiteLockFile from the 

308 lockfile.mkdirlockfile module. 

309 """ 

310 from . import sqlitelockfile 

311 return _fl_helper(sqlitelockfile.SQLiteLockFile, "lockfile.sqlitelockfile", 

312 *args, **kwds) 

313 

314 

315def locked(path, timeout=None): 

316 """Decorator which enables locks for decorated function. 

317 

318 Arguments: 

319 - path: path for lockfile. 

320 - timeout (optional): Timeout for acquiring lock. 

321 

322 Usage: 

323 @locked('/var/run/myname', timeout=0) 

324 def myname(...): 

325 ... 

326 """ 

327 def decor(func): 

328 @functools.wraps(func) 

329 def wrapper(*args, **kwargs): 

330 lock = FileLock(path, timeout=timeout) 

331 lock.acquire() 

332 try: 

333 return func(*args, **kwargs) 

334 finally: 

335 lock.release() 

336 return wrapper 

337 return decor 

338 

339 

340if hasattr(os, "link"): 

341 from . import linklockfile as _llf 

342 LockFile = _llf.LinkLockFile 

343else: 

344 from . import mkdirlockfile as _mlf 

345 LockFile = _mlf.MkdirLockFile 

346 

347FileLock = LockFile