Skip to content

Commit

Permalink
Add: Make CPE hashable
Browse files Browse the repository at this point in the history
Add __hash__ and __equal__ method to make the CPE class hashable. When
being hashable it can be used as keys for dicts or in sets. The __hash__
defines the identity of the CPE instance. Also add a __repr__ method for
easier debugging and better representation of a CPE instance.
  • Loading branch information
bjoernricks committed Dec 19, 2023
1 parent f280e08 commit 2201ae8
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 3 deletions.
31 changes: 28 additions & 3 deletions pontos/cpe/_cpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import re
import urllib.parse
from dataclasses import dataclass
from enum import Enum
from typing import Optional
from typing import Any, Optional

from pontos.errors import PontosError
from pontos.models import StrEnum

__all__ = (
"ANY",
Expand All @@ -28,7 +28,7 @@ class CPEParsingError(PontosError):
"""


class Part(Enum):
class Part(StrEnum):
"""
Represents the possible values for a part CPE attribute
"""
Expand Down Expand Up @@ -726,3 +726,28 @@ def __str__(self) -> str:
return self.as_uri_binding()

return self.as_formatted_string_binding()

def __repr__(self) -> str:
return (
f"<{self.__class__.__name__} "
f'part="{self.part}" '
f'vendor="{self.vendor}" '
f'product="{self.product}" '
f'version="{self.version}" '
f'update="{self.update}" '
f'edition="{self.edition}" '
f'language="{self.language}" '
f'sw_edition="{self.sw_edition}" '
f'target_sw="{self.target_sw}" '
f'target_hw="{self.target_hw}" '
f'other="{self.other}"'
">"
)

def __hash__(self) -> int:
return hash(str(self))

def __eq__(self, other: Any) -> bool:
if not isinstance(other, CPE):
return False
return str(self) == str(other)
32 changes: 32 additions & 0 deletions tests/cpe/test_cpe.py
Original file line number Diff line number Diff line change
Expand Up @@ -727,3 +727,35 @@ def test_clone(self):
self.assertIsNot(cpe, cpe2)
self.assertEqual(cpe.version, "7.51")
self.assertEqual(cpe2.version, ANY)

def test_equal(self):
cpe1 = CPE.from_string("cpe:2.3:a:3com:3cdaemon:-:*:*:*:*:*:*:*")
cpe2 = CPE.from_string("cpe:2.3:a:adobe:flash_player:-:*:*:*:*:*:*:*")
cpe3 = CPE.from_string("cpe:2.3:a:3com:3cdaemon:-:*:*:*:*:*:*:*")

self.assertNotEqual(cpe1, None)
self.assertNotEqual(cpe1, "foo")
self.assertNotEqual(cpe1, cpe2)
self.assertIsNot(cpe1, cpe3)
self.assertEqual(cpe1, cpe3)

def test_hashable(self):
cpe1 = CPE.from_string("cpe:2.3:a:3com:3cdaemon:-:*:*:*:*:*:*:*")
cpe2 = CPE.from_string("cpe:2.3:a:adobe:flash_player:-:*:*:*:*:*:*:*")
cpe3 = CPE.from_string("cpe:2.3:a:3com:3cdaemon:-:*:*:*:*:*:*:*")

cpe_list = [cpe1, cpe2, cpe3, cpe1]
self.assertTrue(len(cpe_list), 4)

cpe_set = set(cpe_list)
self.assertTrue(len(cpe_set), 2)

def test_repr(self):
cpe1 = CPE.from_string("cpe:2.3:a:3com:3cdaemon:-:*:*:*:*:*:*:*")

self.assertEqual(
repr(cpe1),
'<CPE part="a" vendor="3com" product="3cdaemon" version="-" '
'update="*" edition="*" language="*" sw_edition="*" target_sw="*" '
'target_hw="*" other="*">',
)

0 comments on commit 2201ae8

Please sign in to comment.