26
26
from __future__ import unicode_literals
27
27
28
28
from collections import namedtuple
29
+ from collections import OrderedDict
29
30
30
31
# Python 2 and 3 support
31
32
try :
43
44
try :
44
45
# Python 2
45
46
unicode
46
- str = unicode
47
- basestring = basestring
47
+ str = unicode # NOQA
48
+ basestring = basestring # NOQA
48
49
except NameError :
49
50
# Python 3
50
- unicode = str
51
- basestring = (bytes , str ,)
52
-
51
+ unicode = str # NOQA
52
+ basestring = (bytes , str ,) # NOQA
53
53
54
54
"""
55
55
A purl (aka. Package URL) implementation as specified at:
@@ -65,7 +65,7 @@ def quote(s):
65
65
return quoted .replace ('%3A' , ':' )
66
66
67
67
68
- def normalize (type , namespace , name , version , qualifiers , subpath , encode = True ):
68
+ def normalize (type , namespace , name , version , qualifiers , subpath , encode = True ): # NOQA
69
69
"""
70
70
Return normalized purl components.
71
71
"""
@@ -77,7 +77,7 @@ def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
77
77
quoting = lambda x : x
78
78
79
79
if type :
80
- type = type .strip ().lower ()
80
+ type = type .strip ().lower () # NOQA
81
81
82
82
if namespace :
83
83
namespace = namespace .strip ().strip ('/' )
@@ -110,7 +110,7 @@ def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
110
110
qualifiers = [(k , v ) for k , _ , v in qualifiers ]
111
111
else :
112
112
qualifiers = []
113
- elif isinstance (qualifiers , dict ):
113
+ elif isinstance (qualifiers , ( dict , OrderedDict ,) ):
114
114
qualifiers = qualifiers .items ()
115
115
else :
116
116
raise ValueError (
@@ -141,13 +141,15 @@ def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
141
141
142
142
143
143
_components = ['type' , 'namespace' , 'name' , 'version' , 'qualifiers' , 'subpath' ]
144
+
145
+
144
146
class PackageURL (namedtuple ('PackageURL' , _components )):
145
147
"""
146
148
A purl is a package URL as defined at
147
149
https://github.com/package-url/purl-spec
148
150
"""
149
151
150
- def __new__ (self , type = None , namespace = None , name = None ,
152
+ def __new__ (self , type = None , namespace = None , name = None , # NOQA
151
153
version = None , qualifiers = None , subpath = None ):
152
154
153
155
required = dict (type = type , name = name )
@@ -165,11 +167,11 @@ def __new__(self, type=None, namespace=None, name=None,
165
167
raise ValueError ('Invalid purl: {} argument must be a string: {}.'
166
168
.format (key , repr (value )))
167
169
168
- if qualifiers and not isinstance (qualifiers , dict ):
170
+ if qualifiers and not isinstance (qualifiers , ( dict , OrderedDict ,) ):
169
171
raise ValueError ('Invalid purl: {} argument must be a dict: {}.'
170
172
.format ('qualifiers' , repr (qualifiers )))
171
173
172
- type , namespace , name , version , qualifiers , subpath = normalize (
174
+ type , namespace , name , version , qualifiers , subpath = normalize (# NOQA
173
175
type , namespace , name , version , qualifiers , subpath , encode = None )
174
176
175
177
return super (PackageURL , self ).__new__ (PackageURL , type = type ,
@@ -189,13 +191,13 @@ def to_string(self):
189
191
"""
190
192
Return a purl string built from components.
191
193
"""
192
- type , namespace , name , version , qualifiers , subpath = normalize (
194
+ type , namespace , name , version , qualifiers , subpath = normalize (# NOQA
193
195
self .type , self .namespace , self .name , self .version ,
194
196
self .qualifiers , self .subpath ,
195
197
encode = True
196
198
)
197
199
198
- purl = [type , ': ' ]
200
+ purl = ['pkg:' , type , '/ ' ]
199
201
200
202
if namespace :
201
203
purl .append (namespace )
@@ -227,24 +229,32 @@ def from_string(cls, purl):
227
229
or not purl .strip ()):
228
230
raise ValueError ('A purl string argument is required.' )
229
231
230
- type , sep , remainder = purl .partition (':' )
231
- if not type or not sep :
232
+ scheme , sep , remainder = purl .partition (':' )
233
+ if not sep or scheme != 'pkg' :
232
234
raise ValueError (
233
235
'purl is missing the required '
234
- 'type component: {}.' .format (repr (purl )))
236
+ '"pkg" scheme component: {}.' .format (repr (purl )))
235
237
236
238
# this strip '/, // and /// as possible in :// or :///
237
- remainder = remainder .strip ().strip ('/' )
238
- cleaned = '{}:{}' .format (type , remainder )
239
-
240
- type , authority , path , qualifiers , subpath = urlsplit (
241
- url = cleaned , scheme = type , allow_fragments = True )
239
+ remainder = remainder .strip ().lstrip ('/' )
242
240
243
- if authority :
241
+ type , sep , remainder = remainder .partition ('/' ) # NOQA
242
+ if not type or not sep :
244
243
raise ValueError (
245
- 'Invalid purl {} cannot contain a "user:pass@host:port" URL Authority '
246
- 'component: {}.' .format (repr (purl )), repr (netloc ))
244
+ 'purl is missing the required '
245
+ 'type component: {}.' .format (repr (purl )))
246
+
247
+ scheme , authority , path , qualifiers , subpath = urlsplit (
248
+ url = remainder , scheme = '' , allow_fragments = True )
249
+
250
+ if scheme or authority :
251
+ msg = ('Invalid purl {} cannot contain a "user:pass@host:port" '
252
+ 'URL Authority component: {}.' )
253
+ raise ValueError (msg .format (
254
+ repr (purl ), repr (authority )
255
+ ))
247
256
257
+ path = path .lstrip ('/' )
248
258
remainder , sep , version = path .rpartition ('@' )
249
259
if not sep :
250
260
remainder = version
@@ -267,7 +277,7 @@ def from_string(cls, purl):
267
277
'purl is missing the required '
268
278
'name component: {}' .format (repr (purl )))
269
279
270
- type , namespace , name , version , qualifiers , subpath = normalize (
280
+ type , namespace , name , version , qualifiers , subpath = normalize (# NOQA
271
281
type , namespace , name , version , qualifiers , subpath ,
272
282
encode = False
273
283
)
0 commit comments