-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/self update #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 3 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
65a5a72
Add sync command as replacement for current update command
barisgit bd261a9
Implement auto update and modify update command to use that
barisgit cdd1699
Fix other references and docs regarding previous changes
barisgit a229e75
Implement coderabbit suggestions
barisgit 0db6a05
Add pre-commit config
barisgit e91a265
Fix requests version for python 3.8
barisgit 010d657
Use tuple from typing and fix pre-commit hooks
barisgit File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| """ | ||
| KiCad Library Manager - Library management utilities for KiCad | ||
| """ | ||
|
|
||
| __version__ = "0.4.0" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| """ | ||
| Auto-update functionality for KiLM. | ||
| Handles installation method detection, PyPI integration, and update execution. | ||
| """ | ||
|
|
||
| import json | ||
| import os | ||
| import subprocess | ||
| import sys | ||
| import time | ||
| from pathlib import Path | ||
| from typing import Dict, Optional | ||
|
|
||
| import requests | ||
|
|
||
barisgit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def detect_installation_method() -> str: | ||
| """ | ||
| Detect how KiLM was installed to determine appropriate update strategy. | ||
| Returns: 'pipx' | 'pip' | 'conda' | 'uv' | 'homebrew' | 'unknown' | ||
| """ | ||
| executable_path = Path(sys.executable) | ||
|
|
||
| # Check for pipx installation | ||
| if any( | ||
| part in str(executable_path) for part in [".local/share/pipx", "pipx/venvs"] | ||
| ): | ||
| return "pipx" | ||
|
|
||
| if os.environ.get("PIPX_HOME") and "pipx" in str(executable_path): | ||
| return "pipx" | ||
|
|
||
| # Check for conda installation | ||
| if os.environ.get("CONDA_DEFAULT_ENV") or "conda" in str(executable_path): | ||
| return "conda" | ||
|
|
||
| # Check for homebrew installation | ||
| if any( | ||
| path in str(executable_path) for path in ["/opt/homebrew", "/usr/local/Cellar"] | ||
| ): | ||
| return "homebrew" | ||
|
|
||
| # Check for uv installation | ||
| if os.environ.get("UV_PROJECT_ENVIRONMENT") or "uv" in str(executable_path): | ||
| return "uv" | ||
|
|
||
| # Check for virtual environment (pip in venv) | ||
| if os.environ.get("VIRTUAL_ENV"): | ||
| return "pip-venv" | ||
|
|
||
| # Default to system pip | ||
| return "pip" | ||
|
|
||
barisgit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| class PyPIVersionChecker: | ||
| """Responsible PyPI API client with caching and proper headers.""" | ||
|
|
||
| def __init__(self, package_name: str): | ||
| self.package_name = package_name | ||
| self.base_url = f"https://pypi.org/pypi/{package_name}/json" | ||
| self.cache_file = Path.home() / ".cache" / "kilm" / "version_check.json" | ||
| self.user_agent = "KiLM/0.4.0 (+https://github.com/barisgit/KiLM)" | ||
|
|
||
barisgit marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| def check_latest_version(self) -> Optional[str]: | ||
| """ | ||
| Check latest version from PyPI with caching and rate limiting. | ||
| Returns None if check fails or is rate limited. | ||
| """ | ||
| try: | ||
| headers = {"User-Agent": self.user_agent} | ||
|
|
||
| # Use cached ETag if available | ||
| cached_data = self._load_cache() | ||
| if cached_data is not None and "etag" in cached_data: | ||
| headers["If-None-Match"] = cached_data["etag"] | ||
|
|
||
| response = requests.get(self.base_url, headers=headers, timeout=10) | ||
|
|
||
| if response.status_code == 304: # Not Modified | ||
| return cached_data.get("version") if cached_data else None | ||
|
|
||
| if response.status_code == 200: | ||
| data = response.json() | ||
| latest_version = data["info"]["version"] | ||
|
|
||
| # Cache response with ETag | ||
| self._save_cache( | ||
| { | ||
| "version": latest_version, | ||
| "etag": response.headers.get("ETag"), | ||
| "timestamp": time.time(), | ||
| } | ||
| ) | ||
|
|
||
| return latest_version | ||
|
|
||
| except (requests.RequestException, KeyError, json.JSONDecodeError): | ||
| # Fail silently - don't block CLI functionality | ||
| pass | ||
|
|
||
| return None | ||
|
|
||
| def _load_cache(self) -> Optional[Dict]: | ||
| """Load cached version data.""" | ||
| if self.cache_file.exists(): | ||
| try: | ||
| with Path(self.cache_file).open() as f: | ||
| data = json.load(f) | ||
| # Cache valid for 24 hours | ||
| if time.time() - data.get("timestamp", 0) < 86400: | ||
| return data | ||
| except (json.JSONDecodeError, KeyError): | ||
| pass | ||
| return None | ||
|
|
||
| def _save_cache(self, data: Dict): | ||
| """Save version data to cache.""" | ||
| self.cache_file.parent.mkdir(parents=True, exist_ok=True) | ||
| with Path(self.cache_file).open("w") as f: | ||
| json.dump(data, f) | ||
|
|
||
|
|
||
| def update_via_pipx() -> bool: | ||
| """Update KiLM via pipx. Most reliable method for CLI tools.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["pipx", "upgrade", "kilm"], capture_output=True, text=True, timeout=300 | ||
| ) | ||
| return result.returncode == 0 | ||
| except (subprocess.TimeoutExpired, FileNotFoundError): | ||
| return False | ||
|
|
||
|
|
||
| def update_via_pip() -> bool: | ||
| """Update KiLM via pip.""" | ||
| try: | ||
| # Use same Python interpreter that's running KiLM | ||
| result = subprocess.run( | ||
| [sys.executable, "-m", "pip", "install", "--upgrade", "kilm"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=300, | ||
| ) | ||
| return result.returncode == 0 | ||
| except (subprocess.TimeoutExpired, FileNotFoundError): | ||
| return False | ||
|
|
||
|
|
||
| def update_via_uv() -> bool: | ||
| """Update KiLM via uv.""" | ||
| try: | ||
| result = subprocess.run( | ||
| ["uv", "tool", "upgrade", "kilm"], | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=300, | ||
| ) | ||
| return result.returncode == 0 | ||
| except (subprocess.TimeoutExpired, FileNotFoundError): | ||
| return False | ||
|
|
||
|
|
||
| class UpdateManager: | ||
| """Manages update checking and execution for KiLM.""" | ||
|
|
||
| def __init__(self, current_version: str): | ||
| self.version_checker = PyPIVersionChecker("kilm") | ||
| self.current_version = current_version | ||
| self.installation_method = detect_installation_method() | ||
|
|
||
| def check_latest_version(self) -> Optional[str]: | ||
| """Check for latest version available on PyPI.""" | ||
| return self.version_checker.check_latest_version() | ||
|
|
||
| def is_newer_version_available(self, latest_version: str) -> bool: | ||
| """Compare versions to determine if update is available.""" | ||
| try: | ||
| # Simple version comparison for now | ||
| current_parts = [int(x) for x in self.current_version.split(".")] | ||
| latest_parts = [int(x) for x in latest_version.split(".")] | ||
|
|
||
| # Pad shorter version with zeros | ||
| max_len = max(len(current_parts), len(latest_parts)) | ||
| current_parts.extend([0] * (max_len - len(current_parts))) | ||
| latest_parts.extend([0] * (max_len - len(latest_parts))) | ||
|
|
||
| return latest_parts > current_parts | ||
| except (ValueError, AttributeError): | ||
| return False | ||
barisgit marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def get_update_instruction(self) -> str: | ||
| """Get update instruction for the detected installation method.""" | ||
| instructions = { | ||
| "pipx": "pipx upgrade kilm", | ||
| "pip": "pip install --upgrade kilm", | ||
| "pip-venv": "pip install --upgrade kilm", | ||
| "uv": "uv tool upgrade kilm", | ||
| "conda": "conda update kilm", | ||
| "homebrew": "brew upgrade kilm", | ||
| } | ||
| return instructions.get(self.installation_method, "Check your package manager") | ||
|
|
||
| def can_auto_update(self) -> bool: | ||
| """Check if automatic update is possible for this installation method.""" | ||
| return self.installation_method in ["pipx", "pip", "pip-venv", "uv"] | ||
|
|
||
| def perform_update(self) -> tuple[bool, str]: | ||
| """ | ||
| Execute update using detected installation method. | ||
| Returns: (success: bool, message: str) | ||
| """ | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if not self.can_auto_update(): | ||
| instruction = self.get_update_instruction() | ||
| return False, f"Manual update required. Run: {instruction}" | ||
|
|
||
| update_functions = { | ||
| "pipx": update_via_pipx, | ||
| "pip": update_via_pip, | ||
| "pip-venv": update_via_pip, | ||
| "uv": update_via_uv, | ||
| } | ||
|
|
||
| update_func = update_functions.get(self.installation_method) | ||
| if update_func: | ||
| try: | ||
| success = update_func() | ||
| if success: | ||
| return True, "KiLM updated successfully!" | ||
| else: | ||
| instruction = self.get_update_instruction() | ||
| return False, f"Auto-update failed. Try manually: {instruction}" | ||
| except Exception as e: | ||
| return False, f"Update error: {str(e)}" | ||
| else: | ||
| instruction = self.get_update_instruction() | ||
| return False, f"Unsupported installation method. Run: {instruction}" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.