Skip to content

Commit 1d1c1ff

Browse files
committed
More code cleanup
* XML serialization function * No ERRORs in tests, but still plenty of FAILUREs
1 parent 4f0db04 commit 1d1c1ff

File tree

2 files changed

+71
-29
lines changed

2 files changed

+71
-29
lines changed

cyclonedx/model/bom.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def __init__(self, *, tools: Optional[Union[Iterable[Tool], Dict[AnyStr, Any]]]
7474
# Deprecated as of v1.6
7575
manufacture: Optional[OrganizationalEntity] = None) -> None:
7676
self.timestamp = timestamp or _get_now_utc()
77-
self.tools = tools or [] # type:ignore[assignment]
77+
self.tools = tools or ToolRepository() # type:ignore[assignment]
7878
self.authors = authors or [] # type:ignore[assignment]
7979
self.component = component
8080
self.supplier = supplier
@@ -90,7 +90,7 @@ def __init__(self, *, tools: Optional[Union[Iterable[Tool], Dict[AnyStr, Any]]]
9090
DeprecationWarning)
9191

9292
if not tools:
93-
self.tools.add(ThisTool)
93+
self.tools.add(ThisTool) # type: ignore[attr-defined]
9494

9595
@property
9696
@serializable.type_mapping(serializable.helpers.XsdDateTime)
@@ -123,16 +123,13 @@ def timestamp(self, timestamp: datetime) -> None:
123123
@serializable.type_mapping(ToolRepositoryHelper)
124124
@serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'tool')
125125
@serializable.xml_sequence(3)
126-
def tools(self) -> Union[Iterable[Tool], ToolRepository]:
126+
def tools(self) -> ToolRepository:
127127
"""
128128
Tools used to create this BOM.
129129
130130
Returns:
131-
`Set` of `Tool` objects.
131+
`ToolRepository` objects.
132132
"""
133-
if self._tools._tools: # pylint: disable=protected-access
134-
return self._tools._tools # pylint: disable=protected-access
135-
136133
return self._tools
137134

138135
@tools.setter
@@ -146,7 +143,7 @@ def tools(self, tools: Union[Iterable[Tool], ToolRepository]) -> None:
146143
'onwards. Please use lists of `Component` and `Service` objects as `tools.components` '
147144
'and `tools.services`, respectively.'
148145
)
149-
if self._tools._components or self._tools._services:
146+
if hasattr(self, '_tools') and (self._tools._components or self._tools._services): # pylint: disable=protected-access
150147
raise MutuallyExclusivePropertiesException(
151148
'Cannot serialize both old (CycloneDX <= 1.4) and new '
152149
'(CycloneDX >= 1.5) format for tools.'

cyclonedx/model/tool.py

Lines changed: 66 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class Tool:
2222
2323
Tool(s) are the things used in the creation of the CycloneDX document.
2424
25-
Tool might be deprecated since CycloneDX 1.5, but it is not deprecated in this library.
25+
`Tool` is deprecated since CycloneDX 1.5, but it is not deprecated in this library.
2626
In fact, this library will try to provide a compatibility layer if needed.
2727
2828
.. note::
@@ -142,16 +142,31 @@ def __repr__(self) -> str:
142142

143143
class ToolRepository:
144144
"""
145-
The repo of tool formats
145+
The repository of tool formats
146+
147+
This is done so we can maintain backward-compatibility with CycloneDX <= 1.4
148+
If using a SortedSet of tools, the object will behave like a sorted set of
149+
tools. Otherwise, it will behave like an object with `components`
150+
and `services` attributes (which are SortedSets of their respective types).
146151
"""
147152

148153
def __init__(self, *, components: Optional[Iterable[Component]] = None,
149154
services: Optional[Iterable[Service]] = None,
150155
tools: Optional[Iterable[Tool]] = None) -> None:
151-
self._components = components or SortedSet()
152-
self._services = services or SortedSet()
153-
# Must use components/services or tools. Cannot use both
154-
self._tools = tools or SortedSet()
156+
157+
if tools and (components or services):
158+
# Must use components/services or tools. Cannot use both
159+
raise MutuallyExclusivePropertiesException(
160+
'Cannot define both old (CycloneDX <= 1.4) and new '
161+
'(CycloneDX >= 1.5) format for tools.'
162+
)
163+
164+
self._components = SortedSet(components) or SortedSet()
165+
self._services = SortedSet(services) or SortedSet()
166+
self._tools = SortedSet(tools) or SortedSet()
167+
168+
def __len__(self) -> int:
169+
return len(self._tools)
155170

156171
@property
157172
def components(self) -> Iterable[Component]:
@@ -165,11 +180,11 @@ def components(self) -> Iterable[Component]:
165180
def components(self, components: Iterable[Component]) -> None:
166181
if self._tools:
167182
raise MutuallyExclusivePropertiesException(
168-
'Cannot serialize both old (CycloneDX <= 1.4) and new '
183+
'Cannot define both old (CycloneDX <= 1.4) and new '
169184
'(CycloneDX >= 1.5) format for tools.'
170185
)
171186

172-
self._components = components
187+
self._components = SortedSet(components)
173188

174189
@property
175190
def services(self) -> Iterable[Service]:
@@ -183,10 +198,10 @@ def services(self) -> Iterable[Service]:
183198
def services(self, services: Iterable[Service]) -> None:
184199
if self._tools:
185200
raise MutuallyExclusivePropertiesException(
186-
'Cannot serialize both old (CycloneDX <= 1.4) and new '
187-
'(CycloneDX >= 1.5) format for tools: {o!r}'
201+
'Cannot define both old (CycloneDX <= 1.4) and new '
202+
'(CycloneDX >= 1.5) format for tools.'
188203
)
189-
self._services = services
204+
self._services = SortedSet(services)
190205

191206
def __getattr__(self, name: str) -> Any:
192207
"""
@@ -213,19 +228,19 @@ class ToolRepositoryHelper(BaseHelper):
213228
def json_normalize(cls, o: ToolRepository, *,
214229
view: Optional[Type[ViewType]],
215230
**__: Any) -> Any:
216-
if not any([o._tools, o._components, o._services]): # pylint: disable=protected-access
231+
if not any([o._tools, o.components, o.services]): # pylint: disable=protected-access
217232
return None
218233

219234
if o._tools: # pylint: disable=protected-access
220-
return [Tool.as_json(t) for t in o._tools] # pylint: disable=protected-access
235+
return [Tool.as_json(t) for t in o] # type: ignore[attr-defined]
221236

222237
result = {}
223238

224-
if o._components: # pylint: disable=protected-access
225-
result['components'] = [Component.as_json(c) for c in o._components] # pylint: disable=protected-access
239+
if o.components:
240+
result['components'] = [Component.as_json(c) for c in o.components] # type: ignore[attr-defined]
226241

227-
if o._services: # pylint: disable=protected-access
228-
result['services'] = [Service.as_json(s) for s in o._services] # pylint: disable=protected-access
242+
if o.services:
243+
result['services'] = [Service.as_json(s) for s in o.services] # type: ignore[attr-defined]
229244

230245
return result
231246

@@ -240,15 +255,15 @@ def json_denormalize(cls, o: Union[List[Dict[str, Any]], Dict[str, Any]],
240255
if isinstance(o, Dict):
241256
if 'components' in o:
242257
for c in o['components']:
243-
components.append(Component.from_json(c))
258+
components.append(Component.from_json(c)) # type: ignore[attr-defined]
244259

245260
if 'services' in o:
246261
for s in o['services']:
247-
services.append(Service.from_json(s))
262+
services.append(Service.from_json(s)) # type: ignore[attr-defined]
248263

249264
elif isinstance(o, Iterable):
250265
for t in o:
251-
tools.append(Tool.from_json(t))
266+
tools.append(Tool.from_json(t)) # type: ignore[attr-defined]
252267
else:
253268
raise CycloneDxDeserializationException('unexpected: {o!r}')
254269

@@ -260,7 +275,37 @@ def xml_normalize(cls, o: ToolRepository, *,
260275
view: Optional[Type[ViewType]],
261276
xmlns: Optional[str],
262277
**__: Any) -> Optional[Element]:
263-
pass
278+
if not any([o._tools, o.components, o.services]): # pylint: disable=protected-access
279+
return None
280+
281+
elem = Element(element_name)
282+
283+
if o._tools: # pylint: disable=protected-access
284+
elem.extend(
285+
t.as_xml( # type: ignore[attr-defined]
286+
view_=view, as_string=False, element_name='tool', xmlns=xmlns)
287+
for t in o
288+
)
289+
290+
if o.components:
291+
c_elem = Element('components')
292+
293+
c_elem.extend(
294+
c.as_xml( # type: ignore[attr-defined]
295+
view_=view, as_string=False, element_name='component', xmlns=xmlns)
296+
for c in o.components
297+
)
298+
299+
if o.services:
300+
s_elem = Element('services')
301+
302+
s_elem.extend(
303+
s.as_xml( # type: ignore[attr-defined]
304+
view_=view, as_string=False, element_name='services', xmlns=xmlns)
305+
for s in o.services
306+
)
307+
308+
return elem
264309

265310
@classmethod
266311
def xml_denormalize(cls, o: Element,

0 commit comments

Comments
 (0)