Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: qualifiers type annotation #172

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 53 additions & 57 deletions src/packageurl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
# Visit https://github.com/package-url/packageurl-python for support and
# download.

from __future__ import annotations

import string
from collections import namedtuple
from typing import TYPE_CHECKING
from typing import Any
from typing import AnyStr
from typing import Dict
from typing import Optional
from typing import Tuple
Expand All @@ -43,12 +44,15 @@
from collections.abc import Iterable

from typing_extensions import Literal
from typing_extensions import Self

AnyStr = Union[str, bytes]

# Python 3
basestring = (
bytes,
str,
) # NOQA
)

"""
A purl (aka. Package URL) implementation as specified at:
Expand Down Expand Up @@ -84,16 +88,16 @@ def unquote(s: AnyStr) -> str:


@overload
def get_quoter(encode: bool = True) -> "Callable[[AnyStr], str]": ...
def get_quoter(encode: bool = True) -> Callable[[AnyStr], str]: ...


@overload
def get_quoter(encode: None) -> "Callable[[str], str]": ...
def get_quoter(encode: None) -> Callable[[str], str]: ...


def get_quoter(
encode: Optional[bool] = True,
) -> "Union[Callable[[AnyStr], str], Callable[[str], str]]":
) -> Union[Callable[[AnyStr], str], Callable[[str], str]]:
"""
Return quoting callable given an `encode` tri-boolean (True, False or None)
"""
Expand All @@ -105,22 +109,22 @@ def get_quoter(
return lambda x: x


def normalize_type(type: Optional[AnyStr], encode: Optional[bool] = True) -> Optional[str]: # NOQA
def normalize_type(type: Optional[AnyStr], encode: Optional[bool] = True) -> Optional[str]:
if not type:
return None
if not isinstance(type, str):
type_str = type.decode("utf-8") # NOQA
type_str = type.decode("utf-8")
else:
type_str = type

quoter = get_quoter(encode)
type_str = quoter(type_str) # NOQA
type_str = quoter(type_str)
return type_str.strip().lower() or None


def normalize_namespace(
namespace: Optional[AnyStr], ptype: Optional[str], encode: Optional[bool] = True
) -> Optional[str]: # NOQA
) -> Optional[str]:
if not namespace:
return None
if not isinstance(namespace, str):
Expand All @@ -138,7 +142,7 @@ def normalize_namespace(

def normalize_name(
name: Optional[AnyStr], ptype: Optional[str], encode: Optional[bool] = True
) -> Optional[str]: # NOQA
) -> Optional[str]:
if not name:
return None
if not isinstance(name, str):
Expand All @@ -156,9 +160,7 @@ def normalize_name(
return name_str or None


def normalize_version(
version: Optional[AnyStr], encode: Optional[bool] = True
) -> Optional[str]: # NOQA
def normalize_version(version: Optional[AnyStr], encode: Optional[bool] = True) -> Optional[str]:
if not version:
return None
if not isinstance(version, str):
Expand All @@ -173,25 +175,25 @@ def normalize_version(

@overload
def normalize_qualifiers(
qualifiers: Union[AnyStr, Dict[str, str], None], encode: "Literal[True]" = ...
qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: Literal[True] = ...
) -> Optional[str]: ...


@overload
def normalize_qualifiers(
qualifiers: Union[AnyStr, Dict[str, str], None], encode: "Optional[Literal[False]]"
) -> Optional[Dict[str, str]]: ...
qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: Optional[Literal[False]]
) -> Dict[str, str]: ...


@overload
def normalize_qualifiers(
qualifiers: Union[AnyStr, Dict[str, str], None], encode: Optional[bool] = ...
) -> Union[str, Dict[str, str], None]: ...
qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: Optional[bool] = ...
) -> Optional[Union[str, Dict[str, str]]]: ...


def normalize_qualifiers(
qualifiers: Union[AnyStr, Dict[str, str], None], encode: Optional[bool] = True
) -> Union[str, Dict[str, str], None]: # NOQA
qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: Optional[bool] = True
) -> Optional[Union[str, Dict[str, str]]]:
"""
Return normalized `qualifiers` as a mapping (or as a string if `encode` is
True). The `qualifiers` arg is either a mapping or a string.
Expand All @@ -213,7 +215,7 @@ def normalize_qualifiers(
f"Invalid qualifier. Must be a string of key=value pairs:{repr(qualifiers_list)}"
)
qualifiers_parts = [kv.partition("=") for kv in qualifiers_list]
qualifiers_pairs: "Iterable[Tuple[str, str]]" = [(k, v) for k, _, v in qualifiers_parts]
qualifiers_pairs: Iterable[Tuple[str, str]] = [(k, v) for k, _, v in qualifiers_parts]
elif isinstance(qualifiers, dict):
qualifiers_pairs = qualifiers.items()
else:
Expand Down Expand Up @@ -255,9 +257,7 @@ def normalize_qualifiers(
return qualifiers_map


def normalize_subpath(
subpath: Optional[AnyStr], encode: Optional[bool] = True
) -> Optional[str]: # NOQA
def normalize_subpath(subpath: Optional[AnyStr], encode: Optional[bool] = True) -> Optional[str]:
if not subpath:
return None
if not isinstance(subpath, str):
Expand All @@ -278,9 +278,9 @@ def normalize(
namespace: Optional[AnyStr],
name: Optional[AnyStr],
version: Optional[AnyStr],
qualifiers: Union[AnyStr, Dict[str, str], None],
qualifiers: Optional[Union[AnyStr, Dict[str, str]]],
subpath: Optional[AnyStr],
encode: "Literal[True]" = ...,
encode: Literal[True] = ...,
) -> Tuple[str, Optional[str], str, Optional[str], Optional[str], Optional[str]]: ...


Expand All @@ -290,10 +290,10 @@ def normalize(
namespace: Optional[AnyStr],
name: Optional[AnyStr],
version: Optional[AnyStr],
qualifiers: Union[AnyStr, Dict[str, str], None],
qualifiers: Optional[Union[AnyStr, Dict[str, str]]],
subpath: Optional[AnyStr],
encode: "Optional[Literal[False]]",
) -> Tuple[str, Optional[str], str, Optional[str], Optional[Dict[str, str]], Optional[str]]: ...
encode: Optional[Literal[False]],
) -> Tuple[str, Optional[str], str, Optional[str], Dict[str, str], Optional[str]]: ...


@overload
Expand All @@ -302,11 +302,11 @@ def normalize(
namespace: Optional[AnyStr],
name: Optional[AnyStr],
version: Optional[AnyStr],
qualifiers: Union[AnyStr, Dict[str, str], None],
qualifiers: Optional[Union[AnyStr, Dict[str, str]]],
subpath: Optional[AnyStr],
encode: Optional[bool] = ...,
) -> Tuple[
str, Optional[str], str, Optional[str], Union[str, Dict[str, str], None], Optional[str]
str, Optional[str], str, Optional[str], Optional[Union[str, Dict[str, str]]], Optional[str]
]: ...


Expand All @@ -315,21 +315,21 @@ def normalize(
namespace: Optional[AnyStr],
name: Optional[AnyStr],
version: Optional[AnyStr],
qualifiers: Union[AnyStr, Dict[str, str], None],
qualifiers: Optional[Union[AnyStr, Dict[str, str]]],
subpath: Optional[AnyStr],
encode: Optional[bool] = True,
) -> Tuple[
Optional[str],
Optional[str],
Optional[str],
Optional[str],
Union[str, Dict[str, str], None],
Optional[Union[str, Dict[str, str]]],
Optional[str],
]: # NOQA
]:
"""
Return normalized purl components
"""
type_norm = normalize_type(type, encode) # NOQA
type_norm = normalize_type(type, encode)
namespace_norm = normalize_namespace(namespace, type_norm, encode)
name_norm = normalize_name(name, type_norm, encode)
version_norm = normalize_version(version, encode)
Expand All @@ -346,27 +346,25 @@ class PackageURL(
https://github.com/package-url/purl-spec
"""

name: str
namespace: Optional[str]
qualifiers: Union[str, Dict[str, str], None]
subpath: Optional[str]
type: str
namespace: Optional[str]
name: str
version: Optional[str]
qualifiers: Dict[str, str]
subpath: Optional[str]

def __new__(
self,
cls,
type: Optional[AnyStr] = None,
namespace: Optional[AnyStr] = None,
name: Optional[AnyStr] = None, # NOQA
name: Optional[AnyStr] = None,
version: Optional[AnyStr] = None,
qualifiers: Union[AnyStr, Dict[str, str], None] = None,
qualifiers: Optional[Union[AnyStr, Dict[str, str]]] = None,
subpath: Optional[AnyStr] = None,
) -> "PackageURL": # this should be 'Self' https://github.com/python/mypy/pull/13133
required = dict(type=type, name=name)
for key, value in required.items():
if value:
continue
raise ValueError(f"Invalid purl: {key} is a required argument.")
) -> Self:
for arg in type, name:
if not arg:
raise ValueError(f"Invalid purl: {arg} is a required argument.")

strings = dict(
type=type,
Expand Down Expand Up @@ -399,12 +397,10 @@ def __new__(
version_norm,
qualifiers_norm,
subpath_norm,
) = normalize( # NOQA
type, namespace, name, version, qualifiers, subpath, encode=None
)
) = normalize(type, namespace, name, version, qualifiers, subpath, encode=None)

return super().__new__(
PackageURL,
cls,
type=type_norm,
namespace=namespace_norm,
name=name_norm,
Expand Down Expand Up @@ -439,7 +435,7 @@ def to_string(self) -> str:
"""
Return a purl string built from components.
"""
type, namespace, name, version, qualifiers, subpath = normalize( # NOQA
type, namespace, name, version, qualifiers, subpath = normalize(
self.type,
self.namespace,
self.name,
Expand Down Expand Up @@ -472,7 +468,7 @@ def to_string(self) -> str:
return "".join(purl)

@classmethod
def from_string(cls, purl: str) -> "PackageURL":
def from_string(cls, purl: str) -> Self:
"""
Return a PackageURL object parsed from a string.
Raise ValueError on errors.
Expand All @@ -490,7 +486,7 @@ def from_string(cls, purl: str) -> "PackageURL":
version: Optional[str] # this line is just for type hinting
subpath: Optional[str] # this line is just for type hinting

type, sep, remainder = remainder.partition("/") # NOQA
type, sep, remainder = remainder.partition("/")
if not type or not sep:
raise ValueError(f"purl is missing the required type component: {repr(purl)}.")

Expand Down Expand Up @@ -536,7 +532,7 @@ def from_string(cls, purl: str) -> "PackageURL":
if not name:
raise ValueError(f"purl is missing the required name component: {repr(purl)}")

type, namespace, name, version, qualifiers, subpath = normalize( # NOQA
type, namespace, name, version, qualifiers, subpath = normalize(
type,
namespace,
name,
Expand All @@ -546,4 +542,4 @@ def from_string(cls, purl: str) -> "PackageURL":
encode=False,
)

return PackageURL(type, namespace, name, version, qualifiers, subpath)
return cls(type, namespace, name, version, qualifiers, subpath)
Loading