A purl aka. Package URL parser and builder
—
Essential PackageURL parsing, construction, and manipulation functionality. The core operations provide the fundamental capabilities for working with Package URLs including parsing strings, creating objects, and normalizing components.
The main class for representing Package URLs with full parsing, validation, and serialization capabilities.
class PackageURL:
"""
A Package URL object representing a standardized package identifier.
Attributes:
type (str): Package type (e.g., 'maven', 'npm', 'pypi')
namespace (str | None): Package namespace/group
name (str): Package name
version (str | None): Package version
qualifiers (dict[str, str]): Additional qualifiers as key-value pairs
subpath (str | None): Subpath within package
"""
def __init__(
self,
type: str | bytes | None = None,
namespace: str | bytes | None = None,
name: str | bytes | None = None,
version: str | bytes | None = None,
qualifiers: str | bytes | dict[str, str] | None = None,
subpath: str | bytes | None = None
):
"""
Create a PackageURL object with normalized components.
Args:
type: Package type (required)
namespace: Package namespace (optional)
name: Package name (required)
version: Package version (optional)
qualifiers: Qualifiers as dict or string (optional)
subpath: Subpath within package (optional)
Raises:
ValueError: If required fields (type, name) are missing or invalid
"""
@classmethod
def from_string(cls, purl: str) -> 'PackageURL':
"""
Parse a PackageURL from a string.
Args:
purl: PURL string to parse
Returns:
PackageURL object
Raises:
ValueError: If purl string is invalid or malformed
"""
def to_string(self, encode: bool = True) -> str:
"""
Convert PackageURL to string representation.
Args:
encode: Whether to percent-encode components (default: True)
Returns:
PURL string representation
"""
def to_dict(self, encode: bool = False, empty: any = None) -> dict[str, any]:
"""
Convert PackageURL to dictionary representation.
Args:
encode: Whether to encode qualifiers as string (default: False)
empty: Value to use for empty/None fields (default: None)
Returns:
Dictionary with PURL components
"""
def __str__(self) -> str:
"""String representation (same as to_string())."""
def __hash__(self) -> int:
"""Hash based on string representation."""
def _asdict(self) -> dict[str, any]:
"""
Return PackageURL fields as an ordered dictionary.
Returns:
Ordered dictionary of field names to values
"""
SCHEME: str = "pkg" # PURL scheme constantComponent-wise normalization and validation functions for PackageURL components.
def normalize(
type: str | bytes | None,
namespace: str | bytes | None,
name: str | bytes | None,
version: str | bytes | None,
qualifiers: str | bytes | dict[str, str] | None,
subpath: str | bytes | None,
encode: bool = True
) -> tuple[str | None, str | None, str | None, str | None, str | dict[str, str] | None, str | None]:
"""
Normalize all PackageURL components.
Args:
type: Package type to normalize
namespace: Namespace to normalize
name: Name to normalize
version: Version to normalize
qualifiers: Qualifiers to normalize
subpath: Subpath to normalize
encode: Whether to encode components (default: True)
Returns:
Tuple of normalized components
"""
def normalize_type(type: str | bytes | None, encode: bool = True) -> str | None:
"""
Normalize package type.
Args:
type: Package type to normalize
encode: Whether to percent-encode (default: True)
Returns:
Normalized type string (lowercase, stripped)
"""
def normalize_namespace(
namespace: str | bytes | None,
ptype: str | None,
encode: bool = True
) -> str | None:
"""
Normalize package namespace.
Args:
namespace: Namespace to normalize
ptype: Package type (affects normalization rules)
encode: Whether to percent-encode (default: True)
Returns:
Normalized namespace string
"""
def normalize_name(
name: str | bytes | None,
ptype: str | None,
encode: bool = True
) -> str | None:
"""
Normalize package name.
Args:
name: Package name to normalize
ptype: Package type (affects normalization rules)
encode: Whether to percent-encode (default: True)
Returns:
Normalized name string
"""
def normalize_version(version: str | bytes | None, encode: bool = True) -> str | None:
"""
Normalize package version.
Args:
version: Version to normalize
encode: Whether to percent-encode (default: True)
Returns:
Normalized version string
"""
def normalize_qualifiers(
qualifiers: str | bytes | dict[str, str] | None,
encode: bool = True
) -> str | dict[str, str] | None:
"""
Normalize qualifiers.
Args:
qualifiers: Qualifiers as string or dict
encode: Whether to encode as string (True) or return dict (False)
Returns:
Normalized qualifiers as string or dict
Raises:
ValueError: If qualifiers format is invalid
"""
def normalize_subpath(subpath: str | bytes | None, encode: bool = True) -> str | None:
"""
Normalize subpath.
Args:
subpath: Subpath to normalize
encode: Whether to percent-encode (default: True)
Returns:
Normalized subpath string
"""Low-level utility functions for encoding and decoding.
def quote(s: str | bytes) -> str:
"""
Percent-encode string except for colons.
Args:
s: String or bytes to encode
Returns:
Percent-encoded string
"""
def unquote(s: str | bytes) -> str:
"""
Percent-decode string.
Args:
s: String or bytes to decode
Returns:
Percent-decoded string
"""
def get_quoter(encode: bool | None = True) -> callable:
"""
Get appropriate encoding/decoding function.
Args:
encode: True for quote, False for unquote, None for identity
Returns:
Encoding/decoding function
"""from packageurl import PackageURL
# Create from components
purl = PackageURL(
type="maven",
namespace="org.apache.commons",
name="io",
version="1.3.4"
)
# Create with qualifiers
purl = PackageURL(
type="npm",
name="lodash",
version="4.17.21",
qualifiers={"arch": "x64", "os": "linux"}
)# Parse various PURL formats
maven_purl = PackageURL.from_string("pkg:maven/org.springframework/spring-core@5.3.21")
npm_purl = PackageURL.from_string("pkg:npm/%40angular/core@12.0.0")
pypi_purl = PackageURL.from_string("pkg:pypi/django@3.2.0?extra=security")
# Access components
print(maven_purl.type) # "maven"
print(maven_purl.namespace) # "org.springframework"
print(maven_purl.name) # "spring-core"
print(maven_purl.version) # "5.3.21"from packageurl import normalize, normalize_qualifiers
# Normalize all components
type_n, ns_n, name_n, ver_n, qual_n, sub_n = normalize(
"MAVEN",
"Org.Apache.Commons",
"IO",
"1.3.4",
"classifier=sources&type=jar",
"src/main"
)
# Normalize qualifiers specifically
qualifiers_dict = normalize_qualifiers("key1=value1&key2=value2", encode=False)
qualifiers_str = normalize_qualifiers({"key1": "value1", "key2": "value2"}, encode=True)Install with Tessl CLI
npx tessl i tessl/pypi-packageurl-python