CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-packageurl-python

A purl aka. Package URL parser and builder

Pending
Overview
Eval results
Files

ecosystem-utilities.mddocs/

Ecosystem-Specific Utilities

Helper functions for specific package ecosystems and specialized use cases, including Go module handling and custom routing functionality. These utilities provide ecosystem-specific logic for parsing and constructing PackageURLs according to different package manager conventions.

Capabilities

Go Module Utilities

Specialized utilities for working with Go modules and packages, supporting both go.mod format and import path parsing.

from packageurl.utils import get_golang_purl

def get_golang_purl(go_package: str):
    """
    Create a PackageURL object from a Go package import path or go.mod entry.
    
    Supports both import paths and versioned go.mod entries:
    - Import path: "github.com/gorilla/mux" 
    - go.mod entry: "github.com/gorilla/mux v1.8.1"
    
    Args:
        go_package (str): Go package import path or "name version" string from go.mod
        
    Returns:
        PackageURL | None: PackageURL with type='golang', or None if invalid input
        
    Raises:
        Exception: If go_package contains '@' character (invalid for Go modules)
    """

Advanced Routing System

Extended routing capabilities for custom URL pattern matching and processing workflows.

from packageurl.contrib.route import Router, NoRouteAvailable, Rule, RouteAlreadyDefined, MultipleRoutesDefined

class Rule:
    """
    A mapping between a URL pattern and a callable endpoint.
    
    The pattern is a regex string that must match entirely for the rule
    to be considered and the endpoint to be invoked.
    """
    
    def __init__(self, pattern, endpoint):
        """
        Initialize rule with pattern and endpoint.
        
        Args:
            pattern (str): Regular expression pattern to match URLs
            endpoint (callable): Function or class to handle matched URLs
        """
    
    def match(self, string):
        """
        Match a string with the rule pattern.
        
        Args:
            string (str): String to match against pattern
            
        Returns:
            Match object or None if no match
        """

class RouteAlreadyDefined(TypeError):
    """Raised when a route Rule already exists in the route map."""

class MultipleRoutesDefined(TypeError):
    """Raised when multiple routes match the same string."""

class Router:
    """
    Advanced URL routing system supporting regex patterns and custom handlers.
    
    Enables pattern-based URL matching for extensible PURL inference and
    custom URL processing workflows.
    """
    
    def __init__(self, route_map=None):
        """
        Initialize router with optional route map.
        
        Args:
            route_map (dict, optional): Ordered mapping of pattern -> Rule
        """
    
    def append(self, pattern, endpoint):
        """
        Add a URL route with pattern and endpoint function.
        
        Args:
            pattern (str): Regular expression pattern to match URLs
            endpoint (callable): Function to process matched URLs
        """
    
    def process(self, string, *args, **kwargs):
        """
        Process a URL through registered patterns to find matching handler.
        
        Args:
            string (str): URL to route and process
            *args, **kwargs: Additional arguments passed to endpoint
            
        Returns:
            Result of the matched endpoint function
            
        Raises:
            NoRouteAvailable: If no registered pattern matches the URL
        """
    
    def route(self, *patterns):
        """
        Decorator to make a callable routed to one or more patterns.
        
        Args:
            *patterns (str): URL patterns to match
            
        Returns:
            Decorator function for registering endpoints
        """
    
    def resolve(self, string):
        """
        Resolve a string to an endpoint function.
        
        Args:
            string (str): URL to resolve
            
        Returns:
            callable: Endpoint function for the URL
            
        Raises:
            NoRouteAvailable: If no pattern matches the URL
            MultipleRoutesDefined: If multiple patterns match the URL
        """
    
    def is_routable(self, string):
        """
        Check if a string is routable by this router.
        
        Args:
            string (str): URL to check
            
        Returns:
            bool: True if URL matches any route pattern
        """
    
    def keys(self):
        """
        Return route pattern keys.
        
        Returns:
            dict_keys: Pattern keys from the route map
        """
    
    def __iter__(self):
        """
        Iterate over route map items.
        
        Returns:
            iterator: Iterator over (pattern, rule) pairs
        """

class NoRouteAvailable(Exception):
    """
    Exception raised when URL routing fails to find a matching pattern.
    
    Attributes:
        url (str): The URL that failed to match any route
        message (str): Error description
    """
    
    def __init__(self, url, message="No route available"):
        self.url = url
        self.message = message
        super().__init__(f"{message}: {url}")

Route Decorators

Decorator utilities for registering route handlers with routers.

def route(pattern, router=None):
    """
    Decorator for registering a function as a route handler.
    
    Args:
        pattern (str): URL pattern to match
        router (Router, optional): Router instance to register with
        
    Returns:
        Decorated function registered as route handler
    """

Usage Examples

Go Module Processing

from packageurl.utils import get_golang_purl

# Parse Go import paths
purl1 = get_golang_purl("github.com/gorilla/mux")
print(purl1)
# PackageURL(type='golang', namespace='github.com/gorilla', name='mux', version=None, qualifiers={}, subpath=None)

# Parse go.mod entries with versions
purl2 = get_golang_purl("github.com/gorilla/mux v1.8.1")
print(purl2)
# PackageURL(type='golang', namespace='github.com/gorilla', name='mux', version='v1.8.1', qualifiers={}, subpath=None)

# Handle multi-level namespaces
purl3 = get_golang_purl("go.uber.org/zap/zapcore")
print(purl3)
# PackageURL(type='golang', namespace='go.uber.org/zap', name='zapcore', version=None, qualifiers={}, subpath=None)

# Handle standard library (empty namespace)
purl4 = get_golang_purl("fmt")
print(purl4)
# PackageURL(type='golang', namespace='', name='fmt', version=None, qualifiers={}, subpath=None)

Advanced Routing

from packageurl.contrib.route import Router, NoRouteAvailable, route
from packageurl import PackageURL
import re

# Create and configure router
router = Router()

# Manual route registration
def handle_custom_npm(url):
    """Handle custom npm registry URLs."""
    match = re.search(r'/package/(@[^/]+/[^/]+|[^/]+)', url)
    if match:
        name = match.group(1)
        return PackageURL(type="npm", name=name)
    return None

router.append(r'https://custom-npm\.example\.com/package/', handle_custom_npm)

# Decorator-based registration
@route(r'https://internal-pypi\.company\.com/simple/([^/]+)/?', router)
def handle_internal_pypi(url, match):
    """Handle internal PyPI URLs."""
    package_name = match.group(1)
    return PackageURL(type="pypi", name=package_name)

# Route URLs
try:
    npm_purl = router.process("https://custom-npm.example.com/package/@angular/core")
    pypi_purl = router.process("https://internal-pypi.company.com/simple/requests/")
    print(f"NPM: {npm_purl}")
    print(f"PyPI: {pypi_purl}")
except NoRouteAvailable as e:
    print(f"Routing failed: {e}")

# Check route matches without execution
match_result = router.match("https://custom-npm.example.com/package/lodash")
if match_result:
    pattern, handler, groups = match_result
    print(f"Matched pattern: {pattern}")

Complex Routing Scenarios

from packageurl.contrib.route import Router
from packageurl import PackageURL
import re
from urllib.parse import urlparse, parse_qs

# Enterprise routing setup
enterprise_router = Router()

def handle_nexus_repository(url):
    """Handle Sonatype Nexus repository URLs."""
    parsed = urlparse(url)
    path_parts = parsed.path.strip('/').split('/')
    
    if 'maven' in path_parts:
        # Maven artifact URL
        try:
            idx = path_parts.index('maven')
            group_parts = path_parts[idx+1:-2]
            artifact_id = path_parts[-2]
            version = path_parts[-1]
            namespace = '.'.join(group_parts)
            return PackageURL(type="maven", namespace=namespace, name=artifact_id, version=version)
        except (ValueError, IndexError):
            return None
    
    return None

def handle_artifactory(url):
    """Handle JFrog Artifactory URLs."""
    # Custom Artifactory URL parsing logic
    match = re.search(r'/artifactory/([^/]+)/(.+)', url)
    if match:
        repo_name, path = match.groups()
        # Parse path based on repository type
        if 'npm' in repo_name:
            return PackageURL(type="npm", name=path.split('/')[-1])
        elif 'pypi' in repo_name:
            return PackageURL(type="pypi", name=path.split('/')[-1])
    return None

# Register enterprise handlers
enterprise_router.append(r'https://nexus\.company\.com/repository/', handle_nexus_repository)
enterprise_router.append(r'https://artifactory\.company\.com/artifactory/', handle_artifactory)

# Multi-step routing with fallbacks
def route_with_fallback(url, routers):
    """Try multiple routers in sequence."""
    for router in routers:
        try:
            return router.process(url)
        except NoRouteAvailable:
            continue
    raise NoRouteAvailable(url, "No router could handle URL")

# Usage
routers = [enterprise_router, purl_router]  # purl_router from url2purl module
result = route_with_fallback("https://nexus.company.com/repository/maven/org/springframework/spring-core/5.3.21/", routers)
print(result)

Ecosystem Pattern Matching

from packageurl.utils import get_golang_purl
from packageurl.contrib.route import Router

# Combine ecosystem utilities with routing
golang_router = Router()

@route(r'https://pkg\.go\.dev/([^@]+)(?:@([^?]+))?', golang_router)
def handle_pkg_go_dev(url, match):
    """Handle pkg.go.dev URLs."""
    import_path = match.group(1)
    version = match.group(2)
    
    if version:
        go_spec = f"{import_path} {version}"
    else:
        go_spec = import_path
    
    return get_golang_purl(go_spec)

# Test Go package URL handling
go_purl = golang_router.process("https://pkg.go.dev/github.com/gin-gonic/gin@v1.8.1")
print(go_purl)
# PackageURL(type='golang', namespace='github.com/gin-gonic', name='gin', version='v1.8.1', qualifiers={}, subpath=None)

Error Handling and Validation

from packageurl.utils import get_golang_purl
from packageurl.contrib.route import NoRouteAvailable

def safe_golang_purl(go_package):
    """Safely create Go PURL with error handling."""
    try:
        if '@' in go_package:
            raise ValueError("Go packages should not contain '@' character")
        
        purl = get_golang_purl(go_package)
        if purl is None:
            raise ValueError(f"Could not parse Go package: {go_package}")
        
        return purl
    except Exception as e:
        print(f"Error processing Go package '{go_package}': {e}")
        return None

# Safe usage
valid_purl = safe_golang_purl("github.com/stretchr/testify v1.7.0")
invalid_purl = safe_golang_purl("invalid@package")  # Will return None

def safe_route(router, url):
    """Safely route URL with comprehensive error handling."""
    try:
        return router.route(url)
    except NoRouteAvailable as e:
        print(f"No route available for {url}: {e}")
        return None
    except Exception as e:
        print(f"Routing error for {url}: {e}")
        return None

# Safe routing usage
result = safe_route(router, "https://unknown-registry.com/package/foo")

Install with Tessl CLI

npx tessl i tessl/pypi-packageurl-python

docs

core-operations.md

ecosystem-utilities.md

framework-integrations.md

index.md

url-conversion.md

tile.json