11from __future__ import annotations # Python 3.6+ compatibility
2+
3+ from omnipkg .common_utils import safe_print
4+
25try :
36 from .common_utils import safe_print
47except ImportError :
1013
1114Can be used as a decorator, a context manager, or a standalone fixer.
1215"""
13- import os
14- import shutil
1516import functools
1617import json
1718import logging
18- from omnipkg .i18n import _
19+ import os
20+ import shutil
1921from pathlib import Path
20- logging .basicConfig (level = logging .INFO , format = '%(asctime)s - CondaGuard - %(levelname)s - %(message)s' )
22+
23+ from omnipkg .i18n import _
24+
25+ logging .basicConfig (
26+ level = logging .INFO , format = "%(asctime)s - CondaGuard - %(levelname)s - %(message)s"
27+ )
28+
2129
2230class CondaGuard :
2331 """
2432 Protects critical Conda metadata files by creating temporary backups
2533 before a sensitive operation and restoring them if corruption is detected.
2634 """
27- PROTECTED_FILES = {'/opt/conda/envs/evocoder_env/conda-meta/nodejs-24.4.1-heeeca48_0.json' }
35+
36+ PROTECTED_FILES = {"/opt/conda/envs/evocoder_env/conda-meta/nodejs-24.4.1-heeeca48_0.json" }
2837
2938 def __init__ (self ):
3039 self ._backup_paths = {}
@@ -38,17 +47,19 @@ def __exit__(self, exc_type, exc_value, traceback):
3847 """Context manager exit: check and restore if needed."""
3948 restored_files = self .restore ()
4049 if restored_files :
41- logging .warning (_ ('🛡️ Restored corrupted metadata for: {}' ).format (', ' .join (restored_files )))
50+ logging .warning (
51+ _ ("🛡️ Restored corrupted metadata for: {}" ).format (", " .join (restored_files ))
52+ )
4253 else :
43- logging .info (' ✅ Post-op check complete. All protected files are healthy.' )
54+ logging .info (" ✅ Post-op check complete. All protected files are healthy." )
4455
4556 def backup (self ):
4657 """Creates temporary backups of all protected files."""
4758 self ._backup_paths .clear ()
4859 for filepath_str in self .PROTECTED_FILES :
4960 src_path = Path (filepath_str )
5061 if src_path .exists () and src_path .stat ().st_size > 0 :
51- backup_path = src_path .with_suffix (f' { src_path .suffix } .guardbak' )
62+ backup_path = src_path .with_suffix (f" { src_path .suffix } .guardbak" )
5263 try :
5364 shutil .copy2 (src_path , backup_path )
5465 self ._backup_paths [src_path ] = backup_path
@@ -68,14 +79,18 @@ def restore(self) -> list:
6879 is_corrupted = False
6980 if not src_path .exists () or src_path .stat ().st_size == 0 :
7081 is_corrupted = True
71- logging .warning (_ ("Detected corruption (file missing or empty): '{}'" ).format (src_path .name ))
72- elif src_path .suffix == '.json' :
82+ logging .warning (
83+ _ ("Detected corruption (file missing or empty): '{}'" ).format (src_path .name )
84+ )
85+ elif src_path .suffix == ".json" :
7386 try :
74- with open (src_path , 'r' ) as f :
87+ with open (src_path , "r" ) as f :
7588 json .load (f )
7689 except (json .JSONDecodeError , OSError ):
7790 is_corrupted = True
78- logging .warning (_ ("Detected corruption (invalid JSON): '{}'" ).format (src_path .name ))
91+ logging .warning (
92+ _ ("Detected corruption (invalid JSON): '{}'" ).format (src_path .name )
93+ )
7994 if is_corrupted :
8095 if backup_path .exists ():
8196 try :
@@ -85,7 +100,9 @@ def restore(self) -> list:
85100 except Exception as e :
86101 logging .error (_ ("Failed to restore '{}': {}" ).format (src_path .name , e ))
87102 else :
88- logging .error (_ ("Cannot restore '{}', backup file is missing!" ).format (src_path .name ))
103+ logging .error (
104+ _ ("Cannot restore '{}', backup file is missing!" ).format (src_path .name )
105+ )
89106 return restored
90107
91108 def cleanup (self ):
@@ -96,9 +113,12 @@ def cleanup(self):
96113 os .remove (backup_path )
97114 logging .info (_ ("Cleaned up backup: '{}'" ).format (backup_path .name ))
98115 except Exception as e :
99- logging .error (_ ("Failed to clean up backup '{}': {}" ).format (backup_path .name , e ))
116+ logging .error (
117+ _ ("Failed to clean up backup '{}': {}" ).format (backup_path .name , e )
118+ )
100119 self ._backup_paths .clear ()
101120
121+
102122def protected_operation (func ):
103123 """Decorator to wrap any function with CondaGuard protection."""
104124
@@ -107,49 +127,55 @@ def wrapper(*args, **kwargs):
107127 guard = CondaGuard ()
108128 with guard :
109129 return func (* args , ** kwargs )
130+
110131 return wrapper
111132
133+
112134def fix_conda_corruption ():
113135 """
114136 A standalone function to manually trigger a check and restore.
115137 This is useful for scripts or manual repair. It creates its own backups
116138 and cleans them up.
117139 """
118- safe_print (_ (' --- Running Standalone Conda Corruption Check & Fix ---' ))
140+ safe_print (_ (" --- Running Standalone Conda Corruption Check & Fix ---" ))
119141 guard = CondaGuard ()
120142 guard .backup ()
121143 restored_files = guard .restore ()
122144 guard .cleanup ()
123145 if restored_files :
124- safe_print (_ (' 🔧 Fix applied. Restored files: {}' ).format (', ' .join (restored_files )))
146+ safe_print (_ (" 🔧 Fix applied. Restored files: {}" ).format (", " .join (restored_files )))
125147 return True
126148 else :
127- safe_print (_ (' ✅ No corruption found in protected files.' ))
149+ safe_print (_ (" ✅ No corruption found in protected files." ))
128150 return False
129-
130- if __name__ == '__main__' :
151+
152+
153+ if __name__ == "__main__" :
131154 fix_conda_corruption ()
132- safe_print (' \n ' + '=' * 50 + ' \n ' )
155+ safe_print (" \n " + "=" * 50 + " \n " )
133156
134157 @protected_operation
135158 def my_risky_hotswap_function (file_to_corrupt ):
136- safe_print (_ (' -> Running a risky function that might corrupt Conda...' ))
159+ safe_print (_ (" -> Running a risky function that might corrupt Conda..." ))
137160 p = Path (file_to_corrupt )
138161 if p .exists ():
139162 safe_print (_ (" ...simulating corruption of '{}'" ).format (p .name ))
140- with open (p , 'w' ) as f :
141- f .write ('this is corrupted json' )
142- safe_print (_ ('-> Risky function finished.' ))
143- safe_print (_ ('🧪 Testing decorator...' ))
144- my_risky_hotswap_function ('/opt/conda/envs/evocoder_env/conda-meta/nodejs-24.4.1-heeeca48_0.json' )
145- safe_print ('\n ' + '=' * 50 + '\n ' )
146- safe_print (_ ('🧪 Testing context manager...' ))
163+ with open (p , "w" ) as f :
164+ f .write ("this is corrupted json" )
165+ safe_print (_ ("-> Risky function finished." ))
166+
167+ safe_print (_ ("🧪 Testing decorator..." ))
168+ my_risky_hotswap_function (
169+ "/opt/conda/envs/evocoder_env/conda-meta/nodejs-24.4.1-heeeca48_0.json"
170+ )
171+ safe_print ("\n " + "=" * 50 + "\n " )
172+ safe_print (_ ("🧪 Testing context manager..." ))
147173 with CondaGuard ():
148- safe_print (' -> Entering safe context for a risky operation...' )
149- file_to_corrupt = ' /opt/conda/envs/evocoder_env/conda-meta/nodejs-24.4.1-heeeca48_0.json'
174+ safe_print (" -> Entering safe context for a risky operation..." )
175+ file_to_corrupt = " /opt/conda/envs/evocoder_env/conda-meta/nodejs-24.4.1-heeeca48_0.json"
150176 p = Path (file_to_corrupt )
151177 if p .exists ():
152178 safe_print (_ (" ...simulating deletion of '{}'" ).format (p .name ))
153179 os .remove (p )
154- safe_print (_ (' -> Leaving safe context...' ))
155- safe_print (_ (' \n ✅ Demo finished.' ))
180+ safe_print (_ (" -> Leaving safe context..." ))
181+ safe_print (_ (" \n ✅ Demo finished." ))
0 commit comments