Skip to content

Commit df0fdf0

Browse files
authored
Merge pull request #1 from CycloneDX/Churro/feat/lifecycles
changes on top of PR#698
2 parents a4deeaf + 0e842ad commit df0fdf0

File tree

3 files changed

+34
-29
lines changed

3 files changed

+34
-29
lines changed

cyclonedx/model/bom.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def lifecycles(self) -> LifecycleRepository:
125125
return self._lifecycles
126126

127127
@lifecycles.setter
128-
def lifecycles(self, lifecycles: Optional[Iterable[Lifecycle]]) -> None:
128+
def lifecycles(self, lifecycles: Iterable[Lifecycle]) -> None:
129129
self._lifecycles = LifecycleRepository(lifecycles)
130130

131131
@property

cyclonedx/model/lifecycle.py

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,19 @@
4343

4444
@serializable.serializable_enum
4545
class LifecyclePhase(str, Enum):
46+
"""
47+
Enum object that defines the permissible 'phase' for a Lifecycle according to the CycloneDX schema.
48+
49+
.. note::
50+
See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.3/#type_classification
51+
"""
4652
DESIGN = 'design'
47-
PREBUILD = 'pre-build'
53+
PRE_BUILD = 'pre-build'
4854
BUILD = 'build'
49-
POSTBUILD = 'post-build'
55+
POST_BUILD = 'post-build'
5056
OPERATIONS = 'operations'
5157
DISCOVERY = 'discovery'
52-
DECOMISSION = 'decommission'
58+
DECOMMISSION = 'decommission'
5359

5460

5561
@serializable.serializable_class
@@ -88,11 +94,18 @@ def __lt__(self, other: Any) -> bool:
8894
return NotImplemented
8995

9096
def __repr__(self) -> str:
91-
return f'<PredefinedLifecycle name={self._phase}>'
97+
return f'<PredefinedLifecycle phase={self._phase}>'
9298

9399

94100
@serializable.serializable_class
95101
class NamedLifecycle:
102+
"""
103+
Object that defines custom state in the product lifecycle.
104+
105+
.. note::
106+
See the CycloneDX Schema definition: https://cyclonedx.org/docs/1.5/#metadata_lifecycles
107+
"""
108+
96109
def __init__(self, name: str, *, description: Optional[str] = None) -> None:
97110
self._name = name
98111
self._description = description
@@ -165,9 +178,7 @@ class LifecycleRepository(SortedSet[Lifecycle]):
165178
166179
This is a `set`, not a `list`. Order MUST NOT matter here.
167180
"""
168-
169181
else:
170-
171182
class LifecycleRepository(SortedSet):
172183
"""Collection of :class:`Lifecycle`.
173184
@@ -182,7 +193,6 @@ def json_normalize(cls, o: LifecycleRepository, *,
182193
**__: Any) -> Any:
183194
if len(o) == 0:
184195
return None
185-
186196
return [json_loads(li.as_json( # type:ignore[union-attr]
187197
view_=view)) for li in o]
188198

@@ -192,12 +202,13 @@ def json_denormalize(cls, o: List[Dict[str, Any]],
192202
repo = LifecycleRepository()
193203
for li in o:
194204
if 'phase' in li:
195-
repo.add(PredefinedLifecycle.from_json(li)) # type:ignore[attr-defined]
205+
repo.add(PredefinedLifecycle.from_json( # type:ignore[attr-defined]
206+
li))
196207
elif 'name' in li:
197-
repo.add(NamedLifecycle.from_json(li)) # type:ignore[attr-defined]
208+
repo.add(NamedLifecycle.from_json( # type:ignore[attr-defined]
209+
li))
198210
else:
199211
raise CycloneDxDeserializationException(f'unexpected: {li!r}')
200-
201212
return repo
202213

203214
@classmethod
@@ -208,33 +219,27 @@ def xml_normalize(cls, o: LifecycleRepository, *,
208219
**__: Any) -> Optional[Element]:
209220
if len(o) == 0:
210221
return None
211-
212222
elem = Element(element_name)
213223
for li in o:
214224
elem.append(li.as_xml( # type:ignore[union-attr]
215225
view_=view, as_string=False, element_name='lifecycle', xmlns=xmlns))
216-
217226
return elem
218227

219228
@classmethod
220229
def xml_denormalize(cls, o: Element,
221230
default_ns: Optional[str],
222231
**__: Any) -> LifecycleRepository:
223232
repo = LifecycleRepository()
224-
225-
for li in o:
226-
tag = li.tag if default_ns is None else li.tag.replace(f'{{{default_ns}}}', '')
227-
228-
if tag == 'lifecycle':
229-
stages = list(li)
230-
231-
predefined_lifecycle = next((el for el in stages if 'phase' in el.tag), None)
232-
named_lifecycle = next((el for el in stages if 'name' in el.tag), None)
233-
if predefined_lifecycle is not None:
234-
repo.add(PredefinedLifecycle.from_xml(li, default_ns)) # type:ignore[attr-defined]
235-
elif named_lifecycle is not None:
236-
repo.add(NamedLifecycle.from_xml(li, default_ns)) # type:ignore[attr-defined]
233+
ns_map = {'bom': default_ns or ''}
234+
# Do not iterate over `o` and do not check for expected `.tag` of items.
235+
# This check could have been done by schema validators before even deserializing.
236+
for li in o.iterfind('bom:lifecycle', ns_map):
237+
if li.find('bom:phase', ns_map) is not None:
238+
repo.add(PredefinedLifecycle.from_xml( # type:ignore[attr-defined]
239+
li, default_ns))
240+
elif li.find('bom:name', ns_map) is not None:
241+
repo.add(NamedLifecycle.from_xml( # type:ignore[attr-defined]
242+
li, default_ns))
237243
else:
238-
raise CycloneDxDeserializationException(f'unexpected: {li!r}')
239-
244+
raise CycloneDxDeserializationException(f'unexpected content: {li!r}')
240245
return repo

tests/_data/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1130,7 +1130,7 @@ def get_bom_with_lifecycles() -> Bom:
11301130
metadata=BomMetaData(
11311131
lifecycles=[
11321132
PredefinedLifecycle(LifecyclePhase.BUILD),
1133-
PredefinedLifecycle(LifecyclePhase.POSTBUILD),
1133+
PredefinedLifecycle(LifecyclePhase.POST_BUILD),
11341134
NamedLifecycle(name='platform-integration-testing',
11351135
description='Integration testing specific to the runtime platform'),
11361136
],

0 commit comments

Comments
 (0)