Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
[metadata]
license_file = LICENSE

[bdist_wheel]
universal=1

[aliases]
release = clean --all sdist --formats=gztar bdist_wheel
12 changes: 6 additions & 6 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ def test_suite():
long_description = fh.read()

setup(
name='spdx-tools',
version='0.7.0a1',
name='ws-spdx-tools',
version='0.7.0a3.post7',
Comment on lines +15 to +16

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume these changes are from the fork and shouldn't be committed?

description='SPDX parser and tools.',
long_description=long_description,
long_description_content_type='text/markdown',
Expand Down Expand Up @@ -51,9 +51,9 @@ def test_suite():
classifiers=[
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python :: 3.6'
'Programming Language :: Python :: 3.7'
'Programming Language :: Python :: 3.8'
'Programming Language :: Python :: 3.9'
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
]
)
16 changes: 11 additions & 5 deletions spdx/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,17 @@ def validate_str_fields(self, fields, optional, messages):
return messages

def validate_checksum(self, messages):
if self.check_sum is not None:
if not isinstance(self.check_sum, checksum.Algorithm):
messages.append(
"Package checksum must be instance of spdx.checksum.Algorithm"
)
try:
if self.check_sum is not None:
if not isinstance(self.check_sum, checksum.Algorithm):
messages.append(
"Package checksum must be instance of spdx.checksum.Algorithm"
)
# this check was added by the WhiteSouse PS Team ( for cases where SHA1 is not available at WhiteSource )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen such attribution comments elsewhere in the project, and I don't think they are viable. If there are many contributors, it would lead to a lot of clutter. I would be grateful if you remove them here and in other locations where they were added

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @rammatzkvosky ! Since we didn't hear back for a while, @meretp took over this PR and put up a new version here. Hope that is alright!

if not self.check_sum.value:
delattr(self, 'check_sum')
Comment on lines +281 to +282

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this accomplishes. If a checksum object without a value is present, it gets removed - does some downstream check depend on this?
Also, if the instance check above fails, we are still adding a validation error message. Is that intended?

Sorry if I am being slow here, I just started working on this project 😅

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicoweidner -In our platform, there are cases where a package sha1 is missing ( empty value ).
For tv format, there is a downstream check for that which fails the report.

For that reason, we add this check.

except:
pass
Comment on lines +283 to +284

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just swallowing all exceptions is bad. Why was this added?


return messages

Expand Down
2 changes: 1 addition & 1 deletion spdx/parsers/tagvalue.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"FILE_CHKSUM_VALUE": "FileChecksum must be a single line of text starting with 'SHA1:', line:{0}",
"FILE_LICS_CONC_VALUE": "LicenseConcluded must be NOASSERTION, NONE, license identifier or license list, line:{0}",
"FILE_LICS_INFO_VALUE": "LicenseInfoInFile must be NOASSERTION, NONE or license identifier, line: {0}",
"FILE_LICS_COMMENT_VALUE": "LicenseComments must be free form lext, line: {0}",
"FILE_LICS_COMMENT_VALUE": "LicenseComments must be free form text, line: {0}",
"FILE_CR_TEXT_VALUE": "FileCopyrightText must be one of NOASSERTION, NONE or free form text, line: {0}",
"FILE_NOTICE_VALUE": "FileNotice must be free form text, line: {0}",
"FILE_CONTRIB_VALUE": "FileContributor must be a single line, line: {0}",
Expand Down
7 changes: 6 additions & 1 deletion spdx/relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ def __init__(self, relationship=None, relationship_comment=None):
self.relationship = relationship
self.relationship_comment = relationship_comment

def __eq__(self, other):
return True if self.relationship == other.relationship else False

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include an instance check on the other object:

Suggested change
return True if self.relationship == other.relationship else False
return (
isInstance(other, Relationship)
and self.relationship == other.relationship
)


@property
def has_comment(self):
return self.relationship_comment is not None
Expand All @@ -103,7 +106,7 @@ def validate(self, messages):
Check that all the fields are valid.
Appends any error messages to messages parameter shall be a ErrorMessages.
"""
self.validate_relationship(messages)
return self.validate_relationship(messages)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! From a quick look, I see the same thing happening for annotations. I will create a separate issue for this.
In general, I find the pattern a bit weird: We modify a mutable object (messages) in place, but then still rely on the mutated object being returned. I feel that either mutating in place and not returning, or alternatively returning a new object would be the canonical options... but that's not relevant for this PR 😅


def validate_relationship(self, messages):
r_type = self.relationship.split(" ")[1]
Expand All @@ -112,3 +115,5 @@ def validate_relationship(self, messages):
"Relationship type must be one of the constants defined in "
"class spdx.relationship.Relationship"
)

return messages
4 changes: 4 additions & 0 deletions spdx/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def to_value(self):
def __str__(self):
return self.to_value()

def __repr__(self):
return self.to_value()


class UnKnown(object):
"""
Expand All @@ -91,6 +94,7 @@ def __repr__(self):
def __eq__(self, other):
return self.to_value() == other.to_value()


class SPDXNone(object):
"""
Represent SPDX None value.
Expand Down
16 changes: 15 additions & 1 deletion spdx/writers/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@
from spdx.writers.tagvalue import InvalidDocumentError
from spdx.writers.jsonyamlxml import Writer
from spdx.parsers.loggers import ErrorMessages
import numpy as np

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove the dependency on numpy? You should be able to test for regular int, float, etc.

import datetime


# this method has been added by the WhiteSouse PS Team.
def json_converter(obj):
if isinstance(obj, np.integer):
return int(obj)
elif isinstance(obj, np.floating):
return float(obj)
elif isinstance(obj, np.ndarray):
return obj.tolist()
elif isinstance(obj, datetime.datetime):
Comment on lines +22 to +29

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the documentation, I believe the default method should raise TypeError if it cannot serialize the object (i.e. it's none of the four cases):

        If specified, default is a function that gets called for objects
        that can't otherwise be serialized.  It should return a JSON encodable
        version of the object or raise a ``TypeError``.

I also verified this myself: If you don't raise TypeError for unknown types in your default implementation, it will now simply return null for all unhandled types

return obj.__str__()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return obj.__str__()
return str(obj)



def write_document(document, out, validate=True):
Expand All @@ -26,4 +40,4 @@ def write_document(document, out, validate=True):

writer = Writer(document)
document_object = writer.create_document()
json.dump(document_object, out, indent=4)
json.dump(document_object, out, indent=4, default=json_converter)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the use case for numpy type serialization. The internal representation of the SPDX document should always be created by the tool itself (using either "parse document" or "convert document"). So how would we ever end up in a situation where it contains numpy types that need to be serialized?

63 changes: 51 additions & 12 deletions spdx/writers/jsonyamlxml.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ def checksum(self, checksum_field):
"""
checksum_object = dict()
checksum_object["algorithm"] = (
"checksumAlgorithm_" + checksum_field.identifier.lower()
#"checksumAlgorithm_" + checksum_field.identifier.lower()
# Changed by the WhiteSouse PS Team.
checksum_field.identifier
Comment on lines -50 to +52

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I agree that the checksumAlgorithm_ prefix is entirely redundant here, it seems to be prescribed by the spec, see https://spdx.github.io/spdx-spec/RDF-object-model-and-identifier-syntax/

)
checksum_object["checksumValue"] = checksum_field.value
return checksum_object
Expand Down Expand Up @@ -112,16 +114,16 @@ def create_package_info(self, package):
package_object["SPDXID"] = self.spdx_id(package.spdx_id)
package_object["name"] = package.name
package_object["downloadLocation"] = package.download_location.__str__()
package_object["packageVerificationCode"] = self.package_verification_code(
package
)
package_object["licenseConcluded"] = self.license(package.conc_lics)
package_object["licenseInfoFromFiles"] = list(
map(self.license, package.licenses_from_files)
)
package_object["licenseDeclared"] = self.license(package.license_declared)
package_object["copyrightText"] = package.cr_text.__str__()

if package.verif_code is not None:
package_object["packageVerificationCode"] = self.package_verification_code(package)
Comment on lines +124 to +125

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!


if package.has_optional_field("version"):
package_object["versionInfo"] = package.version

Expand All @@ -145,7 +147,8 @@ def create_package_info(self, package):

if package.has_optional_field("check_sum"):
package_object["checksums"] = [self.checksum(package.check_sum)]
package_object["sha1"] = package.check_sum.value
# package_object["sha1"] = package.check_sum.value
# commented by the WS PS Team, sha1 is already exist in check_sum
Comment on lines +150 to +151

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, but can you please remove the comments?


if package.has_optional_field("description"):
package_object["description"] = package.description
Expand All @@ -156,6 +159,9 @@ def create_package_info(self, package):
if package.has_optional_field("homepage"):
package_object["homepage"] = package.homepage.__str__()

if package.has_optional_field("files_analyzed"):
package_object["filesAnalyzed"] = package.files_analyzed
Comment on lines +162 to +163

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yet another good catch! I think this project really needs more extensive test cases to catch issues like this.


return package_object


Expand Down Expand Up @@ -371,9 +377,16 @@ def __init__(self, document):

def create_extracted_license(self):
extracted_license_objects = []
extracted_licenses = self.document.extracted_licenses
# commented by the WS PS Team
# extracted_licenses = self.document.extracted_licenses

# filter by unique identifier attribute in ExtractedLicense object, added by the WhiteSouse PS Team
unique_extracted_licenses = {}
for lic in self.document.extracted_licenses:
if lic.identifier not in unique_extracted_licenses.keys():
unique_extracted_licenses[lic.identifier] = lic

for extracted_license in extracted_licenses:
for extracted_license in unique_extracted_licenses.values():
Comment on lines +380 to +389

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another good change - from my understanding at least, extracted license ids should be unique within one document.

While this is fine in this PR, I think on a higher level it would be nicer to sure that the internal representation of the document is valid before passing it to the writer. Then the writer wouldn't need to include such checks. But that is a change for another time...

However, please remove the comments :-)

extracted_license_object = dict()

if isinstance(extracted_license.identifier, Literal):
Expand Down Expand Up @@ -471,13 +484,37 @@ def create_document(self):
self.document_object["SPDXID"] = self.spdx_id(self.document.spdx_id)
self.document_object["name"] = self.document.name

# filter by unique spdx_id attribute in Package object, added by the WhiteSouse PS Team
unique_doc_packages = {}
for doc_package in self.document.packages:
if doc_package.spdx_id not in unique_doc_packages.keys():
unique_doc_packages[doc_package.spdx_id] = doc_package
Comment on lines +488 to +491

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the extracted licenses: Fine for now, but eventually I would move this to validations


# commented by the WS PS Team
# package_objects = []
# for package in self.document.packages:
# package_info_object = self.create_package_info(package)
# package_info_object["files"] = self.create_file_info(package)
# package_objects.append({"Package": package_info_object})
#
# self.document_object["documentDescribes"] = package_objects

# the previous code block has been adjusted by the WhiteSouse PS Team.
# adding documentDescribes and packages sections.
Comment on lines +493 to +503

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove the comments. It will be in the git history anyway, and the comments will just clutter the code

package_objects = []
for package in self.document.packages:
document_describes_objects = []
for package in unique_doc_packages.values():
package_info_object = self.create_package_info(package)
package_info_object["files"] = self.create_file_info(package)
package_objects.append({"Package": package_info_object})
if package.files:
package_info_object["files"] = self.create_file_info(package)
# create a list of the packages' names for the 'documentDescribes' field

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# create a list of the packages' names for the 'documentDescribes' field
# create a list of the packages' spdx ids for the 'documentDescribes' field

package_spdxid = package_info_object.get("SPDXID")
document_describes_objects.append(package_spdxid)
package_objects.append(package_info_object)

self.document_object["documentDescribes"] = package_objects
self.document_object["documentDescribes"] = document_describes_objects
self.document_object["packages"] = package_objects
# end

if self.document.has_comment:
self.document_object["comment"] = self.document.comment
Expand All @@ -504,4 +541,6 @@ def create_document(self):
if self.document.relationships:
self.document_object["relationships"] = self.create_relationship_info()

return {"Document": self.document_object}
# return {"Document": self.document_object}. No need in "Document" key for json and tv format.
# Changed by the WhiteSouse PS Team.
Comment on lines +544 to +545

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more superfluous comments

return self.document_object
11 changes: 6 additions & 5 deletions spdx/writers/rdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ def relationships(self):
"""
Return a list of relationship nodes
"""
return map(self.create_relationship_node, self.document.relationships)
return list(map(self.create_relationship_node, self.document.relationships))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it makes a difference here whether we return an iterable or a list. The doc says it returns a list, but then again all other similar functions return an iterable as well, so I'm inclined to leave it that way (or fix all the docs)



class CreationInfoWriter(BaseWriter):
Expand Down Expand Up @@ -1016,10 +1016,11 @@ def write(self):
for package_node in self.packages():
package_triple = (doc_node, self.spdx_namespace.describesPackage, package_node)
self.graph.add(package_triple)
"""# Add relationship
relate_node = self.relationships()
relate_triple = (doc_node, self.spdx_namespace.relationship, relate_node)
self.graph.add(relate_triple)"""
# Add relationship
for relate_node in self.relationships():
relate_triple = (doc_node, self.spdx_namespace.relationship, relate_node)
self.graph.add(relate_triple)
Comment on lines +1019 to +1022

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this out using

$ convertor -f tag data/SPDXTagExample -o output.rdf

and the output contained this, which doesn't seem to make sense (relationships should be separate entities):

    <ns1:relationship>
      <ns1:Relationship rdf:about="SPDXRef-DOCUMENT">
        <ns1:relationshipType>CONTAINS</ns1:relationshipType>
        <ns1:relationshipType>DESCRIBES</ns1:relationshipType>
        <ns1:relationshipType>COPY_OF</ns1:relationshipType>
        <ns1:relatedSpdxElement>SPDXRef-File</ns1:relatedSpdxElement>
        <ns1:relatedSpdxElement>DocumentRef-spdx-tool-1.2:SPDXRef-ToolsElement</ns1:relatedSpdxElement>
        <ns1:relatedSpdxElement>SPDXRef-Package</ns1:relatedSpdxElement>
      </ns1:Relationship>
    </ns1:relationship>

I think this either needs more work, or should be handled as a separate issue - whichever way you prefer!


# Add snippet
snippet_nodes = self.snippets()
for snippet in snippet_nodes:
Expand Down
4 changes: 3 additions & 1 deletion spdx/writers/xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ def write_document(document, out, validate=True):
raise InvalidDocumentError(messages)

writer = Writer(document)
document_object = {"SpdxDocument": writer.create_document()}
# document_object = {"SpdxDocument": writer.create_document()}.
# hanged by the WhiteSouse PS Team
document_object = {"Document": writer.create_document()}
Comment on lines -28 to +30

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this changed? Looks internal


xmltodict.unparse(document_object, out, encoding="utf-8", pretty=True)
2 changes: 1 addition & 1 deletion spdx/writers/yaml.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ def write_document(document, out, validate=True):
writer = Writer(document)
document_object = writer.create_document()

yaml.safe_dump(document_object, out, indent=2, explicit_start=True)
yaml.safe_dump(document_object, out, indent=2, explicit_start=True, encoding='utf-8')