3838from binaryornot .helpers import is_binary_string
3939from git import DiffIndex
4040from git import Repo
41+ from license_expression import Licensing
4142from packageurl import PackageURL
4243from univers .version_range import VersionRange
4344from univers .versions import Version
@@ -117,22 +118,28 @@ class AffectedPackage:
117118 """
118119 Contains a range of affected versions and a fixed version of a given package
119120 The PackageURL supplied must *not* have a version
121+ It must contain either `affected_version_range` or `fixed_version`
120122 """
121123
122124 package : PackageURL
123- affected_version_range : VersionRange
125+ affected_version_range : Optional [ VersionRange ] = None
124126 fixed_version : Optional [Version ] = None
125127
126128 def __post_init__ (self ):
127129 if self .package .version :
128- raise ValueError
130+ raise ValueError ("The PackageURL supplied must not have a version" )
131+ if not (self .affected_version_range or self .fixed_version ):
132+ raise ValueError (
133+ "Affected Package should at least have either a fixed version or affected version range"
134+ )
129135
130136 def get_fixed_purl (self ):
131137 """
132138 Return PackageURL corresponding to object's fixed_version
133139 """
134- fixed_version = self .fixed_version
135- fixed_purl = self .package ._replace (version = str (fixed_version ))
140+ if not self .fixed_version :
141+ raise ValueError ("Affected package should have a fixed version" )
142+ fixed_purl = self .package ._replace (version = str (self .fixed_version ))
136143 return fixed_purl
137144
138145 @classmethod
@@ -152,7 +159,8 @@ def merge(cls, affected_packages: Iterable):
152159 fixed_versions = set ()
153160 purls = set ()
154161 for pkg in affected_packages :
155- affected_version_ranges .add (pkg .affected_version_range )
162+ if pkg .affected_version_range :
163+ affected_version_ranges .add (pkg .affected_version_range )
156164 if pkg .fixed_version :
157165 fixed_versions .add (pkg .fixed_version )
158166 purls .add (pkg .package )
@@ -164,9 +172,12 @@ def to_dict(self):
164172 """
165173 Return a serializable dict that can be converted back using self.from_dict
166174 """
175+ affected_version_range = None
176+ if self .affected_version_range :
177+ affected_version_range = str (self .affected_version_range )
167178 return {
168179 "package" : self .package .to_dict (),
169- "affected_version_range" : str ( self . affected_version_range ) ,
180+ "affected_version_range" : affected_version_range ,
170181 "fixed_version" : str (self .fixed_version ) if self .fixed_version else None ,
171182 }
172183
@@ -176,9 +187,16 @@ def from_dict(cls, affected_pkg: dict):
176187 Return an AffectedPackage object from dict generated by self.to_dict
177188 """
178189 package = PackageURL (** affected_pkg ["package" ])
179- affected_version_range = VersionRange .from_string (affected_pkg ["affected_version_range" ])
190+ affected_version_range = None
191+ if (
192+ affected_pkg ["affected_version_range" ]
193+ and affected_pkg ["affected_version_range" ] != "None"
194+ ):
195+ affected_version_range = VersionRange .from_string (
196+ affected_pkg ["affected_version_range" ]
197+ )
180198 fixed_version = affected_pkg ["fixed_version" ]
181- if fixed_version :
199+ if fixed_version and affected_version_range :
182200 # TODO: revisit after https://github.com/nexB/univers/issues/10
183201 fixed_version = affected_version_range .version_class (fixed_version )
184202
@@ -203,7 +221,7 @@ class AdvisoryData:
203221 """
204222
205223 aliases : List [str ] = dataclasses .field (default_factory = list )
206- summary : str = None
224+ summary : Optional [ str ] = None
207225 affected_packages : List [AffectedPackage ] = dataclasses .field (default_factory = list )
208226 references : List [Reference ] = dataclasses .field (default_factory = list )
209227 date_published : Optional [datetime .datetime ] = None
@@ -217,17 +235,29 @@ class NoLicenseError(Exception):
217235 pass
218236
219237
238+ class InvalidSPDXLicense (Exception ):
239+ pass
240+
241+
220242class Importer :
221243 """
222244 An Importer collects data from various upstreams and returns corresponding AdvisoryData objects
223245 in its advisory_data method. Subclass this class to implement an importer
224246 """
225247
226248 spdx_license_expression = ""
249+ license_url = ""
227250
228251 def __init__ (self ):
229252 if not self .spdx_license_expression :
230253 raise Exception (f"Cannot run importer { self !r} without a license" )
254+ licensing = Licensing ()
255+ try :
256+ licensing .parse (self .spdx_license_expression )
257+ except InvalidSPDXLicense as e :
258+ raise ValueError (
259+ f"{ self .spdx_license_expression !r} is not a valid SPDX license expression"
260+ ) from e
231261
232262 @classproperty
233263 def qualified_name (cls ):
0 commit comments