feat: Add version_range property to Component for CycloneDX v1.7 - #1032
feat: Add version_range property to Component for CycloneDX v1.7#1032nulano wants to merge 2 commits into
Conversation
Implement the versionRange property for Compoments as specified in the CycloneDX v1.7 schema. The versionRange can be specified instead of the version for external components to specify the accepted version range. * Add version_range property to Component with serialization decorators. * Adjust Component.__repr__ to show whether a component is external and the version_range if provided. * Extend Bom.validate with validation for CycloneDX v1.7 requirements regarding versionRange and isExternal. * Add unit tests for Component.version_range proprety and Bom.validate changes. * Add serialization snapshot tests for various kinds of external components. Implements part of CycloneDX#903 Signed-off-by: Ondrej Baranovič <nulano@nulano.eu>
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Documentation build overview
|
CAOShurong
left a comment
There was a problem hiding this comment.
Reviewed exact head c1b71ab5055b684aae1e160582dda1c10671ee80.
I checked the v1.7-only versionRange serialization, component equality/order behavior, root isExternal warning, and both invalid combinations (version plus versionRange, and versionRange without isExternal=true). The invalid model fixtures are picked up by the suite's reflective *_invalid discovery, so the new validation paths are exercised in both JSON and XML output tests.
Verification on this exact head:
- full suite with
PYTHONUTF8=1: 7,061 tests, pass - focused model/output suite: 834 tests, pass
- mypy, flake8, compileall, and
git diff --check: pass
The three failures without PYTHONUTF8=1 were existing Windows GBK decoding of UTF-8 fixture files, not failures in this change. I did not find a blocking correctness issue in the patch.
AI assistance disclosure: Codex helped run and cross-check the review; I verified the cited head, diff, tests, and conclusions.
|
|
||
| @version_range.setter | ||
| def version_range(self, version_range: Optional[str]) -> None: | ||
| if version_range and not 1 <= len(version_range) <= 4096: |
There was a problem hiding this comment.
for version_range='' - when the len is <=1 - this would not print the appropriate warning
or does it?
There was a problem hiding this comment.
I knew I was gonna forget an is not None somewhere... fixed.
| def __comparable_tuple(self) -> _ComparableTuple: | ||
| return _ComparableTuple(( | ||
| self.type, self.group, self.name, self.version, | ||
| self.type, self.group, self.name, self.version, self.version_range, |
There was a problem hiding this comment.
new properties shall go to the end of the list.
| def __repr__(self) -> str: | ||
| return f'<Component bom-ref={self.bom_ref!r}, group={self.group}, name={self.name}, ' \ | ||
| f'version={self.version}, type={self.type}>' | ||
| if not self.is_external: |
There was a problem hiding this comment.
too complex.
can this be shortened to something like
version_constraint = f'versionRange={self.versionRange}' \
if self.versionRange \
else f'version={self.version}'
return f'<Component bom-ref={self.bom_ref!r}, group={self.group}, name={self.name}, ' \
) \
f'{versionRange}, type={self.type}>There was a problem hiding this comment.
applied suggestion with minor changes
| <component type="library" bom-ref="external-lib-1.0.0"> | ||
| <name>external-lib</name> | ||
| <version>1.0.0</version> | ||
| <scope>required</scope> |
There was a problem hiding this comment.
It got left out while reorganizing code as it is not mandatory, but I've put it back now.
|
|
||
| def get_bom_with_external_component_1_7() -> Bom: | ||
| bom = _make_bom(components=[get_component_external()]) | ||
| def get_bom_with_external_component_without_version() -> Bom: |
There was a problem hiding this comment.
i'd rather not have one file for each individual case,
just add all your component cases to the existing get_bom_with_external_component_1_7
and extend it.
Something like
def get_bom_with_external_component_1_7() -> Bom:
bom = _make_bom(components=[
get_component_external_without_version(),
get_component_external_with_version(),
get_component_external_with_version_range()
])
def get_bom_with_external_component_invalid() -> Bom:
bom = _make_bom(components=[
get_component_external_with_version_and_version_range_invalid(),
])There was a problem hiding this comment.
I assume you mean one BOM with all valid components and separate BOMs for invalid components, otherwise the invalid tests wouldn't work right. Valid BOMs combined.
| self.assertEqual(len(w.warnings), 1) | ||
| self.assertIn('has no defined dependencies ', str(w.warnings[0])) | ||
|
|
||
| def test_warning_root_component_is_external(self) -> None: |
| raise LicenseExpressionAlongWithOthersException( | ||
| f'Found LicenseExpression along with others licenses in: {elem!r}') | ||
|
|
||
| # 4. Validates that each component conforms to CycloneDX 1.7 constraints: |
Signed-off-by: Ondrej Baranovič <nulano@nulano.eu>
Description
Implement the versionRange property for Compoments as specified in the CycloneDX v1.7 schema. The versionRange can be specified instead of the version for external components to specify the accepted version range.
Resolves or fixes issue: part of #903 (versionRange)
AI Tool Disclosure
[e.g. GitHub CoPilot, ChatGPT, JetBrains Junie etc.][e.g. GPT-4.1, Claude Haiku 4.5, Gemini 2.5 Pro etc.][Summarize the key prompts or instructions given to the AI tools]Affirmation