Skip to content

Commit 07f8fad

Browse files
committed
refactor(core): Finalize core logic and enable streaming pip output
This is the final refactoring commit after a marathon debugging session. - Purged all duplicated, legacy function definitions from the core engine, resulting in a cleaner, more maintainable codebase. - Re-implemented the internal and methods to provide live, unbuffered, streaming output. This gives the user full transparency into package operations, which is critical for both debugging and trust. The core engine is now stable, tested, and transparent. All major known bugs related to environment context and state management have been resolved.
1 parent c72b7d4 commit 07f8fad

3 files changed

Lines changed: 245 additions & 309 deletions

File tree

omnipkg/core.py

Lines changed: 47 additions & 308 deletions
Original file line numberDiff line numberDiff line change
@@ -402,31 +402,6 @@ def _align_config_to_interpreter(self, python_exe_path_str: str):
402402
self.multiversion_base = Path(self.config['multiversion_base'])
403403
print(_(' ✅ Configuration updated and saved successfully.'))
404404

405-
def _get_venv_root(self) -> Path:
406-
"""
407-
Reliably finds the root of the virtual environment with a strict priority order.
408-
"""
409-
# PRIORITY 1: An override from a relaunch is the absolute source of truth.
410-
venv_root_override = os.environ.get('OMNIPKG_VENV_ROOT')
411-
if venv_root_override:
412-
return Path(venv_root_override)
413-
414-
# PRIORITY 2: VIRTUAL_ENV is the standard for venv/virtualenv.
415-
# This is the critical fix for your Conda vs. venv conflict.
416-
virtual_env_path = os.environ.get('VIRTUAL_ENV')
417-
if virtual_env_path and Path(virtual_env_path).exists():
418-
return Path(virtual_env_path)
419-
420-
# PRIORITY 3: Upward search for pyvenv.cfg is the next best for standard venvs.
421-
current_dir = Path(sys.executable).resolve().parent
422-
while current_dir != current_dir.parent:
423-
if (current_dir / 'pyvenv.cfg').exists():
424-
return current_dir
425-
current_dir = current_dir.parent
426-
427-
# PRIORITY 4: Fallback for Conda and other non-standard environments.
428-
return Path(sys.prefix)
429-
430405
def _setup_native_311_environment(self):
431406
"""
432407
Performs the one-time setup for an environment that already has Python 3.11.
@@ -3952,272 +3927,6 @@ def switch_active_python(self, version: str) -> int:
39523927
print(_('Just kidding, omnipkg handled it for you automatically!'))
39533928
return 0
39543929

3955-
def adopt_interpreter(self, version: str) -> int:
3956-
"""
3957-
Safely adopts a Python version by checking the registry, then trying to copy
3958-
from the local system, and finally falling back to download.
3959-
A rescan is forced after any successful filesystem change to ensure registration.
3960-
"""
3961-
print(_('🐍 Attempting to adopt Python {} into the environment...').format(version))
3962-
3963-
# First, check if it's already perfectly managed.
3964-
managed_interpreters = self.interpreter_manager.list_available_interpreters()
3965-
if version in managed_interpreters:
3966-
print(_(' - ✅ Python {} is already adopted and managed.').format(version))
3967-
return 0
3968-
3969-
# Attempt to find it locally to copy.
3970-
discovered_pythons = self.config_manager.list_available_pythons()
3971-
source_path_str = discovered_pythons.get(version)
3972-
3973-
if not source_path_str:
3974-
print(_(' - No local Python {} found. Falling back to download strategy.').format(version))
3975-
result = self._fallback_to_download(version)
3976-
if result == 0:
3977-
print(_('🔧 Forcing rescan to register the new interpreter...'))
3978-
self.rescan_interpreters()
3979-
return result
3980-
3981-
source_exe_path = Path(source_path_str)
3982-
try:
3983-
cmd = [str(source_exe_path), '-c', 'import sys; print(sys.prefix)']
3984-
cmd_result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=10)
3985-
source_root = Path(os.path.realpath(cmd_result.stdout.strip()))
3986-
current_venv_root = self.config_manager.venv_path.resolve()
3987-
3988-
# Perform safety checks before attempting a copy
3989-
if self._is_same_or_child_path(source_root, current_venv_root) or \
3990-
not self._is_valid_python_installation(source_root, source_exe_path) or \
3991-
self._estimate_directory_size(source_root) > 2 * 1024 * 1024 * 1024 or \
3992-
self._is_system_critical_path(source_root):
3993-
print(_(' - ⚠️ Safety checks failed for local copy. Falling back to download.'))
3994-
result = self._fallback_to_download(version)
3995-
if result == 0:
3996-
print(_('🔧 Forcing rescan to register the downloaded interpreter...'))
3997-
self.rescan_interpreters()
3998-
return result
3999-
4000-
dest_root = self.config_manager.venv_path / '.omnipkg' / 'interpreters' / f'cpython-{version}'
4001-
if dest_root.exists():
4002-
print(_(' - ✅ Adopted copy of Python {} already exists. Ensuring it is registered.').format(version))
4003-
self.rescan_interpreters()
4004-
return 0
4005-
4006-
print(_(' - Starting safe copy operation...'))
4007-
result = self._perform_safe_copy(source_root, dest_root, version)
4008-
4009-
if result == 0:
4010-
print(_('🔧 Forcing rescan to register the copied interpreter...'))
4011-
self.rescan_interpreters()
4012-
return result
4013-
4014-
except Exception as e:
4015-
print(_(' - ❌ An error occurred during the copy attempt: {}. Falling back to download.').format(e))
4016-
result = self._fallback_to_download(version)
4017-
if result == 0:
4018-
print(_('🔧 Forcing rescan to register the downloaded interpreter...'))
4019-
self.rescan_interpreters()
4020-
return result
4021-
4022-
def _fallback_to_download(self, version: str) -> int:
4023-
"""
4024-
Fallback to downloading Python. This function now surgically detects an incomplete
4025-
installation by checking for a valid executable, cleans it up if broken,
4026-
and includes a safety stop to prevent deleting the active interpreter.
4027-
"""
4028-
print(_('\n--- Running robust download strategy ---'))
4029-
try:
4030-
full_versions = {'3.12': '3.12.3', '3.10': '3.10.13', '3.9': '3.9.18', '3.11': '3.11.6'}
4031-
full_version = full_versions.get(version)
4032-
if not full_version:
4033-
print(f'❌ Error: No known standalone build for Python {version}.')
4034-
return 1
4035-
4036-
dest_path = self.config_manager.venv_path / '.omnipkg' / 'interpreters' / f'cpython-{full_version}'
4037-
4038-
if dest_path.exists():
4039-
print(_(' - Found existing directory for Python {}. Verifying integrity...').format(full_version))
4040-
if self._is_interpreter_directory_valid(dest_path):
4041-
print(_(' - ✅ Integrity check passed. Installation is valid and complete.'))
4042-
# Even if valid, we ensure it's registered by forcing a rescan later.
4043-
return 0 # Success, no download needed.
4044-
else:
4045-
print(_(' - ⚠️ Integrity check failed: Incomplete installation detected (missing or broken executable).'))
4046-
4047-
# --- CRITICAL SAFETY CHECK ---
4048-
# Never delete the interpreter that is currently running this script.
4049-
try:
4050-
active_interpreter_root = Path(sys.executable).resolve().parents[1]
4051-
if dest_path.resolve() == active_interpreter_root:
4052-
print(_(' - ❌ CRITICAL ERROR: The broken interpreter is the currently active one!'))
4053-
print(_(' - Aborting to prevent self-destruction. Please fix the environment manually.'))
4054-
return 1
4055-
except (IndexError, OSError):
4056-
pass # Path structure is unexpected, proceed with caution but don't block.
4057-
# --- END SAFETY CHECK ---
4058-
4059-
print(_(' - Preparing to clean up broken directory...'))
4060-
try:
4061-
shutil.rmtree(dest_path)
4062-
print(_(' - ✅ Removed broken directory successfully.'))
4063-
except Exception as e:
4064-
print(_(" - ❌ FATAL: Failed to remove existing broken directory: {}").format(e))
4065-
return 1
4066-
4067-
# Proceed with a fresh installation into a guaranteed-to-be-clean directory.
4068-
print(_(' - Starting fresh download and installation...'))
4069-
4070-
# Call the correct method name - check your config_manager for the actual method
4071-
if hasattr(self.config_manager, '_install_managed_python'):
4072-
self.config_manager._install_managed_python(self.config_manager.venv_path, full_version)
4073-
elif hasattr(self.config_manager, 'install_managed_python'):
4074-
self.config_manager.install_managed_python(self.config_manager.venv_path, full_version)
4075-
elif hasattr(self.config_manager, 'download_python'):
4076-
self.config_manager.download_python(full_version)
4077-
else:
4078-
print(_('❌ Error: No download method found on config_manager'))
4079-
return 1
4080-
4081-
# Verify the installation worked
4082-
if dest_path.exists() and self._is_interpreter_directory_valid(dest_path):
4083-
print(_(' - ✅ Download and installation completed successfully.'))
4084-
return 0
4085-
else:
4086-
print(_(' - ❌ Installation completed but integrity check still fails.'))
4087-
return 1
4088-
except Exception as e:
4089-
print(_('❌ Download and installation process failed: {}').format(e))
4090-
return 1 # Return 1 for failure
4091-
4092-
def _is_same_or_child_path(self, source: Path, target: Path) -> bool:
4093-
"""Check if source is the same as target or a child of target."""
4094-
try:
4095-
source = source.resolve()
4096-
target = target.resolve()
4097-
if source == target:
4098-
return True
4099-
try:
4100-
source.relative_to(target)
4101-
return True
4102-
except ValueError:
4103-
return False
4104-
except (OSError, RuntimeError):
4105-
return True
4106-
4107-
def _is_valid_python_installation(self, root: Path, exe_path: Path) -> bool:
4108-
"""Validate that the source looks like a proper Python installation."""
4109-
try:
4110-
if not exe_path.exists():
4111-
return False
4112-
try:
4113-
exe_path.resolve().relative_to(root.resolve())
4114-
except ValueError:
4115-
return False
4116-
expected_dirs = ['lib', 'bin']
4117-
if sys.platform == 'win32':
4118-
expected_dirs = ['Lib', 'Scripts']
4119-
has_expected_structure = any(((root / d).exists() for d in expected_dirs))
4120-
test_cmd = [str(exe_path), '-c', 'import sys, os']
4121-
test_result = subprocess.run(test_cmd, capture_output=True, timeout=5)
4122-
return has_expected_structure and test_result.returncode == 0
4123-
except Exception:
4124-
return False
4125-
4126-
def _estimate_directory_size(self, path: Path, max_files_to_check: int=1000) -> int:
4127-
"""Estimate directory size with early termination for safety."""
4128-
total_size = 0
4129-
file_count = 0
4130-
try:
4131-
for root, dirs, files in os.walk(path):
4132-
dirs[:] = [d for d in dirs if not d.startswith(('.git', '__pycache__', '.mypy_cache', 'node_modules'))]
4133-
for file in files:
4134-
if file_count >= max_files_to_check:
4135-
return total_size * 10
4136-
try:
4137-
file_path = os.path.join(root, file)
4138-
total_size += os.path.getsize(file_path)
4139-
file_count += 1
4140-
except (OSError, IOError):
4141-
continue
4142-
except Exception:
4143-
return float('inf')
4144-
return total_size
4145-
4146-
def _is_system_critical_path(self, path: Path) -> bool:
4147-
"""Check if path is a system-critical directory that shouldn't be copied."""
4148-
critical_paths = [Path('/'), Path('/usr'), Path('/usr/local'), Path('/System'), Path('/Library'), Path('/opt'), Path('/bin'), Path('/sbin'), Path('/etc'), Path('/var'), Path('/tmp'), Path('/proc'), Path('/dev'), Path('/sys')]
4149-
if sys.platform == 'win32':
4150-
critical_paths.extend([Path('C:\\Windows'), Path('C:\\Program Files'), Path('C:\\Program Files (x86)'), Path('C:\\System32')])
4151-
try:
4152-
resolved_path = path.resolve()
4153-
for critical in critical_paths:
4154-
if resolved_path == critical.resolve():
4155-
return True
4156-
return False
4157-
except Exception:
4158-
return True
4159-
4160-
def _perform_safe_copy(self, source: Path, dest: Path, version: str) -> int:
4161-
"""Perform the actual copy operation with additional safety measures."""
4162-
try:
4163-
dest.parent.mkdir(parents=True, exist_ok=True)
4164-
4165-
def copy_function(src, dst, *, follow_symlinks=True):
4166-
try:
4167-
if os.path.getsize(src) > 100 * 1024 * 1024:
4168-
print(_(' - ⚠️ Skipping large file: {}').format(src))
4169-
return dst
4170-
except OSError:
4171-
pass
4172-
return shutil.copy2(src, dst, follow_symlinks=follow_symlinks)
4173-
4174-
def ignore_patterns(dir, files):
4175-
ignored = []
4176-
for file in files:
4177-
if file in {'.git', '__pycache__', '.mypy_cache', '.pytest_cache', '.tox', '.coverage', 'node_modules', '.DS_Store'}:
4178-
ignored.append(file)
4179-
try:
4180-
filepath = os.path.join(dir, file)
4181-
if os.path.isfile(filepath) and os.path.getsize(filepath) > 50 * 1024 * 1024:
4182-
ignored.append(file)
4183-
except OSError:
4184-
pass
4185-
return ignored
4186-
print(_(' - Copying {} -> {}').format(source, dest))
4187-
shutil.copytree(source, dest, symlinks=True, ignore=ignore_patterns, dirs_exist_ok=False)
4188-
copied_python = self._find_python_executable_in_dir(dest)
4189-
if not copied_python or not copied_python.exists():
4190-
print(_(' - ❌ Copy completed but Python executable not found in destination'))
4191-
shutil.rmtree(dest, ignore_errors=True)
4192-
return self._fallback_to_download(version)
4193-
test_cmd = [str(copied_python), '-c', 'import sys; print(sys.version)']
4194-
test_result = subprocess.run(test_cmd, capture_output=True, timeout=10)
4195-
if test_result.returncode != 0:
4196-
print(_(' - ❌ Copied Python executable failed basic test'))
4197-
shutil.rmtree(dest, ignore_errors=True)
4198-
return self._fallback_to_download(version)
4199-
print(_(' - ✅ Copy successful and verified!'))
4200-
self.config_manager._register_all_interpreters(self.config_manager.venv_path)
4201-
print(f'\n🎉 Successfully adopted Python {version} from local source!')
4202-
print(_(" You can now use 'omnipkg swap python {}'").format(version))
4203-
return 0
4204-
except Exception as e:
4205-
print(_(' - ❌ Copy operation failed: {}').format(e))
4206-
if dest.exists():
4207-
shutil.rmtree(dest, ignore_errors=True)
4208-
return self._fallback_to_download(version)
4209-
4210-
def _find_python_executable_in_dir(self, directory: Path) -> Path:
4211-
"""Find the Python executable in a copied directory."""
4212-
possible_names = ['python', 'python3', 'python.exe']
4213-
possible_dirs = ['bin', 'Scripts', '.']
4214-
for subdir in possible_dirs:
4215-
for name in possible_names:
4216-
candidate = directory / subdir / name
4217-
if candidate.exists() and candidate.is_file():
4218-
return candidate
4219-
return None
4220-
42213930
def _resolve_package_versions(self, packages: List[str]) -> List[str]:
42223931
"""
42233932
Takes a list of packages and ensures every entry has an explicit version.
@@ -4242,34 +3951,64 @@ def _resolve_package_versions(self, packages: List[str]) -> List[str]:
42423951

42433952

42443953
def _run_pip_install(self, packages: List[str]) -> int:
3954+
"""Runs `pip install` with LIVE, STREAMING output."""
42453955
if not packages:
42463956
return 0
42473957
try:
4248-
cmd = [self.config['python_executable'], '-m', 'pip', 'install'] + packages
4249-
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
4250-
print(result.stdout)
4251-
return result.returncode
4252-
except subprocess.CalledProcessError as e:
4253-
print(_('❌ Pip install command failed with exit code {}:').format(e.returncode))
4254-
print(e.stderr)
4255-
return e.returncode
3958+
# Add '-u' for unbuffered output to force pip to talk in real-time.
3959+
cmd = [self.config['python_executable'], '-u', '-m', 'pip', 'install'] + packages
3960+
3961+
# Use Popen for live, line-by-line streaming.
3962+
process = subprocess.Popen(
3963+
cmd,
3964+
stdout=subprocess.PIPE,
3965+
stderr=subprocess.STDOUT, # Redirect stderr to stdout
3966+
text=True,
3967+
encoding='utf-8',
3968+
errors='replace',
3969+
bufsize=1, # Line-buffered
3970+
universal_newlines=True
3971+
)
3972+
3973+
# Print each line of pip's output as it happens
3974+
print() # Add a newline for better formatting
3975+
for line in iter(process.stdout.readline, ''):
3976+
# Print the raw line to preserve pip's own formatting (like progress bars)
3977+
print(line, end='')
3978+
3979+
process.stdout.close()
3980+
return_code = process.wait()
3981+
return return_code
42563982
except Exception as e:
42573983
print(_(' ❌ An unexpected error occurred during pip install: {}').format(e))
42583984
return 1
42593985

42603986
def _run_pip_uninstall(self, packages: List[str]) -> int:
4261-
"""Runs `pip uninstall` for a list of packages."""
3987+
"""Runs `pip uninstall` with LIVE, STREAMING output."""
42623988
if not packages:
42633989
return 0
42643990
try:
4265-
cmd = [self.config['python_executable'], '-m', 'pip', 'uninstall', '-y'] + packages
4266-
result = subprocess.run(cmd, check=True, text=True, capture_output=True)
4267-
print(result.stdout)
4268-
return result.returncode
4269-
except subprocess.CalledProcessError as e:
4270-
print(_('❌ Pip uninstall command failed with exit code {}:').format(e.returncode))
4271-
print(e.stderr)
4272-
return e.returncode
3991+
# Add '-u' and streaming here as well for consistency.
3992+
cmd = [self.config['python_executable'], '-u', '-m', 'pip', 'uninstall', '-y'] + packages
3993+
3994+
process = subprocess.Popen(
3995+
cmd,
3996+
stdout=subprocess.PIPE,
3997+
stderr=subprocess.STDOUT,
3998+
text=True,
3999+
encoding='utf-8',
4000+
errors='replace',
4001+
bufsize=1,
4002+
universal_newlines=True
4003+
)
4004+
4005+
print() # Add a newline for better formatting
4006+
for line in iter(process.stdout.readline, ''):
4007+
print(line, end='')
4008+
4009+
process.stdout.close()
4010+
return_code = process.wait()
4011+
return return_code
42734012
except Exception as e:
42744013
print(_(' ❌ An unexpected error occurred during pip uninstall: {}').format(e))
42754014
return 1

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "omnipkg"
7-
version = "1.3.5"
7+
version = "1.3.6"
88
authors = [
99
{ name = "1minds3t", email = "1minds3t@proton.me" },
1010
]

0 commit comments

Comments
 (0)