26
26
27
27
import string
28
28
from collections import namedtuple
29
+ from collections .abc import Mapping
29
30
from typing import TYPE_CHECKING
30
31
from typing import Any
31
32
from typing import Union
37
38
if TYPE_CHECKING :
38
39
from collections .abc import Callable
39
40
from collections .abc import Iterable
41
+ from typing import ClassVar
40
42
41
43
from typing_extensions import Literal
42
44
from typing_extensions import Self
@@ -230,9 +232,12 @@ def normalize_qualifiers(
230
232
231
233
if not encode :
232
234
return qualifiers_map
235
+ return _qualifier_map_to_string (qualifiers_map ) or None
233
236
234
- qualifiers_list = [f"{ key } ={ value } " for key , value in qualifiers_map .items ()]
235
- return "&" .join (qualifiers_list ) or None
237
+
238
+ def _qualifier_map_to_string (qualifiers : dict [str , str ]) -> str :
239
+ qualifiers_list = [f"{ key } ={ value } " for key , value in qualifiers .items ()]
240
+ return "&" .join (qualifiers_list )
236
241
237
242
238
243
def normalize_subpath (subpath : AnyStr | None , encode : bool | None = True ) -> str | None :
@@ -319,6 +324,8 @@ class PackageURL(
319
324
https://github.com/package-url/purl-spec
320
325
"""
321
326
327
+ SCHEME : ClassVar [str ] = "pkg"
328
+
322
329
type : str
323
330
namespace : str | None
324
331
name : str
@@ -400,7 +407,7 @@ def to_dict(self, encode: bool | None = False, empty: Any = None) -> dict[str, A
400
407
401
408
return data
402
409
403
- def to_string (self ) -> str :
410
+ def to_string (self , encode : bool | None = True ) -> str :
404
411
"""
405
412
Return a purl string built from components.
406
413
"""
@@ -411,10 +418,10 @@ def to_string(self) -> str:
411
418
self .version ,
412
419
self .qualifiers ,
413
420
self .subpath ,
414
- encode = True ,
421
+ encode = encode ,
415
422
)
416
423
417
- purl = ["pkg :" , type , "/" ]
424
+ purl = [self . SCHEME , " :" , type , "/" ]
418
425
419
426
if namespace :
420
427
purl .extend ((namespace , "/" ))
@@ -427,6 +434,8 @@ def to_string(self) -> str:
427
434
428
435
if qualifiers :
429
436
purl .append ("?" )
437
+ if isinstance (qualifiers , Mapping ):
438
+ qualifiers = _qualifier_map_to_string (qualifiers )
430
439
purl .append (qualifiers )
431
440
432
441
if subpath :
@@ -445,8 +454,10 @@ def from_string(cls, purl: str) -> Self:
445
454
raise ValueError ("A purl string argument is required." )
446
455
447
456
scheme , sep , remainder = purl .partition (":" )
448
- if not sep or scheme != "pkg" :
449
- raise ValueError (f'purl is missing the required "pkg" scheme component: { purl !r} .' )
457
+ if not sep or scheme != cls .SCHEME :
458
+ raise ValueError (
459
+ f'purl is missing the required "{ cls .SCHEME } " scheme component: { purl !r} .'
460
+ )
450
461
451
462
# this strip '/, // and /// as possible in :// or :///
452
463
remainder = remainder .strip ().lstrip ("/" )
0 commit comments