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
4 changes: 3 additions & 1 deletion src/spdx_tools/spdx3/bump_from_spdx2/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ def bump_package(
elif isinstance(id_or_ref, ExternalIdentifier):
external_identifier.append(id_or_ref)

package_purpose = SoftwarePurpose[spdx2_package.primary_package_purpose.name]
package_purpose = (
SoftwarePurpose[spdx2_package.primary_package_purpose.name] if spdx2_package.primary_package_purpose else None
)

payload.add_element(
Package(
Expand Down
4 changes: 2 additions & 2 deletions src/spdx_tools/spdx3/model/hash.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class HashAlgorithm(Enum):
SHA3_512 = auto()
SHA384 = auto()
SHA512 = auto()
SPDXPVCSHA1 = auto()
SPDXPVCSHA256 = auto()
SPDX_PVC_SHA1 = auto()
SPDX_PVC_SHA256 = auto()
SPHINCS_PLUS = auto()


Expand Down
Empty file.
Empty file.
114 changes: 114 additions & 0 deletions src/spdx_tools/spdx3/parser/json/json_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# SPDX-FileCopyrightText: 2022 spdx contributors
#
# SPDX-License-Identifier: Apache-2.0
import json
from typing import Union

from beartype.typing import Dict, List

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 is probably the wrong import, should just be typing?

from semantic_version import Version

from spdx_tools.spdx3.model import HashAlgorithm
from spdx_tools.spdx3.parser.model_definition_dicts import (
CLASS_DICT,
DATETIME_PROPERTIES,
ENUM_DICT,
KEYS_WITH_URI_VALUES,
SPECIALLY_TREATED_KEYS,
VERSION_PROPERTIES,
)
from spdx_tools.spdx3.payload import Payload
from spdx_tools.spdx.casing_tools import camel_case_to_snake_case
from spdx_tools.spdx.datetime_conversions import datetime_from_str


def parse_from_file(file_name: str) -> Payload:
with open(file_name) as file:
input_as_dict: Dict = json.load(file)

context = input_as_dict["@context"]
custom_context = {}
if isinstance(context, list):

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.

What happens if @context is just a single dict? Shouldn't custom_context then be seeded with that dict?

for entry in context:
if isinstance(entry, dict):
custom_context.update(entry)

if "@graph" in input_as_dict:
payload = parse_list_of_elements(input_as_dict["@graph"], custom_context)
else: # in case of only a single serialized element
input_as_dict.pop("@context")
payload = Payload()
payload.add_element(parse_element_object(input_as_dict, custom_context))

return payload


def parse_list_of_elements(list_of_elements: List[Dict], custom_context: Dict):
payload = Payload()
for element_object in list_of_elements:
payload.add_element(parse_element_object(element_object, custom_context))
return payload


def parse_element_object(element_object: Union[Dict, str], custom_context: Dict):
if isinstance(element_object, 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.

In general, JSON also supports numbers, booleans and null directly. Don't we need to handle them in similar fashion?

return element_object

element_properties = {}
if "@id" in element_object:
element_properties["spdx_id"] = expand_uri(element_object.pop("@id"), custom_context)

element_type = element_object.pop("type")

for key, value in element_object.items():
if key in ENUM_DICT:
value = parse_enum_property(key, value)
elif isinstance(value, dict):
value = parse_element_object(value, custom_context)
elif isinstance(value, list):
value = [parse_element_object(entry, custom_context) for entry in value]
elif key in VERSION_PROPERTIES:
value = Version(value)
elif key in DATETIME_PROPERTIES:
value = datetime_from_str(value)

if key in KEYS_WITH_URI_VALUES:
if isinstance(value, list):
value = [expand_uri(entry, custom_context) for entry in value]
else:
value = expand_uri(value, custom_context)

if key in SPECIALLY_TREATED_KEYS:
element_properties[SPECIALLY_TREATED_KEYS[key]] = value
else:
element_properties[camel_case_to_snake_case(key)] = value

if element_type not in CLASS_DICT:

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 could be moved before the for loop?

raise ValueError(f"element type {element_type} not found in CLASS_DICT. Maybe it was forgotten?")
return CLASS_DICT[element_type](**element_properties)


def expand_uri(spdx_id, custom_context):
if ":" in spdx_id:
prefix, suffix = spdx_id.split(":", 1)
if prefix in custom_context and not suffix.startswith("//"):
spdx_id = custom_context[prefix] + suffix
return spdx_id


def parse_enum_property(key: str, value: str):
enum_class = ENUM_DICT[key]
if enum_class == HashAlgorithm:
return parse_hash_algorithm(value)
if isinstance(value, str):
return enum_class[camel_case_to_snake_case(value).upper()]
elif isinstance(value, list):
return [enum_class[camel_case_to_snake_case(entry).upper()] for entry in value]
else:
raise ValueError(f"unrecognized Enum property: {key}: {value}")


def parse_hash_algorithm(value: str):
if value.startswith("blake"):
return HashAlgorithm[value.upper()]

return HashAlgorithm[camel_case_to_snake_case(value).upper()]
102 changes: 102 additions & 0 deletions src/spdx_tools/spdx3/parser/model_definition_dicts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# SPDX-FileCopyrightText: 2023 spdx contributors
#
# SPDX-License-Identifier: Apache-2.0
from spdx_tools.spdx3.model import (
Agent,
Annotation,
AnnotationType,
Bom,
Bundle,
CreationInfo,
ExternalIdentifier,
ExternalIdentifierType,
ExternalMap,
ExternalReference,
ExternalReferenceType,
Hash,
HashAlgorithm,
LifecycleScopedRelationship,
LifecycleScopeType,
NamespaceMap,
Organization,
Person,
ProfileIdentifier,
Relationship,
RelationshipCompleteness,
RelationshipType,
SoftwareAgent,
SpdxDocument,
Tool,
)
from spdx_tools.spdx3.model.ai import AIPackage
from spdx_tools.spdx3.model.build import Build
from spdx_tools.spdx3.model.dataset import Dataset
from spdx_tools.spdx3.model.positive_integer_range import PositiveIntegerRange
from spdx_tools.spdx3.model.software import (
DependencyConditionalityType,
File,
Package,
Sbom,
SBOMType,
Snippet,
SoftwareDependencyLinkType,
SoftwareDependencyRelationship,
SoftwarePurpose,
)

CLASS_DICT = {
"Agent": Agent,
"AIPackage": AIPackage,
"Annotation": Annotation,
"Bom": Bom,
"Build": Build,
"Bundle": Bundle,
"CreationInfo": CreationInfo,
"Dataset": Dataset,
"ExternalIdentifier": ExternalIdentifier,
"ExternalMap": ExternalMap,
"ExternalReference": ExternalReference,
"File": File,
"Hash": Hash,
"LifecycleScopedRelationship": LifecycleScopedRelationship,
"NamespaceMap": NamespaceMap,
"Organization": Organization,
"Package": Package,
"Person": Person,
"PositiveIntegerRange": PositiveIntegerRange,
"Relationship": Relationship,
"Sbom": Sbom,
"Snippet": Snippet,
"SoftwareAgent": SoftwareAgent,
"SoftwareDependencyRelationship": SoftwareDependencyRelationship,
"SpdxDocument": SpdxDocument,
"Tool": Tool,
}
ENUM_DICT = {
"algorithm": HashAlgorithm,
"annotationType": AnnotationType,
"completeness": RelationshipCompleteness,
"conditionality": DependencyConditionalityType,
"externalIdentifierType": ExternalIdentifierType,
"externalReferenceType": ExternalReferenceType,
"profile": ProfileIdentifier,
"primaryPurpose": SoftwarePurpose,
"additionalPurpose": SoftwarePurpose,
"relationshipType": RelationshipType,
"sbomType": SBOMType,
"scope": LifecycleScopeType,
"softwareLinkage": SoftwareDependencyLinkType,
}
SPECIALLY_TREATED_KEYS = {"from": "from_element"}
KEYS_WITH_URI_VALUES = [
"createdBy",
"createdUsing",
"dataLicense",
"from",
"originatedBy",
"subject",
"suppliedBy",
"to",
]
DATETIME_PROPERTIES = ["created", "builtTime", "releaseTime", "validUntilTime"]
VERSION_PROPERTIES = ["specVersion"]
Loading