|
19 | 19 | from collections.abc import Iterable |
20 | 20 | from decimal import Decimal |
21 | 21 | from enum import Enum |
| 22 | +from json import loads as json_loads |
22 | 23 | from typing import Any, Optional, Union |
23 | | -from xml.etree.ElementTree import Element as XmlElement |
| 24 | +from xml.etree.ElementTree import Element as XmlElement # nosec B405 |
24 | 25 |
|
25 | 26 | # See https://github.com/package-url/packageurl-python/issues/65 |
26 | 27 | import py_serializable as serializable |
@@ -579,21 +580,22 @@ class CallStack: |
579 | 580 |
|
580 | 581 | def __init__( |
581 | 582 | self, *, |
582 | | - frames: Optional[Iterable[CallStackFrame]] = None, |
| 583 | + frames: Optional[SortedSet[CallStackFrame]] = None, |
583 | 584 | ) -> None: |
584 | 585 | self.frames = frames or [] # type:ignore[assignment] |
585 | 586 |
|
586 | 587 | @property |
587 | 588 | @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, 'frame') |
588 | | - def frames(self) -> 'list[CallStackFrame]': |
| 589 | + @serializable.xml_sequence(1) |
| 590 | + def frames(self) -> 'SortedSet[CallStackFrame]': |
589 | 591 | """ |
590 | 592 | Array of stack frames |
591 | 593 | """ |
592 | 594 | return self._frames |
593 | 595 |
|
594 | 596 | @frames.setter |
595 | | - def frames(self, frames: Iterable[CallStackFrame]) -> None: |
596 | | - self._frames = list(frames) |
| 597 | + def frames(self, frames: SortedSet[CallStackFrame]) -> None: |
| 598 | + self._frames = frames |
597 | 599 |
|
598 | 600 | def __comparable_tuple(self) -> _ComparableTuple: |
599 | 601 | return _ComparableTuple(( |
@@ -621,6 +623,33 @@ def __repr__(self) -> str: |
621 | 623 | return f'<CallStack frames={len(self.frames)}>' |
622 | 624 |
|
623 | 625 |
|
| 626 | +class _IdentitySerializationHelper(serializable.helpers.BaseHelper): |
| 627 | + """THIS CLASS IS NON-PUBLIC API""" |
| 628 | + |
| 629 | + @classmethod |
| 630 | + def json_normalize(cls, o: SortedSet[Identity], *, |
| 631 | + view: Optional[type[serializable.ViewType]], |
| 632 | + **__: Any) -> Any: |
| 633 | + if not o: |
| 634 | + return None |
| 635 | + |
| 636 | + # For Schema 1.5 JSON, return first identity as a single object |
| 637 | + if view and issubclass(view, SchemaVersion1Dot5): |
| 638 | + first_identity = next(iter(o)) |
| 639 | + return json_loads(first_identity.as_json(view_=view)) # type: ignore[attr-defined] |
| 640 | + |
| 641 | + # For Schema 1.6 and others, return array of all identities |
| 642 | + return [json_loads(identity.as_json(view_=view)) for identity in o] # type: ignore[attr-defined] |
| 643 | + |
| 644 | + @classmethod |
| 645 | + def json_denormalize(cls, o: Any, **__: Any) -> SortedSet[Identity]: |
| 646 | + if isinstance(o, dict): # Single Identity object (Schema 1.5) |
| 647 | + return SortedSet([Identity.from_json(o)]) # type: ignore[attr-defined] |
| 648 | + elif isinstance(o, (list, tuple)): # Array of Identity objects (Schema 1.6) |
| 649 | + return SortedSet(Identity.from_json(i) for i in o) # type: ignore[attr-defined] |
| 650 | + return SortedSet() |
| 651 | + |
| 652 | + |
624 | 653 | @serializable.serializable_class |
625 | 654 | class ComponentEvidence: |
626 | 655 | """ |
@@ -649,10 +678,11 @@ def __init__( |
649 | 678 | @property |
650 | 679 | @serializable.view(SchemaVersion1Dot5) |
651 | 680 | @serializable.view(SchemaVersion1Dot6) |
652 | | - @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'identity') |
653 | 681 | @serializable.xml_sequence(1) |
| 682 | + @serializable.type_mapping(_IdentitySerializationHelper) |
| 683 | + @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, 'identity') |
654 | 684 | # TODO: CDX 1.5 knows only one identity, all versions later known multiple ... |
655 | | - # TODO: need to fix the serializatoin/normlaization |
| 685 | + # TODO: need to fix the serialization/normalization |
656 | 686 | def identity(self) -> 'SortedSet[Identity]': |
657 | 687 | """ |
658 | 688 | Provides a way to identify components via various methods. |
|
0 commit comments