2025-05-27 11:14:49 +08:00
|
|
|
"""Custom exceptions for PyPI Query MCP Server."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PyPIError(Exception):
|
|
|
|
|
"""Base exception for PyPI-related errors."""
|
|
|
|
|
|
2025-05-27 11:25:25 +08:00
|
|
|
def __init__(self, message: str, status_code: int | None = None):
|
2025-05-27 11:14:49 +08:00
|
|
|
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."""
|
|
|
|
|
|
2025-05-27 11:25:25 +08:00
|
|
|
def __init__(self, message: str, original_error: Exception | None = None):
|
2025-05-27 11:14:49 +08:00
|
|
|
super().__init__(message)
|
|
|
|
|
self.original_error = original_error
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class RateLimitError(PyPIError):
|
|
|
|
|
"""Raised when API rate limit is exceeded."""
|
|
|
|
|
|
2025-05-27 11:25:25 +08:00
|
|
|
def __init__(self, retry_after: int | None = None):
|
2025-05-27 11:14:49 +08:00
|
|
|
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."""
|
|
|
|
|
|
2025-05-27 11:25:25 +08:00
|
|
|
def __init__(self, status_code: int, message: str | None = None):
|
2025-05-27 11:14:49 +08:00
|
|
|
if not message:
|
|
|
|
|
message = f"PyPI server error (HTTP {status_code})"
|
|
|
|
|
super().__init__(message, status_code=status_code)
|
feat: add comprehensive PyPI search functionality with advanced filtering
- Implemented PyPISearchClient with semantic search, filtering, and sorting
- Added 4 new search tools: search_packages, search_by_category, find_alternatives, get_trending_packages
- Created SearchFilter and SearchSort classes for flexible configuration
- Added SearchError exception for search-specific error handling
- Comprehensive test suite with 13 tests covering all search functionality
- Enhanced MCP server with 4 new search endpoints
- Support for filtering by Python version, license, category, downloads, maintenance status
- Multiple sorting options: relevance, popularity, quality, recency, name, downloads
- Semantic search using description similarity scoring
- Category-based package discovery with intelligent keyword matching
- Package alternatives finder using metadata analysis
- Trending packages analysis with download activity tracking
- Robust fallback mechanisms using curated package database
- All tests passing (13/13)
This implements feature #6 from the roadmap: "Advanced PyPI Search with filtering by Python version, license, maintenance status and sorting by popularity, recency, quality score with semantic search capabilities"
2025-08-16 07:45:40 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class SearchError(PyPIError):
|
|
|
|
|
"""Raised when search operations fail."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, message: str, query: str | None = None):
|
|
|
|
|
super().__init__(message)
|
|
|
|
|
self.query = query
|
feat: complete PyPI platform integration with all 25 tools and MCP endpoints
🎉 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.
2025-08-17 21:06:59 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|