Skip to content

Commit 0af98fd

Browse files
committed
fix: lazy-import lxml and fix Python 3.9 union syntax in complex_validation
- Move 'from lxml import etree' inside _detect_xml_format to make it a lazy import; lxml is an optional dependency and a top-level import crashes when the xml-validation extra is not installed - Replace 'X | Y' union syntax with Optional[Tuple[...]] for Python 3.9 compatibility (X | Y requires Python 3.10+) - Apply maintainer review: replace if-not-spec_version_str guard with try/except around SchemaVersion.from_version to handle all malformed inputs (None, empty string, unknown version string) - Remove hardcoded SchemaVersion.V1_5 fallback default in XML detection - Clean up unnecessary inline comments Signed-off-by: Saquib Saifee <saquibsaifee2@gmail.com>
1 parent a617abe commit 0af98fd

1 file changed

Lines changed: 33 additions & 36 deletions

File tree

examples/complex_validation.py

Lines changed: 33 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@
1717

1818
import json
1919
import sys
20-
from typing import TYPE_CHECKING
21-
22-
from lxml import etree # type: ignore[import-untyped]
20+
from typing import TYPE_CHECKING, Optional, Tuple
2321

2422
from cyclonedx.exception import MissingOptionalDependencyException
2523
from cyclonedx.schema import OutputFormat, SchemaVersion
@@ -136,41 +134,45 @@
136134
print('--- Dynamic Validation ---')
137135

138136

139-
def _detect_json_format(raw_data: str) -> tuple[OutputFormat, SchemaVersion] | None:
137+
def _detect_json_format(raw_data: str) -> 'Optional[Tuple[OutputFormat, SchemaVersion]]':
140138
"""Detect JSON format and extract schema version."""
141139
try:
142140
data = json.loads(raw_data)
143-
spec_version_str = data.get('specVersion')
144-
if not spec_version_str:
145-
print('Error: Missing specVersion in JSON SBOM', file=sys.stderr)
146-
return None
141+
except json.JSONDecodeError:
142+
return None
143+
144+
spec_version_str = data.get('specVersion')
145+
try:
147146
schema_version = SchemaVersion.from_version(spec_version_str)
148-
return (OutputFormat.JSON, schema_version)
149-
except (json.JSONDecodeError, ValueError):
147+
except Exception:
148+
print('failed to detect schema_version from', repr(spec_version_str), file=sys.stderr)
150149
return None
150+
return (OutputFormat.JSON, schema_version)
151151

152152

153-
def _detect_xml_format(raw_data: str) -> tuple[OutputFormat, SchemaVersion] | None:
154-
"""Detect XML format and extract schema version."""
153+
def _detect_xml_format(raw_data: str) -> 'Optional[Tuple[OutputFormat, SchemaVersion]]':
154+
try:
155+
from lxml import etree # type: ignore[import-untyped]
156+
except ImportError:
157+
return None
158+
155159
try:
156160
xml_tree = etree.fromstring(raw_data.encode('utf-8'))
157-
# Extract version from CycloneDX namespace
158-
schema_version = SchemaVersion.V1_5 # Default
159-
for ns in xml_tree.nsmap.values():
160-
if ns and ns.startswith('http://cyclonedx.org/schema/bom/'):
161-
try:
162-
schema_version = SchemaVersion.from_version(ns.split('/')[-1])
163-
break
164-
except ValueError:
165-
pass
166-
return (OutputFormat.XML, schema_version)
167161
except etree.XMLSyntaxError:
168-
print('Error: Unknown or malformed SBOM format', file=sys.stderr)
169-
return None
170-
except Exception as e:
171-
print(f'Error: Format detection failed: {e}', file=sys.stderr)
172162
return None
173163

164+
for ns in xml_tree.nsmap.values():
165+
if ns and ns.startswith('http://cyclonedx.org/schema/bom/'):
166+
version_str = ns.split('/')[-1]
167+
try:
168+
return (OutputFormat.XML, SchemaVersion.from_version(version_str))
169+
except Exception:
170+
print('failed to detect schema_version from namespace', repr(ns), file=sys.stderr)
171+
return None
172+
173+
print('failed to detect CycloneDX namespace in XML document', file=sys.stderr)
174+
return None
175+
174176

175177
def validate_sbom(raw_data: str) -> bool:
176178
"""Validate an SBOM by detecting its format and version."""
@@ -180,22 +182,17 @@ def validate_sbom(raw_data: str) -> bool:
180182
return False
181183

182184
input_format, schema_version = format_info
183-
184-
# Perform validation
185185
try:
186186
validator = make_schemabased_validator(input_format, schema_version)
187187
errors = validator.validate_str(raw_data)
188-
189188
if errors:
190-
print(f'Validation failed for {input_format.name} version {schema_version.to_version()}', file=sys.stderr)
191-
print(f'Reason: {errors}', file=sys.stderr)
189+
print(f'Validation failed ({input_format.name} {schema_version.to_version()}): {errors}',
190+
file=sys.stderr)
192191
return False
193-
194-
print(f'Successfully validated {input_format.name} SBOM (Version {schema_version.to_version()})')
192+
print(f'Valid {input_format.name} SBOM (schema {schema_version.to_version()})')
195193
return True
196-
197-
except MissingOptionalDependencyException as error:
198-
print(f'Validation skipped due to missing dependencies: {error}')
194+
except MissingOptionalDependencyException as e:
195+
print(f'Validation skipped (missing dependencies): {e}')
199196
return False
200197

201198

0 commit comments

Comments
 (0)