import contextlib import datetime import logging from pathlib import Path logger = logging.getLogger(__name__) class LockAcquireException(Exception): """Raised when a lock cannot be acquired (held and not yet expired).""" pass @contextlib.contextmanager def try_lock(lock_path: Path, ttl: datetime.timedelta = None): logger.info(f'Trying to acquire lock at {lock_path}') now = datetime.datetime.now() if lock_path.exists(): created_at = datetime.datetime.fromtimestamp(lock_path.stat().st_mtime) logger.info( f'Lockfile {lock_path} already exists. ' f'Created at {created_at}, current time {now}, ttl {ttl}.') if not ttl or (now - created_at < ttl): logger.info( 'Lockfile is not expired. raising LockAcquireException') raise LockAcquireException() logger.info('Lockfile is expired... Continue with lock') logger.info(f'Acquired lock at {lock_path}') lock_path.write_text(f'Locked at {now}') try: yield lock_path finally: lock_path.unlink(missing_ok=True) logger.info(f'Released lock at {lock_path}')