From 249f3235f1b845b882d9ef2a2dedb8c1e204fb34 Mon Sep 17 00:00:00 2001 From: Jonathan Howard Date: Tue, 22 Oct 2024 15:15:11 -0500 Subject: [PATCH 1/5] fix: qualifiers type annotation Signed-off-by: Jonathan Howard --- src/packageurl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index 170038b..625f94d 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -348,7 +348,7 @@ class PackageURL( name: str namespace: Optional[str] - qualifiers: Union[str, Dict[str, str], None] + qualifiers: Dict[str, str] subpath: Optional[str] type: str version: Optional[str] From 900d37679f6035c489152678729da81e3bbe036f Mon Sep 17 00:00:00 2001 From: Jonathan Howard Date: Wed, 23 Oct 2024 11:31:40 -0500 Subject: [PATCH 2/5] fix: miscellaneous type annotation issues Signed-off-by: Jonathan Howard --- src/packageurl/__init__.py | 91 ++++++++++++++++++-------------------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index 625f94d..1144003 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -43,12 +43,13 @@ from collections.abc import Iterable from typing_extensions import Literal + from typing_extensions import Self # Python 3 basestring = ( bytes, str, -) # NOQA +) """ A purl (aka. Package URL) implementation as specified at: @@ -105,22 +106,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): @@ -138,7 +139,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): @@ -156,9 +157,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): @@ -173,25 +172,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. @@ -255,9 +254,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): @@ -278,7 +275,7 @@ 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]" = ..., ) -> Tuple[str, Optional[str], str, Optional[str], Optional[str], Optional[str]]: ... @@ -290,10 +287,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]]: ... +) -> Tuple[str, Optional[str], str, Optional[str], Dict[str, str], Optional[str]]: ... @overload @@ -302,11 +299,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] ]: ... @@ -315,7 +312,7 @@ 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[ @@ -323,13 +320,13 @@ def normalize( 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) @@ -346,27 +343,25 @@ class PackageURL( https://github.com/package-url/purl-spec """ - name: str + type: str namespace: Optional[str] + name: str + version: Optional[str] qualifiers: Dict[str, str] subpath: Optional[str] - type: str - version: 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, @@ -399,12 +394,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, @@ -439,7 +432,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, @@ -472,7 +465,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. @@ -490,7 +483,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)}.") @@ -536,7 +529,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, @@ -546,4 +539,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) # type: ignore[arg-type] From 5c7dddb94f1c6a711b6016f8e722f4a0bf05cdc8 Mon Sep 17 00:00:00 2001 From: Jonathan Howard Date: Wed, 23 Oct 2024 12:33:12 -0500 Subject: [PATCH 3/5] style: remove quotes from forward references Signed-off-by: Jonathan Howard --- src/packageurl/__init__.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index 1144003..4dda2cf 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -24,6 +24,8 @@ # 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 @@ -85,16 +87,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) """ @@ -172,13 +174,13 @@ def normalize_version(version: Optional[AnyStr], encode: Optional[bool] = True) @overload def normalize_qualifiers( - qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: "Literal[True]" = ... + qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: Literal[True] = ... ) -> Optional[str]: ... @overload def normalize_qualifiers( - qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: "Optional[Literal[False]]" + qualifiers: Optional[Union[AnyStr, Dict[str, str]]], encode: Optional[Literal[False]] ) -> Dict[str, str]: ... @@ -212,7 +214,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: @@ -277,7 +279,7 @@ def normalize( version: Optional[AnyStr], 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]]: ... @@ -289,7 +291,7 @@ def normalize( version: Optional[AnyStr], qualifiers: Optional[Union[AnyStr, Dict[str, str]]], subpath: Optional[AnyStr], - encode: "Optional[Literal[False]]", + encode: Optional[Literal[False]], ) -> Tuple[str, Optional[str], str, Optional[str], Dict[str, str], Optional[str]]: ... @@ -358,7 +360,7 @@ def __new__( version: Optional[AnyStr] = None, qualifiers: Optional[Union[AnyStr, Dict[str, str]]] = None, subpath: Optional[AnyStr] = None, - ) -> "Self": + ) -> Self: for arg in type, name: if not arg: raise ValueError(f"Invalid purl: {arg} is a required argument.") @@ -465,7 +467,7 @@ def to_string(self) -> str: return "".join(purl) @classmethod - def from_string(cls, purl: str) -> "Self": + def from_string(cls, purl: str) -> Self: """ Return a PackageURL object parsed from a string. Raise ValueError on errors. From 00ca1bc7d4f30e9db376ff608843b9726b71365c Mon Sep 17 00:00:00 2001 From: Jonathan Howard Date: Wed, 23 Oct 2024 13:47:33 -0500 Subject: [PATCH 4/5] refactor: add AnyStr type alias Signed-off-by: Jonathan Howard --- src/packageurl/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index 4dda2cf..c379e88 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -30,7 +30,6 @@ 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 @@ -47,6 +46,8 @@ from typing_extensions import Literal from typing_extensions import Self + AnyStr = Union[str, bytes] + # Python 3 basestring = ( bytes, @@ -541,4 +542,4 @@ def from_string(cls, purl: str) -> Self: encode=False, ) - return cls(type, namespace, name, version, qualifiers, subpath) # type: ignore[arg-type] + return cls(type, namespace, name, version, qualifiers, subpath) From 6a819e2e6ba408a2d4b8b45e75876fe1d86805f0 Mon Sep 17 00:00:00 2001 From: Jonathan Howard Date: Fri, 25 Oct 2024 10:46:52 -0500 Subject: [PATCH 5/5] revert: required argument check Signed-off-by: Jonathan Howard --- src/packageurl/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/packageurl/__init__.py b/src/packageurl/__init__.py index c379e88..f1e5551 100644 --- a/src/packageurl/__init__.py +++ b/src/packageurl/__init__.py @@ -362,9 +362,11 @@ def __new__( qualifiers: Optional[Union[AnyStr, Dict[str, str]]] = None, subpath: Optional[AnyStr] = None, ) -> Self: - for arg in type, name: - if not arg: - raise ValueError(f"Invalid purl: {arg} is a required argument.") + 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.") strings = dict( type=type,