Skip to content

Commit 740e3e1

Browse files
committed
feat: Add pip-audit fallback and refactor test suite
- Implemented pip-audit as a security scanner for Python 3.14+ where safety is incompatible. - Fixed interpreter registration bugs for Python 3.13 and 3.14. - Refactored and relocated test files from the main package directory into the /tests directory for better project structure.
1 parent 0572ad1 commit 740e3e1

File tree

9 files changed

+332
-75
lines changed

9 files changed

+332
-75
lines changed

omnipkg/cli.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -513,8 +513,9 @@ def main():
513513
safe_print(_('6. Flask test (under construction)'))
514514
safe_print(_('7. Auto-healing Test (omnipkg run)')) # <--- ADD THIS
515515
safe_print(_('8. 🌠 Quantum Multiverse Warp (Concurrent Python Installations)'))
516+
safe_print(_('9. Flask Port Finder Test (auto-healing with Flask)')) # <--- ADD THIS
516517
try:
517-
response = input(_('Enter your choice (1-8): ')).strip()
518+
response = input(_('Enter your choice (1-9): ')).strip()
518519
except EOFError:
519520
response = ''
520521
test_file = None
@@ -533,7 +534,7 @@ def main():
533534
elif response == '3':
534535
if not handle_python_requirement('3.11', pkg_instance, parser.prog):
535536
return 1
536-
test_file = DEMO_DIR / 'stress_test.py'
537+
test_file = TESTS_DIR / 'test_version_combos.py'
537538
demo_name = 'numpy_scipy'
538539
elif response == '4':
539540
if not handle_python_requirement('3.11', pkg_instance, parser.prog):
@@ -548,7 +549,8 @@ def main():
548549
safe_print('!'*60)
549550

550551
# 1. Find the source script.
551-
source_script_path = TESTS_DIR / 'multiverse_healing.py'
552+
source_script_path = TESTS_DIR / 'test_multiverse_healing.py'
553+
552554
if not source_script_path.exists():
553555
safe_print(_('❌ Error: Source test file {} not found.').format(source_script_path))
554556
return 1
@@ -642,8 +644,28 @@ def main():
642644
temp_script_path.unlink(missing_ok=True)
643645

644646
return returncode
647+
elif response == '9':
648+
demo_name = 'flask_port_finder'
649+
test_file = TESTS_DIR / 'test_flask_port_finder.py'
650+
if not test_file.exists():
651+
safe_print(_('❌ Error: Test file {} not found.').format(test_file))
652+
return 1
653+
# This demo is best run via 'omnipkg run' to showcase auto-healing
654+
safe_print(_('🚀 This demo uses "omnipkg run" to showcase auto-healing of missing modules.'))
655+
cmd = [parser.prog, 'run', str(test_file)]
656+
process = subprocess.Popen(cmd, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', errors='replace')
657+
for line in process.stdout:
658+
safe_print(line, end='')
659+
returncode = process.wait()
660+
661+
safe_print('-' * 60)
662+
if returncode == 0:
663+
safe_print(_('🎉 Demo completed successfully!'))
664+
else:
665+
safe_print(_('❌ Demo failed with return code {}').format(returncode))
666+
return returncode
645667
else:
646-
safe_print(_('❌ Invalid choice. Please select 1, 2, 3, 4, 5, 6, 7, or 8.'))
668+
safe_print(_('❌ Invalid choice. Please select 1, 2, 3, 4, 5, 6, 7, 8, or 9.'))
647669
return 1
648670
if not test_file.exists():
649671
safe_print(_('❌ Error: Test file {} not found.').format(test_file))

omnipkg/core.py

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,7 @@ def _register_all_interpreters(self, venv_path: Path):
459459
safe_print(_(' -> Scanning directory: {}').format(interp_dir.name))
460460
found_exe_path = None
461461
search_locations = [interp_dir / 'bin', interp_dir / 'Scripts', interp_dir]
462-
possible_exe_names = ['python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3', 'python', 'python.exe']
462+
possible_exe_names = ['python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3.8', 'python3.9', 'python3', 'python', 'python.exe']
463463
for location in search_locations:
464464
if location.is_dir():
465465
for exe_name in possible_exe_names:
@@ -553,6 +553,27 @@ def run_verbose(cmd: List[str], error_msg: str):
553553
safe_print(' ----------------')
554554
raise
555555
try:
556+
safe_print(' - Attempting bootstrap with built-in ensurepip (most reliable)...')
557+
ensurepip_cmd = [str(python_exe), '-m', 'ensurepip', '--upgrade']
558+
run_verbose(ensurepip_cmd, "ensurepip bootstrap failed.")
559+
safe_print(' ✅ Pip bootstrap complete via ensurepip.')
560+
core_deps = _get_core_dependencies()
561+
if core_deps:
562+
safe_print(_(' - Installing omnipkg core dependencies...'))
563+
deps_install_cmd = [str(python_exe), '-m', 'pip', 'install', '--no-cache-dir'] + sorted(list(core_deps))
564+
run_verbose(deps_install_cmd, 'Failed to install omnipkg dependencies.')
565+
safe_print(_(' ✅ Core dependencies installed.'))
566+
safe_print(_(' - Installing omnipkg application layer...'))
567+
project_root = self._find_project_root()
568+
if project_root:
569+
safe_print(_(' (Developer mode detected: performing editable install)'))
570+
install_cmd = [str(python_exe), '-m', 'pip', 'install', '--no-cache-dir', '--no-deps', '-e', str(project_root)]
571+
else:
572+
safe_print(' (Standard mode detected: installing from PyPI)')
573+
install_cmd = [str(python_exe), '-m', 'pip', 'install', '--no-cache-dir', '--no-deps', 'omnipkg']
574+
run_verbose(install_cmd, 'Failed to install omnipkg application.')
575+
safe_print(_(' ✅ Omnipkg bootstrapped successfully!'))
576+
except Exception as e:
556577
safe_print(_(' - Bootstrapping pip, setuptools, wheel...'))
557578
with tempfile.NamedTemporaryFile(suffix='.py', delete=False, mode='w', encoding='utf-8') as tmp_file:
558579
script_path = tmp_file.name
@@ -790,7 +811,8 @@ def _install_managed_python(self, venv_path: Path, full_version: str) -> Path:
790811
py_arch = py_arch_map.get(arch)
791812
if not py_arch:
792813
raise OSError(_('Unsupported architecture: {}').format(arch))
793-
VERSION_TO_RELEASE_TAG_MAP = {'3.13.7': '20250818', '3.13.6': '20250807', '3.13.1': '20241211', '3.13.0': '20241016', '3.12.11': '20250818', '3.12.8': '20241211', '3.12.7': '20241008', '3.12.6': '20240814', '3.12.5': '20240726', '3.12.4': '20240726', '3.12.3': '20240415', '3.11.13': '20250603', '3.11.12': '20241211', '3.11.10': '20241008', '3.11.9': '20240726', '3.11.6': '20231002', '3.10.18': '20250818', '3.10.15': '20241008', '3.10.14': '20240726', '3.10.13': '20231002', '3.9.23': '20250818', '3.9.21': '20241211', '3.9.20': '20241008', '3.9.19': '20240726', '3.9.18': '20231002'}
814+
VERSION_TO_RELEASE_TAG_MAP = {'3.8.20': '20241002',
815+
'3.14.0': '20251014','3.13.7': '20250818', '3.13.6': '20250807', '3.13.1': '20241211', '3.13.0': '20241016', '3.12.11': '20250818', '3.12.8': '20241211', '3.12.7': '20241008', '3.12.6': '20240814', '3.12.5': '20240726', '3.12.4': '20240726', '3.12.3': '20240415', '3.11.13': '20250603', '3.11.12': '20241211', '3.11.10': '20241008', '3.11.9': '20240726', '3.11.6': '20231002', '3.10.18': '20250818', '3.10.15': '20241008', '3.10.14': '20240726', '3.10.13': '20231002', '3.9.23': '20250818', '3.9.21': '20241211', '3.9.20': '20241008', '3.9.19': '20240726', '3.9.18': '20231002'}
794816
release_tag = VERSION_TO_RELEASE_TAG_MAP.get(full_version)
795817
if not release_tag:
796818
available_versions = list(VERSION_TO_RELEASE_TAG_MAP.keys())
@@ -2994,7 +3016,7 @@ def redis_key_prefix(self) -> str:
29943016
py_ver_str = f'py{match.group(1)}'
29953017
else:
29963018
try:
2997-
result = subprocess.run([python_exe_path, '-c', "import sys; safe_print(f'py{sys.version_info.major}.{sys.version_info.minor}')"], capture_output=True, text=True, check=True, timeout=2)
3019+
result = subprocess.run([python_exe_path, '-c', "import sys; print(f'py{sys.version_info.major}.{sys.version_info.minor}')"], capture_output=True, text=True, check=True, timeout=2)
29983020
py_ver_str = result.stdout.strip()
29993021
except Exception:
30003022
py_ver_str = f'py{sys.version_info.major}.{sys.version_info.minor}'
@@ -4635,7 +4657,7 @@ def _is_interpreter_directory_valid(self, path: Path) -> bool:
46354657
return False
46364658
bin_dir = path / 'bin'
46374659
if bin_dir.is_dir():
4638-
for name in ['python', 'python3', 'python3.9', 'python3.10', 'python3.11', 'python3.12']:
4660+
for name in ['python3.14', 'python3.13', 'python3.12', 'python3.11', 'python3.10', 'python3.9', 'python3.8', 'python3', 'python', 'python.exe']:
46394661
exe_path = bin_dir / name
46404662
if exe_path.is_file() and os.access(exe_path, os.X_OK):
46414663
try:
@@ -4673,7 +4695,7 @@ def _fallback_to_download(self, version: str) -> int:
46734695
"""
46744696
safe_print(_('\n--- Running robust download strategy ---'))
46754697
try:
4676-
full_versions = {'3.13': '3.13.7', '3.12': '3.12.11', '3.11': '3.11.9', '3.10': '3.10.18', '3.9': '3.9.23'}
4698+
full_versions = {'3.14': '3.14.0', '3.13': '3.13.7', '3.12': '3.12.11', '3.11': '3.11.9', '3.10': '3.10.18', '3.9': '3.9.23', '3.8': '3.8.20'}
46774699
full_version = full_versions.get(version)
46784700
if not full_version:
46794701
safe_print(f'❌ Error: No known standalone build for Python {version}.')
@@ -5652,7 +5674,6 @@ def _heal_conda_environment(self, also_run_clean: bool = True):
56525674
if not conda_meta_path.is_dir():
56535675
return # No metadata directory
56545676

5655-
safe_print('\n' + '─' * 60)
56565677
safe_print("🛡️ AUTO-HEAL: Scanning conda environment for corruption...")
56575678

56585679
# Proactive scan for corrupted files
@@ -5687,7 +5708,6 @@ def _heal_conda_environment(self, also_run_clean: bool = True):
56875708

56885709
if not corrupted_files_found:
56895710
safe_print(" - ✅ No corruption detected in conda metadata")
5690-
safe_print('─' * 60)
56915711
return
56925712

56935713
# Healing process

omnipkg/package_meta_builder.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@
3333
import subprocess
3434
import json
3535
import re
36-
36+
try:
37+
import safety
38+
SAFETY_AVAILABLE = True
39+
except ImportError:
40+
SAFETY_AVAILABLE = False
3741
# Add this global recursion tracking code at module level (after imports, before class definition)
3842
_security_scan_depth = threading.local()
3943
_max_depth = 10 # Adjust as needed
@@ -584,6 +588,23 @@ def _perform_security_scan(self, packages: Dict[str, str]):
584588
Runs a security check using a dedicated, isolated 'safety' tool bubble,
585589
created on-demand by the bubble_manager to guarantee isolation.
586590
"""
591+
is_incompatible_with_safety = False
592+
if self.target_context_version:
593+
try:
594+
major, minor = map(int, self.target_context_version.split('.'))
595+
if (major, minor) >= (3, 14):
596+
is_incompatible_with_safety = True
597+
except (ValueError, TypeError):
598+
pass
599+
600+
if is_incompatible_with_safety:
601+
safe_print("🛡️ 'safety' is incompatible with Python 3.14+. Using 'pip audit' as a fallback.")
602+
self._run_pip_audit_fallback(packages)
603+
return
604+
if not SAFETY_AVAILABLE:
605+
safe_print("⚠️ Security scan skipped: 'safety' package is not installed or is incompatible with the current Python version.")
606+
self.security_report = {}
607+
return
587608
safe_print(f'🛡️ Performing security scan for {len(packages)} active package(s) using isolated tool...')
588609
if not packages:
589610
safe_print(_(' - No active packages found to scan.'))
@@ -639,6 +660,60 @@ def _perform_security_scan(self, packages: Dict[str, str]):
639660
issue_count = len(self.security_report['vulnerabilities'])
640661
safe_print(_('✅ Security scan complete. Found {} potential issues.').format(issue_count))
641662

663+
def _run_pip_audit_fallback(self, packages: Dict[str, str]):
664+
"""Runs `pip audit` as a fallback security scanner."""
665+
if not packages:
666+
safe_print(_(' - No active packages found to scan.'))
667+
self.security_report = {}
668+
return
669+
670+
reqs_file_path = None
671+
try:
672+
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as reqs_file:
673+
reqs_file_path = reqs_file.name
674+
for name, version in packages.items():
675+
reqs_file.write(f'{name}=={version}\n')
676+
677+
python_exe = self.config.get('python_executable', sys.executable)
678+
cmd = [python_exe, '-m', 'pip', 'audit', '--json', '-r', reqs_file_path]
679+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
680+
681+
if result.returncode == 0 and result.stdout:
682+
audit_data = json.loads(result.stdout)
683+
self.security_report = self._parse_pip_audit_output(audit_data)
684+
else:
685+
self.security_report = [] # No issues found or error occurred
686+
687+
issue_count = len(self.security_report)
688+
safe_print(_('✅ Security scan complete (via pip audit). Found {} potential issues.').format(issue_count))
689+
690+
except (json.JSONDecodeError, subprocess.SubprocessError, FileNotFoundError) as e:
691+
safe_print(_(' ⚠️ An error occurred during the pip audit fallback scan: {}').format(e))
692+
self.security_report = {}
693+
finally:
694+
if reqs_file_path and os.path.exists(reqs_file_path):
695+
os.unlink(reqs_file_path)
696+
697+
def _parse_pip_audit_output(self, audit_data: List[Dict]) -> List[Dict]:
698+
"""
699+
Parses the JSON output from `pip audit` and transforms it into the same
700+
format used by the `safety` tool for consistency.
701+
"""
702+
report = []
703+
for item in audit_data:
704+
package_name = item.get('name')
705+
installed_version = item.get('version')
706+
for vuln in item.get('vulns', []):
707+
report.append({
708+
"package_name": package_name,
709+
"vulnerable_spec": f"<{','.join(vuln.get('fixed_in', []))}",
710+
"analyzed_version": installed_version,
711+
"advisory": vuln.get('summary', 'N/A'),
712+
"vulnerability_id": vuln.get('id', 'N/A'),
713+
"fixed_in": vuln.get('fixed_in', []),
714+
})
715+
return report
716+
642717
def run(self, targeted_packages: Optional[List[str]]=None, newly_active_packages: Optional[Dict[str, str]]=None):
643718
"""
644719
(V5.3 - Robust Path Fix) The main execution loop. Now uses robust logic
@@ -728,12 +803,8 @@ def run(self, targeted_packages: Optional[List[str]]=None, newly_active_packages
728803
total_packages = len(distributions_to_process)
729804
pkgs_per_sec = total_packages / total_time if total_time > 0 else float('inf')
730805

731-
print("\n" + "="*60)
732806
print("🚀 KNOWLEDGE BASE BUILD - PERFORMANCE SUMMARY 🚀")
733-
print("="*60)
734-
print(f" - Processed {total_packages} packages in {total_time:.2f} seconds.")
735807
print(f" - 🔥 Average Throughput: {pkgs_per_sec:.2f} pkg/s")
736-
print("="*60 + "\n")
737808

738809
safe_print(_('🎉 Metadata building complete! Updated {} package(s) for this context.').format(updated_count))
739810
return distributions_to_process

pyproject.toml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
[build-system]
2-
requires = ["setuptools>=61.0", "pip==25.1.1"]
2+
requires = ["setuptools>=61.0"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "omnipkg"
7-
version = "1.5.5"
7+
version = "1.5.6"
88
authors = [
99
{ name = "1minds3t", email = "1minds3t@proton.me" },
1010
]
1111
description = "The Ultimate Python Dependency Resolver. One environment. Infinite packages. Zero conflicts."
1212
readme = "README.md"
13-
requires-python = ">=3.9, <3.14"
13+
requires-python = ">=3.7, <3.16"
1414
license = { text = "AGPL-3.0-only OR LicenseRef-Proprietary" }
1515
classifiers = [
1616
"Development Status :: 5 - Production/Stable",
@@ -19,11 +19,14 @@ classifiers = [
1919
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
2020
"Operating System :: OS Independent",
2121
"Programming Language :: Python :: 3",
22+
"Programming Language :: Python :: 3.8",
2223
"Programming Language :: Python :: 3.9",
2324
"Programming Language :: Python :: 3.10",
2425
"Programming Language :: Python :: 3.11",
2526
"Programming Language :: Python :: 3.12",
2627
"Programming Language :: Python :: 3.13",
28+
"Programming Language :: Python :: 3.14",
29+
"Programming Language :: Python :: 3.15",
2730
"Environment :: Console",
2831
"Topic :: Software Development :: Build Tools",
2932
"Topic :: System :: Software Distribution",
@@ -38,9 +41,9 @@ dependencies = [
3841
"authlib>=1.6.5",
3942
"filelock>=3.9",
4043
"tomli; python_version < '3.11'",
41-
"safety>=2.3.5,<3.0; python_version < '3.10'",
42-
"safety>=3.0; python_version >= '3.10'",
44+
"safety>=3.0; python_version >= '3.10' and python_version < '3.14'",
4345
"aiohttp",
46+
"pip-audit>=2.6.0; python_version >= '3.14'",
4447
"uv"
4548
]
4649

0 commit comments

Comments
 (0)