Skip to content

[Issue 359] Add Json writer for new data model #379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 55 commits into from
Jan 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b750cdf
[bugfix] Fix snippet copyright text type
nicoweidner Dec 21, 2022
72145b0
[issue-359] Recover datetime utils from git history, add tests
nicoweidner Dec 13, 2022
6b892fd
[issue-359][squash] add license header
nicoweidner Dec 30, 2022
e2b06dd
[issue-359] Add Json property conversion setup for CreationInfo prope…
nicoweidner Dec 14, 2022
f84ef3f
[issue-359] Remove logic from json property enums (will be included i…
nicoweidner Dec 15, 2022
cb395c5
[issue-359] Add abstract base converter class and checksum converter
nicoweidner Dec 15, 2022
92c183e
[issue-359][squash] Remove license header from empty __init__.py
nicoweidner Jan 2, 2023
aca25d1
[issue-359] Add CreationInfo converter
nicoweidner Dec 15, 2022
dad3227
[issue-359] Add external document ref converter
nicoweidner Dec 15, 2022
5f625b9
[issue-359][squash] Rename variable
nicoweidner Dec 30, 2022
1804c21
[issue-359] Enable passing the full document to converters to account…
nicoweidner Dec 15, 2022
70a99bc
[issue-359] Add annotation properties and converter
nicoweidner Dec 15, 2022
a33766b
[issue-359] Add external package ref properties and converter
nicoweidner Dec 15, 2022
525d451
[issue-359] Add package verification code properties and converter
nicoweidner Dec 15, 2022
0ae5968
[issue-359] Add __str__ method to LicenseExpression for easy serializ…
nicoweidner Dec 21, 2022
e5058b6
[issue-359] add package converter
nicoweidner Dec 19, 2022
d0baa7f
[issue-359] add relationship converter
nicoweidner Dec 19, 2022
92e2dc3
[issue-359] add extracted licensing info converter
nicoweidner Dec 19, 2022
dbca30a
[issue-359] add file converter
nicoweidner Dec 20, 2022
8f97552
[issue-359] add snippet converter
nicoweidner Dec 20, 2022
dd355d4
[issue-359] add document converter
nicoweidner Dec 20, 2022
e92d398
[issue-359][squash] Remove deprecated REVIEWEDS property
nicoweidner Dec 30, 2022
f3bc25b
[issue-359] json converters no longer set dictionary properties if th…
nicoweidner Dec 20, 2022
f4c313f
[issue-359][squash] Fix optional utils return value
nicoweidner Dec 30, 2022
dc29f1d
[issue-359] Improve converter typing and some parameter names
nicoweidner Dec 21, 2022
0cf085f
[issue-359] Extract creation info fixture for reuse in tests
nicoweidner Dec 21, 2022
50e0a54
[issue-359] Extract file fixture for reuse in tests
nicoweidner Dec 21, 2022
09a4af5
[issue-359] Extract package fixture for reuse in tests
nicoweidner Dec 21, 2022
f669f6b
[issue-359] Extract external document ref fixture for reuse in tests
nicoweidner Dec 21, 2022
d640c58
[issue-359] Extract external package ref fixture for reuse in tests
nicoweidner Dec 21, 2022
1aabe99
[issue-359] Extract snippet fixture for reuse in tests
nicoweidner Dec 21, 2022
0adbb75
[issue-359] Add SpdxNoAssertion and SpdxNone tests
nicoweidner Dec 21, 2022
c430a0d
[issue-359] Don't write empty lists into the converted dictionary
nicoweidner Dec 21, 2022
976287d
[issue-359] Add json writer
nicoweidner Dec 21, 2022
23894d7
[issue-359][squash] extract variables in test_json_writer.py
nicoweidner Dec 30, 2022
35d235e
[issue-359] Make conversion test assertions more precise by excluding…
nicoweidner Dec 21, 2022
000cd80
[issue-359] Add more test fixtures
nicoweidner Dec 22, 2022
fba6e21
[issue-359] Add utility assertion function for mocks
nicoweidner Dec 22, 2022
410dd34
[issue-359] Add tests for annotation writing logic
nicoweidner Dec 22, 2022
fbf6e57
[issue-359] Add relationship fixture to test fixtures
nicoweidner Dec 22, 2022
f9997da
[issue-359] Add document_describes and has_files tests
nicoweidner Dec 22, 2022
fe7b0bd
[issue-359] Add tests for relationship conversion logic in DocumentCo…
nicoweidner Dec 22, 2022
c2b9ac0
[issue-359] Fix mock ordering in converter tests
nicoweidner Dec 22, 2022
7aa1b74
[issue-359] Make mock utilities compatible with Python 3.7
nicoweidner Dec 22, 2022
4aac32c
[issue-359] Remove license headers from empty init files
nicoweidner Dec 30, 2022
030b1fc
[issue-359] Move relationship_filters to model package
nicoweidner Dec 30, 2022
b371f4c
[issue-359] Add NOASSERTION case to extracted license name
nicoweidner Dec 30, 2022
f511d02
[issue-359] Add default implementation of json_property_name to conve…
nicoweidner Dec 30, 2022
57b70af
[issue-359] Allow NOASSERTION and NONE for related element id in conv…
nicoweidner Dec 30, 2022
4727af7
[issue-359] Remove JsonWriter class, convert to plain function
nicoweidner Dec 30, 2022
6c2d577
[issue-359] Add validation to json writer, with the possibility of di…
nicoweidner Dec 30, 2022
03fca1d
[issue-359] Add docstrings to some core files
nicoweidner Jan 2, 2023
3f0f111
[issue-359][squash] Fix docstring
nicoweidner Jan 2, 2023
ebae51a
[issue-359] Extract common method to get all spdx element ids inside …
nicoweidner Jan 2, 2023
a0a804b
[issue-359] Use extracted relationship filtering methods in document_…
nicoweidner Jan 2, 2023
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
2 changes: 0 additions & 2 deletions src/datetime_conversions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
# limitations under the License.
from datetime import datetime

from src.parser.error import SPDXParsingError


def datetime_from_str(date_str: str) -> datetime:
if not isinstance(date_str, str):
Expand Down
20 changes: 20 additions & 0 deletions src/document_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import List

from src.model.document import Document


def get_contained_spdx_element_ids(document: Document) -> List[str]:
element_ids = [file.spdx_id for file in document.files]
element_ids.extend([package.spdx_id for package in document.packages])
element_ids.extend([snippet.spdx_id for snippet in document.snippets])
return element_ids
Empty file added src/jsonschema/__init__.py
Empty file.
37 changes: 37 additions & 0 deletions src/jsonschema/annotation_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Type, Any

from src.datetime_conversions import datetime_to_iso_string
from src.jsonschema.annotation_properties import AnnotationProperty
from src.jsonschema.converter import TypedConverter
from src.jsonschema.json_property import JsonProperty
from src.model.annotation import Annotation
from src.model.document import Document


class AnnotationConverter(TypedConverter[Annotation]):
def _get_property_value(self, annotation: Annotation, annotation_property: AnnotationProperty,
document: Document = None) -> Any:
if annotation_property == AnnotationProperty.ANNOTATION_DATE:
return datetime_to_iso_string(annotation.annotation_date)
elif annotation_property == AnnotationProperty.ANNOTATION_TYPE:
return annotation.annotation_type.name
elif annotation_property == AnnotationProperty.ANNOTATOR:
return annotation.annotator.to_serialized_string()
elif annotation_property == AnnotationProperty.COMMENT:
return annotation.annotation_comment

def get_json_type(self) -> Type[JsonProperty]:
return AnnotationProperty

def get_data_model_type(self) -> Type[Annotation]:
return Annotation
20 changes: 20 additions & 0 deletions src/jsonschema/annotation_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import auto

from src.jsonschema.json_property import JsonProperty


class AnnotationProperty(JsonProperty):
ANNOTATION_DATE = auto()
ANNOTATION_TYPE = auto()
ANNOTATOR = auto()
COMMENT = auto()
40 changes: 40 additions & 0 deletions src/jsonschema/checksum_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Type

from src.jsonschema.checksum_properties import ChecksumProperty
from src.jsonschema.converter import TypedConverter
from src.jsonschema.json_property import JsonProperty
from src.model.checksum import Checksum, ChecksumAlgorithm
from src.model.document import Document


class ChecksumConverter(TypedConverter[Checksum]):

def get_data_model_type(self) -> Type[Checksum]:
return Checksum

def get_json_type(self) -> Type[JsonProperty]:
return ChecksumProperty

def _get_property_value(self, checksum: Checksum, checksum_property: ChecksumProperty,
_document: Document = None) -> str:
if checksum_property == ChecksumProperty.ALGORITHM:
return algorithm_to_json_string(checksum.algorithm)
elif checksum_property == ChecksumProperty.CHECKSUM_VALUE:
return checksum.value


def algorithm_to_json_string(algorithm: ChecksumAlgorithm) -> str:
name_with_dash: str = algorithm.name.replace("_", "-")
if "BLAKE2B" in name_with_dash:
return name_with_dash.replace("BLAKE2B", "BLAKE2b")
return name_with_dash
Comment on lines +36 to +40
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is needed in the tag-value writer, too

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But that isn't merged yet, right? Then once this PR is merged, the other one can be rebased and can extract it

18 changes: 18 additions & 0 deletions src/jsonschema/checksum_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import auto

from src.jsonschema.json_property import JsonProperty


class ChecksumProperty(JsonProperty):
ALGORITHM = auto()
CHECKSUM_VALUE = auto()
73 changes: 73 additions & 0 deletions src/jsonschema/converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABC, abstractmethod
from typing import Any, Type, Dict, TypeVar, Generic

from src.jsonschema.json_property import JsonProperty
from src.model.document import Document
from src.writer.casing_tools import snake_case_to_camel_case

MISSING_IMPLEMENTATION_MESSAGE = "Must be implemented"

T = TypeVar("T")


class TypedConverter(ABC, Generic[T]):
"""
Base class for all converters between an instance of the tools-python data model and the corresponding dictionary
representation, following the json schema. The generic type T is the type according to the data model.
Each converter has several methods:
- get_json_type and get_data_model_type: return the data model type and the corresponding JsonProperty subclass.
These methods are abstract in the base class and need to be implemented in subclasses.
- json_property_name: converts an enum value of a JsonProperty subclass to the corresponding property name in the
json schema. The default implementation simply converts from snake case to camel case. Can be overridden in case
of exceptions like "SPDXID".
- convert: converts an instance of type T (one of the data model types) to a dictionary representation. In some
cases, the full document is required (see below). The logic should be generic for all types.
- requires_full_document: indicates whether the full document is required for conversion. Returns False by
default, can be overridden as needed for specific types.
- _get_property_value: Retrieves the value of a specific json property from the data model instance. In some
cases, the full document is required.
"""

@abstractmethod
def _get_property_value(self, instance: T, json_property: JsonProperty, document: Document = None) -> Any:
raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE)

@abstractmethod
def get_json_type(self) -> Type[JsonProperty]:
raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE)

@abstractmethod
def get_data_model_type(self) -> Type[T]:
raise NotImplementedError(MISSING_IMPLEMENTATION_MESSAGE)

def json_property_name(self, json_property: JsonProperty) -> str:
return snake_case_to_camel_case(json_property.name)

def requires_full_document(self) -> bool:
return False

def convert(self, instance: T, document: Document = None) -> Dict:
if not isinstance(instance, self.get_data_model_type()):
raise TypeError(
f"Converter of type {self.__class__} can only convert objects of type "
f"{self.get_data_model_type()}. Received {type(instance)} instead.")
if self.requires_full_document() and not document:
raise ValueError(f"Converter of type {self.__class__} requires the full document")

result = {}
for property_name in self.get_json_type():
property_value = self._get_property_value(instance, property_name, document)
if property_value is None:
continue
result[self.json_property_name(property_name)] = property_value
return result
37 changes: 37 additions & 0 deletions src/jsonschema/creation_info_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Type, Any

from src.datetime_conversions import datetime_to_iso_string
from src.jsonschema.converter import TypedConverter
from src.jsonschema.creation_info_properties import CreationInfoProperty
from src.jsonschema.json_property import JsonProperty
from src.jsonschema.optional_utils import apply_if_present
from src.model.document import CreationInfo, Document


class CreationInfoConverter(TypedConverter[CreationInfo]):
def get_data_model_type(self) -> Type[CreationInfo]:
return CreationInfo

def get_json_type(self) -> Type[JsonProperty]:
return CreationInfoProperty

def _get_property_value(self, creation_info: CreationInfo, creation_info_property: CreationInfoProperty,
_document: Document = None) -> Any:
if creation_info_property == CreationInfoProperty.CREATED:
return datetime_to_iso_string(creation_info.created)
elif creation_info_property == CreationInfoProperty.CREATORS:
return [creator.to_serialized_string() for creator in creation_info.creators] or None
elif creation_info_property == CreationInfoProperty.LICENSE_LIST_VERSION:
return apply_if_present(str, creation_info.license_list_version)
elif creation_info_property == CreationInfoProperty.COMMENT:
return creation_info.creator_comment
20 changes: 20 additions & 0 deletions src/jsonschema/creation_info_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copyright (c) 2022 spdx contributors
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from enum import auto

from src.jsonschema.json_property import JsonProperty


class CreationInfoProperty(JsonProperty):
CREATED = auto()
CREATORS = auto()
LICENSE_LIST_VERSION = auto()
COMMENT = auto()
Loading