-
Notifications
You must be signed in to change notification settings - Fork 4
Feat/rollback #610
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Karrenbelt
wants to merge
7
commits into
main
Choose a base branch
from
feat/rollback
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Feat/rollback #610
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
844fc32
feat: signals & rollback
Karrenbelt e765961
chore: mv utils.py -> utils/__init__.py
Karrenbelt 1d93b31
feat: add pwd restoration to rollback
Karrenbelt 3514d15
refactor: replace isolated_filesystem with rollback
Karrenbelt 467c49f
chore: remove isolated_filesystem contextmanager
Karrenbelt 5e4a44a
chore: remove str cast in tests for Path
Karrenbelt f277c3a
chore: make fmt lint
Karrenbelt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| """Filesystem utilities for temporary backups and rollback mechanisms.""" | ||
|
|
||
| import shutil | ||
| import signal | ||
| import tempfile | ||
| from pathlib import Path | ||
| from contextlib import chdir, contextmanager | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it possible to not use this chidr? iirc os.chdir can be used as a context as well, and then it means we dont drop an entire python version |
||
|
|
||
| from auto_dev.utils import signals | ||
|
|
||
|
|
||
| # https://www.youtube.com/watch?v=0GRLhpMao3I | ||
| # async-signal safe is the strongest concept of reentrancy. | ||
| # async-signal safe implies thread safe. | ||
|
|
||
| # signal.SIGKILL cannot be intercepted | ||
| SIGNALS_TO_BLOCK = (signal.SIGINT, signal.SIGTERM) | ||
|
|
||
|
|
||
| def _restore_from_backup(directory: Path, backup: Path): | ||
| for item in directory.rglob("*"): | ||
| backup_item = backup / item.relative_to(directory) | ||
| if item.is_file() or item.is_symlink(): | ||
| item.unlink() | ||
| elif item.is_dir() and not backup_item.exists(): | ||
| shutil.rmtree(item) | ||
|
|
||
| for item in backup.rglob("*"): | ||
| directory_item = directory / item.relative_to(backup) | ||
| if item.is_symlink(): | ||
| directory_item.symlink_to(item.readlink()) | ||
| elif item.is_dir(): | ||
| directory_item.mkdir(parents=True, exist_ok=True) | ||
| elif item.is_file(): | ||
| shutil.copy2(item, directory_item) | ||
|
|
||
|
|
||
| @contextmanager | ||
| def on_exit(directory: Path): | ||
| """Creates a temporary backup of the directory and restores it upon exit.""" | ||
| backup = Path(tempfile.mkdtemp(prefix="backup_")) / directory.name | ||
| shutil.copytree(directory, backup, symlinks=True) | ||
| with chdir(Path.cwd()): | ||
| try: | ||
| yield | ||
| finally: | ||
| with signals.mask(*SIGNALS_TO_BLOCK): | ||
| _restore_from_backup(directory, backup) | ||
| shutil.rmtree(backup) | ||
|
|
||
|
|
||
| @contextmanager | ||
| def on_exception(directory: Path): | ||
| """Creates a temporary backup of the directory and restores it only if an exception occurs.""" | ||
| backup = Path(tempfile.mkdtemp(prefix="backup_")) / directory.name | ||
| shutil.copytree(directory, backup, symlinks=True) | ||
| with chdir(Path.cwd()): | ||
| try: | ||
| yield | ||
| except BaseException: | ||
| with signals.mask(*SIGNALS_TO_BLOCK): | ||
| _restore_from_backup(directory, backup) | ||
| raise | ||
| finally: | ||
| shutil.rmtree(backup) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| """Signal management utilities.""" | ||
|
|
||
| import signal | ||
| from types import FrameType | ||
| from contextlib import contextmanager | ||
| from collections.abc import Callable | ||
|
|
||
|
|
||
| CallableSignalHandler = Callable[[int, FrameType], None] | ||
8ball030 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| SignalHandler = signal.Handlers | CallableSignalHandler | ||
|
|
||
|
|
||
| @contextmanager | ||
| def block(*signals: int): | ||
| """Context manager to globally block specified signals by replacing their handlers with signal.SIG_IGN.""" | ||
| original_handlers = {sig: signal.getsignal(sig) for sig in signals} | ||
| try: | ||
| for sig in signals: | ||
| signal.signal(sig, signal.SIG_IGN) | ||
| yield | ||
| finally: | ||
| for sig, handler in original_handlers.items(): | ||
| signal.signal(sig, handler) | ||
|
|
||
|
|
||
| @contextmanager | ||
| def mask(*signals: int): | ||
| """Context manager to temporarily block specified signals for the current thread by modifying its signal mask.""" | ||
| original_mask = signal.pthread_sigmask(signal.SIG_BLOCK, signals) | ||
| try: | ||
| yield | ||
| finally: | ||
| signal.pthread_sigmask(signal.SIG_SETMASK, original_mask) | ||
|
|
||
|
|
||
| @contextmanager | ||
| def replace_handler(signal_handler: SignalHandler, *signals: int): | ||
| """Context manager to replace the signal handlers for specified signals with a custom handler.""" | ||
| original_handlers = {sig: signal.getsignal(sig) for sig in signals} | ||
| try: | ||
| for sig in signals: | ||
| signal.signal(sig, signal_handler) | ||
| yield | ||
| finally: | ||
| for sig, handler in original_handlers.items(): | ||
| signal.signal(sig, handler) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is gonna be great,
Only issue i can foresee is that currently, we use sys.exit(1) such that we exit from the cli.
Id like to basically raise exceptions and then handle them in the cli code with a sys.exit
That makes the output from the tool look a lot nicer as then we dont have like the stack trace every time it throws if that makes sense.