Skip to content

trestle.transforms.results

trestle.transforms.results ¤

Define Results class returned by transformers.

Classes¤

Results ¤

Bases: RootModel[List[Result]]


              flowchart TD
              trestle.transforms.results.Results[Results]

              

              click trestle.transforms.results.Results href "" "trestle.transforms.results.Results"
            

Transformer results as a list.

The OSCAL schema requires at least one Result (minItems: 1), enforced here via min_length=1 so that model_validate (used by oscal_read) rejects an empty list loaded from a file.

When building a Results incrementally (as all transformer implementations do), use Results.model_construct(root=[]) followed by .root.append(...). model_construct intentionally bypasses Pydantic field validation, so an empty list is allowed during construction before items are appended.

Source code in trestle/transforms/results.py
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
class Results(RootModel[List[Result]]):
    """Transformer results as a list.

    The OSCAL schema requires at least one Result (minItems: 1), enforced here via
    ``min_length=1`` so that ``model_validate`` (used by ``oscal_read``) rejects an
    empty list loaded from a file.

    When building a Results incrementally (as all transformer implementations do),
    use ``Results.model_construct(root=[])`` followed by ``.root.append(...)``.
    ``model_construct`` intentionally bypasses Pydantic field validation, so an
    empty list is allowed during construction before items are appended.
    """

    root: List[Result] = Field(default=[], min_length=1)

    def oscal_dict(self):
        """Return an 'oscal wrapped' dictionary."""
        class_name = self.__class__.__name__
        result = {}
        # For RootModel, model_dump() returns the root value directly (a list in this case)
        raw_data = self.model_dump(by_alias=True, exclude_none=True, mode='json')
        result[classname_to_alias(class_name, AliasMode.JSON)] = raw_data
        return result

    def oscal_serialize_json_bytes(self, pretty: bool = False, wrapped: bool = True) -> bytes:
        """
        Return an 'oscal wrapped' json object serialized in a compressed form as bytes.

        Args:
            pretty: Whether or not to pretty-print json output or have in compressed form.
            wrapped: Whether to wrap in OSCAL format.
        Returns:
            Oscal model serialized to a json object including packaging inside of a single top level key.
        """
        if wrapped:
            odict = self.oscal_dict()
        else:
            odict = self.model_dump(by_alias=True, exclude_none=True, mode='json')

        def default_encoder(obj):
            if isinstance(obj, datetime.datetime):
                return robust_datetime_serialization(obj)
            raise TypeError(f'Type {type(obj)} not serializable')

        if pretty:
            return orjson.dumps(odict, default=default_encoder, option=orjson.OPT_INDENT_2)
        return orjson.dumps(odict, default=default_encoder)

    def oscal_serialize_json(self, pretty: bool = False, wrapped: bool = True) -> str:
        """
        Return an 'oscal wrapped' json object serialized in a compressed form as string.

        Args:
            pretty: Whether or not to pretty-print json output or have in compressed form.
            wrapped: Whether to wrap in OSCAL format.
        Returns:
            Oscal model serialized to a json object including packaging inside of a single top level key.
        """
        return self.oscal_serialize_json_bytes(pretty, wrapped).decode(const.FILE_ENCODING)

    def oscal_write(self, path: pathlib.Path) -> None:
        """
        Write out a pydantic data model in an oscal friendly way.

        OSCAL schema mandates that top level elements are wrapped in a singular
        json/yaml field. This function handles both json and yaml output as well
        as formatting of the json.

        Args:
            path: The output file location for the oscal object.

        Raises:
            err.TrestleError: If Results contains no items (OSCAL minItems: 1 violation).
            err.TrestleError: If an unknown file extension is provided.
        """
        if not self.root:
            raise err.TrestleError('Results must contain at least one Result before writing (OSCAL minItems: 1)')
        content_type = FileContentType.to_content_type(path.suffix)

        if content_type == FileContentType.YAML:
            with pathlib.Path(path).open('w', encoding=const.FILE_ENCODING) as write_file:
                yaml = YAML(typ='safe')
                yaml.dump(yaml.load(self.oscal_serialize_json()), write_file)
        elif content_type == FileContentType.JSON:
            with pathlib.Path(path).open('wb') as write_file:
                write_file.write(self.oscal_serialize_json_bytes(pretty=True))
Attributes¤
root = Field(default=[], min_length=1) class-attribute instance-attribute ¤
Methods:¤
oscal_dict() ¤

Return an 'oscal wrapped' dictionary.

Source code in trestle/transforms/results.py
49
50
51
52
53
54
55
56
def oscal_dict(self):
    """Return an 'oscal wrapped' dictionary."""
    class_name = self.__class__.__name__
    result = {}
    # For RootModel, model_dump() returns the root value directly (a list in this case)
    raw_data = self.model_dump(by_alias=True, exclude_none=True, mode='json')
    result[classname_to_alias(class_name, AliasMode.JSON)] = raw_data
    return result
oscal_serialize_json(pretty=False, wrapped=True) ¤

Return an 'oscal wrapped' json object serialized in a compressed form as string.

Parameters:

Name Type Description Default
pretty bool

Whether or not to pretty-print json output or have in compressed form.

False
wrapped bool

Whether to wrap in OSCAL format.

True

Returns: Oscal model serialized to a json object including packaging inside of a single top level key.

Source code in trestle/transforms/results.py
82
83
84
85
86
87
88
89
90
91
92
def oscal_serialize_json(self, pretty: bool = False, wrapped: bool = True) -> str:
    """
    Return an 'oscal wrapped' json object serialized in a compressed form as string.

    Args:
        pretty: Whether or not to pretty-print json output or have in compressed form.
        wrapped: Whether to wrap in OSCAL format.
    Returns:
        Oscal model serialized to a json object including packaging inside of a single top level key.
    """
    return self.oscal_serialize_json_bytes(pretty, wrapped).decode(const.FILE_ENCODING)
oscal_serialize_json_bytes(pretty=False, wrapped=True) ¤

Return an 'oscal wrapped' json object serialized in a compressed form as bytes.

Parameters:

Name Type Description Default
pretty bool

Whether or not to pretty-print json output or have in compressed form.

False
wrapped bool

Whether to wrap in OSCAL format.

True

Returns: Oscal model serialized to a json object including packaging inside of a single top level key.

Source code in trestle/transforms/results.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def oscal_serialize_json_bytes(self, pretty: bool = False, wrapped: bool = True) -> bytes:
    """
    Return an 'oscal wrapped' json object serialized in a compressed form as bytes.

    Args:
        pretty: Whether or not to pretty-print json output or have in compressed form.
        wrapped: Whether to wrap in OSCAL format.
    Returns:
        Oscal model serialized to a json object including packaging inside of a single top level key.
    """
    if wrapped:
        odict = self.oscal_dict()
    else:
        odict = self.model_dump(by_alias=True, exclude_none=True, mode='json')

    def default_encoder(obj):
        if isinstance(obj, datetime.datetime):
            return robust_datetime_serialization(obj)
        raise TypeError(f'Type {type(obj)} not serializable')

    if pretty:
        return orjson.dumps(odict, default=default_encoder, option=orjson.OPT_INDENT_2)
    return orjson.dumps(odict, default=default_encoder)
oscal_write(path) ¤

Write out a pydantic data model in an oscal friendly way.

OSCAL schema mandates that top level elements are wrapped in a singular json/yaml field. This function handles both json and yaml output as well as formatting of the json.

Parameters:

Name Type Description Default
path Path

The output file location for the oscal object.

required

Raises:

Type Description
TrestleError

If Results contains no items (OSCAL minItems: 1 violation).

TrestleError

If an unknown file extension is provided.

Source code in trestle/transforms/results.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
def oscal_write(self, path: pathlib.Path) -> None:
    """
    Write out a pydantic data model in an oscal friendly way.

    OSCAL schema mandates that top level elements are wrapped in a singular
    json/yaml field. This function handles both json and yaml output as well
    as formatting of the json.

    Args:
        path: The output file location for the oscal object.

    Raises:
        err.TrestleError: If Results contains no items (OSCAL minItems: 1 violation).
        err.TrestleError: If an unknown file extension is provided.
    """
    if not self.root:
        raise err.TrestleError('Results must contain at least one Result before writing (OSCAL minItems: 1)')
    content_type = FileContentType.to_content_type(path.suffix)

    if content_type == FileContentType.YAML:
        with pathlib.Path(path).open('w', encoding=const.FILE_ENCODING) as write_file:
            yaml = YAML(typ='safe')
            yaml.dump(yaml.load(self.oscal_serialize_json()), write_file)
    elif content_type == FileContentType.JSON:
        with pathlib.Path(path).open('wb') as write_file:
            write_file.write(self.oscal_serialize_json_bytes(pretty=True))

Functions:¤

handler: python