fx-bin 2.2.2__tar.gz → 2.4.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fx-bin
3
- Version: 2.2.2
3
+ Version: 2.4.0
4
4
  Summary: A common bin collection for my own usage
5
5
  Keywords: fx_bin
6
6
  Author: Frank Xu
@@ -15,4 +15,4 @@ try:
15
15
  __version__ = version("fx-bin")
16
16
  except Exception:
17
17
  # Fallback version in case package is not installed
18
- __version__ = "2.2.2"
18
+ __version__ = "2.4.0"
@@ -0,0 +1,123 @@
1
+ """Shared backup utilities for file operations.
2
+
3
+ This module provides reusable backup functionality that can be used
4
+ across different modules (replace.py, replace_functional.py, etc.).
5
+
6
+ Functions follow functional patterns with IOResult for error handling.
7
+ """
8
+
9
+ import os
10
+ import shutil
11
+ import stat
12
+
13
+ from loguru import logger as L
14
+ from returns.io import IOResult
15
+
16
+ from fx_bin.errors import IOError as FxIOError
17
+ from fx_bin.shared_types import FileBackup
18
+
19
+ # Re-export for backward compatibility
20
+ __all__ = ["FileBackup", "create_backup", "restore_from_backup", "cleanup_backup"]
21
+
22
+
23
+ def create_backup(filename: str) -> IOResult[FileBackup, FxIOError]:
24
+ """Create a backup of the file.
25
+
26
+ Creates a backup copy of the specified file with preserved metadata.
27
+ The backup file is created with a ".backup" suffix.
28
+
29
+ Args:
30
+ filename: Path to the file to backup
31
+
32
+ Returns:
33
+ IOResult containing:
34
+ - Success: FileBackup object with backup metadata
35
+ - Failure: FxIOError if backup creation fails
36
+
37
+ Example:
38
+ >>> from fx_bin.lib import unsafe_ioresult_unwrap
39
+ >>> result = create_backup("/path/to/file.txt")
40
+ >>> backup = unsafe_ioresult_unwrap(result)
41
+ >>> print(backup.backup_path)
42
+ /path/to/file.txt.backup
43
+ """
44
+ try:
45
+ # Get original file stats
46
+ original_stat = os.stat(filename)
47
+ original_mode = original_stat.st_mode
48
+
49
+ # Create backup path
50
+ backup_path = f"{filename}.backup"
51
+
52
+ # Copy file with metadata
53
+ shutil.copy2(filename, backup_path)
54
+
55
+ return IOResult.from_value(
56
+ FileBackup(
57
+ original_path=filename,
58
+ backup_path=backup_path,
59
+ original_mode=original_mode,
60
+ )
61
+ )
62
+ except Exception as e:
63
+ return IOResult.from_failure(FxIOError(f"Failed to create backup: {e}"))
64
+
65
+
66
+ def restore_from_backup(backup: FileBackup) -> IOResult[None, FxIOError]:
67
+ """Restore original file from backup on failure.
68
+
69
+ Restores the original file from the backup copy and restores
70
+ the original file permissions. Gracefully handles missing backup
71
+ files (already cleaned up).
72
+
73
+ Args:
74
+ backup: FileBackup object containing backup metadata
75
+
76
+ Returns:
77
+ IOResult containing:
78
+ - Success: None (restore succeeded or backup already cleaned up)
79
+ - Failure: FxIOError if restore operation fails
80
+
81
+ Example:
82
+ >>> backup = FileBackup(...)
83
+ >>> result = restore_from_backup(backup)
84
+ """
85
+ try:
86
+ if os.path.exists(backup.backup_path):
87
+ # Restore the backup
88
+ shutil.move(backup.backup_path, backup.original_path)
89
+ # Restore permissions
90
+ os.chmod(backup.original_path, stat.S_IMODE(backup.original_mode))
91
+ return IOResult.from_value(None)
92
+ except Exception as e:
93
+ return IOResult.from_failure(FxIOError(f"Failed to restore from backup: {e}"))
94
+
95
+
96
+ def cleanup_backup(backup: FileBackup) -> IOResult[None, FxIOError]:
97
+ """Remove backup file after successful operation.
98
+
99
+ Cleans up the backup file after a successful operation.
100
+ Gracefully handles missing backup files (already cleaned up).
101
+ This is a non-critical operation - failures are logged but don't
102
+ fail the overall operation.
103
+
104
+ Args:
105
+ backup: FileBackup object containing backup metadata
106
+
107
+ Returns:
108
+ IOResult containing:
109
+ - Success: None (cleanup succeeded or backup already gone)
110
+ - Failure: Never fails - errors logged but returns Success
111
+
112
+ Example:
113
+ >>> backup = FileBackup(...)
114
+ >>> result = cleanup_backup(backup)
115
+ """
116
+ try:
117
+ if os.path.exists(backup.backup_path):
118
+ os.unlink(backup.backup_path)
119
+ return IOResult.from_value(None)
120
+ except Exception as e:
121
+ # Non-critical error - log but don't fail
122
+ L.warning(f"Could not remove backup {backup.backup_path}: {e}")
123
+ return IOResult.from_value(None)