1+ """omnipkg CLI - Enhanced with runtime interpreter switching and language support"""
12try :
23 from .common_utils import safe_print
34except ImportError :
45 from omnipkg .common_utils import safe_print
5- """omnipkg CLI - Enhanced with runtime interpreter switching and language support"""
66import sys
77import argparse
88from pathlib import Path
@@ -38,41 +38,115 @@ def get_actual_python_version():
3838 except Exception :
3939 return sys .version_info [:2 ]
4040
41+ def run_demo_with_enforced_context (
42+ source_script_path : Path ,
43+ demo_name : str ,
44+ pkg_instance : OmnipkgCore ,
45+ parser_prog : str ,
46+ required_version : str = None
47+ ) -> int :
48+ """
49+ Run a demo test with enforced Python context.
50+
51+ Args:
52+ source_script_path: Path to the test script to run
53+ demo_name: Name of the demo (for display purposes)
54+ pkg_instance: Initialized OmnipkgCore instance
55+ parser_prog: Parser program name (for error messages)
56+ required_version: Optional specific version (e.g., "3.11").
57+ If None, uses currently detected version.
58+
59+ Returns:
60+ Exit code (0 for success, 1 for failure)
61+ """
62+ # Detect the actual current Python version
63+ actual_version = get_actual_python_version ()
64+
65+ # Use required version if specified, otherwise use detected version
66+ target_version_str = required_version if required_version else f"{ actual_version [0 ]} .{ actual_version [1 ]} "
67+
68+ # Validate the source script exists
69+ if not source_script_path .exists ():
70+ safe_print (f'❌ Error: Source test file { source_script_path } not found.' )
71+ return 1
72+
73+ # Get the Python executable for the target version
74+ python_exe = pkg_instance .config_manager .get_interpreter_for_version (target_version_str )
75+ if not python_exe or not python_exe .exists ():
76+ safe_print (f"❌ Python { target_version_str } is not managed by omnipkg." )
77+ safe_print (f" Please adopt it first: { parser_prog } python adopt { target_version_str } " )
78+ return 1
79+
80+ safe_print (f'🚀 Running { demo_name } demo with Python { target_version_str } via sterile environment...' )
81+
82+ # Create a sterile copy of the script in /tmp to avoid PYTHONPATH contamination
83+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.py' , delete = False , encoding = 'utf-8' ) as temp_script :
84+ temp_script_path = Path (temp_script .name )
85+ temp_script .write (source_script_path .read_text (encoding = 'utf-8' ))
86+
87+ safe_print (f" - Sterile script created at: { temp_script_path } " )
88+
89+ try :
90+ # Execute using the enforced Python context
91+ return run_demo_with_live_streaming (
92+ test_file_name = str (temp_script_path ),
93+ demo_name = demo_name ,
94+ python_exe = str (python_exe )
95+ )
96+ finally :
97+ # Always clean up the temporary file
98+ temp_script_path .unlink (missing_ok = True )
99+
41100def handle_python_requirement (required_version_str : str , pkg_instance : OmnipkgCore , parser_prog : str ) -> bool :
42101 """
43102 Checks if the current Python version matches the requirement.
44- If not, it attempts to automatically adopt and/or swap .
103+ If not, it automatically finds, adopts (or downloads), and swaps to it .
45104 """
105+ # Get the true version of the currently configured Python context
46106 actual_version_tuple = get_actual_python_version ()
47107 required_version_tuple = tuple (map (int , required_version_str .split ('.' )))
108+
109+ # If we are already in the correct context, we're done.
48110 if actual_version_tuple == required_version_tuple :
49111 return True
112+
113+ # --- Start the full healing process ---
50114 print_header (_ ('Python Version Requirement' ))
51- safe_print (_ (' ⚠️ This Demo Requires Python {}' ).format (required_version_str ))
52- safe_print (_ (' - Current Python version: {}.{}' ).format (actual_version_tuple [0 ], actual_version_tuple [1 ]))
53- safe_print (_ (' - omnipkg will now attempt to automatically configure the correct interpreter.' ))
54- safe_print ('-' * 60 )
115+ safe_print (_ (' - Diagnosis: This operation requires Python {}' ).format (required_version_str ))
116+ safe_print (_ (' - Current Context: Python {}.{}' ).format (actual_version_tuple [0 ], actual_version_tuple [1 ]))
117+ safe_print (_ (' - Action: omnipkg will now attempt to automatically configure the correct interpreter.' ))
118+
119+ # Check if the required version is already managed by omnipkg
55120 managed_interpreters = pkg_instance .interpreter_manager .list_available_interpreters ()
121+
56122 if required_version_str not in managed_interpreters :
57- discovered_interpreters = pkg_instance . config_manager . list_available_pythons ()
58- if required_version_str in discovered_interpreters :
59- safe_print ( _ ( '🐍 Python {} found on your system. Adopting it...' ). format ( required_version_str ))
60- if pkg_instance . adopt_interpreter ( required_version_str ) != 0 :
61- safe_print ( _ ( '❌ Failed to adopt Python {}. Please try manually.' ). format ( required_version_str ))
62- safe_print (_ (' Run: {} python adopt {} ' ).format (parser_prog , required_version_str ))
63- return False
64- safe_print ( _ ( '✅ Successfully adopted Python {}.' ). format ( required_version_str ))
65- else :
66- safe_print ( _ ( '❌ Required Python version {} not found on your system.' ). format (required_version_str ))
67- safe_print (_ (' Please install Python {} and ensure it is in your PATH .' ).format (required_version_str ))
123+ #
124+ # >>>>>>>> THIS IS THE CRITICAL NEW LOGIC <<<<<<<<
125+ #
126+ # The required version is NOT managed. We must now run the full adopt
127+ # process, which will either find it locally or download it.
128+ safe_print (_ ('\n - Step 1: Adopting Python {}... (This may trigger a download) ' ).format (required_version_str ))
129+
130+ # Call the core adopt_interpreter method. This is the robust function that
131+ # contains the full logic: check local system -> fallback to download.
132+ if pkg_instance . adopt_interpreter (required_version_str ) != 0 :
133+ safe_print (_ (' - ❌ Failed to adopt Python {}. Cannot proceed with healing .' ).format (required_version_str ))
68134 return False
69- safe_print (_ ('🔄 Swapping active interpreter to Python {}...' ).format (required_version_str ))
135+
136+ safe_print (_ (' - ✅ Successfully adopted Python {}.' ).format (required_version_str ))
137+ #
138+ # >>>>>>>> END OF CRITICAL NEW LOGIC <<<<<<<<
139+ #
140+
141+ # By this point, we are GUARANTEED that the interpreter is managed. Now we can swap.
142+ safe_print (_ ('\n - Step 2: Swapping active context to Python {}...' ).format (required_version_str ))
70143 if pkg_instance .switch_active_python (required_version_str ) != 0 :
71- safe_print (_ ('❌ Failed to swap to Python {}. Please try manually.' ).format (required_version_str ))
72- safe_print (_ (' Run: {} swap python {}' ).format (parser_prog , required_version_str ))
144+ safe_print (_ (' - ❌ Failed to swap to Python {}. Please try manually.' ).format (required_version_str ))
145+ safe_print (_ (' Run: {} swap python {}' ).format (parser_prog , required_version_str ))
73146 return False
74- safe_print (_ ('✅ Environment successfully configured for Python {}.' ).format (required_version_str ))
75- safe_print (_ ('🚀 Proceeding to run the demo...' ))
147+
148+ safe_print (_ (' - ✅ Environment successfully configured for Python {}.' ).format (required_version_str ))
149+ safe_print (_ ('🚀 Proceeding...' ))
76150 safe_print ('=' * 60 )
77151 return True
78152
@@ -141,13 +215,17 @@ def stress_test_command():
141215 return False
142216
143217def run_actual_stress_test ():
144- """Run the actual stress test - only called if Python 3.11 ."""
218+ """Run the actual stress test by locating and executing the test file ."""
145219 safe_print (_ ('🔥 Starting stress test...' ))
146220 try :
147- from . import stress_test
148- stress_test .run ()
149- except ImportError :
150- safe_print (_ ('❌ Stress test module not found. Implementation needed.' ))
221+ # Define the correct path to the refactored test file
222+ test_file_path = TESTS_DIR / 'test_version_combos.py'
223+
224+ # Reuse the robust live streaming runner
225+ run_demo_with_live_streaming (
226+ test_file_name = str (test_file_path ),
227+ demo_name = "Stress Test"
228+ )
151229 except Exception as e :
152230 safe_print (_ ('❌ An error occurred during stress test execution: {}' ).format (e ))
153231 import traceback
@@ -179,15 +257,16 @@ def run_demo_with_live_streaming(test_file_name: str, demo_name: str, python_exe
179257
180258 # Step 2: Determine the final path to the SCRIPT to be executed.
181259 input_path = Path (test_file_name )
260+
182261 if input_path .is_absolute ():
183- # For temp files, the path is already correct and absolute .
262+ # If we're given an absolute path (like for a temp file), use it directly .
184263 test_file_path = input_path
185264 else :
186- # For project-internal tests, build the path relative to the context's project root .
187- source_dir_name = 'omnipkg' if "stress_test" in str ( test_file_name ) else 'tests'
188- test_file_path = project_root_in_context / source_dir_name / input_path .name
189- # --- END: ROBUST PATHING LOGIC ---
190-
265+ # Otherwise, assume all standard demo tests are in the 'tests' directory .
266+ # This is simpler and more reliable than checking filenames.
267+ test_file_path = project_root_in_context / 'tests' / input_path .name
268+ # --- END OF NEW LOGIC ---
269+
191270 safe_print (_ ('🚀 Running {} demo from source: {}...' ).format (demo_name .capitalize (), test_file_path ))
192271
193272 if not test_file_path .exists ():
@@ -355,6 +434,11 @@ def create_parser():
355434 prune_parser .add_argument ('package' , help = _ ('Package whose bubbles to prune' ))
356435 prune_parser .add_argument ('--keep-latest' , type = int , metavar = 'N' , help = _ ('Keep N most recent bubbled versions' ))
357436 prune_parser .add_argument ('--yes' , '-y' , dest = 'force' , action = 'store_true' , help = _ ('Skip confirmation' ))
437+ upgrade_parser = subparsers .add_parser ('upgrade' , help = _ ('Upgrade omnipkg or other packages to the latest version' ))
438+ upgrade_parser .add_argument ('package' , nargs = '?' , default = 'omnipkg' , help = _ ('Package to upgrade (defaults to omnipkg itself)' ))
439+ upgrade_parser .add_argument ('--version' , help = _ ('Specify a version to upgrade/downgrade to' ))
440+ upgrade_parser .add_argument ('--yes' , '-y' , dest = 'force' , action = 'store_true' , help = _ ('Skip confirmation prompt' ))
441+ upgrade_parser .add_argument ('--force-dev' , action = 'store_true' , help = _ ('Force upgrade even in a developer environment (use with caution)' ))
358442 return parser
359443
360444def print_header (title ):
@@ -532,9 +616,14 @@ def main():
532616 safe_print (_ ('6. Flask test (under construction)' ))
533617 safe_print (_ ('7. Auto-healing Test (omnipkg run)' )) # <--- ADD THIS
534618 safe_print (_ ('8. 🌠 Quantum Multiverse Warp (Concurrent Python Installations)' ))
535- safe_print (_ ('9. Flask Port Finder Test (auto-healing with Flask)' )) # <--- ADD THIS
619+ safe_print (_ ('9. Flask Port Finder Test (auto-healing with Flask)' )) # <-- ADD THIS LINE
620+ def run_and_stream (cmd_list ):
621+ process = subprocess .Popen (cmd_list , text = True , stdout = subprocess .PIPE , stderr = subprocess .STDOUT , encoding = 'utf-8' , errors = 'replace' )
622+ for line in process .stdout :
623+ safe_print (line , end = '' )
624+ return process .wait ()
536625 try :
537- response = input (_ ('Enter your choice (1-9 ): ' )).strip ()
626+ response = input (_ ('Enter your choice (1-8 ): ' )).strip ()
538627 except EOFError :
539628 response = ''
540629 test_file = None
@@ -548,8 +637,44 @@ def main():
548637 return 1
549638 return run_demo_with_live_streaming (str (test_file ), demo_name )
550639 elif response == '2' :
551- test_file = TESTS_DIR / 'test_uv_switching.py'
640+ # INSERT THESE LINES HERE (after line 586):
641+ actual_version = get_actual_python_version ()
642+ actual_version_str = f"{ actual_version [0 ]} .{ actual_version [1 ]} "
643+
552644 demo_name = 'uv'
645+ demo_name = 'uv'
646+ source_script_path = TESTS_DIR / 'test_uv_switching.py'
647+ if not source_script_path .exists ():
648+ safe_print (f'❌ Error: Source test file { source_script_path } not found.' )
649+ return 1
650+
651+ # Get the Python executable for the current version
652+ python_exe = pkg_instance .config_manager .get_interpreter_for_version (actual_version_str )
653+ if not python_exe or not python_exe .exists ():
654+ safe_print (f"❌ Python { actual_version_str } is not managed by omnipkg." )
655+ safe_print (f" Please adopt it first: { parser .prog } python adopt { actual_version_str } " )
656+ return 1
657+
658+ safe_print (f'🚀 Running { demo_name } demo with Python { actual_version_str } via sterile environment...' )
659+
660+ # Create sterile copy
661+ with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.py' , delete = False , encoding = 'utf-8' ) as temp_script :
662+ temp_script_path = Path (temp_script .name )
663+ temp_script .write (source_script_path .read_text (encoding = 'utf-8' ))
664+
665+ safe_print (f" - Sterile script created at: { temp_script_path } " )
666+
667+ returncode = 1
668+ try :
669+ # Use run_demo_with_live_streaming with the detected Python version
670+ return run_demo_with_live_streaming (
671+ test_file_name = str (temp_script_path ),
672+ demo_name = demo_name ,
673+ python_exe = str (python_exe )
674+ )
675+ finally :
676+ temp_script_path .unlink (missing_ok = True )
677+
553678 elif response == '3' :
554679 if not handle_python_requirement ('3.11' , pkg_instance , parser .prog ):
555680 return 1
@@ -567,37 +692,14 @@ def main():
567692 safe_print (_ (' Creating a sterile temporary copy to ensure a clean run...' ))
568693 safe_print ('!' * 60 )
569694
570- # 1. Find the source script.
571- source_script_path = TESTS_DIR / 'test_multiverse_healing.py'
695+ return run_demo_with_enforced_context (
696+ source_script_path = TESTS_DIR / 'test_multiverse_healing.py' ,
697+ demo_name = 'multiverse_healing' ,
698+ pkg_instance = pkg_instance ,
699+ parser_prog = parser .prog ,
700+ required_version = '3.11' # This test requires 3.11 specifically
701+ )
572702
573- if not source_script_path .exists ():
574- safe_print (_ ('❌ Error: Source test file {} not found.' ).format (source_script_path ))
575- return 1
576-
577- # 2. Create a temporary, sterile copy of the script.
578- with tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.py' , delete = False , encoding = 'utf-8' ) as temp_script :
579- temp_script_path = Path (temp_script .name )
580- temp_script .write (source_script_path .read_text (encoding = 'utf-8' ))
581-
582- safe_print (f" - Sterile script created at: { temp_script_path } " )
583-
584- try :
585- # 3. Get the required Python 3.11 interpreter.
586- python_311_exe = pkg_instance .config_manager .get_interpreter_for_version ('3.11' )
587- if not python_311_exe or not python_311_exe .exists ():
588- safe_print ("❌ Python 3.11 is required and not managed by omnipkg." )
589- safe_print (" Please adopt it first: omnipkg python adopt 3.11" )
590- return 1
591-
592- # 4. Execute the STERILE script, which will have a clean sys.path.
593- return run_demo_with_live_streaming (
594- test_file_name = str (temp_script_path ), # Use the absolute path to the temp file
595- demo_name = 'multiverse_healing' ,
596- python_exe = str (python_311_exe )
597- )
598- finally :
599- # 5. Clean up the temporary script no matter what.
600- temp_script_path .unlink (missing_ok = True )
601703 elif response == '6' :
602704 test_file = TESTS_DIR / 'test_rich_switching.py'
603705 demo_name = 'rich'
@@ -684,7 +786,7 @@ def main():
684786 safe_print (_ ('❌ Demo failed with return code {}' ).format (returncode ))
685787 return returncode
686788 else :
687- safe_print (_ ('❌ Invalid choice. Please select 1, 2, 3, 4, 5, 6, 7, 8, or 9.' ))
789+ safe_print (_ ('❌ Invalid choice. Please select 1, 2, 3, 4, 5, 6, 7, 8 or 9.' ))
688790 return 1
689791 if not test_file .exists ():
690792 safe_print (_ ('❌ Error: Test file {} not found.' ).format (test_file ))
@@ -752,6 +854,14 @@ def main():
752854 return pkg_instance .reset_configuration (force = args .force )
753855 elif args .command == 'run' :
754856 return execute_run_command (args .script_and_args , cm , verbose = args .verbose )
857+ elif args .command == 'upgrade' :
858+ if args .package .lower () == 'omnipkg' :
859+ return pkg_instance .smart_upgrade (version = args .version , force = args .force , skip_dev_check = args .force_dev )
860+
861+ else :
862+ # Upgrading other packages is just a reinstall of the latest
863+ safe_print (_ ("Redirecting to smart_install to get the latest version of '{}'..." ).format (args .package ))
864+ return pkg_instance .smart_install ([args .package ], force_reinstall = True )
755865 else :
756866 parser .print_help ()
757867 safe_print (_ ("\n 💡 Did you mean 'omnipkg config set language <code>'?" ))
@@ -762,6 +872,7 @@ def main():
762872 except Exception as e :
763873 safe_print (_ ('\n ❌ An unexpected error occurred: {}' ).format (e ))
764874 import traceback
875+
765876 traceback .print_exc ()
766877 return 1
767878if __name__ == '__main__' :
0 commit comments