A CLI tool to find the latest stable version of an arbitrary project
—
Custom exception classes for specific error conditions encountered during version discovery and file operations. These exceptions provide structured error handling and enable applications to respond appropriately to different failure scenarios.
Exception classes for handling API-related errors and authentication failures across different platforms.
class ApiCredentialsError(Exception):
"""
Raised when there's an API error related to credentials or authentication.
This exception occurs when:
- API tokens are invalid or expired
- Rate limits are exceeded due to unauthenticated requests
- Private repositories require authentication
- API endpoints require elevated permissions
Common scenarios:
- GitHub API rate limiting without token
- GitLab private project access
- Enterprise instance authentication failures
"""
def __init__(self, message: str, platform: str = None):
"""
Initialize API credentials error.
Parameters:
- message: Descriptive error message
- platform: Platform where error occurred (github, gitlab, etc.)
"""Exception for handling cases where specified projects or repositories cannot be found or accessed.
class BadProjectError(Exception):
"""
Raised when no such project exists or cannot be accessed.
This exception occurs when:
- Repository does not exist on specified platform
- Project has been deleted or made private
- Invalid repository identifier format
- Platform does not host the specified project
- Network issues prevent project access
Common scenarios:
- Typos in repository names
- Projects moved between platforms
- Private repositories without access
- Deleted or archived projects
"""
def __init__(self, message: str, repo: str = None):
"""
Initialize bad project error.
Parameters:
- message: Descriptive error message
- repo: Repository identifier that caused the error
"""Exception for security-related errors during file operations, particularly archive extraction.
class TarPathTraversalException(Exception):
"""
Custom exception for path traversal attempts during tar extraction.
This exception is raised when archive extraction detects potentially
malicious archive contents that attempt to write files outside the
intended extraction directory.
Security concerns addressed:
- Path traversal attacks using "../" sequences
- Absolute path entries in archives
- Symbolic link attacks
- Archive bombs (deeply nested directories)
Prevention measures:
- All paths are validated before extraction
- Relative paths are enforced
- Symbolic links are restricted or resolved safely
"""
def __init__(self, message: str, path: str = None):
"""
Initialize path traversal exception.
Parameters:
- message: Descriptive error message
- path: Problematic path that triggered the security check
"""from lastversion import latest
from lastversion.exceptions import ApiCredentialsError
def get_version_with_auth_handling(repo):
"""Get version with proper authentication error handling."""
try:
return latest(repo)
except ApiCredentialsError as e:
print(f"Authentication required for {repo}")
print("To resolve:")
print("1. Set GITHUB_TOKEN environment variable")
print("2. Use personal access token for private repos")
print(f"Error details: {e}")
return None
# Example usage
version = get_version_with_auth_handling("private-org/private-repo")
if version:
print(f"Version: {version}")
else:
print("Could not retrieve version due to authentication issues")from lastversion import latest
from lastversion.exceptions import BadProjectError
def find_project_across_platforms(project_name):
"""Try to find project across multiple platforms."""
platforms = ['github', 'gitlab', 'pip', 'sf']
for platform in platforms:
try:
version = latest(project_name, at=platform)
if version:
print(f"Found {project_name} on {platform}: {version}")
return version, platform
except BadProjectError:
print(f"{project_name} not found on {platform}")
continue
except Exception as e:
print(f"Error checking {platform}: {e}")
continue
raise BadProjectError(f"Project {project_name} not found on any platform")
# Example usage
try:
version, platform = find_project_across_platforms("some-project")
print(f"Successfully found project on {platform}")
except BadProjectError as e:
print(f"Project search failed: {e}")from lastversion.utils import extract_file
from lastversion.exceptions import TarPathTraversalException
import os
def safe_extract_with_error_handling(url, extract_dir):
"""Safely extract archive with comprehensive error handling."""
try:
# Ensure extraction directory exists
os.makedirs(extract_dir, exist_ok=True)
# Attempt extraction
result = extract_file(url, extract_dir)
print(f"Successfully extracted to {extract_dir}")
return result
except TarPathTraversalException as e:
print(f"Security error: Archive contains unsafe paths")
print(f"Blocked path: {e}")
print("This archive may be malicious and was not extracted")
return None
except Exception as e:
print(f"Extraction failed: {e}")
return None
# Example usage
url = "https://example.com/suspicious-archive.tar.gz"
result = safe_extract_with_error_handling(url, "/tmp/safe-extract")from lastversion import latest, has_update
from lastversion.exceptions import (
ApiCredentialsError,
BadProjectError,
TarPathTraversalException
)
from packaging.version import InvalidVersion
def robust_version_check(repo, current_version=None):
"""Comprehensive version checking with full error handling."""
try:
# Get latest version
latest_version = latest(repo)
if current_version:
# Check for updates
update = has_update(repo, current_version)
return {
'latest': latest_version,
'current': current_version,
'update_available': bool(update),
'newer_version': update if update else None,
'status': 'success'
}
else:
return {
'latest': latest_version,
'status': 'success'
}
except ApiCredentialsError as e:
return {
'status': 'auth_error',
'error': str(e),
'suggestion': 'Set appropriate API token environment variable'
}
except BadProjectError as e:
return {
'status': 'not_found',
'error': str(e),
'suggestion': 'Check repository name and platform'
}
except InvalidVersion as e:
return {
'status': 'version_error',
'error': f"Invalid version format: {e}",
'suggestion': 'Check version string format'
}
except Exception as e:
return {
'status': 'unknown_error',
'error': str(e),
'suggestion': 'Check network connectivity and repository access'
}
# Example usage
result = robust_version_check("kubernetes/kubernetes", "1.28.0")
if result['status'] == 'success':
print(f"Latest: {result['latest']}")
if result.get('update_available'):
print(f"Update available: {result['current']} → {result['newer_version']}")
else:
print(f"Error ({result['status']}): {result['error']}")
print(f"Suggestion: {result['suggestion']}")from lastversion import latest
from lastversion.exceptions import BadProjectError, ApiCredentialsError
class VersionDiscoveryError(Exception):
"""High-level exception for version discovery failures."""
pass
def enterprise_version_check(internal_repo, fallback_repo=None):
"""Check enterprise repository with fallback handling."""
try:
# Try internal/enterprise repository first
version = latest(internal_repo, at='github')
return version, 'enterprise'
except ApiCredentialsError as e:
print(f"Enterprise auth failed: {e}")
if fallback_repo:
try:
# Fallback to public repository
version = latest(fallback_repo)
return version, 'public_fallback'
except BadProjectError as fallback_error:
# Chain exceptions to preserve error context
raise VersionDiscoveryError(
f"Both enterprise and fallback failed"
) from fallback_error
else:
raise VersionDiscoveryError(
"Enterprise access failed and no fallback configured"
) from e
except BadProjectError as e:
raise VersionDiscoveryError(
f"Enterprise repository not found: {internal_repo}"
) from e
# Example usage with exception chaining
try:
version, source = enterprise_version_check(
"internal/secret-project",
"public/open-project"
)
print(f"Version {version} from {source}")
except VersionDiscoveryError as e:
print(f"Version discovery failed: {e}")
# Access original exception via __cause__
if e.__cause__:
print(f"Root cause: {e.__cause__}")from lastversion import latest
from lastversion.exceptions import BadProjectError, ApiCredentialsError
def get_version_with_retries(repo, max_retries=3):
"""Get version with retry logic based on exception types."""
import time
for attempt in range(max_retries):
try:
return latest(repo)
except ApiCredentialsError:
# Don't retry auth errors
raise
except BadProjectError:
# Don't retry not found errors
raise
except Exception as e:
# Retry network/temporary errors
if attempt < max_retries - 1:
wait_time = 2 ** attempt # Exponential backoff
print(f"Attempt {attempt + 1} failed: {e}")
print(f"Retrying in {wait_time} seconds...")
time.sleep(wait_time)
else:
print(f"All {max_retries} attempts failed")
raise
# Example usage
try:
version = get_version_with_retries("kubernetes/kubernetes")
print(f"Retrieved version: {version}")
except (BadProjectError, ApiCredentialsError) as e:
print(f"Permanent error: {e}")
except Exception as e:
print(f"Failed after retries: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-lastversion