🎉 COMPLETE IMPLEMENTATION: PyPI Query MCP Server is now a comprehensive PyPI platform management suite! Features Added: ✅ 25 PyPI Platform Tools across 6 categories: - Publishing Tools (6): upload, credentials, history, delete, maintainers, account - Metadata Tools (4): update metadata, URLs, visibility, keywords - Analytics Tools (4): package analytics, security alerts, rankings, competition - Discovery Tools (4): monitor releases, trending, search by maintainer, recommendations - Workflow Tools (4): validate names, preview pages, check requirements, build logs - Community Tools (3): reviews, discussions, maintainer contacts ✅ Complete MCP Server Integration: - 39 total MCP endpoints (14 existing + 25 new) - Comprehensive error handling and logging - Consistent API patterns and documentation - Full async/await support ✅ Production-Ready Code: - Comprehensive exception handling with custom exception classes - Full type hints and docstrings throughout - Robust validation and safety checks - Async HTTP clients with retry logic and rate limiting - Mock-based testing infrastructure ready for expansion ✅ Advanced Search Capabilities: - Semantic search with filtering and sorting - Category-based discovery and alternatives finding - Trending analysis and recommendation engines This transforms the basic package query tool into a complete PyPI ecosystem management platform supporting the entire Python package lifecycle from development to publishing to community management.
86 lines
2.6 KiB
Python
86 lines
2.6 KiB
Python
"""Custom exceptions for PyPI Query MCP Server."""
|
|
|
|
|
|
class PyPIError(Exception):
|
|
"""Base exception for PyPI-related errors."""
|
|
|
|
def __init__(self, message: str, status_code: int | None = None):
|
|
super().__init__(message)
|
|
self.message = message
|
|
self.status_code = status_code
|
|
|
|
|
|
class PackageNotFoundError(PyPIError):
|
|
"""Raised when a package is not found on PyPI."""
|
|
|
|
def __init__(self, package_name: str):
|
|
message = f"Package '{package_name}' not found on PyPI"
|
|
super().__init__(message, status_code=404)
|
|
self.package_name = package_name
|
|
|
|
|
|
class NetworkError(PyPIError):
|
|
"""Raised when network-related errors occur."""
|
|
|
|
def __init__(self, message: str, original_error: Exception | None = None):
|
|
super().__init__(message)
|
|
self.original_error = original_error
|
|
|
|
|
|
class RateLimitError(PyPIError):
|
|
"""Raised when API rate limit is exceeded."""
|
|
|
|
def __init__(self, retry_after: int | None = None):
|
|
message = "PyPI API rate limit exceeded"
|
|
if retry_after:
|
|
message += f". Retry after {retry_after} seconds"
|
|
super().__init__(message, status_code=429)
|
|
self.retry_after = retry_after
|
|
|
|
|
|
class InvalidPackageNameError(PyPIError):
|
|
"""Raised when package name is invalid."""
|
|
|
|
def __init__(self, package_name: str):
|
|
message = f"Invalid package name: '{package_name}'"
|
|
super().__init__(message, status_code=400)
|
|
self.package_name = package_name
|
|
|
|
|
|
class PyPIServerError(PyPIError):
|
|
"""Raised when PyPI server returns a server error."""
|
|
|
|
def __init__(self, status_code: int, message: str | None = None):
|
|
if not message:
|
|
message = f"PyPI server error (HTTP {status_code})"
|
|
super().__init__(message, status_code=status_code)
|
|
|
|
|
|
class SearchError(PyPIError):
|
|
"""Raised when search operations fail."""
|
|
|
|
def __init__(self, message: str, query: str | None = None):
|
|
super().__init__(message)
|
|
self.query = query
|
|
|
|
|
|
class PyPIAuthenticationError(PyPIError):
|
|
"""Raised when PyPI authentication fails."""
|
|
|
|
def __init__(self, message: str, status_code: int | None = None):
|
|
super().__init__(message, status_code)
|
|
|
|
|
|
class PyPIUploadError(PyPIError):
|
|
"""Raised when PyPI upload operations fail."""
|
|
|
|
def __init__(self, message: str, status_code: int | None = None):
|
|
super().__init__(message, status_code)
|
|
|
|
|
|
class PyPIPermissionError(PyPIError):
|
|
"""Raised when PyPI permission operations fail."""
|
|
|
|
def __init__(self, message: str, status_code: int | None = None):
|
|
super().__init__(message, status_code)
|