-
Notifications
You must be signed in to change notification settings - Fork 53
fix: qualifiers type annotation #172
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
249f323
fix: qualifiers type annotation
jhoward-lm 900d376
fix: miscellaneous type annotation issues
jhoward-lm 5c7dddb
style: remove quotes from forward references
jhoward-lm 00ca1bc
refactor: add AnyStr type alias
jhoward-lm 6a819e2
revert: required argument check
jhoward-lm File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
|
@@ -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: | ||
|
|
@@ -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) | ||
| """ | ||
|
|
@@ -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): | ||
|
|
@@ -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): | ||
|
|
@@ -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): | ||
|
|
@@ -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. | ||
|
|
@@ -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: | ||
|
|
@@ -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): | ||
|
|
@@ -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]]: ... | ||
|
|
||
|
|
||
|
|
@@ -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 | ||
|
|
@@ -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] | ||
| ]: ... | ||
|
|
||
|
|
||
|
|
@@ -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) | ||
|
|
@@ -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, | ||
|
|
@@ -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, | ||
|
|
@@ -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, | ||
|
|
@@ -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. | ||
|
|
@@ -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)}.") | ||
|
|
||
|
|
@@ -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, | ||
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an important fix that enables appropriate subclassing, which deserves its own bullet item in the PR description if not the changelog, imo! Thanks! |
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.