diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..6bfb174 --- /dev/null +++ b/.flake8 @@ -0,0 +1,4 @@ +[flake8] +max-line-length = 120 +extend-ignore = E203 +exclude = .git,__pycache__,docs/source/conf.py,old,build,dist,.venv,.tox diff --git a/src/pyvm_updater/cli.py b/src/pyvm_updater/cli.py index 7a6ffad..1e1afe9 100644 --- a/src/pyvm_updater/cli.py +++ b/src/pyvm_updater/cli.py @@ -549,34 +549,40 @@ def venv() -> None: @click.option("--python", "-p", "python_version", help="Python version to use (e.g., 3.12)") @click.option("--path", type=click.Path(), help="Custom path for the venv") @click.option("--system-site-packages", is_flag=True, help="Include system site-packages") +@click.option("--requirements", "-r", type=click.Path(exists=True), help="Install dependencies from requirements file") def venv_create( name: str, python_version: str | None, path: str | None, system_site_packages: bool, + requirements: str | None, ) -> None: """Create a new virtual environment. Examples: pyvm venv create myproject pyvm venv create myproject --python 3.12 - pyvm venv create myproject --path ./venv + pyvm venv create myproject --requirements requirements.txt """ from pathlib import Path as PathLib from .venv import create_venv venv_path = PathLib(path) if path else None + req_path = PathLib(requirements) if requirements else None click.echo(f"Creating venv '{name}'...") if python_version: click.echo(f"Using Python {python_version}") + if req_path: + click.echo(f"Installing dependencies from {req_path.name}") success, message = create_venv( name=name, python_version=python_version, path=venv_path, system_site_packages=system_site_packages, + requirements_file=req_path, ) if success: diff --git a/src/pyvm_updater/installers.py b/src/pyvm_updater/installers.py index e9c651a..232fecb 100644 --- a/src/pyvm_updater/installers.py +++ b/src/pyvm_updater/installers.py @@ -12,7 +12,7 @@ def update_python_windows(version_str: str, preferred: str = "auto", **kwargs: Any) -> bool: """Update Python on Windows.""" - return _install_with_plugins(version_str, preferred=preferred, **kwargs) + return _install_with_plugins(version_str, preferred=preferred) def update_python_linux( @@ -43,7 +43,8 @@ def _install_with_plugins(version_str: str, preferred: str = "auto", **kwargs: A if preferred != "auto" and installer.get_name() != preferred: click.echo( - f"⚠️ Requested installer '{preferred}' is not supported or not found. Falling back to '{installer.get_name()}'." + f"⚠️ Requested installer '{preferred}' is not supported or not found. " + f"Falling back to '{installer.get_name()}'." ) return installer.install(version_str, **kwargs) diff --git a/src/pyvm_updater/tui.py b/src/pyvm_updater/tui.py index 30bf1fd..dd432cc 100644 --- a/src/pyvm_updater/tui.py +++ b/src/pyvm_updater/tui.py @@ -299,7 +299,8 @@ def compose(self) -> ComposeResult: yield Label("Loading...") yield Static( - "[dim]Tab: switch panels | Arrow keys: navigate | Enter: install | W: wizard | X: remove | R: refresh | U: update | B: rollback | Q: quit[/dim]", + "[dim]Tab: switch panels | Arrow keys: navigate | Enter: install | X: remove | " + "R: refresh | U: update | B: rollback | Q: quit[/dim]", id="hint-bar", ) diff --git a/src/pyvm_updater/venv.py b/src/pyvm_updater/venv.py index fd468ba..f7212da 100644 --- a/src/pyvm_updater/venv.py +++ b/src/pyvm_updater/venv.py @@ -112,6 +112,7 @@ def create_venv( python_version: str | None = None, path: Path | None = None, system_site_packages: bool = False, + requirements_file: Path | None = None, ) -> tuple[bool, str]: """Create a new virtual environment. @@ -120,6 +121,7 @@ def create_venv( python_version: Python version to use (e.g., "3.12"). If None, uses current Python. path: Custom path for venv. If None, uses default location. system_site_packages: Whether to include system site-packages. + requirements_file: Path to requirements.txt file to install. Returns: Tuple of (success, message). @@ -146,7 +148,6 @@ def create_venv( python_exe = sys.executable python_version = f"{sys.version_info.major}.{sys.version_info.minor}" - # Build venv command cmd = [python_exe, "-m", "venv"] if system_site_packages: cmd.append("--system-site-packages") @@ -169,7 +170,43 @@ def create_venv( } save_venv_registry(registry) - return True, f"Created venv '{name}' at {venv_path}" + success_msg = f"Created venv '{name}' at {venv_path}" + + # Install requirements if specified + if requirements_file: + os_name, _ = get_os_info() + if os_name == "windows": + pip_exe = venv_path / "Scripts" / "pip.exe" + else: + pip_exe = venv_path / "bin" / "pip" + + if not pip_exe.exists(): + if os_name == "windows": + python_in_venv = venv_path / "Scripts" / "python.exe" + else: + python_in_venv = venv_path / "bin" / "python" + pip_cmd = [str(python_in_venv), "-m", "pip"] + else: + pip_cmd = [str(pip_exe)] + + try: + # Upgrade pip first (optional helper) + # subprocess.run(pip_cmd + ["install", "--upgrade", "pip"], capture_output=True, check=False) + + # Install requirements + subprocess.run( + pip_cmd + ["install", "-r", str(requirements_file)], capture_output=True, text=True, check=True + ) + success_msg += f"\n Installed requirements from {requirements_file.name}" + except subprocess.CalledProcessError as e: + error_output = e.stderr or e.stdout + return ( + True, + f"{success_msg}\n ⚠️ Warning: Failed to install requirements from {requirements_file.name}:\n" + f"{error_output}", + ) + + return True, success_msg except subprocess.CalledProcessError as e: return ( diff --git a/tests/test_venv.py b/tests/test_venv.py index 1fbaf27..db5fb96 100644 --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -45,6 +45,29 @@ def test_create_venv_already_exists(self, temp_venv_dir): assert success is False assert "already exists" in message + def test_create_venv_with_requirements(self, temp_venv_dir): + """Test venv creation with requirements file.""" + venv_path = temp_venv_dir / "req_venv" + req_file = temp_venv_dir / "requirements.txt" + req_file.write_text("requests==2.25.0") + + with patch("pyvm_updater.venv.get_venv_dir", return_value=temp_venv_dir): + with patch("pyvm_updater.venv.save_venv_registry"): + with patch("pyvm_updater.venv.subprocess.run") as mock_run: + success, message = create_venv("req_venv", path=venv_path, requirements_file=req_file) + + assert success is True + assert "Installed requirements" in message + + # Verify pip install was called + assert mock_run.call_count == 2 + + args, _ = mock_run.call_args_list[1] + cmd = args[0] + assert "install" in cmd + assert "-r" in cmd + assert str(req_file) in cmd + class TestListVenvs: """Tests for list_venvs function."""