Skip to content

Commit

Permalink
Add an option for exact_match purl QuerySet lookups #118 (#156)
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <tdruez@nexb.com>
  • Loading branch information
tdruez authored Jun 13, 2024
1 parent 18672be commit 29dd138
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 13 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ Changelog
- Add support for Composer in ``purl2url`` and ``url2purl``.
https://github.com/package-url/packageurl-python/pull/144

- Add an option for ``exact_match`` purl QuerySet lookups in the
``PackageURLQuerySetMixin.for_package_url``method.
https://github.com/package-url/packageurl-python/issues/118
0.15.0 (2024-03-12)
-------------------
Expand Down
12 changes: 8 additions & 4 deletions src/packageurl/contrib/django/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,24 @@
class PackageURLFilter(django_filters.CharFilter):
"""
Filter by an exact Package URL string.
The special "EMPTY" value allows to retrieve objects with empty
Package URL.
This filter depends on a `for_package_url` and `empty_package_url`
The special "EMPTY" value allows retrieval of objects with an empty Package URL.
This filter depends on `for_package_url` and `empty_package_url`
methods to be available on the Model Manager,
see for example `PackageURLQuerySetMixin`.
When exact_match_only is True, the filter will match only exact Package URL strings.
"""

is_empty = "EMPTY"
exact_match_only = False
help_text = (
'Match Package URL. Use "EMPTY" as value to retrieve objects with empty Package URL.'
)

def __init__(self, *args, **kwargs):
self.exact_match_only = kwargs.pop("exact_match_only", False)
kwargs.setdefault("help_text", self.help_text)
super().__init__(*args, **kwargs)

Expand All @@ -58,4 +62,4 @@ def filter(self, qs, value):
if value == self.is_empty:
return qs.empty_package_url()

return qs.for_package_url(value)
return qs.for_package_url(value, exact_match=self.exact_match_only)
14 changes: 10 additions & 4 deletions src/packageurl/contrib/django/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,18 @@ class PackageURLQuerySetMixin:
Add Package URL filtering methods to a django.db.models.QuerySet.
"""

def for_package_url(self, purl_str, encode=True):
def for_package_url(self, purl_str, encode=True, exact_match=False):
"""
Filter the QuerySet with the provided Package URL string.
The purl string is validated and transformed into filtering lookups.
Filter the QuerySet based on a Package URL (purl) string with an option for
exact match filtering.
When `exact_match` is False (default), the method will match any purl with the
same base fields as `purl_str` and allow variations in other fields.
When `exact_match` is True, only the identical purl will be returned.
"""
lookups = purl_to_lookups(purl_str=purl_str, encode=encode)
lookups = purl_to_lookups(
purl_str=purl_str, encode=encode, include_empty_fields=exact_match
)
if lookups:
return self.filter(**lookups)
return self.none()
Expand Down
17 changes: 12 additions & 5 deletions src/packageurl/contrib/django/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,14 @@
from packageurl import PackageURL


def purl_to_lookups(purl_str, encode=True):
def purl_to_lookups(purl_str, encode=True, include_empty_fields=False):
"""
Return a lookups dict built from the provided `purl` string.
Those lookups can be used as QuerySet filters.
Return a lookups dictionary built from the provided `purl` (Package URL) string.
These lookups can be used as QuerySet filters.
If include_empty_fields is provided, the resulting dictionary will include fields
with empty values. This is useful to get exact match.
Note that empty values are always returned as empty strings as the model fields
are defined with `blank=True` and `null=False`.
"""
if not purl_str.startswith("pkg:"):
purl_str = "pkg:" + purl_str
Expand All @@ -41,8 +45,11 @@ def purl_to_lookups(purl_str, encode=True):
except ValueError:
return # Not a valid PackageURL

package_url_dict = package_url.to_dict(encode=encode)
return without_empty_values(package_url_dict)
package_url_dict = package_url.to_dict(encode=encode, empty="")
if include_empty_fields:
return package_url_dict
else:
return without_empty_values(package_url_dict)


def without_empty_values(input_dict):
Expand Down
16 changes: 16 additions & 0 deletions tests/contrib/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,22 @@ def test_purl_to_lookups_with_encode():
}


def test_purl_to_lookups_include_empty_fields():
purl_str = "pkg:alpine/openssl"
assert purl_to_lookups(purl_str) == {
"type": "alpine",
"name": "openssl",
}
assert purl_to_lookups(purl_str, include_empty_fields=True) == {
"type": "alpine",
"namespace": "",
"name": "openssl",
"version": "",
"qualifiers": "",
"subpath": "",
}


def test_get_golang_purl():
assert None == get_golang_purl(None)
golang_purl_1 = get_golang_purl(
Expand Down

0 comments on commit 29dd138

Please sign in to comment.