Skip to content

Commit a8feb73

Browse files
committed
Update parser and builder to latest WIP spec
* use pkg: scheme * as documented in package-url/purl-spec#31 Signed-off-by: Philippe Ombredanne <pombredanne@nexb.com>
1 parent fcf24e2 commit a8feb73

File tree

4 files changed

+130
-82
lines changed

4 files changed

+130
-82
lines changed

setup.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@
1010

1111
from setuptools import setup
1212

13-
1413
setup(
1514
name='packageurl-python',
16-
version='0.4.0',
15+
version='0.5.0',
1716
license='MIT',
1817
description='A "purl" aka. package URL parser and builder',
1918
long_description='Python library to parse and build "purl" aka. package URLs. '
2019
'This is a microlibrary implementing the purl spec at https://github.com/package-url',
2120
author='the purl authors',
2221
url='https://github.com/package-url/packageurl-python',
23-
#packages=find_packages('src'),
22+
# packages=find_packages('src'),
2423
package_dir={'': 'src'},
2524
py_modules=[splitext(basename(path))[0] for path in glob('src/*.py')],
2625

src/packageurl.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from __future__ import unicode_literals
2727

2828
from collections import namedtuple
29+
from collections import OrderedDict
2930

3031
# Python 2 and 3 support
3132
try:
@@ -43,13 +44,12 @@
4344
try:
4445
# Python 2
4546
unicode
46-
str = unicode
47-
basestring = basestring
47+
str = unicode # NOQA
48+
basestring = basestring # NOQA
4849
except NameError:
4950
# Python 3
50-
unicode = str
51-
basestring = (bytes, str,)
52-
51+
unicode = str # NOQA
52+
basestring = (bytes, str,) # NOQA
5353

5454
"""
5555
A purl (aka. Package URL) implementation as specified at:
@@ -65,7 +65,7 @@ def quote(s):
6565
return quoted.replace('%3A', ':')
6666

6767

68-
def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
68+
def normalize(type, namespace, name, version, qualifiers, subpath, encode=True): # NOQA
6969
"""
7070
Return normalized purl components.
7171
"""
@@ -77,7 +77,7 @@ def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
7777
quoting = lambda x: x
7878

7979
if type:
80-
type = type.strip().lower()
80+
type = type.strip().lower() # NOQA
8181

8282
if namespace:
8383
namespace = namespace.strip().strip('/')
@@ -110,7 +110,7 @@ def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
110110
qualifiers = [(k, v) for k, _, v in qualifiers]
111111
else:
112112
qualifiers = []
113-
elif isinstance(qualifiers, dict):
113+
elif isinstance(qualifiers, (dict, OrderedDict,)):
114114
qualifiers = qualifiers.items()
115115
else:
116116
raise ValueError(
@@ -141,13 +141,15 @@ def normalize(type, namespace, name, version, qualifiers, subpath, encode=True):
141141

142142

143143
_components = ['type', 'namespace', 'name', 'version', 'qualifiers', 'subpath']
144+
145+
144146
class PackageURL(namedtuple('PackageURL', _components)):
145147
"""
146148
A purl is a package URL as defined at
147149
https://github.com/package-url/purl-spec
148150
"""
149151

150-
def __new__(self, type=None, namespace=None, name=None,
152+
def __new__(self, type=None, namespace=None, name=None, # NOQA
151153
version=None, qualifiers=None, subpath=None):
152154

153155
required = dict(type=type, name=name)
@@ -165,11 +167,11 @@ def __new__(self, type=None, namespace=None, name=None,
165167
raise ValueError('Invalid purl: {} argument must be a string: {}.'
166168
.format(key, repr(value)))
167169

168-
if qualifiers and not isinstance(qualifiers, dict):
170+
if qualifiers and not isinstance(qualifiers, (dict, OrderedDict,)):
169171
raise ValueError('Invalid purl: {} argument must be a dict: {}.'
170172
.format('qualifiers', repr(qualifiers)))
171173

172-
type, namespace, name, version, qualifiers, subpath = normalize(
174+
type, namespace, name, version, qualifiers, subpath = normalize(# NOQA
173175
type, namespace, name, version, qualifiers, subpath, encode=None)
174176

175177
return super(PackageURL, self).__new__(PackageURL, type=type,
@@ -189,13 +191,13 @@ def to_string(self):
189191
"""
190192
Return a purl string built from components.
191193
"""
192-
type, namespace, name, version, qualifiers, subpath = normalize(
194+
type, namespace, name, version, qualifiers, subpath = normalize(# NOQA
193195
self.type, self.namespace, self.name, self.version,
194196
self.qualifiers, self.subpath,
195197
encode=True
196198
)
197199

198-
purl = [type, ':']
200+
purl = ['pkg:', type, '/']
199201

200202
if namespace:
201203
purl.append(namespace)
@@ -227,24 +229,32 @@ def from_string(cls, purl):
227229
or not purl.strip()):
228230
raise ValueError('A purl string argument is required.')
229231

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':
232234
raise ValueError(
233235
'purl is missing the required '
234-
'type component: {}.'.format(repr(purl)))
236+
'"pkg" scheme component: {}.'.format(repr(purl)))
235237

236238
# 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('/')
242240

243-
if authority:
241+
type, sep, remainder = remainder.partition('/') # NOQA
242+
if not type or not sep:
244243
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+
))
247256

257+
path = path.lstrip('/')
248258
remainder, sep, version = path.rpartition('@')
249259
if not sep:
250260
remainder = version
@@ -267,7 +277,7 @@ def from_string(cls, purl):
267277
'purl is missing the required '
268278
'name component: {}'.format(repr(purl)))
269279

270-
type, namespace, name, version, qualifiers, subpath = normalize(
280+
type, namespace, name, version, qualifiers, subpath = normalize(# NOQA
271281
type, namespace, name, version, qualifiers, subpath,
272282
encode=False
273283
)

0 commit comments

Comments
 (0)