Skip to content

Commit 427add4

Browse files
committed
refactor: move ToolsRepositoryHelper to other helpers
Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 7b15d40 commit 427add4

File tree

4 files changed

+116
-119
lines changed

4 files changed

+116
-119
lines changed

cyclonedx/model/bom.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@
3636
SchemaVersion1Dot5,
3737
SchemaVersion1Dot6,
3838
)
39-
from ..serialization import LicenseRepositoryHelper, UrnUuidHelper
39+
from ..serialization import LicenseRepositoryHelper, ToolsRepositoryHelper, UrnUuidHelper
4040
from . import ExternalReference, Property, ThisTool
4141
from .bom_ref import BomRef
4242
from .component import Component
4343
from .contact import OrganizationalContact, OrganizationalEntity
4444
from .dependency import Dependable, Dependency
4545
from .license import License, LicenseExpression, LicenseRepository
4646
from .service import Service
47-
from .tool import Tool, ToolsRepository, ToolsRepositoryHelper
47+
from .tool import Tool, ToolsRepository
4848
from .vulnerability import Vulnerability
4949

5050
if TYPE_CHECKING: # pragma: no cover

cyclonedx/model/tool.py

Lines changed: 1 addition & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,18 @@
1414
# Copyright (c) OWASP Foundation. All Rights Reserved.
1515

1616

17-
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Type, Union
17+
from typing import Any, Iterable, Optional, Type
1818
from warnings import warn
19-
from xml.etree.ElementTree import Element # nosec B405
2019

2120
import serializable
22-
from serializable.helpers import BaseHelper
2321
from sortedcontainers import SortedSet
2422

2523
from .._internal.compare import ComparableTuple as _ComparableTuple
26-
from ..exception.serialization import CycloneDxDeserializationException
27-
from ..schema import SchemaVersion
2824
from ..schema.schema import SchemaVersion1Dot4, SchemaVersion1Dot5, SchemaVersion1Dot6
2925
from . import ExternalReference, HashType, _HashTypeRepositorySerializationHelper
3026
from .component import Component
3127
from .service import Service
3228

33-
if TYPE_CHECKING: # pragma: no cover
34-
from serializable import ObjectMetadataLibrary, ViewType
35-
3629

3730
@serializable.serializable_class
3831
class Tool:
@@ -257,108 +250,3 @@ def __eq__(self, other: object) -> bool:
257250

258251
def __hash__(self) -> int:
259252
return hash((tuple(self._tools), tuple(self._components), tuple(self._services)))
260-
261-
262-
class ToolsRepositoryHelper(BaseHelper):
263-
264-
@staticmethod
265-
def __all_as_tools(o: ToolsRepository) -> Tuple[Tool, ...]:
266-
return (
267-
*o.tools,
268-
*map(Tool.from_component, o.components),
269-
*map(Tool.from_service, o.services),
270-
)
271-
272-
@staticmethod
273-
def __supports_components_and_services(view: Any) -> bool:
274-
try:
275-
return view is not None and view().schema_version_enum >= SchemaVersion.V1_5
276-
except Exception:
277-
return False
278-
279-
@classmethod
280-
def json_normalize(cls, o: ToolsRepository, *,
281-
view: Optional[Type['ViewType']],
282-
**__: Any) -> Any:
283-
if not o:
284-
return None
285-
if len(o.tools) > 0 or not cls.__supports_components_and_services(view):
286-
return cls.__all_as_tools(o)
287-
return {
288-
'components': tuple(o.components) if len(o.components) > 0 else None,
289-
'services': tuple(o.services) if len(o.services) > 0 else None,
290-
}
291-
292-
@classmethod
293-
def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]],
294-
**__: Any) -> ToolsRepository:
295-
tools = None
296-
components = None
297-
services = None
298-
if isinstance(o, Dict):
299-
components = map(lambda c: Component.from_json( # type:ignore[attr-defined]
300-
c), o.get('components', ()))
301-
services = map(lambda s: Service.from_json( # type:ignore[attr-defined]
302-
s), o.get('services', ()))
303-
elif isinstance(o, Iterable):
304-
tools = map(lambda t: Tool.from_json(t), o) # type:ignore[attr-defined]
305-
return ToolsRepository(components=components, services=services, tools=tools)
306-
307-
@classmethod
308-
def xml_normalize(cls, o: ToolsRepository, *,
309-
element_name: str,
310-
view: Optional[Type['ViewType']],
311-
xmlns: Optional[str],
312-
**__: Any) -> Optional[Element]:
313-
if not o:
314-
return None
315-
elem = Element(element_name)
316-
if len(o.tools) > 0 or not cls.__supports_components_and_services(view):
317-
elem.extend(
318-
ti.as_xml( # type:ignore[attr-defined]
319-
view_=view, as_string=False, element_name='tool', xmlns=xmlns)
320-
for ti in cls.__all_as_tools(o)
321-
)
322-
else:
323-
if o.components:
324-
elem_c = Element(f'{{{xmlns}}}components' if xmlns else 'components')
325-
elem_c.extend(
326-
ci.as_xml( # type:ignore[attr-defined]
327-
view_=view, as_string=False, element_name='component', xmlns=xmlns)
328-
for ci in o.components)
329-
elem.append(elem_c)
330-
if o.services:
331-
elem_s = Element(f'{{{xmlns}}}services' if xmlns else 'services')
332-
elem_s.extend(
333-
si.as_xml( # type:ignore[attr-defined]
334-
view_=view, as_string=False, element_name='service', xmlns=xmlns)
335-
for si in o.services)
336-
elem.append(elem_s)
337-
return elem
338-
339-
@classmethod
340-
def xml_denormalize(cls, o: Element, *,
341-
default_ns: Optional[str],
342-
prop_info: 'ObjectMetadataLibrary.SerializableProperty',
343-
ctx: Type[Any],
344-
**kwargs: Any) -> ToolsRepository:
345-
tools = []
346-
components = None
347-
services = None
348-
for e in o:
349-
tag = e.tag if default_ns is None else e.tag.replace(f'{{{default_ns}}}', '')
350-
if tag == 'tool':
351-
tools.append(Tool.from_xml( # type:ignore[attr-defined]
352-
e, default_ns))
353-
elif tag == 'components':
354-
components = map(lambda s: Component.from_xml( # type:ignore[attr-defined]
355-
s, default_ns), e)
356-
elif tag == 'services':
357-
services = map(lambda s: Service.from_xml( # type:ignore[attr-defined]
358-
s, default_ns), e)
359-
else:
360-
raise CycloneDxDeserializationException(f'unexpected: {e!r}')
361-
return ToolsRepository(
362-
tools=tools,
363-
components=components,
364-
services=services)

cyclonedx/model/vulnerability.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
from .._internal.compare import ComparableTuple as _ComparableTuple
4242
from ..exception.model import MutuallyExclusivePropertiesException, NoPropertiesProvidedException
4343
from ..schema.schema import SchemaVersion1Dot4, SchemaVersion1Dot5, SchemaVersion1Dot6
44-
from ..serialization import BomRefHelper
44+
from ..serialization import BomRefHelper, ToolsRepositoryHelper
4545
from . import Property, XsUri
4646
from .bom_ref import BomRef
4747
from .contact import OrganizationalContact, OrganizationalEntity
@@ -51,7 +51,7 @@
5151
ImpactAnalysisResponse,
5252
ImpactAnalysisState,
5353
)
54-
from .tool import Tool, ToolsRepository, ToolsRepositoryHelper
54+
from .tool import Tool, ToolsRepository
5555

5656

5757
@serializable.serializable_class

cyclonedx/serialization/__init__.py

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"""
2020

2121
from json import loads as json_loads
22-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type
22+
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple, Type, Union
2323
from uuid import UUID
2424
from xml.etree.ElementTree import Element # nosec B405
2525

@@ -29,10 +29,14 @@
2929

3030
from ..exception.serialization import CycloneDxDeserializationException, SerializationOfUnexpectedValueException
3131
from ..model.bom_ref import BomRef
32+
from ..model.component import Component
3233
from ..model.license import DisjunctiveLicense, LicenseExpression, LicenseRepository
34+
from ..model.service import Service
35+
from ..model.tool import Tool, ToolsRepository
36+
from ..schema import SchemaVersion
3337

3438
if TYPE_CHECKING: # pragma: no cover
35-
from serializable import ViewType
39+
from serializable import ObjectMetadataLibrary, ViewType
3640

3741

3842
class BomRefHelper(BaseHelper):
@@ -171,3 +175,108 @@ def xml_denormalize(cls, o: Element,
171175
else:
172176
raise CycloneDxDeserializationException(f'unexpected: {li!r}')
173177
return repo
178+
179+
180+
class ToolsRepositoryHelper(BaseHelper):
181+
182+
@staticmethod
183+
def __all_as_tools(o: ToolsRepository) -> Tuple[Tool, ...]:
184+
return (
185+
*o.tools,
186+
*map(Tool.from_component, o.components),
187+
*map(Tool.from_service, o.services),
188+
)
189+
190+
@staticmethod
191+
def __supports_components_and_services(view: Any) -> bool:
192+
try:
193+
return view is not None and view().schema_version_enum >= SchemaVersion.V1_5
194+
except Exception:
195+
return False
196+
197+
@classmethod
198+
def json_normalize(cls, o: ToolsRepository, *,
199+
view: Optional[Type['ViewType']],
200+
**__: Any) -> Any:
201+
if not o:
202+
return None
203+
if len(o.tools) > 0 or not cls.__supports_components_and_services(view):
204+
return cls.__all_as_tools(o)
205+
return {
206+
'components': tuple(o.components) if len(o.components) > 0 else None,
207+
'services': tuple(o.services) if len(o.services) > 0 else None,
208+
}
209+
210+
@classmethod
211+
def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]],
212+
**__: Any) -> ToolsRepository:
213+
tools = None
214+
components = None
215+
services = None
216+
if isinstance(o, Dict):
217+
components = map(lambda c: Component.from_json( # type:ignore[attr-defined]
218+
c), o.get('components', ()))
219+
services = map(lambda s: Service.from_json( # type:ignore[attr-defined]
220+
s), o.get('services', ()))
221+
elif isinstance(o, Iterable):
222+
tools = map(lambda t: Tool.from_json(t), o) # type:ignore[attr-defined]
223+
return ToolsRepository(components=components, services=services, tools=tools)
224+
225+
@classmethod
226+
def xml_normalize(cls, o: ToolsRepository, *,
227+
element_name: str,
228+
view: Optional[Type['ViewType']],
229+
xmlns: Optional[str],
230+
**__: Any) -> Optional[Element]:
231+
if not o:
232+
return None
233+
elem = Element(element_name)
234+
if len(o.tools) > 0 or not cls.__supports_components_and_services(view):
235+
elem.extend(
236+
ti.as_xml( # type:ignore[attr-defined]
237+
view_=view, as_string=False, element_name='tool', xmlns=xmlns)
238+
for ti in cls.__all_as_tools(o)
239+
)
240+
else:
241+
if o.components:
242+
elem_c = Element(f'{{{xmlns}}}components' if xmlns else 'components')
243+
elem_c.extend(
244+
ci.as_xml( # type:ignore[attr-defined]
245+
view_=view, as_string=False, element_name='component', xmlns=xmlns)
246+
for ci in o.components)
247+
elem.append(elem_c)
248+
if o.services:
249+
elem_s = Element(f'{{{xmlns}}}services' if xmlns else 'services')
250+
elem_s.extend(
251+
si.as_xml( # type:ignore[attr-defined]
252+
view_=view, as_string=False, element_name='service', xmlns=xmlns)
253+
for si in o.services)
254+
elem.append(elem_s)
255+
return elem
256+
257+
@classmethod
258+
def xml_denormalize(cls, o: Element, *,
259+
default_ns: Optional[str],
260+
prop_info: 'ObjectMetadataLibrary.SerializableProperty',
261+
ctx: Type[Any],
262+
**kwargs: Any) -> ToolsRepository:
263+
tools = []
264+
components = None
265+
services = None
266+
for e in o:
267+
tag = e.tag if default_ns is None else e.tag.replace(f'{{{default_ns}}}', '')
268+
if tag == 'tool':
269+
tools.append(Tool.from_xml( # type:ignore[attr-defined]
270+
e, default_ns))
271+
elif tag == 'components':
272+
components = map(lambda s: Component.from_xml( # type:ignore[attr-defined]
273+
s, default_ns), e)
274+
elif tag == 'services':
275+
services = map(lambda s: Service.from_xml( # type:ignore[attr-defined]
276+
s, default_ns), e)
277+
else:
278+
raise CycloneDxDeserializationException(f'unexpected: {e!r}')
279+
return ToolsRepository(
280+
tools=tools,
281+
components=components,
282+
services=services)

0 commit comments

Comments
 (0)