#!/usr/bin/env python3 """ Demonstration of PyPI Query MCP Server extras functionality. This script shows how to properly use the include_extras parameter to resolve optional dependencies for Python packages. """ import asyncio from pypi_query_mcp.core.pypi_client import PyPIClient from pypi_query_mcp.tools.dependency_resolver import resolve_package_dependencies async def show_available_extras(package_name: str): """Show what extras are available for a package.""" print(f"\nšŸ“¦ Available extras for {package_name}:") async with PyPIClient() as client: package_data = await client.get_package_info(package_name) info = package_data.get("info", {}) provides_extra = info.get("provides_extra", []) requires_dist = info.get("requires_dist", []) or [] if provides_extra: print(f" Provides extras: {', '.join(provides_extra)}") else: print(" No provides_extra field found") # Find extras from requires_dist extras_in_deps = set() for req in requires_dist: if "extra ==" in req: # Extract extra name from requirement like: pytest>=6.0.0; extra=='test' import re match = re.search(r'extra\s*==\s*["\']([^"\']+)["\']', req) if match: extras_in_deps.add(match.group(1)) if extras_in_deps: print(f" Extras with dependencies: {', '.join(sorted(extras_in_deps))}") else: print(" No extras with dependencies found") async def demo_extras_resolution(): """Demonstrate extras resolution with various packages.""" # Examples of packages with well-known extras examples = [ { "package": "requests", "extras": ["socks"], "description": "HTTP library with SOCKS proxy support", }, { "package": "django", "extras": ["argon2", "bcrypt"], "description": "Web framework with password hashing extras", }, { "package": "setuptools", "extras": ["test"], "description": "Package development tools with testing extras", }, { "package": "flask", "extras": ["async", "dotenv"], "description": "Web framework with async and dotenv support", }, ] for example in examples: package_name = example["package"] extras = example["extras"] description = example["description"] print(f"\n{'=' * 60}") print(f"šŸ” Example: {package_name}") print(f"šŸ“‹ Description: {description}") print(f"šŸŽÆ Testing extras: {extras}") # Show available extras await show_available_extras(package_name) try: # Resolve without extras print(f"\nšŸ“Š Resolving {package_name} WITHOUT extras...") result_no_extras = await resolve_package_dependencies( package_name=package_name, python_version="3.10", include_extras=[], max_depth=1, # Limit depth for demo ) # Resolve with extras print(f"šŸ“Š Resolving {package_name} WITH extras {extras}...") result_with_extras = await resolve_package_dependencies( package_name=package_name, python_version="3.10", include_extras=extras, max_depth=1, ) # Compare results print("\nšŸ“ˆ Results comparison:") print( f" Without extras: {result_no_extras['summary']['total_extra_dependencies']} extra deps" ) print( f" With extras: {result_with_extras['summary']['total_extra_dependencies']} extra deps" ) # Show actual extras resolved main_pkg = next(iter(result_with_extras["dependency_tree"].values()), {}) extras_resolved = main_pkg.get("dependencies", {}).get("extras", {}) if extras_resolved: print(" āœ… Extras resolved successfully:") for extra_name, deps in extras_resolved.items(): print(f" - {extra_name}: {len(deps)} dependencies") for dep in deps[:2]: # Show first 2 print(f" * {dep}") if len(deps) > 2: print(f" * ... and {len(deps) - 2} more") else: print( " āš ļø No extras resolved (may not exist or have no dependencies)" ) except Exception as e: print(f" āŒ Error: {e}") async def demo_incorrect_usage(): """Demonstrate common mistakes with extras usage.""" print(f"\n{'=' * 60}") print("āŒ Common Mistakes with Extras") print("='*60") mistakes = [ { "package": "requests", "extras": ["dev", "test"], # These don't exist for requests "error": "Using generic extra names instead of package-specific ones", }, { "package": "setuptools", "extras": ["testing"], # Should be "test" not "testing" "error": "Using similar but incorrect extra names", }, ] for mistake in mistakes: package_name = mistake["package"] extras = mistake["extras"] error_desc = mistake["error"] print(f"\n🚫 Mistake: {error_desc}") print(f" Package: {package_name}") print(f" Incorrect extras: {extras}") try: result = await resolve_package_dependencies( package_name=package_name, python_version="3.10", include_extras=extras, max_depth=1, ) total_extras = result["summary"]["total_extra_dependencies"] print(f" Result: {total_extras} extra dependencies resolved") if total_extras == 0: print(" āš ļø No extras resolved - these extras likely don't exist") except Exception as e: print(f" āŒ Error: {e}") async def main(): """Main demonstration function.""" print("šŸš€ PyPI Query MCP Server - Extras Usage Demo") print("=" * 60) print() print("This demo shows how to properly use the include_extras parameter") print("to resolve optional dependencies for Python packages.") await demo_extras_resolution() await demo_incorrect_usage() print(f"\n{'=' * 60}") print("✨ Demo completed!") print() print("šŸ’” Key takeaways:") print(" 1. Always check what extras are available for a package first") print(" 2. Use the exact extra names defined by the package") print(" 3. Check package documentation or PyPI page for available extras") print( " 4. Not all packages have extras, and some extras may have no dependencies" ) print() print("šŸ“š To find available extras:") print(" - Check the package's PyPI page") print(" - Look for 'provides_extra' in package metadata") print(" - Check package documentation") print(" - Look for requirements with 'extra ==' in requires_dist") if __name__ == "__main__": asyncio.run(main())