Skip to content

Commit 9aabc27

Browse files
author
kylinye
committed
🎨add packageurl
1 parent ea76e5c commit 9aabc27

File tree

17 files changed

+2609
-0
lines changed

17 files changed

+2609
-0
lines changed

lib/python3.7/site-packages/packageurl/__init__.py

Lines changed: 549 additions & 0 deletions
Large diffs are not rendered by default.

lib/python3.7/site-packages/packageurl/contrib/__init__.py

Whitespace-only changes.

lib/python3.7/site-packages/packageurl/contrib/django/__init__.py

Whitespace-only changes.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) the purl authors
4+
# SPDX-License-Identifier: MIT
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in all
14+
# copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
24+
# Visit https://github.com/package-url/packageurl-python for support and
25+
# download.
26+
27+
import django_filters
28+
29+
30+
class PackageURLFilter(django_filters.CharFilter):
31+
"""
32+
Filter by an exact Package URL string.
33+
34+
The special "EMPTY" value allows retrieval of objects with an empty Package URL.
35+
36+
This filter depends on `for_package_url` and `empty_package_url`
37+
methods to be available on the Model Manager,
38+
see for example `PackageURLQuerySetMixin`.
39+
40+
When exact_match_only is True, the filter will match only exact Package URL strings.
41+
"""
42+
43+
is_empty = "EMPTY"
44+
exact_match_only = False
45+
help_text = (
46+
'Match Package URL. Use "EMPTY" as value to retrieve objects with empty Package URL.'
47+
)
48+
49+
def __init__(self, *args, **kwargs):
50+
self.exact_match_only = kwargs.pop("exact_match_only", False)
51+
kwargs.setdefault("help_text", self.help_text)
52+
super().__init__(*args, **kwargs)
53+
54+
def filter(self, qs, value):
55+
none_values = ([], (), {}, "", None)
56+
if value in none_values:
57+
return qs
58+
59+
if self.distinct:
60+
qs = qs.distinct()
61+
62+
if value == self.is_empty:
63+
return qs.empty_package_url()
64+
65+
return qs.for_package_url(value, exact_match=self.exact_match_only)
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) the purl authors
4+
# SPDX-License-Identifier: MIT
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in all
14+
# copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
24+
# Visit https://github.com/package-url/packageurl-python for support and
25+
# download.
26+
27+
from django.core.exceptions import ValidationError
28+
from django.db import models
29+
from django.utils.translation import gettext_lazy as _
30+
31+
from packageurl import PackageURL
32+
from packageurl.contrib.django.utils import purl_to_lookups
33+
34+
PACKAGE_URL_FIELDS = ("type", "namespace", "name", "version", "qualifiers", "subpath")
35+
36+
37+
class PackageURLQuerySetMixin:
38+
"""
39+
Add Package URL filtering methods to a django.db.models.QuerySet.
40+
"""
41+
42+
def for_package_url(self, purl_str, encode=True, exact_match=False):
43+
"""
44+
Filter the QuerySet based on a Package URL (purl) string with an option for
45+
exact match filtering.
46+
47+
When `exact_match` is False (default), the method will match any purl with the
48+
same base fields as `purl_str` and allow variations in other fields.
49+
When `exact_match` is True, only the identical purl will be returned.
50+
"""
51+
lookups = purl_to_lookups(
52+
purl_str=purl_str, encode=encode, include_empty_fields=exact_match
53+
)
54+
if lookups:
55+
return self.filter(**lookups)
56+
return self.none()
57+
58+
def with_package_url(self):
59+
"""Return objects with Package URL defined."""
60+
return self.filter(~models.Q(type="") & ~models.Q(name=""))
61+
62+
def without_package_url(self):
63+
"""Return objects with empty Package URL."""
64+
return self.filter(models.Q(type="") | models.Q(name=""))
65+
66+
def empty_package_url(self):
67+
"""Return objects with empty Package URL. Alias of without_package_url."""
68+
return self.without_package_url()
69+
70+
def order_by_package_url(self):
71+
"""Order by Package URL fields."""
72+
return self.order_by(*PACKAGE_URL_FIELDS)
73+
74+
75+
class PackageURLQuerySet(PackageURLQuerySetMixin, models.QuerySet):
76+
pass
77+
78+
79+
class PackageURLMixin(models.Model):
80+
"""
81+
Abstract Model for Package URL "purl" fields support.
82+
"""
83+
84+
type = models.CharField(
85+
max_length=16,
86+
blank=True,
87+
help_text=_(
88+
"A short code to identify the type of this package. "
89+
"For example: gem for a Rubygem, docker for a container, "
90+
"pypi for a Python Wheel or Egg, maven for a Maven Jar, "
91+
"deb for a Debian package, etc."
92+
),
93+
)
94+
95+
namespace = models.CharField(
96+
max_length=255,
97+
blank=True,
98+
help_text=_(
99+
"Package name prefix, such as Maven groupid, Docker image owner, "
100+
"GitHub user or organization, etc."
101+
),
102+
)
103+
104+
name = models.CharField(
105+
max_length=100,
106+
blank=True,
107+
help_text=_("Name of the package."),
108+
)
109+
110+
version = models.CharField(
111+
max_length=100,
112+
blank=True,
113+
help_text=_("Version of the package."),
114+
)
115+
116+
qualifiers = models.CharField(
117+
max_length=1024,
118+
blank=True,
119+
help_text=_(
120+
"Extra qualifying data for a package such as the name of an OS, "
121+
"architecture, distro, etc."
122+
),
123+
)
124+
125+
subpath = models.CharField(
126+
max_length=200,
127+
blank=True,
128+
help_text=_("Extra subpath within a package, relative to the package root."),
129+
)
130+
131+
objects = PackageURLQuerySet.as_manager()
132+
133+
class Meta:
134+
abstract = True
135+
136+
@property
137+
def package_url(self):
138+
"""
139+
Return the Package URL "purl" string.
140+
"""
141+
try:
142+
package_url = self.get_package_url()
143+
except ValueError:
144+
return ""
145+
146+
return str(package_url)
147+
148+
def get_package_url(self):
149+
"""
150+
Get the PackageURL instance.
151+
"""
152+
return PackageURL(
153+
self.type,
154+
self.namespace,
155+
self.name,
156+
self.version,
157+
self.qualifiers,
158+
self.subpath,
159+
)
160+
161+
def set_package_url(self, package_url):
162+
"""
163+
Set each field values to the values of the provided `package_url` string
164+
or PackageURL object.
165+
Existing values are always overwritten, forcing the new value or an
166+
empty string on all the `package_url` fields since we do not want to
167+
keep any previous values.
168+
"""
169+
if not isinstance(package_url, PackageURL):
170+
package_url = PackageURL.from_string(package_url)
171+
172+
package_url_dict = package_url.to_dict(encode=True, empty="")
173+
for field_name, value in package_url_dict.items():
174+
model_field = self._meta.get_field(field_name)
175+
176+
if value and len(value) > model_field.max_length:
177+
message = _(f'Value too long for field "{field_name}".')
178+
raise ValidationError(message)
179+
180+
setattr(self, field_name, value)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) the purl authors
4+
# SPDX-License-Identifier: MIT
5+
#
6+
# Permission is hereby granted, free of charge, to any person obtaining a copy
7+
# of this software and associated documentation files (the "Software"), to deal
8+
# in the Software without restriction, including without limitation the rights
9+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
# copies of the Software, and to permit persons to whom the Software is
11+
# furnished to do so, subject to the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be included in all
14+
# copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
# SOFTWARE.
23+
24+
# Visit https://github.com/package-url/packageurl-python for support and
25+
# download.
26+
27+
28+
from packageurl import PackageURL
29+
30+
31+
def purl_to_lookups(purl_str, encode=True, include_empty_fields=False):
32+
"""
33+
Return a lookups dictionary built from the provided `purl` (Package URL) string.
34+
These lookups can be used as QuerySet filters.
35+
If include_empty_fields is provided, the resulting dictionary will include fields
36+
with empty values. This is useful to get exact match.
37+
Note that empty values are always returned as empty strings as the model fields
38+
are defined with `blank=True` and `null=False`.
39+
"""
40+
if not purl_str.startswith("pkg:"):
41+
purl_str = "pkg:" + purl_str
42+
43+
try:
44+
package_url = PackageURL.from_string(purl_str)
45+
except ValueError:
46+
return # Not a valid PackageURL
47+
48+
package_url_dict = package_url.to_dict(encode=encode, empty="")
49+
if include_empty_fields:
50+
return package_url_dict
51+
else:
52+
return without_empty_values(package_url_dict)
53+
54+
55+
def without_empty_values(input_dict):
56+
"""
57+
Return a new dict not including empty value entries from `input_dict`.
58+
59+
`None`, empty string, empty list, and empty dict/set are cleaned.
60+
`0` and `False` values are kept.
61+
"""
62+
empty_values = ([], (), {}, "", None)
63+
64+
return {key: value for key, value in input_dict.items() if value not in empty_values}

0 commit comments

Comments
 (0)