comparison env/lib/python3.7/site-packages/lockfile/__init__.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 # -*- coding: utf-8 -*-
2
3 """
4 lockfile.py - Platform-independent advisory file locks.
5
6 Requires Python 2.5 unless you apply 2.4.diff
7 Locking is done on a per-thread basis instead of a per-process basis.
8
9 Usage:
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'
20 got lock
21 >>> print lock.is_locked()
22 True
23 >>> lock.release()
24
25 >>> lock = LockFile('somefile')
26 >>> print lock.is_locked()
27 False
28 >>> with lock:
29 ... print lock.is_locked()
30 True
31 >>> print lock.is_locked()
32 False
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()
41 False
42
43 Exceptions:
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
54 from __future__ import absolute_import
55
56 import functools
57 import os
58 import socket
59 import threading
60 import warnings
61
62 # Work with PEP8 and non-PEP8 versions of threading module.
63 if not hasattr(threading, "current_thread"):
64 threading.current_thread = threading.currentThread
65 if 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
74 class 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
86 class 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
98 class 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
109 class 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
120 class 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
131 class 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
143 class 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
154 class 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
165 class _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
210 class 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
268 def _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
282 def 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
293 def 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
304 def 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
315 def 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
340 if hasattr(os, "link"):
341 from . import linklockfile as _llf
342 LockFile = _llf.LinkLockFile
343 else:
344 from . import mkdirlockfile as _mlf
345 LockFile = _mlf.MkdirLockFile
346
347 FileLock = LockFile