-
-
Notifications
You must be signed in to change notification settings - Fork 54
feat!: Add component and services for tools #635
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
jkowalleck
merged 68 commits into
CycloneDX:8.0.0-dev
from
jkugler:561_add_components_and_services
Sep 6, 2024
Merged
Changes from 1 commit
Commits
Show all changes
68 commits
Select commit
Hold shift + click to select a range
2bbd659
#561: Add component and services for tools
jkugler 7394c3b
Additioal cleanup and improving semantics
jkugler d8a1f87
More code cleanup
jkugler 280dc92
Fix json_normalize
jkugler c90ce1f
Add an explicit __bool__ to ToolRepository
jkugler e7b4bd9
Removed Tool class from model/__init__.py
jkugler 3fe5ab6
Rename Tool* to Tools*
jkugler 582741e
Fixing a circular import issue
jkugler 7e95ea8
Remove unneeded code
jkugler ea9ffd4
Adding an XML de-serializer
jkugler f23ab80
More code clenaup
jkugler f8efa5a
Final feature addition
jkugler a40cbe7
Adding tests for new functionality
jkugler 6876042
100% test coverage for tool.py
jkugler 45dd1ce
Put schema version guards around components/services in tools
jkugler 3910030
Merge branch 'main' into 561_add_components_and_services
jkugler 920f0e1
Formatting fix
jkugler 00d287d
Python 3.8 fixes
jkugler 5768a9b
Merge remote-tracking branch 'origin/main' into 561_add_components_an…
jkugler 1a4ccfb
Improve and cleanup tests
jkugler 024a582
Merge remote-tracking branch 'origin/main' into 561_add_components_an…
jkugler 303e29c
Additional test case for ToolsRepository equality
jkugler 1a639a6
First part of changes to use the `.tools` setter/getter
jkugler ce00e81
Merge remote-tracking branch 'origin/main' into 561_add_components_an…
jkugler 8e32d48
Render components and services to tools when rendering to old schemas
jkugler b5c7057
Correct an incorrect variable name
jkugler c730146
Added rendering for XML "down convert."
jkugler aec8393
Forgot "name" on the service
jkugler 2635278
Additional test coverage for model/tool.py
jkugler e267c2a
Doc and type updates
jkugler eeff89b
`bom.tools` revert internal default assignment
jkowalleck 0c70062
chore: add legal header
jkowalleck 3ac3b2b
style: remove redndant braces
jkowalleck f889823
style: rearance line breaks
jkowalleck cf0826c
style: simplified init
jkowalleck 3271db7
refactor: rework hashing of `...tools` / `ToolsRepository`
jkowalleck 26f6007
style: simplify init
jkowalleck 61e2d90
fix: tool serialization
jkowalleck 0b6d14d
style: default values
jkowalleck b90bec5
docs: type hint
jkowalleck 6dc2aab
Merge remote-tracking branch 'origin/main' into 561_add_components_an…
jkugler 76743d9
Addressing comments
jkugler 73007f8
Add logic to down-convert all components/services if there are Tools
jkugler 95fb0a2
tests: remove unnessessary serialization tests
jkowalleck 702893d
tetss: improved deseriualization assert/expect
jkowalleck 2254703
reformat
jkowalleck e9fe527
tests: improved toolRepo tests
jkowalleck 59b0987
get rid of tool-repo mutial exclusive tool vs component/service
jkowalleck 1a48106
refactor: reimplemented XML/JSON normalization
jkowalleck 96fe96c
style
jkowalleck 7b15d40
refactor: reimplemented XML/JSON denormalization
jkowalleck 427add4
refactor: move ToolsRepositoryHelper to other helpers
jkowalleck 4a2ac65
refactor: move ToolsRepositoryHelper back to tool
jkowalleck 623056e
fix: do not emit empty tools repo
jkowalleck 5a6c3eb
style
jkowalleck 75d20d2
tests
jkowalleck a3b5eca
tests
jkowalleck 3032416
tests
jkowalleck 5cc06ae
tests
jkowalleck 614968e
tests
jkowalleck 8b9b488
tests
jkowalleck c937f21
tests
jkowalleck 5329fdf
Merge branch '8.0.0-dev' into 561_add_components_and_services
jkugler 1d3c3fe
refactor: use property setters in `ToolsRepository.__init__()`
jkowalleck 9876139
style: rephrase warning test
jkowalleck f1fdb53
coverage ignore non-scope
jkowalleck a678c8c
tests for invalid tool deserialization
jkowalleck d93fac5
tets: fixed windows compat
jkowalleck File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
from typing import Any, Dict, Iterable, List, Optional, Type, Union | ||
from xml.etree.ElementTree import Element # nosec B405 | ||
|
||
import serializable | ||
from serializable import ViewType | ||
from serializable.helpers import BaseHelper | ||
from sortedcontainers import SortedSet | ||
|
||
from .._internal.compare import ComparableTuple as _ComparableTuple | ||
from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException | ||
from ..model import ExternalReference, HashType, _HashTypeRepositorySerializationHelper | ||
from ..model.component import Component | ||
from ..model.service import Service | ||
from ..schema.schema import SchemaVersion1Dot4, SchemaVersion1Dot5, SchemaVersion1Dot6 | ||
|
||
|
||
@serializable.serializable_class | ||
class Tool: | ||
jkugler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
This is our internal representation of the `toolType` complex type within the CycloneDX standard. | ||
|
||
Tool(s) are the things used in the creation of the CycloneDX document. | ||
|
||
Tool might be deprecated since CycloneDX 1.5, but it is not deprecated in this library. | ||
In fact, this library will try to provide a compatibility layer if needed. | ||
|
||
.. note:: | ||
See the CycloneDX Schema for toolType: https://cyclonedx.org/docs/1.3/#type_toolType | ||
""" | ||
|
||
def __init__(self, *, vendor: Optional[str] = None, name: Optional[str] = None, version: Optional[str] = None, | ||
hashes: Optional[Iterable[HashType]] = None, | ||
external_references: Optional[Iterable[ExternalReference]] = None) -> None: | ||
self.vendor = vendor | ||
self.name = name | ||
self.version = version | ||
self.hashes = hashes or [] # type:ignore[assignment] | ||
self.external_references = external_references or [] # type:ignore[assignment] | ||
|
||
@property | ||
@serializable.xml_sequence(1) | ||
def vendor(self) -> Optional[str]: | ||
""" | ||
The name of the vendor who created the tool. | ||
|
||
Returns: | ||
`str` if set else `None` | ||
""" | ||
return self._vendor | ||
|
||
@vendor.setter | ||
def vendor(self, vendor: Optional[str]) -> None: | ||
self._vendor = vendor | ||
|
||
@property | ||
@serializable.xml_sequence(2) | ||
def name(self) -> Optional[str]: | ||
""" | ||
The name of the tool. | ||
|
||
Returns: | ||
`str` if set else `None` | ||
""" | ||
return self._name | ||
|
||
@name.setter | ||
def name(self, name: Optional[str]) -> None: | ||
self._name = name | ||
|
||
@property | ||
@serializable.xml_sequence(3) | ||
def version(self) -> Optional[str]: | ||
""" | ||
The version of the tool. | ||
|
||
Returns: | ||
`str` if set else `None` | ||
""" | ||
return self._version | ||
|
||
@version.setter | ||
def version(self, version: Optional[str]) -> None: | ||
self._version = version | ||
|
||
@property | ||
@serializable.type_mapping(_HashTypeRepositorySerializationHelper) | ||
@serializable.xml_sequence(4) | ||
def hashes(self) -> 'SortedSet[HashType]': | ||
""" | ||
The hashes of the tool (if applicable). | ||
|
||
Returns: | ||
Set of `HashType` | ||
""" | ||
return self._hashes | ||
|
||
@hashes.setter | ||
def hashes(self, hashes: Iterable[HashType]) -> None: | ||
self._hashes = SortedSet(hashes) | ||
|
||
@property | ||
@serializable.view(SchemaVersion1Dot4) | ||
@serializable.view(SchemaVersion1Dot5) | ||
@serializable.view(SchemaVersion1Dot6) | ||
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'reference') | ||
@serializable.xml_sequence(5) | ||
def external_references(self) -> 'SortedSet[ExternalReference]': | ||
""" | ||
External References provides a way to document systems, sites, and information that may be relevant but which | ||
are not included with the BOM. | ||
|
||
Returns: | ||
Set of `ExternalReference` | ||
""" | ||
return self._external_references | ||
|
||
@external_references.setter | ||
def external_references(self, external_references: Iterable[ExternalReference]) -> None: | ||
self._external_references = SortedSet(external_references) | ||
|
||
def __eq__(self, other: object) -> bool: | ||
if isinstance(other, Tool): | ||
return hash(other) == hash(self) | ||
return False | ||
|
||
def __lt__(self, other: Any) -> bool: | ||
if isinstance(other, Tool): | ||
return _ComparableTuple(( | ||
self.vendor, self.name, self.version | ||
)) < _ComparableTuple(( | ||
other.vendor, other.name, other.version | ||
)) | ||
return NotImplemented | ||
|
||
def __hash__(self) -> int: | ||
return hash((self.vendor, self.name, self.version, tuple(self.hashes), tuple(self.external_references))) | ||
|
||
def __repr__(self) -> str: | ||
return f'<Tool name={self.name}, version={self.version}, vendor={self.vendor}>' | ||
|
||
|
||
class ToolRepository: | ||
""" | ||
The repo of tool formats | ||
""" | ||
|
||
def __init__(self, *, components: Optional[Iterable[Component]] = None, | ||
services: Optional[Iterable[Service]] = None, | ||
tools: Optional[Iterable[Tool]] = None) -> None: | ||
self._components = components or SortedSet() | ||
self._services = services or SortedSet() | ||
# Must use components/services or tools. Cannot use both | ||
jkugler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self._tools = tools or SortedSet() | ||
|
||
def __getattr__(self, name: str) -> Any: | ||
jkugler marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if name == 'components': | ||
return self._components | ||
if name == 'services': | ||
return self._services | ||
|
||
return getattr(self._tools, name) | ||
|
||
def __iter__(self) -> Tool: | ||
for t in self._tools: | ||
yield t | ||
|
||
|
||
class ToolRepositoryHelper(BaseHelper): | ||
@classmethod | ||
def json_normalize(cls, o: ToolRepository, *, | ||
view: Optional[Type[ViewType]], | ||
**__: Any) -> Any: | ||
if not any([o._tools, o._components, o._services]): # pylint: disable=protected-access | ||
return None | ||
|
||
if o._tools and any([o._components, o._services]): # pylint: disable=protected-access | ||
raise SerializationOfUnexpectedValueException( | ||
'Cannot serialize both old (CycloneDX <= 1.4) and new ' | ||
'(CycloneDX >= 1.5) format for tools: {o!r}' | ||
) | ||
|
||
if o._tools: # pylint: disable=protected-access | ||
return [Tool.as_json(t) for t in o._tools] # pylint: disable=protected-access | ||
|
||
result = {} | ||
|
||
if o._components: # pylint: disable=protected-access | ||
result['components'] = [Component.as_json(c) for c in o._components] # pylint: disable=protected-access | ||
|
||
if o._services: # pylint: disable=protected-access | ||
result['services'] = [Service.as_json(s) for s in o._services] # pylint: disable=protected-access | ||
|
||
return result | ||
|
||
@classmethod | ||
def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]], | ||
**__: Any) -> ToolRepository: | ||
|
||
components = [] | ||
services = [] | ||
tools = [] | ||
|
||
if isinstance(o, Dict): | ||
if 'components' in o: | ||
for c in o['components']: | ||
components.append(Component.from_json(c)) | ||
|
||
if 'services' in o: | ||
for s in o['services']: | ||
services.append(Service.from_json(s)) | ||
|
||
elif isinstance(o, Iterable): | ||
for t in o: | ||
tools.append(Tool.from_json(t)) | ||
else: | ||
raise CycloneDxDeserializationException('unexpected: {o!r}') | ||
|
||
return ToolRepository(components=components, services=services, tools=tools) | ||
|
||
@classmethod | ||
def xml_normalize(cls, o: ToolRepository, *, | ||
element_name: str, | ||
view: Optional[Type[ViewType]], | ||
xmlns: Optional[str], | ||
**__: Any) -> Optional[Element]: | ||
pass | ||
|
||
@classmethod | ||
def xml_denormalize(cls, o: Element, | ||
default_ns: Optional[str], | ||
**__: Any) -> ToolRepository: | ||
return ToolRepository() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.