Spaces:
Sleeping
Sleeping
File size: 8,979 Bytes
4e067f2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 |
# coding: utf-8
# Copyright (c) 2025 inclusionAI.
import os.path
import time
import sys
import importlib
import subprocess
from importlib import metadata
from aworld.logs.util import logger
class ModuleAlias:
def __init__(self, module):
self.module = module
def __getattr__(self, name):
return getattr(self.module, name)
def is_package_installed(package_name: str, version: str = "") -> bool:
"""
Check if package is already installed and matches version if specified.
Args:
package_name: Name of the package to check
version: Required version of the package
Returns:
bool: True if package is installed (and version matches if specified), False otherwise
"""
try:
dist = metadata.distribution(package_name)
if version and dist.version != version:
logger.info(f"Package {package_name} is installed but version {dist.version} "
f"does not match required version {version}")
return False
logger.info(f"Package {package_name} is already installed (version: {dist.version})")
return True
except metadata.PackageNotFoundError:
logger.info(f"Package {package_name} is not installed")
return False
except Exception as e:
logger.warning(f"Error checking if {package_name} is installed: {str(e)}")
return False
def import_packages(packages: list[str]) -> dict:
"""
Import and install multiple packages
Args:
packages: List of packages to import
Returns:
dict: Dictionary mapping package names to imported modules
"""
modules = {}
for package in packages:
package_ = import_package(package)
if package_:
modules[package] = package_
return modules
def import_package(
package_name: str,
alias: str = '',
install_name: str = '',
version: str = '',
installer: str = 'pip',
timeout: int = 300,
retry_count: int = 3,
retry_delay: int = 5
) -> object:
"""
Import and install package if not available.
Args:
package_name: Name of the package to import
alias: Alias to use for the imported module
install_name: Name of the package to install (if different from import name)
version: Required version of the package
installer: Package installer to use ('pip' or 'conda')
timeout: Installation timeout in seconds
retry_count: Number of installation retries if install fails
retry_delay: Delay between retries in seconds
Returns:
Imported module
Raises:
ValueError: If input parameters are invalid
ImportError: If package cannot be imported or installed
TimeoutError: If installation exceeds timeout
"""
# Validate input parameters
if not package_name:
raise ValueError("Package name cannot be empty")
if installer not in ['pip', 'conda']:
raise ValueError(f"Unsupported installer: {installer}")
# Use package_name as install_name if not provided
real_install_name = install_name if install_name else package_name
# First, check if we need to install the package
need_install = False
# Try to import the module first
try:
logger.debug(f"Attempting to import {package_name}")
module = importlib.import_module(package_name)
logger.debug(f"Successfully imported {package_name}")
# If we successfully imported the module, check version if specified
if version:
try:
# For packages with different import and install names,
# we need to check the install name for version info
installed_version = metadata.version(real_install_name)
if installed_version != version:
logger.warning(
f"Package {real_install_name} version mismatch. "
f"Required: {version}, Installed: {installed_version}"
)
need_install = True
except metadata.PackageNotFoundError:
logger.warning(f"Could not determine version for {real_install_name}")
# If no need to reinstall for version mismatch, return the module
if not need_install:
return ModuleAlias(module) if alias else module
except ImportError as import_err:
logger.info(f"Could not import {package_name}: {str(import_err)}")
# Check if the package is installed
if not is_package_installed(real_install_name, version):
need_install = True
else:
# If package is installed but import failed, there might be an issue with dependencies
# or the package itself. Still, let's try to reinstall it.
logger.warning(f"Package {real_install_name} is installed but import of {package_name} failed. "
f"Will attempt reinstallation.")
need_install = True
# Install the package if needed
if need_install:
logger.info(f"Installation needed for {real_install_name}")
# Attempt installation with retries
for attempt in range(retry_count):
try:
cmd = _get_install_command(installer, real_install_name, version)
logger.info(f"Installing {real_install_name} with command: {' '.join(cmd)}")
_execute_install_command(cmd, timeout)
# Break out of retry loop if installation succeeds
break
except (ImportError, TimeoutError, subprocess.SubprocessError) as e:
if attempt < retry_count - 1:
logger.warning(
f"Installation attempt {attempt + 1} failed: {str(e)}. Retrying in {retry_delay} seconds...")
time.sleep(retry_delay)
else:
logger.error(f"All installation attempts failed for {real_install_name}")
raise ImportError(f"Failed to install {real_install_name} after {retry_count} attempts: {str(e)}")
# Try importing after installation
try:
logger.debug(f"Attempting to import {package_name} after installation")
module = importlib.import_module(package_name)
logger.debug(f"Successfully imported {package_name}")
return ModuleAlias(module) if alias else module
except ImportError as e:
error_msg = f"Failed to import {package_name} even after installation of {real_install_name}: {str(e)}"
logger.error(error_msg)
def _get_install_command(installer: str, package_name: str, version: str = "") -> list:
"""
Generate installation command based on specified installer.
Args:
installer: Package installer to use ('pip' or 'conda')
package_name: Name of the package to install
version: Required version of the package
Returns:
list: Command as a list of strings
Raises:
ValueError: If unsupported installer is specified
"""
if installer == 'pip':
# Use sys.executable to ensure the right Python interpreter is used
pytho3 = os.path.basename(sys.executable)
cmd = [sys.executable, '-m', 'pip', 'install', '--upgrade']
if version:
cmd.append(f'{package_name}=={version}')
else:
cmd.append(package_name)
elif installer == 'conda':
cmd = ['conda', 'install', '-y', package_name]
if version:
cmd.extend([f'={version}'])
else:
raise ValueError(f"Unsupported installer: {installer}")
return cmd
def _execute_install_command(cmd: list, timeout: int) -> None:
"""
Execute package installation command.
Args:
cmd: Installation command as list of strings
timeout: Installation timeout in seconds
Raises:
TimeoutError: If installation exceeds timeout
ImportError: If installation fails
"""
logger.info(f"Executing: {' '.join(cmd)}")
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
try:
stdout, stderr = process.communicate(timeout=timeout)
# Log installation output for debugging
if stdout:
logger.debug(f"Installation stdout: {stdout.decode()}")
if stderr:
logger.debug(f"Installation stderr: {stderr.decode()}")
except subprocess.TimeoutExpired:
process.kill()
error_msg = f"Package installation timed out after {timeout} seconds"
logger.error(error_msg)
raise TimeoutError(error_msg)
if process.returncode != 0:
error_msg = f"Installation failed with code {process.returncode}: {stderr.decode()}"
logger.error(error_msg)
raise ImportError(error_msg)
logger.info("Installation completed successfully") |