From 1f1e32d50a76fcc5f538490e79449179b4ae5174 Mon Sep 17 00:00:00 2001 From: psywarrior1998 Date: Sun, 10 Aug 2025 15:44:02 +0530 Subject: [PATCH 1/3] updated TOML file --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index fa8ac64..d309391 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ultimate-python-upgrader" -version = "1.0.0" +version = "1.1.0" authors = [ { name="Your Name", email="your@email.com" }, ] From 0061ee4688703de73563f19ccc8f4fdf2b66aee0 Mon Sep 17 00:00:00 2001 From: psywarrior1998 Date: Sun, 10 Aug 2025 18:48:51 +0530 Subject: [PATCH 2/3] docs: Update project files for v1.2.0 release --- README.md | 29 +++++++++++++++++++---------- pyproject.toml | 10 +++++----- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index eb324d0..7a2f4e0 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,64 @@ # Ultimate Python Upgrader (`py-upgrade`) -[![CI](https://github.com/psywarrior1998/upgrade_all_python/actions/workflows/ci.yml/badge.svg)](https://github.com/psywarrior1998/upgrade_all_python/actions/workflows/ci.yml) [![PyPI version](https://badge.fury.io/py/ultimate-python-upgrader.svg)](https://badge.fury.io/py/ultimate-python-upgrader) +[![CI](https://github.com/psywarrior1998/upgrade_all_python/actions/workflows/ci.yml/badge.svg)](https://github.com/psywarrior1998/upgrade_all_python/actions/workflows/ci.yml) + +An intelligent, feature-rich CLI tool to manage and upgrade Python packages with a clean, modern interface and a powerful dependency safety-check. + -An intelligent, feature-rich CLI tool to manage and upgrade Python packages with a clean, modern interface. ## Key Features -- **Interactive & Beautiful UI**: Uses Rich to display outdated packages in a clean, readable table. -- **Blazing Fast**: Upgrades packages with a clear progress bar. +- **Intelligent Dependency Analysis**: Automatically performs a pre-flight check to detect and warn you about potential dependency conflicts *before* you upgrade, preventing broken environments. +- **Concurrent & Fast**: Upgrades packages in parallel using multiple workers, dramatically reducing the time you spend waiting. +- **Rich & Interactive UI**: Uses `rich` to display outdated packages in a clean, readable table with clear progress bars. - **Selective Upgrades**: Upgrade all packages, or specify exactly which ones to include or exclude. -- **Safety First**: Includes a `--dry-run` mode to see what would be upgraded without making changes. +- **Safety First**: Includes a `--dry-run` mode to see what would be upgraded without making any changes. - **Automation Friendly**: A `--yes` flag allows for use in automated scripts. ## Installation -The tool is now available on PyPI. Install it with pip: +The tool is available on PyPI. Install it with pip: ```bash pip install ultimate-python-upgrader -``` +```` ## Usage Once installed, the `py-upgrade` command will be available. **1. Check and upgrade all packages interactively** +The tool will first check for dependency conflicts before asking to proceed. + ```bash py-upgrade ``` -**2. Upgrade all packages without confirmation** +**2. Upgrade with more parallel workers** + ```bash -py-upgrade --yes +py-upgrade --yes --workers 20 ``` **3. Perform a dry run to see what needs upgrading** + ```bash py-upgrade --dry-run ``` **4. Upgrade only specific packages** + ```bash py-upgrade numpy pandas ``` **5. Upgrade all packages EXCEPT certain ones** + ```bash py-upgrade --exclude black ruff ``` ## Contributing -Contributions are welcome! Please feel free to submit a pull request. +Contributions are welcome\! Please feel free to submit a pull request. diff --git a/pyproject.toml b/pyproject.toml index 2105cf8..a2c0470 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,11 +4,11 @@ build-backend = "hatchling.build" [project] name = "ultimate-python-upgrader" -version = "1.1.0" +version = "1.2.0" authors = [ { name="Your Name", email="your@email.com" }, ] -description = "An intelligent, feature-rich CLI tool to manage and upgrade Python packages." +description = "An intelligent, feature-rich CLI tool to manage and upgrade Python packages with dependency analysis." readme = "README.md" requires-python = ">=3.8" license = { file="LICENSE" } @@ -32,11 +32,11 @@ dependencies = [ [project.scripts] py-upgrade = "upgrade_tool.main:app" -# --- ADD THIS SECTION --- -[tool.hatch.build.targets.wheel] -packages = ["upgrade_tool"] [project.optional-dependencies] test = [ "pytest>=8.0.0", "pytest-cov>=4.0.0" ] + +[tool.hatch.build.targets.wheel] +packages = ["upgrade_tool"] \ No newline at end of file From dbb219769b5fdeb65e5e9661c9fcf3adea81958c Mon Sep 17 00:00:00 2001 From: psywarrior1998 Date: Sun, 10 Aug 2025 18:52:22 +0530 Subject: [PATCH 3/3] feat: Add a description of the new feature --- upgrade_tool/main.py | 89 ++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/upgrade_tool/main.py b/upgrade_tool/main.py index dbba525..8e27426 100644 --- a/upgrade_tool/main.py +++ b/upgrade_tool/main.py @@ -1,41 +1,68 @@ import subprocess import sys +import re from typing import List, Optional, Tuple import typer from rich.console import Console +from rich.panel import Panel from rich.progress import Progress, SpinnerColumn, BarColumn, TextColumn - -# Import the concurrent futures module for threading from concurrent.futures import ThreadPoolExecutor, as_completed -# Import the refactored utility functions from .utils import get_outdated_packages, generate_packages_table -# Initialize Rich Console for beautiful printing console = Console() -# Create a Typer app for our CLI app = typer.Typer( name="py-upgrade", help="An intelligent, feature-rich CLI tool to manage and upgrade Python packages.", add_completion=False, ) -def upgrade_package(pkg: dict) -> Tuple[str, str, bool]: +def check_for_conflicts(packages_to_check: List[str]) -> Optional[str]: """ - Worker function to upgrade a single package in a separate thread. - + Performs a dry-run upgrade to detect dependency conflicts. + Args: - pkg: A dictionary containing package information ('name', 'latest_version'). + packages_to_check: A list of package names to be upgraded. Returns: - A tuple containing (package_name, latest_version, success_boolean). + A formatted string of conflict messages, or None if no conflicts are found. + """ + console.print("\n[bold cyan]Checking for potential dependency conflicts...[/bold cyan]") + command = [ + sys.executable, + "-m", + "pip", + "install", + "--dry-run", + "--upgrade", + ] + packages_to_check + + process = subprocess.Popen( + command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, encoding='utf-8' + ) + _, stderr = process.communicate() + + # Find the specific dependency conflict block in pip's output + conflict_match = re.search( + r"ERROR: pip's dependency resolver does not currently take into account all the packages that are installed\. This behaviour is the source of the following dependency conflicts\.(.+)", + stderr, + re.DOTALL, + ) + + if conflict_match: + conflict_text = conflict_match.group(1).strip() + return conflict_text + return None + +def upgrade_package(pkg: dict) -> Tuple[str, str, bool]: + """ + Worker function to upgrade a single package in a separate thread. """ pkg_name = pkg['name'] latest_version = pkg['latest_version'] try: - # Execute the pip upgrade command, suppressing output subprocess.check_call( [sys.executable, "-m", "pip", "install", "--upgrade", pkg_name], stdout=subprocess.DEVNULL, @@ -61,18 +88,18 @@ def upgrade( ), workers: int = typer.Option( 10, "--workers", "-w", help="Number of concurrent workers for parallel upgrades." - ) + ), ): """ - Checks for and concurrently upgrades outdated Python packages. + Checks for and concurrently upgrades outdated Python packages with dependency analysis. """ - # --- Filtering Logic (Unchanged) --- outdated_packages = get_outdated_packages() if not outdated_packages: console.print("[bold green]✨ All packages are up to date! ✨[/bold green]") raise typer.Exit() + # --- Filtering Logic --- if packages_to_upgrade: name_to_pkg = {pkg['name'].lower(): pkg for pkg in outdated_packages} target_packages = [name_to_pkg[name.lower()] for name in packages_to_upgrade if name.lower() in name_to_pkg] @@ -87,17 +114,37 @@ def upgrade( console.print("[bold yellow]No packages match the specified criteria for upgrade.[/bold yellow]") raise typer.Exit() - # --- Display and Confirmation (Unchanged) --- table = generate_packages_table(target_packages, title="Outdated Python Packages") console.print(table) if dry_run: - console.print(f"\n[bold yellow]--dry-run enabled. Would upgrade {len(target_packages)} packages with {workers} workers.[/bold yellow]") + console.print(f"\n[bold yellow]--dry-run enabled. Would simulate upgrade of {len(target_packages)} packages.[/bold yellow]") raise typer.Exit() + + # --- Intelligent Dependency Analysis --- + package_names = [pkg['name'] for pkg in target_packages] + conflicts = check_for_conflicts(package_names) + + if conflicts: + console.print( + Panel.fit( + f"[bold]The following dependency conflicts were found:[/bold]\n\n{conflicts}", + title="[bold yellow]⚠️ Dependency Warning[/bold yellow]", + border_style="yellow", + padding=(1, 2), + ) + ) + else: + console.print("[bold green]✅ No dependency conflicts detected.[/bold green]") + # --- Confirmation --- if not yes: + prompt_message = "\nProceed with the upgrade?" + if conflicts: + prompt_message = "\nConflicts were detected. Do you still wish to proceed with the upgrade?" + try: - confirmed = typer.confirm("\nProceed with the upgrade?") + confirmed = typer.confirm(prompt_message) if not confirmed: console.print("Upgrade cancelled by user.") raise typer.Exit() @@ -105,7 +152,7 @@ def upgrade( console.print("\nUpgrade cancelled by user.") raise typer.Exit() - # --- Concurrent Execution Logic (The New Engine) --- + # --- Concurrent Execution Logic --- console.print(f"\n[bold blue]Starting parallel upgrade with {workers} workers...[/bold blue]") progress = Progress( @@ -122,12 +169,9 @@ def upgrade( with progress: upgrade_task = progress.add_task("[green]Upgrading...", total=len(target_packages)) - # Create a thread pool with the specified number of workers with ThreadPoolExecutor(max_workers=workers) as executor: - # Submit an upgrade task for each package future_to_pkg = {executor.submit(upgrade_package, pkg): pkg for pkg in target_packages} - # Process results as they complete for future in as_completed(future_to_pkg): pkg_name, latest_version, success = future.result() @@ -138,10 +182,9 @@ def upgrade( progress.console.print(f" ❌ [red]Failed to upgrade {pkg_name}[/red]") fail_count += 1 - # Advance the progress bar for each completed task progress.advance(upgrade_task) - # --- Summary Report (Unchanged) --- + # --- Summary Report --- console.print("\n--- [bold]Upgrade Complete[/bold] ---") console.print(f"[green]Successfully upgraded:[/green] {success_count} packages") if fail_count > 0: