Skip to content

control_interface

trestle.core.control_interface ¤

Handle queries and utility operations on controls in memory.

CompDict ¤

logger ¤

Classes¤

ComponentImpInfo dataclass ¤

Class to capture component prose and status.

Source code in trestle/core/control_interface.py
@dataclass
class ComponentImpInfo:
    """Class to capture component prose and status."""

    prose: str
    rules: List[str]
    props: List[common.Property]
    # the lambda is needed to prevent a mutable from being used as a default
    # without the lambda it would break python 3.11 and is a bug either way
    status: common.ImplementationStatus = field(
        default_factory=lambda: common.ImplementationStatus(state=const.STATUS_OPERATIONAL)
    )
props: List[trestle.oscal.common.Property] dataclass-field ¤
prose: str dataclass-field ¤
rules: List[str] dataclass-field ¤
status: ImplementationStatus dataclass-field ¤
__eq__(self, other) special ¤
__init__(self, prose, rules, props, status=<factory>) special ¤
__repr__(self) special ¤

ControlInterface ¤

Class to interact with controls in memory.

Source code in trestle/core/control_interface.py
class ControlInterface:
    """Class to interact with controls in memory."""

    @staticmethod
    def _wrap_label(label: str):
        l_side = '\['
        r_side = '\]'
        wrapped = '' if label == '' else f'{l_side}{label}{r_side}'
        return wrapped

    @staticmethod
    def _gap_join(a_str: str, b_str: str) -> str:
        a_clean = a_str.strip()
        b_clean = b_str.strip()
        if not b_clean:
            return a_clean
        gap = '\n\n' if a_clean else ''
        return a_clean + gap + b_clean

    @staticmethod
    def _get_control_section_part(part: common.Part, section_name: str) -> str:
        """Get the prose for a named section in the control."""
        prose = ''
        if part.name == section_name and part.prose is not None:
            prose = ControlInterface._gap_join(prose, part.prose)
        if part.parts:
            for sub_part in part.parts:
                prose = ControlInterface._gap_join(
                    prose, ControlInterface._get_control_section_part(sub_part, section_name)
                )
        return prose

    @staticmethod
    def get_control_section_prose(control: cat.Control, section_name: str) -> str:
        """Get the prose for the control section."""
        prose = ''
        if control.parts:
            for part in control.parts:
                prose = ControlInterface._gap_join(
                    prose, ControlInterface._get_control_section_part(part, section_name)
                )
        return prose

    @staticmethod
    def _find_section_info(part: common.Part, skip_section_list: List[str]) -> Tuple[str, str, str]:
        """Find section not in list."""
        if part.prose and part.name not in skip_section_list:
            return part.id, part.name, part.title
        if part.parts:
            for sub_part in part.parts:
                id_, name, title = ControlInterface._find_section_info(sub_part, skip_section_list)
                if id_:
                    return id_, name, title
        return '', '', ''

    @staticmethod
    def _find_section(control: cat.Control, skip_section_list: List[str]) -> Tuple[str, str, str]:
        """Find next section not in list."""
        if control.parts:
            for part in control.parts:
                id_, name, title = ControlInterface._find_section_info(part, skip_section_list)
                if id_:
                    return id_, name, title
        return '', '', ''

    @staticmethod
    def strip_to_make_ncname(label: str) -> str:
        """Strip chars to conform with NCNAME regex."""
        orig_label = label
        # make sure first char is allowed
        while label and label[0] not in const.NCNAME_UTF8_FIRST_CHAR_OPTIONS:
            label = label[1:]
        new_label = label[:1]
        # now check remaining chars
        if len(label) > 1:
            for ii in range(1, len(label)):
                if label[ii] in const.NCNAME_UTF8_OTHER_CHAR_OPTIONS:
                    new_label += label[ii]
        # do final check to confirm it is NCNAME
        match = re.search(const.NCNAME_REGEX, new_label)
        if not match:
            raise TrestleError(f'Unable to convert label {orig_label} to NCNAME format.')
        return new_label

    @staticmethod
    def get_prop(item: TypeWithProps, prop_name: str, default: Optional[str] = None) -> str:
        """Get the property with that name or return empty string."""
        for prop in as_list(item.props):
            if prop.name.strip().lower() == prop_name.strip().lower():
                return prop.value.strip()
        return default if default else ''

    @staticmethod
    def _delete_prop(part_control: TypeWithProps, prop_name: str) -> None:
        """Delete property with that name."""
        # assumes at most one instance
        names = [prop.name for prop in as_list(part_control.props)]
        if prop_name in names:
            index = names.index(prop_name)
            del part_control.props[index]
        part_control.props = none_if_empty(part_control.props)

    @staticmethod
    def _replace_prop(part_control: TypeWithProps, new_prop: common.Property) -> None:
        """Delete property with that name if present and insert new one."""
        # assumes at most one instance
        names = [prop.name for prop in as_list(part_control.props)]
        if new_prop.name in names:
            index = names.index(new_prop.name)
            del part_control.props[index]
        part_control.props = as_list(part_control.props)
        part_control.props.append(new_prop)

    @staticmethod
    def _apply_params_format(param_str: Optional[str], params_format: Optional[str]) -> Optional[str]:
        if param_str is not None and params_format:
            if params_format.count('.') > 1:
                raise TrestleError(
                    f'Additional text {params_format} '
                    f'for the parameter format cannot contain multiple dots (.)'
                )
            param_str = params_format.replace('.', param_str)
        return param_str

    @staticmethod
    def create_statement_id(control_id: str, lower: bool = False) -> str:
        """Create the control statement id from the control id."""
        id_ = f'{control_id}_smt'
        return id_.lower() if lower else id_

    @staticmethod
    def get_statement_id(control: cat.Control) -> str:
        """Find the statement id in the control."""
        for part in as_list(control.parts):
            if part.name == const.STATEMENT:
                return part.id
        return ControlInterface.create_statement_id(control.id)

    @staticmethod
    def get_sort_id(control: cat.Control, allow_none=False) -> Optional[str]:
        """Get the sort-id for the control."""
        for prop in as_list(control.props):
            if prop.name == const.SORT_ID:
                return prop.value.strip()
        return None if allow_none else control.id

    @staticmethod
    def get_label(item: TypeWithProps) -> str:
        """Get the label from the props of a part or control."""
        return ControlInterface.get_prop(item, 'label')

    @staticmethod
    def get_part_by_id(item: TypeWithParts, id_: str) -> Optional[common.Part]:
        """Find the part within this item's list of parts that matches id."""
        for part in as_list(item.parts):
            if part.id == id_:
                return part
            deep_part = ControlInterface.get_part_by_id(part, id_)
            if deep_part:
                return deep_part
        return None

    @staticmethod
    def get_part(part: common.Part, item_type: str, skip_id: Optional[str]) -> List[Union[str, List[str]]]:
        """
        Find parts with the specified item type, within the given part.

        For a part in a control find the parts in it that match the item_type
        Return list of string formatted labels and associated descriptive prose
        """
        items = []
        if part.name in [const.STATEMENT, item_type]:
            # the options here are to force the label to be the part.id or the part.label
            # the label may be of the form (a) while the part.id is ac-1_smt.a.1.a
            # here we choose the latter and extract the final element
            label = ControlInterface.get_label(part)
            label = part.id.split('.')[-1] if not label else label
            wrapped_label = ControlInterface._wrap_label(label)
            pad = '' if wrapped_label == '' or not part.prose else ' '
            prose = '' if part.prose is None else part.prose
            # top level prose has already been written out, if present
            # use presence of . in id to tell if this is top level prose
            if part.id != skip_id:
                if '\n*' in prose:
                    # it is multiline prose
                    sub_items = []
                    multi_prose = prose.split('\n*')
                    items.append(f'{wrapped_label}{pad}{multi_prose[0]}')
                    multi_prose.remove(multi_prose[0])
                    for prose in multi_prose:
                        sub_items.append(f'{prose}')
                    items.append(sub_items)
                else:
                    items.append(f'{wrapped_label}{pad}{prose}')
            if part.parts:
                sub_list = []
                for prt in part.parts:
                    sub_list.extend(ControlInterface.get_part(prt, item_type, skip_id))
                sub_list.append('')
                items.append(sub_list)
        return items

    @staticmethod
    def _get_adds_for_control(profile: prof.Profile, control_id: str) -> List[prof.Add]:
        """Get the adds for a given control id from a profile."""
        adds: List[prof.Add] = []
        if profile.modify:
            for alter in as_list(profile.modify.alters):
                if alter.control_id == control_id:
                    adds.extend(as_list(alter.adds))
        return adds

    @staticmethod
    def get_all_add_info(control_id: str, profile: prof.Profile) -> List[PartInfo]:
        """Get the adds for a control from a profile by control id."""
        part_infos = []
        for add in ControlInterface._get_adds_for_control(profile, control_id):
            # add control level props with no name
            if add.props:
                smt_part = add.by_id if add.by_id else ''
                part_infos.append(PartInfo(name='', prose='', smt_part=smt_part, props=add.props))
            # add part level props with part name
            for part in as_list(add.parts):
                subpart_info = ControlInterface._get_part_and_subpart_info(part, add.by_id)
                part_infos.append(
                    PartInfo(
                        name=part.name, prose=part.prose, smt_part=add.by_id, props=part.props, parts=subpart_info
                    )
                )
        return part_infos

    @staticmethod
    def _get_part_and_subpart_info(part: common.Part, add_by_id: str) -> List[PartInfo]:
        """Get part and its subparts info needed for markdown purposes."""
        part_infos = []
        for subpart in as_list(part.parts):
            subpart_info = None
            if subpart.parts:
                # Recursively add subparts info
                subpart_info = ControlInterface._get_part_and_subpart_info(subpart, add_by_id)
            part_infos.append(
                PartInfo(
                    name=subpart.name, prose=subpart.prose, smt_part=add_by_id, props=subpart.props, parts=subpart_info
                )
            )

        return part_infos

    @staticmethod
    def get_section(control: cat.Control, skip_section_list: List[str]) -> Tuple[str, str, str, str]:
        """Get sections that are not in the list."""
        id_, name, title = ControlInterface._find_section(control, skip_section_list)
        if id_:
            return id_, name, title, ControlInterface.get_control_section_prose(control, name)
        return '', '', '', ''

    @staticmethod
    def get_part_prose(control: cat.Control, part_name: str) -> str:
        """Get the prose for a named part."""
        prose = ''
        for part in as_list(control.parts):
            prose += ControlInterface._get_control_section_part(part, part_name)
        return prose.strip()

    @staticmethod
    def setparam_to_param(param_id: str, set_param: prof.SetParameter) -> common.Parameter:
        """
        Convert setparameter to parameter.

        Args:
            param_id: the id of the parameter
            set_param: the set_parameter from a profile

        Returns:
            a Parameter with param_id and content from the SetParameter
        """
        return common.Parameter(
            id=param_id, values=set_param.values, select=set_param.select, label=set_param.label, props=set_param.props
        )

    @staticmethod
    def uniquify_set_params(set_params: Optional[List[TypeWithParamId]]) -> List[TypeWithParamId]:
        """Remove items with same param_id with priority to later items."""
        found_ids: Set[str] = set()
        unique_list: List[TypeWithParamId] = []
        for set_param in reversed(as_list(set_params)):
            if set_param.param_id not in found_ids:
                unique_list.append(set_param)
                found_ids.add(set_param.param_id)
        return list(reversed(unique_list))

    @staticmethod
    def get_rules_dict_from_item(item: TypeWithProps) -> Tuple[Dict[str, Dict[str, str]], List[common.Property]]:
        """Get all rules found in this items props."""
        # rules is dict containing rule_id and description
        rules_dict = {}
        name = ''
        desc = ''
        id_ = ''
        rules_props = []
        for prop in as_list(item.props):
            if prop.name == const.RULE_ID:
                name = prop.value
                id_ = prop.remarks
                rules_props.append(prop)
            elif prop.name == const.RULE_DESCRIPTION:
                desc = prop.value
                rules_props.append(prop)
            # grab each pair in case there are multiple pairs
            # then clear and look for new pair
            if name and desc:
                rules_dict[id_] = {'name': name, 'description': desc}
                name = desc = id_ = ''
        return rules_dict, rules_props

    @staticmethod
    def item_has_rules(item: TypeWithProps) -> bool:
        """Determine if the item has rules in its props."""
        _, rules_props = ControlInterface.get_rules_dict_from_item(item)
        return bool(rules_props)

    @staticmethod
    def get_rule_list_for_item(item: TypeWithProps) -> Tuple[List[str], List[common.Property]]:
        """Get the list of rules applying to this item from its top level props."""
        props = []
        rule_list = []
        for prop in as_list(item.props):
            if prop.name == const.RULE_ID:
                rule_list.append(prop.value)
                props.append(prop)
        return rule_list, props

    @staticmethod
    def get_rule_list_for_imp_req(
        imp_req: ossp.ImplementedRequirement
    ) -> Tuple[List[str], List[str], List[common.Property]]:
        """Get the list of rules applying to an imp_req as two lists."""
        comp_rules, rule_props = ControlInterface.get_rule_list_for_item(imp_req)
        statement_rules = set()
        for statement in as_list(imp_req.statements):
            stat_rules, statement_props = ControlInterface.get_rule_list_for_item(statement)
            statement_rules.update(stat_rules)
            rule_props.extend(statement_props)
        return comp_rules, sorted(statement_rules), rule_props

    @staticmethod
    def get_params_dict_from_item(item: TypeWithProps) -> Tuple[Dict[str, Dict[str, str]], List[common.Property]]:
        """Get all params found in this item with rule_id as key."""
        # id, description, options - where options is a string containing comma-sep list of items
        # params is dict with rule_id as key and value contains: param_name, description and choices
        params: Dict[str, List[Dict[str, str]]] = {}
        props = []
        for prop in as_list(item.props):
            if const.PARAMETER_ID in prop.name:
                rule_id = prop.remarks
                param_name = prop.value
                # rule already exists in parameters dict
                if rule_id in params.keys():
                    existing_param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
                    if existing_param is not None:
                        raise TrestleError(f'Param id for rule {rule_id} already exists')
                    else:
                        # append a new parameter for the current rule
                        params[rule_id].append({'name': param_name})
                else:
                    # create new param for this rule for the first parameter
                    params[rule_id] = [{'name': param_name}]
                props.append(prop)
            elif const.PARAMETER_DESCRIPTION in prop.name:
                rule_id = prop.remarks
                if rule_id in params:
                    param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
                    param['description'] = prop.value
                    props.append(prop)
                else:
                    raise TrestleError(f'Param description for rule {rule_id} found with no param_id')
            elif const.PARAMETER_VALUE_ALTERNATIVES in prop.name:
                rule_id = prop.remarks
                if rule_id in params:
                    param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
                    param['options'] = prop.value
                    props.append(prop)
                else:
                    raise TrestleError(f'Param options for rule {rule_id} found with no param_id')
        new_params = {}
        for rule_id, rule_params in params.items():
            new_params[rule_id] = []
            for param in rule_params:
                if 'name' not in param:
                    logger.warning(f'Parameter for rule_id {rule_id} has no matching name.  Ignoring the param.')
                else:
                    param['description'] = param.get('description', '')
                    param['options'] = param.get('options', '')
                    new_params[rule_id].append(param)
        return new_params, props

    @staticmethod
    def get_rules_and_params_dict_from_item(
        item: TypeWithProps
    ) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, str]], List[common.Property]]:
        """Get the rule dict and params dict from item with props."""
        rules_dict, rules_props = ControlInterface.get_rules_dict_from_item(item)
        params_dict, params_props = ControlInterface.get_params_dict_from_item(item)
        rules_props.extend(params_props)
        return rules_dict, params_dict, rules_props

    @staticmethod
    def get_set_params_from_item(
        item: Union[comp.ControlImplementation, comp.ImplementedRequirement]
    ) -> Dict[str, comp.SetParameter]:
        """Get set params that have values from control implementation or imp req."""
        return {
            set_param.param_id: set_param
            for set_param in as_filtered_list(item.set_parameters, lambda i: i.values)
        }

    @staticmethod
    def merge_props(dest: Optional[List[common.Property]],
                    src: Optional[List[common.Property]]) -> List[common.Property]:
        """Merge a source list of properties into a destination list."""
        if not src:
            return dest
        new_props: List[common.Property] = []
        src_map = {prop.name: prop for prop in src}
        dest_map = {prop.name: prop for prop in dest}
        all_names = set(src_map.keys()).union(dest_map.keys())
        for name in sorted(all_names):
            if name in src_map and name not in dest_map:
                new_props.append(src_map[name])
            elif name in dest_map and name not in src_map:
                new_props.append(dest_map[name])
            else:
                new_prop = dest_map[name]
                src_prop = src_map[name]
                new_prop.class_ = src_prop.class_ if src_prop.class_ else new_prop.class_
                new_prop.ns = src_prop.ns if src_prop.ns else new_prop.ns
                new_prop.remarks = src_prop.remarks if src_prop.remarks else new_prop.remarks
                new_prop.uuid = src_prop.uuid if src_prop.uuid else new_prop.uuid
                new_prop.value = src_prop.value
                new_props.append(new_prop)
        return new_props

    @staticmethod
    def merge_part(dest: common.Part, src: common.Part) -> common.Part:
        """Merge a source part into the destination part."""
        dest.name = src.name if src.name else dest.name
        dest.ns = src.ns if src.ns else dest.ns
        dest.props = none_if_empty(ControlInterface.merge_props(dest.props, src.props))
        dest.prose = src.prose
        dest.title = src.title if src.title else dest.title
        ControlInterface.merge_parts(dest, src)
        return dest

    @staticmethod
    def merge_parts(dest: TypeWithParts, src: TypeWithParts) -> None:
        """Merge the parts from the source into the destination."""
        if not dest.parts:
            dest.parts = src.parts
        elif not src.parts:
            dest.parts = None
        else:
            new_parts: List[common.Part] = []
            dest_map = {part.id: part for part in dest.parts}
            for src_part in src.parts:
                dest_part = dest_map.get(src_part.id, None)
                if not dest_part:
                    new_parts.append(src_part)
                else:
                    new_part = ControlInterface.merge_part(dest_part, src_part)
                    if new_part:
                        new_parts.append(new_part)
            dest.parts = new_parts

    @staticmethod
    def merge_dicts_deep(
        dest: Dict[Any, Any],
        src: Dict[Any, Any],
        overwrite_header_values: bool,
        depth: int = 0,
        level: int = 0
    ) -> None:
        """
        Merge dict src into dest.

        New items are always added from src to dest.
        Items present in both will be overriden dest if overwrite_header_values is True.
        """
        for key in src.keys():
            if key in dest:
                if depth and level == depth:
                    if overwrite_header_values:
                        dest[key] = src[key]
                    continue
                # if they are both dicts, recurse
                if isinstance(dest[key], dict) and isinstance(src[key], dict):
                    ControlInterface.merge_dicts_deep(dest[key], src[key], overwrite_header_values, depth, level + 1)
                # if they are both lists, add any item that is not already in the list
                elif isinstance(dest[key], list) and isinstance(src[key], list):
                    for item in src[key]:
                        if item not in dest[key]:
                            dest[key].append(item)
                # otherwise override dest if needed
                elif overwrite_header_values:
                    dest[key] = src[key]
            else:
                # if the item was not already in dest, add it from src
                dest[key] = src[key]

    @staticmethod
    def is_withdrawn(control: cat.Control) -> bool:
        """
        Determine if control is marked Withdrawn.

        Args:
            control: The control that may be marked withdrawn.

        Returns:
            True if marked withdrawn, false otherwise.

        This is determined by property with name 'status' with value 'Withdrawn'.
        """
        for _ in as_filtered_list(
                control.props,
                lambda p: strip_lower_equals(p.name, 'status') and strip_lower_equals(p.value, 'withdrawn')):
            return True
        return False

    @staticmethod
    def _setparam_values_as_str(set_param: comp.SetParameter) -> str:
        """Convert values to string."""
        out_str = ''
        for value in as_list(set_param.values):
            value_str = string_from_root(value)
            if value_str:
                if out_str:
                    out_str += ', '
                out_str += value_str
        return out_str

    @staticmethod
    def _param_values_as_str_list(param: common.Parameter) -> List[str]:
        """Convert param values to list of strings."""
        return as_list(param.values)

    @staticmethod
    def _param_values_as_str(param: common.Parameter, brackets=False) -> Optional[str]:
        """Convert param values to string with optional brackets."""
        if not param.values:
            return None
        values_str = ', '.join(ControlInterface._param_values_as_str_list(param))
        return f'[{values_str}]' if brackets else values_str

    @staticmethod
    def _param_selection_as_str(param: common.Parameter, verbose: bool = False, brackets: bool = False) -> str:
        """Convert parameter selection to str."""
        if param.select and param.select.choice:
            how_many_str = ''
            # if all values are specified there is no how_many string and parens are dropped.  See ac-2.2
            if param.select.how_many:
                how_many_str = ' (one)' if param.select.how_many == const.ONE else ' (one or more)'
            choices_str = '; '.join(as_list(param.select.choice))
            choices_str = f'[{choices_str}]' if brackets else choices_str
            choices_str = f'Selection{how_many_str}: {choices_str}' if verbose else choices_str
            return choices_str
        return ''

    @staticmethod
    def _param_label_choices_as_str(param: common.Parameter, verbose: bool = False, brackets: bool = False) -> str:
        """Convert param label or choices to string, using choices if present."""
        choices = ControlInterface._param_selection_as_str(param, verbose, brackets)
        text = choices if choices else param.label
        text = text if text else param.id
        return text

    @staticmethod
    def _param_values_assignment_str(
        param: common.Parameter,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> str:
        """Convert param values, label or choices to string."""
        # use values if present
        param_str = ControlInterface._param_values_as_str(param, False)
        if param_str and value_assigned_prefix:
            param_str = f'{value_assigned_prefix} {param_str}'
        # otherwise use param selection if present
        if not param_str:
            param_str = ControlInterface._param_selection_as_str(param, True, False)
        # finally use label and param_id as fallbacks
        if not param_str:
            param_str = param.label if param.label else param.id
            if value_not_assigned_prefix:
                param_str = f'{value_not_assigned_prefix} {param_str}'
        return f'{param_str}'

    @staticmethod
    def _param_labels_assignment_str(
        param: common.Parameter,
        label_prefix: Optional[str] = None,
    ) -> str:
        """Convert param label or choices to string."""
        # use values if present
        param_str = ControlInterface._param_selection_as_str(param, True, False)
        # finally use label and param_id as fallbacks
        if not param_str:
            param_str = param.label if param.label else param.id
            if label_prefix:
                param_str = f'{label_prefix} {param_str}'
        return f'{param_str}'

    @staticmethod
    def param_to_str(
        param: common.Parameter,
        param_rep: ParameterRep,
        verbose: bool = False,
        brackets: bool = False,
        params_format: Optional[str] = None,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> Optional[str]:
        """
        Convert parameter to string based on best available representation.

        Args:
            param: the parameter to convert
            param_rep: how to represent the parameter
            verbose: provide verbose text for selection choices
            brackets: add brackets around the lists of items
            params_format: a string containing a single dot that represents a form of highlighting around the param
            value_assigned_prefix: string to place before the parameter string if a value was assigned
            value_not_assigned_prefix: string to place before the parameter string if value not assigned

        Returns:
            formatted string or None
        """
        param_str = None
        if param_rep == ParameterRep.VALUE_OR_STRING_NONE:
            param_str = ControlInterface._param_values_as_str(param)
            param_str = param_str if param_str else 'None'
        elif param_rep == ParameterRep.LABEL_OR_CHOICES:
            param_str = ControlInterface._param_label_choices_as_str(param, verbose, brackets)
        elif param_rep == ParameterRep.VALUE_OR_LABEL_OR_CHOICES:
            param_str = ControlInterface._param_values_as_str(param)
            if not param_str:
                param_str = ControlInterface._param_label_choices_as_str(param, verbose, brackets)
        elif param_rep == ParameterRep.VALUE_OR_EMPTY_STRING:
            param_str = ControlInterface._param_values_as_str(param, brackets)
            if not param_str:
                param_str = ''
        elif param_rep == ParameterRep.ASSIGNMENT_FORM:
            param_str = ControlInterface._param_values_assignment_str(
                param, value_assigned_prefix, value_not_assigned_prefix
            )
            if not param_str:
                param_str = ''
        elif param_rep == ParameterRep.LABEL_FORM:
            param_str = ControlInterface._param_labels_assignment_str(param, value_not_assigned_prefix)
            if not param_str:
                param_str = ''
        return ControlInterface._apply_params_format(param_str, params_format)

    @staticmethod
    def get_control_param_dict(control: cat.Control, values_only: bool) -> Dict[str, common.Parameter]:
        """
        Create mapping of param id's to params for params in the control.

        Args:
            control: the control containing params of interest
            values_only: only add params to the dict that have actual values

        Returns:
            Dictionary of param_id mapped to param

        Notes:
            Warning is given if there is a parameter with no ID
        """
        param_dict: Dict[str, common.Parameter] = {}
        for param in as_list(control.params):
            if not param.id:
                logger.warning(f'Control {control.id} has parameter with no id.  Ignoring.')
            if param.values or not values_only:
                param_dict[param.id] = param
        return param_dict

    @staticmethod
    def _replace_ids_with_text(
        prose: str,
        param_rep: ParameterRep,
        param_dict: Dict[str, common.Parameter],
        params_format: Optional[str] = None,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> str:
        """Find all instances of param_ids in prose and replace each with corresponding parameter representation.

        Need to check all values in dict for a match
        Reject matches where the string has an adjacent alphanumeric char: param_1 and param_10 or aparam_1
        """
        for param in param_dict.values():
            if param.id not in prose:
                continue
            # create the replacement text for the param_id
            param_str = ControlInterface.param_to_str(
                param, param_rep, False, False, params_format, value_assigned_prefix, value_not_assigned_prefix
            )
            # non-capturing groups are odd in re.sub so capture all 3 groups and replace the middle one
            pattern = r'(^|[^a-zA-Z0-9_])' + param.id + r'($|[^a-zA-Z0-9_])'
            prose = re.sub(pattern, r'\1' + param_str + r'\2', prose)
        return prose

    @staticmethod
    def _replace_params(
        text: str,
        param_dict: Dict[str, common.Parameter],
        params_format: Optional[str] = None,
        param_rep: ParameterRep = ParameterRep.VALUE_OR_LABEL_OR_CHOICES,
        show_value_warnings: bool = False,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> str:
        """
        Replace params found in moustaches with values from the param_dict.

        A single line of prose may contain multiple moustaches.
        """
        # first check if there are any moustache patterns in the text
        if param_rep == ParameterRep.LEAVE_MOUSTACHE:
            return text
        orig_text = text
        staches: List[str] = re.findall(r'{{.*?}}', text)
        if not staches:
            return text
        # now have list of all staches including braces, e.g. ['{{foo}}', '{{bar}}']
        # clean the staches so they just have the param ids
        param_ids = []
        for stache in staches:
            # remove braces so these are just param_ids but may have extra chars
            stache_contents = stache[2:(-2)]
            param_id = stache_contents.replace('insert: param,', '').strip()
            param_ids.append(param_id)

        # now replace original stache text with param values
        for i, _ in enumerate(staches):
            # A moustache may refer to a param_id not listed in the control's params
            if param_ids[i] not in param_dict:
                if show_value_warnings:
                    logger.warning(f'Control prose references param {param_ids[i]} not set in the control: {staches}')
            elif param_dict[param_ids[i]] is not None:
                param = param_dict[param_ids[i]]
                param_str = ControlInterface.param_to_str(
                    param, param_rep, False, False, params_format, value_assigned_prefix, value_not_assigned_prefix
                )
                text = text.replace(staches[i], param_str, 1).strip()
                if show_value_warnings and param_rep != ParameterRep.LABEL_OR_CHOICES and not param.values:
                    logger.warning(f'Parameter {param_id} has no values and was referenced by prose.')
            elif show_value_warnings:
                logger.warning(f'Control prose references param {param_ids[i]} with no specified value.')
        # there may be staches remaining that we can't replace if not in param_dict
        if text != orig_text:
            while True:
                new_text = ControlInterface._replace_params(
                    text,
                    param_dict,
                    params_format,
                    param_rep,
                    show_value_warnings,
                    value_assigned_prefix,
                    value_not_assigned_prefix
                )
                if new_text == text:
                    break
                text = new_text
        return text

    @staticmethod
    def _replace_part_prose(
        control: cat.Control,
        part: common.Part,
        param_dict: Dict[str, common.Parameter],
        params_format: Optional[str] = None,
        param_rep: ParameterRep = ParameterRep.VALUE_OR_LABEL_OR_CHOICES,
        show_value_warnings: bool = False,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> None:
        """Replace the part prose according to set_param."""
        if part.prose is not None:
            fixed_prose = ControlInterface._replace_params(
                part.prose,
                param_dict,
                params_format,
                param_rep,
                show_value_warnings,
                value_assigned_prefix,
                value_not_assigned_prefix
            )
            # change the prose in the control itself
            part.prose = fixed_prose
        for prt in as_list(part.parts):
            ControlInterface._replace_part_prose(
                control,
                prt,
                param_dict,
                params_format,
                param_rep,
                show_value_warnings,
                value_assigned_prefix,
                value_not_assigned_prefix
            )
        for sub_control in as_list(control.controls):
            for prt in as_list(sub_control.parts):
                ControlInterface._replace_part_prose(
                    sub_control,
                    prt,
                    param_dict,
                    params_format,
                    param_rep,
                    show_value_warnings,
                    value_assigned_prefix,
                    value_not_assigned_prefix
                )

    @staticmethod
    def _replace_param_choices(
        param: common.Parameter,
        param_dict: Dict[str, common.Parameter],
        params_format: Optional[str],
        param_rep: ParameterRep,
        show_value_warnings: bool,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> None:
        """Set values for all choices param that refer to params with values."""
        if param.select:
            new_choices: List[str] = []
            for choice in as_list(param.select.choice):
                new_choice = ControlInterface._replace_params(
                    choice,
                    param_dict,
                    params_format,
                    param_rep,
                    show_value_warnings,
                    value_assigned_prefix,
                    value_not_assigned_prefix
                )
                new_choices.append(new_choice)
            param.select.choice = new_choices

    @staticmethod
    def replace_control_prose(
        control: cat.Control,
        param_dict: Dict[str, common.Parameter],
        params_format: Optional[str] = None,
        param_rep: ParameterRep = ParameterRep.VALUE_OR_LABEL_OR_CHOICES,
        show_value_warnings: bool = False,
        value_assigned_prefix: Optional[str] = None,
        value_not_assigned_prefix: Optional[str] = None
    ) -> None:
        """Replace the control prose according to set_param."""
        # first replace all choices that reference parameters
        # note that in ASSIGNMENT_FORM each choice with a parameter will end up as [Assignment: value]
        for param in as_list(control.params):
            ControlInterface._replace_param_choices(
                param,
                param_dict,
                params_format,
                param_rep,
                show_value_warnings,
                value_assigned_prefix,
                value_not_assigned_prefix
            )
        for part in as_list(control.parts):
            if part.prose is not None:
                fixed_prose = ControlInterface._replace_params(
                    part.prose,
                    param_dict,
                    params_format,
                    param_rep,
                    show_value_warnings,
                    value_assigned_prefix,
                    value_not_assigned_prefix
                )
                # change the prose in the control itself
                part.prose = fixed_prose
            for prt in as_list(part.parts):
                ControlInterface._replace_part_prose(
                    control,
                    prt,
                    param_dict,
                    params_format,
                    param_rep,
                    show_value_warnings,
                    value_assigned_prefix,
                    value_not_assigned_prefix
                )

    @staticmethod
    def bad_header(header: str) -> bool:
        """Return true if header format is bad."""
        if not header or header[0] != '#':
            return True
        n = len(header)
        if n < 2:
            return True
        for ii in range(1, n):
            if header[ii] == ' ':
                return False
            if header[ii] != '#':
                return True
        return True

    @staticmethod
    def get_component_by_name(comp_def: comp.ComponentDefinition, comp_name: str) -> Optional[comp.DefinedComponent]:
        """Get the component with this name from the comp_def."""
        for sub_comp in as_list(comp_def.components):
            if sub_comp.title == comp_name:
                return sub_comp
        return None

    @staticmethod
    def get_status_from_props(item: TypeWithProps) -> common.ImplementationStatus:
        """Get the status of an item from its props."""
        status = common.ImplementationStatus(state=const.STATUS_PLANNED)
        for prop in as_list(item.props):
            if prop.name == const.IMPLEMENTATION_STATUS:
                status = ControlInterface._prop_as_status(prop)
                break
        return status

    @staticmethod
    def clean_props(
        props: Optional[List[common.Property]],
        remove_imp_status: bool = True,
        remove_all_rule_info: bool = False
    ) -> List[common.Property]:
        """Remove duplicate props and implementation status."""
        new_props: List[common.Property] = []
        found_props: Set[Tuple[str, str, str, str]] = set()
        rule_tag_list = [
            const.RULE_DESCRIPTION, const.RULE_ID, const.PARAMETER_DESCRIPTION, const.PARAMETER_VALUE_ALTERNATIVES
        ]
        # reverse the list so the latest items are kept
        for prop in reversed(as_list(props)):
            prop_tuple = (prop.name, as_string(prop.value), as_string(prop.ns), prop.remarks)
            if prop_tuple in found_props or (prop.name == const.IMPLEMENTATION_STATUS and remove_imp_status):
                continue
            if remove_all_rule_info and prop.name in rule_tag_list:
                continue
            found_props.add(prop_tuple)
            new_props.append(prop)
        new_props.reverse()
        return new_props

    @staticmethod
    def cull_props_by_rules(props: Optional[List[common.Property]], rules: List[str]) -> List[common.Property]:
        """Cull properties to the ones needed by rules."""
        needed_rule_ids: Set[str] = set()
        culled_props: List[common.Property] = []
        for prop in as_list(props):
            if prop.value in rules and prop.remarks:
                needed_rule_ids.add(prop.remarks)
        for prop in as_list(props):
            if prop.value in rules or prop.remarks in needed_rule_ids:
                culled_props.append(prop)
        return culled_props

    @staticmethod
    def _status_as_prop(status: common.ImplementationStatus) -> common.Property:
        """Convert status to property."""
        return common.Property(name=const.IMPLEMENTATION_STATUS, value=status.state, remarks=status.remarks)

    @staticmethod
    def _prop_as_status(prop: common.Property) -> common.ImplementationStatus:
        """Convert property to status."""
        return common.ImplementationStatus(state=prop.value, remarks=prop.remarks)

    @staticmethod
    def insert_status_in_props(item: TypeWithProps, status: common.ImplementationStatus) -> None:
        """Insert status content into props of the item."""
        prop = ControlInterface._status_as_prop(status)
        ControlInterface._replace_prop(item, prop)

    @staticmethod
    def _copy_status_in_props(dest: TypeWithProps, src: TypeWithProps) -> None:
        """Copy status in props from one object to another."""
        status = ControlInterface.get_status_from_props(src)
        ControlInterface.insert_status_in_props(dest, status)

    @staticmethod
    def insert_imp_req_into_component(
        component: comp.DefinedComponent,
        new_imp_req: comp.ImplementedRequirement,
        profile_title: str,
        trestle_root: pathlib.Path
    ) -> None:
        """
        Insert imp req into component by matching source title and control id to existing imp req.

        Args:
            component: The defined component receiving the imp_req
            new_imp_req: The new imp_req being added
            profile_title: The title of the source profile for the control implementation containing the imp_req

        Notes:
            Inserts the imp_req on the first match found.  Note it is possible two control implementations could
            have the same source and specify the same control
        """
        for control_imp in as_list(component.control_implementations):
            _, control_imp_param_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(control_imp)
            control_imp_rule_param_ids = [
                param['name'] for params in control_imp_param_dict.values() for param in params
            ]
            if profile_title != ModelUtils.get_title_from_model_uri(trestle_root, control_imp.source):
                continue
            for imp_req in as_list(control_imp.implemented_requirements):
                if imp_req.control_id != new_imp_req.control_id:
                    continue
                _, imp_req_param_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(imp_req)
                imp_req_rule_param_ids = [param['name'] for params in imp_req_param_dict.values() for param in params]
                status = ControlInterface.get_status_from_props(new_imp_req)
                ControlInterface.insert_status_in_props(imp_req, status)
                imp_req.description = new_imp_req.description
                statement_dict = {stat.statement_id: stat for stat in as_list(imp_req.statements)}
                # update set parameter values with values from markdown - but only for rule param vals
                for set_param in as_list(new_imp_req.set_parameters):
                    if set_param.param_id not in (control_imp_rule_param_ids + imp_req_rule_param_ids):
                        continue
                    found = False
                    for dest_param in as_list(imp_req.set_parameters):
                        if dest_param.param_id != set_param.param_id:
                            continue
                        dest_param.values = set_param.values
                        found = True
                        break
                    # if rule parameter val was not already set by a set_param, make new set_param for it
                    if found:
                        continue
                    # but first check if the parameter was already set with the same value in the control_imp
                    # if so we don't need to insert a new set_param in imp_req
                    for dest_param in as_list(control_imp.set_parameters):
                        if dest_param.param_id != set_param.param_id:
                            continue
                        if dest_param.values == set_param.values:
                            found = True
                            break
                    if found:
                        continue
                    imp_req.set_parameters = as_list(imp_req.set_parameters)
                    imp_req.set_parameters.append(
                        comp.SetParameter(param_id=set_param.param_id, values=set_param.values)
                    )
                new_statements: List[comp.Statement] = []
                for statement in as_list(new_imp_req.statements):
                    # get the original version of the statement if available, or use new one
                    stat = statement_dict.get(statement.statement_id, statement)
                    # update the description and status from markdown
                    stat.description = statement.description
                    ControlInterface._copy_status_in_props(stat, statement)
                    new_statements.append(stat)
                imp_req.statements = none_if_empty(new_statements)
                return
        logger.warning(
            f'Unable to add imp req for component {component.title} control {new_imp_req.control_id} and source: {profile_title}'  # noqa E501
        )
Methods¤
bad_header(header) staticmethod ¤

Return true if header format is bad.

Source code in trestle/core/control_interface.py
@staticmethod
def bad_header(header: str) -> bool:
    """Return true if header format is bad."""
    if not header or header[0] != '#':
        return True
    n = len(header)
    if n < 2:
        return True
    for ii in range(1, n):
        if header[ii] == ' ':
            return False
        if header[ii] != '#':
            return True
    return True
clean_props(props, remove_imp_status=True, remove_all_rule_info=False) staticmethod ¤

Remove duplicate props and implementation status.

Source code in trestle/core/control_interface.py
@staticmethod
def clean_props(
    props: Optional[List[common.Property]],
    remove_imp_status: bool = True,
    remove_all_rule_info: bool = False
) -> List[common.Property]:
    """Remove duplicate props and implementation status."""
    new_props: List[common.Property] = []
    found_props: Set[Tuple[str, str, str, str]] = set()
    rule_tag_list = [
        const.RULE_DESCRIPTION, const.RULE_ID, const.PARAMETER_DESCRIPTION, const.PARAMETER_VALUE_ALTERNATIVES
    ]
    # reverse the list so the latest items are kept
    for prop in reversed(as_list(props)):
        prop_tuple = (prop.name, as_string(prop.value), as_string(prop.ns), prop.remarks)
        if prop_tuple in found_props or (prop.name == const.IMPLEMENTATION_STATUS and remove_imp_status):
            continue
        if remove_all_rule_info and prop.name in rule_tag_list:
            continue
        found_props.add(prop_tuple)
        new_props.append(prop)
    new_props.reverse()
    return new_props
create_statement_id(control_id, lower=False) staticmethod ¤

Create the control statement id from the control id.

Source code in trestle/core/control_interface.py
@staticmethod
def create_statement_id(control_id: str, lower: bool = False) -> str:
    """Create the control statement id from the control id."""
    id_ = f'{control_id}_smt'
    return id_.lower() if lower else id_
cull_props_by_rules(props, rules) staticmethod ¤

Cull properties to the ones needed by rules.

Source code in trestle/core/control_interface.py
@staticmethod
def cull_props_by_rules(props: Optional[List[common.Property]], rules: List[str]) -> List[common.Property]:
    """Cull properties to the ones needed by rules."""
    needed_rule_ids: Set[str] = set()
    culled_props: List[common.Property] = []
    for prop in as_list(props):
        if prop.value in rules and prop.remarks:
            needed_rule_ids.add(prop.remarks)
    for prop in as_list(props):
        if prop.value in rules or prop.remarks in needed_rule_ids:
            culled_props.append(prop)
    return culled_props
get_all_add_info(control_id, profile) staticmethod ¤

Get the adds for a control from a profile by control id.

Source code in trestle/core/control_interface.py
@staticmethod
def get_all_add_info(control_id: str, profile: prof.Profile) -> List[PartInfo]:
    """Get the adds for a control from a profile by control id."""
    part_infos = []
    for add in ControlInterface._get_adds_for_control(profile, control_id):
        # add control level props with no name
        if add.props:
            smt_part = add.by_id if add.by_id else ''
            part_infos.append(PartInfo(name='', prose='', smt_part=smt_part, props=add.props))
        # add part level props with part name
        for part in as_list(add.parts):
            subpart_info = ControlInterface._get_part_and_subpart_info(part, add.by_id)
            part_infos.append(
                PartInfo(
                    name=part.name, prose=part.prose, smt_part=add.by_id, props=part.props, parts=subpart_info
                )
            )
    return part_infos
get_component_by_name(comp_def, comp_name) staticmethod ¤

Get the component with this name from the comp_def.

Source code in trestle/core/control_interface.py
@staticmethod
def get_component_by_name(comp_def: comp.ComponentDefinition, comp_name: str) -> Optional[comp.DefinedComponent]:
    """Get the component with this name from the comp_def."""
    for sub_comp in as_list(comp_def.components):
        if sub_comp.title == comp_name:
            return sub_comp
    return None
get_control_param_dict(control, values_only) staticmethod ¤

Create mapping of param id's to params for params in the control.

Parameters:

Name Type Description Default
control cat.Control

the control containing params of interest

required
values_only bool

only add params to the dict that have actual values

required

Returns:

Type Description
Dict[str, common.Parameter]

Dictionary of param_id mapped to param

Notes

Warning is given if there is a parameter with no ID

Source code in trestle/core/control_interface.py
@staticmethod
def get_control_param_dict(control: cat.Control, values_only: bool) -> Dict[str, common.Parameter]:
    """
    Create mapping of param id's to params for params in the control.

    Args:
        control: the control containing params of interest
        values_only: only add params to the dict that have actual values

    Returns:
        Dictionary of param_id mapped to param

    Notes:
        Warning is given if there is a parameter with no ID
    """
    param_dict: Dict[str, common.Parameter] = {}
    for param in as_list(control.params):
        if not param.id:
            logger.warning(f'Control {control.id} has parameter with no id.  Ignoring.')
        if param.values or not values_only:
            param_dict[param.id] = param
    return param_dict
get_control_section_prose(control, section_name) staticmethod ¤

Get the prose for the control section.

Source code in trestle/core/control_interface.py
@staticmethod
def get_control_section_prose(control: cat.Control, section_name: str) -> str:
    """Get the prose for the control section."""
    prose = ''
    if control.parts:
        for part in control.parts:
            prose = ControlInterface._gap_join(
                prose, ControlInterface._get_control_section_part(part, section_name)
            )
    return prose
get_label(item) staticmethod ¤

Get the label from the props of a part or control.

Source code in trestle/core/control_interface.py
@staticmethod
def get_label(item: TypeWithProps) -> str:
    """Get the label from the props of a part or control."""
    return ControlInterface.get_prop(item, 'label')
get_params_dict_from_item(item) staticmethod ¤

Get all params found in this item with rule_id as key.

Source code in trestle/core/control_interface.py
@staticmethod
def get_params_dict_from_item(item: TypeWithProps) -> Tuple[Dict[str, Dict[str, str]], List[common.Property]]:
    """Get all params found in this item with rule_id as key."""
    # id, description, options - where options is a string containing comma-sep list of items
    # params is dict with rule_id as key and value contains: param_name, description and choices
    params: Dict[str, List[Dict[str, str]]] = {}
    props = []
    for prop in as_list(item.props):
        if const.PARAMETER_ID in prop.name:
            rule_id = prop.remarks
            param_name = prop.value
            # rule already exists in parameters dict
            if rule_id in params.keys():
                existing_param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
                if existing_param is not None:
                    raise TrestleError(f'Param id for rule {rule_id} already exists')
                else:
                    # append a new parameter for the current rule
                    params[rule_id].append({'name': param_name})
            else:
                # create new param for this rule for the first parameter
                params[rule_id] = [{'name': param_name}]
            props.append(prop)
        elif const.PARAMETER_DESCRIPTION in prop.name:
            rule_id = prop.remarks
            if rule_id in params:
                param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
                param['description'] = prop.value
                props.append(prop)
            else:
                raise TrestleError(f'Param description for rule {rule_id} found with no param_id')
        elif const.PARAMETER_VALUE_ALTERNATIVES in prop.name:
            rule_id = prop.remarks
            if rule_id in params:
                param = next((prm for prm in params[rule_id] if prm['name'] == param_name), None)
                param['options'] = prop.value
                props.append(prop)
            else:
                raise TrestleError(f'Param options for rule {rule_id} found with no param_id')
    new_params = {}
    for rule_id, rule_params in params.items():
        new_params[rule_id] = []
        for param in rule_params:
            if 'name' not in param:
                logger.warning(f'Parameter for rule_id {rule_id} has no matching name.  Ignoring the param.')
            else:
                param['description'] = param.get('description', '')
                param['options'] = param.get('options', '')
                new_params[rule_id].append(param)
    return new_params, props
get_part(part, item_type, skip_id) staticmethod ¤

Find parts with the specified item type, within the given part.

For a part in a control find the parts in it that match the item_type Return list of string formatted labels and associated descriptive prose

Source code in trestle/core/control_interface.py
@staticmethod
def get_part(part: common.Part, item_type: str, skip_id: Optional[str]) -> List[Union[str, List[str]]]:
    """
    Find parts with the specified item type, within the given part.

    For a part in a control find the parts in it that match the item_type
    Return list of string formatted labels and associated descriptive prose
    """
    items = []
    if part.name in [const.STATEMENT, item_type]:
        # the options here are to force the label to be the part.id or the part.label
        # the label may be of the form (a) while the part.id is ac-1_smt.a.1.a
        # here we choose the latter and extract the final element
        label = ControlInterface.get_label(part)
        label = part.id.split('.')[-1] if not label else label
        wrapped_label = ControlInterface._wrap_label(label)
        pad = '' if wrapped_label == '' or not part.prose else ' '
        prose = '' if part.prose is None else part.prose
        # top level prose has already been written out, if present
        # use presence of . in id to tell if this is top level prose
        if part.id != skip_id:
            if '\n*' in prose:
                # it is multiline prose
                sub_items = []
                multi_prose = prose.split('\n*')
                items.append(f'{wrapped_label}{pad}{multi_prose[0]}')
                multi_prose.remove(multi_prose[0])
                for prose in multi_prose:
                    sub_items.append(f'{prose}')
                items.append(sub_items)
            else:
                items.append(f'{wrapped_label}{pad}{prose}')
        if part.parts:
            sub_list = []
            for prt in part.parts:
                sub_list.extend(ControlInterface.get_part(prt, item_type, skip_id))
            sub_list.append('')
            items.append(sub_list)
    return items
get_part_by_id(item, id_) staticmethod ¤

Find the part within this item's list of parts that matches id.

Source code in trestle/core/control_interface.py
@staticmethod
def get_part_by_id(item: TypeWithParts, id_: str) -> Optional[common.Part]:
    """Find the part within this item's list of parts that matches id."""
    for part in as_list(item.parts):
        if part.id == id_:
            return part
        deep_part = ControlInterface.get_part_by_id(part, id_)
        if deep_part:
            return deep_part
    return None
get_part_prose(control, part_name) staticmethod ¤

Get the prose for a named part.

Source code in trestle/core/control_interface.py
@staticmethod
def get_part_prose(control: cat.Control, part_name: str) -> str:
    """Get the prose for a named part."""
    prose = ''
    for part in as_list(control.parts):
        prose += ControlInterface._get_control_section_part(part, part_name)
    return prose.strip()
get_prop(item, prop_name, default=None) staticmethod ¤

Get the property with that name or return empty string.

Source code in trestle/core/control_interface.py
@staticmethod
def get_prop(item: TypeWithProps, prop_name: str, default: Optional[str] = None) -> str:
    """Get the property with that name or return empty string."""
    for prop in as_list(item.props):
        if prop.name.strip().lower() == prop_name.strip().lower():
            return prop.value.strip()
    return default if default else ''
get_rule_list_for_imp_req(imp_req) staticmethod ¤

Get the list of rules applying to an imp_req as two lists.

Source code in trestle/core/control_interface.py
@staticmethod
def get_rule_list_for_imp_req(
    imp_req: ossp.ImplementedRequirement
) -> Tuple[List[str], List[str], List[common.Property]]:
    """Get the list of rules applying to an imp_req as two lists."""
    comp_rules, rule_props = ControlInterface.get_rule_list_for_item(imp_req)
    statement_rules = set()
    for statement in as_list(imp_req.statements):
        stat_rules, statement_props = ControlInterface.get_rule_list_for_item(statement)
        statement_rules.update(stat_rules)
        rule_props.extend(statement_props)
    return comp_rules, sorted(statement_rules), rule_props
get_rule_list_for_item(item) staticmethod ¤

Get the list of rules applying to this item from its top level props.

Source code in trestle/core/control_interface.py
@staticmethod
def get_rule_list_for_item(item: TypeWithProps) -> Tuple[List[str], List[common.Property]]:
    """Get the list of rules applying to this item from its top level props."""
    props = []
    rule_list = []
    for prop in as_list(item.props):
        if prop.name == const.RULE_ID:
            rule_list.append(prop.value)
            props.append(prop)
    return rule_list, props
get_rules_and_params_dict_from_item(item) staticmethod ¤

Get the rule dict and params dict from item with props.

Source code in trestle/core/control_interface.py
@staticmethod
def get_rules_and_params_dict_from_item(
    item: TypeWithProps
) -> Tuple[Dict[str, Dict[str, str]], Dict[str, Dict[str, str]], List[common.Property]]:
    """Get the rule dict and params dict from item with props."""
    rules_dict, rules_props = ControlInterface.get_rules_dict_from_item(item)
    params_dict, params_props = ControlInterface.get_params_dict_from_item(item)
    rules_props.extend(params_props)
    return rules_dict, params_dict, rules_props
get_rules_dict_from_item(item) staticmethod ¤

Get all rules found in this items props.

Source code in trestle/core/control_interface.py
@staticmethod
def get_rules_dict_from_item(item: TypeWithProps) -> Tuple[Dict[str, Dict[str, str]], List[common.Property]]:
    """Get all rules found in this items props."""
    # rules is dict containing rule_id and description
    rules_dict = {}
    name = ''
    desc = ''
    id_ = ''
    rules_props = []
    for prop in as_list(item.props):
        if prop.name == const.RULE_ID:
            name = prop.value
            id_ = prop.remarks
            rules_props.append(prop)
        elif prop.name == const.RULE_DESCRIPTION:
            desc = prop.value
            rules_props.append(prop)
        # grab each pair in case there are multiple pairs
        # then clear and look for new pair
        if name and desc:
            rules_dict[id_] = {'name': name, 'description': desc}
            name = desc = id_ = ''
    return rules_dict, rules_props
get_section(control, skip_section_list) staticmethod ¤

Get sections that are not in the list.

Source code in trestle/core/control_interface.py
@staticmethod
def get_section(control: cat.Control, skip_section_list: List[str]) -> Tuple[str, str, str, str]:
    """Get sections that are not in the list."""
    id_, name, title = ControlInterface._find_section(control, skip_section_list)
    if id_:
        return id_, name, title, ControlInterface.get_control_section_prose(control, name)
    return '', '', '', ''
get_set_params_from_item(item) staticmethod ¤

Get set params that have values from control implementation or imp req.

Source code in trestle/core/control_interface.py
@staticmethod
def get_set_params_from_item(
    item: Union[comp.ControlImplementation, comp.ImplementedRequirement]
) -> Dict[str, comp.SetParameter]:
    """Get set params that have values from control implementation or imp req."""
    return {
        set_param.param_id: set_param
        for set_param in as_filtered_list(item.set_parameters, lambda i: i.values)
    }
get_sort_id(control, allow_none=False) staticmethod ¤

Get the sort-id for the control.

Source code in trestle/core/control_interface.py
@staticmethod
def get_sort_id(control: cat.Control, allow_none=False) -> Optional[str]:
    """Get the sort-id for the control."""
    for prop in as_list(control.props):
        if prop.name == const.SORT_ID:
            return prop.value.strip()
    return None if allow_none else control.id
get_statement_id(control) staticmethod ¤

Find the statement id in the control.

Source code in trestle/core/control_interface.py
@staticmethod
def get_statement_id(control: cat.Control) -> str:
    """Find the statement id in the control."""
    for part in as_list(control.parts):
        if part.name == const.STATEMENT:
            return part.id
    return ControlInterface.create_statement_id(control.id)
get_status_from_props(item) staticmethod ¤

Get the status of an item from its props.

Source code in trestle/core/control_interface.py
@staticmethod
def get_status_from_props(item: TypeWithProps) -> common.ImplementationStatus:
    """Get the status of an item from its props."""
    status = common.ImplementationStatus(state=const.STATUS_PLANNED)
    for prop in as_list(item.props):
        if prop.name == const.IMPLEMENTATION_STATUS:
            status = ControlInterface._prop_as_status(prop)
            break
    return status
insert_imp_req_into_component(component, new_imp_req, profile_title, trestle_root) staticmethod ¤

Insert imp req into component by matching source title and control id to existing imp req.

Parameters:

Name Type Description Default
component comp.DefinedComponent

The defined component receiving the imp_req

required
new_imp_req comp.ImplementedRequirement

The new imp_req being added

required
profile_title str

The title of the source profile for the control implementation containing the imp_req

required

Notes

Inserts the imp_req on the first match found. Note it is possible two control implementations could have the same source and specify the same control

Source code in trestle/core/control_interface.py
@staticmethod
def insert_imp_req_into_component(
    component: comp.DefinedComponent,
    new_imp_req: comp.ImplementedRequirement,
    profile_title: str,
    trestle_root: pathlib.Path
) -> None:
    """
    Insert imp req into component by matching source title and control id to existing imp req.

    Args:
        component: The defined component receiving the imp_req
        new_imp_req: The new imp_req being added
        profile_title: The title of the source profile for the control implementation containing the imp_req

    Notes:
        Inserts the imp_req on the first match found.  Note it is possible two control implementations could
        have the same source and specify the same control
    """
    for control_imp in as_list(component.control_implementations):
        _, control_imp_param_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(control_imp)
        control_imp_rule_param_ids = [
            param['name'] for params in control_imp_param_dict.values() for param in params
        ]
        if profile_title != ModelUtils.get_title_from_model_uri(trestle_root, control_imp.source):
            continue
        for imp_req in as_list(control_imp.implemented_requirements):
            if imp_req.control_id != new_imp_req.control_id:
                continue
            _, imp_req_param_dict, _ = ControlInterface.get_rules_and_params_dict_from_item(imp_req)
            imp_req_rule_param_ids = [param['name'] for params in imp_req_param_dict.values() for param in params]
            status = ControlInterface.get_status_from_props(new_imp_req)
            ControlInterface.insert_status_in_props(imp_req, status)
            imp_req.description = new_imp_req.description
            statement_dict = {stat.statement_id: stat for stat in as_list(imp_req.statements)}
            # update set parameter values with values from markdown - but only for rule param vals
            for set_param in as_list(new_imp_req.set_parameters):
                if set_param.param_id not in (control_imp_rule_param_ids + imp_req_rule_param_ids):
                    continue
                found = False
                for dest_param in as_list(imp_req.set_parameters):
                    if dest_param.param_id != set_param.param_id:
                        continue
                    dest_param.values = set_param.values
                    found = True
                    break
                # if rule parameter val was not already set by a set_param, make new set_param for it
                if found:
                    continue
                # but first check if the parameter was already set with the same value in the control_imp
                # if so we don't need to insert a new set_param in imp_req
                for dest_param in as_list(control_imp.set_parameters):
                    if dest_param.param_id != set_param.param_id:
                        continue
                    if dest_param.values == set_param.values:
                        found = True
                        break
                if found:
                    continue
                imp_req.set_parameters = as_list(imp_req.set_parameters)
                imp_req.set_parameters.append(
                    comp.SetParameter(param_id=set_param.param_id, values=set_param.values)
                )
            new_statements: List[comp.Statement] = []
            for statement in as_list(new_imp_req.statements):
                # get the original version of the statement if available, or use new one
                stat = statement_dict.get(statement.statement_id, statement)
                # update the description and status from markdown
                stat.description = statement.description
                ControlInterface._copy_status_in_props(stat, statement)
                new_statements.append(stat)
            imp_req.statements = none_if_empty(new_statements)
            return
    logger.warning(
        f'Unable to add imp req for component {component.title} control {new_imp_req.control_id} and source: {profile_title}'  # noqa E501
    )
insert_status_in_props(item, status) staticmethod ¤

Insert status content into props of the item.

Source code in trestle/core/control_interface.py
@staticmethod
def insert_status_in_props(item: TypeWithProps, status: common.ImplementationStatus) -> None:
    """Insert status content into props of the item."""
    prop = ControlInterface._status_as_prop(status)
    ControlInterface._replace_prop(item, prop)
is_withdrawn(control) staticmethod ¤

Determine if control is marked Withdrawn.

Parameters:

Name Type Description Default
control cat.Control

The control that may be marked withdrawn.

required

Returns:

Type Description
bool

True if marked withdrawn, false otherwise.

This is determined by property with name 'status' with value 'Withdrawn'.

Source code in trestle/core/control_interface.py
@staticmethod
def is_withdrawn(control: cat.Control) -> bool:
    """
    Determine if control is marked Withdrawn.

    Args:
        control: The control that may be marked withdrawn.

    Returns:
        True if marked withdrawn, false otherwise.

    This is determined by property with name 'status' with value 'Withdrawn'.
    """
    for _ in as_filtered_list(
            control.props,
            lambda p: strip_lower_equals(p.name, 'status') and strip_lower_equals(p.value, 'withdrawn')):
        return True
    return False
item_has_rules(item) staticmethod ¤

Determine if the item has rules in its props.

Source code in trestle/core/control_interface.py
@staticmethod
def item_has_rules(item: TypeWithProps) -> bool:
    """Determine if the item has rules in its props."""
    _, rules_props = ControlInterface.get_rules_dict_from_item(item)
    return bool(rules_props)
merge_dicts_deep(dest, src, overwrite_header_values, depth=0, level=0) staticmethod ¤

Merge dict src into dest.

New items are always added from src to dest. Items present in both will be overriden dest if overwrite_header_values is True.

Source code in trestle/core/control_interface.py
@staticmethod
def merge_dicts_deep(
    dest: Dict[Any, Any],
    src: Dict[Any, Any],
    overwrite_header_values: bool,
    depth: int = 0,
    level: int = 0
) -> None:
    """
    Merge dict src into dest.

    New items are always added from src to dest.
    Items present in both will be overriden dest if overwrite_header_values is True.
    """
    for key in src.keys():
        if key in dest:
            if depth and level == depth:
                if overwrite_header_values:
                    dest[key] = src[key]
                continue
            # if they are both dicts, recurse
            if isinstance(dest[key], dict) and isinstance(src[key], dict):
                ControlInterface.merge_dicts_deep(dest[key], src[key], overwrite_header_values, depth, level + 1)
            # if they are both lists, add any item that is not already in the list
            elif isinstance(dest[key], list) and isinstance(src[key], list):
                for item in src[key]:
                    if item not in dest[key]:
                        dest[key].append(item)
            # otherwise override dest if needed
            elif overwrite_header_values:
                dest[key] = src[key]
        else:
            # if the item was not already in dest, add it from src
            dest[key] = src[key]
merge_part(dest, src) staticmethod ¤

Merge a source part into the destination part.

Source code in trestle/core/control_interface.py
@staticmethod
def merge_part(dest: common.Part, src: common.Part) -> common.Part:
    """Merge a source part into the destination part."""
    dest.name = src.name if src.name else dest.name
    dest.ns = src.ns if src.ns else dest.ns
    dest.props = none_if_empty(ControlInterface.merge_props(dest.props, src.props))
    dest.prose = src.prose
    dest.title = src.title if src.title else dest.title
    ControlInterface.merge_parts(dest, src)
    return dest
merge_parts(dest, src) staticmethod ¤

Merge the parts from the source into the destination.

Source code in trestle/core/control_interface.py
@staticmethod
def merge_parts(dest: TypeWithParts, src: TypeWithParts) -> None:
    """Merge the parts from the source into the destination."""
    if not dest.parts:
        dest.parts = src.parts
    elif not src.parts:
        dest.parts = None
    else:
        new_parts: List[common.Part] = []
        dest_map = {part.id: part for part in dest.parts}
        for src_part in src.parts:
            dest_part = dest_map.get(src_part.id, None)
            if not dest_part:
                new_parts.append(src_part)
            else:
                new_part = ControlInterface.merge_part(dest_part, src_part)
                if new_part:
                    new_parts.append(new_part)
        dest.parts = new_parts
merge_props(dest, src) staticmethod ¤

Merge a source list of properties into a destination list.

Source code in trestle/core/control_interface.py
@staticmethod
def merge_props(dest: Optional[List[common.Property]],
                src: Optional[List[common.Property]]) -> List[common.Property]:
    """Merge a source list of properties into a destination list."""
    if not src:
        return dest
    new_props: List[common.Property] = []
    src_map = {prop.name: prop for prop in src}
    dest_map = {prop.name: prop for prop in dest}
    all_names = set(src_map.keys()).union(dest_map.keys())
    for name in sorted(all_names):
        if name in src_map and name not in dest_map:
            new_props.append(src_map[name])
        elif name in dest_map and name not in src_map:
            new_props.append(dest_map[name])
        else:
            new_prop = dest_map[name]
            src_prop = src_map[name]
            new_prop.class_ = src_prop.class_ if src_prop.class_ else new_prop.class_
            new_prop.ns = src_prop.ns if src_prop.ns else new_prop.ns
            new_prop.remarks = src_prop.remarks if src_prop.remarks else new_prop.remarks
            new_prop.uuid = src_prop.uuid if src_prop.uuid else new_prop.uuid
            new_prop.value = src_prop.value
            new_props.append(new_prop)
    return new_props
param_to_str(param, param_rep, verbose=False, brackets=False, params_format=None, value_assigned_prefix=None, value_not_assigned_prefix=None) staticmethod ¤

Convert parameter to string based on best available representation.

Parameters:

Name Type Description Default
param common.Parameter

the parameter to convert

required
param_rep ParameterRep

how to represent the parameter

required
verbose bool

provide verbose text for selection choices

False
brackets bool

add brackets around the lists of items

False
params_format Optional[str]

a string containing a single dot that represents a form of highlighting around the param

None
value_assigned_prefix Optional[str]

string to place before the parameter string if a value was assigned

None
value_not_assigned_prefix Optional[str]

string to place before the parameter string if value not assigned

None

Returns:

Type Description
Optional[str]

formatted string or None

Source code in trestle/core/control_interface.py
@staticmethod
def param_to_str(
    param: common.Parameter,
    param_rep: ParameterRep,
    verbose: bool = False,
    brackets: bool = False,
    params_format: Optional[str] = None,
    value_assigned_prefix: Optional[str] = None,
    value_not_assigned_prefix: Optional[str] = None
) -> Optional[str]:
    """
    Convert parameter to string based on best available representation.

    Args:
        param: the parameter to convert
        param_rep: how to represent the parameter
        verbose: provide verbose text for selection choices
        brackets: add brackets around the lists of items
        params_format: a string containing a single dot that represents a form of highlighting around the param
        value_assigned_prefix: string to place before the parameter string if a value was assigned
        value_not_assigned_prefix: string to place before the parameter string if value not assigned

    Returns:
        formatted string or None
    """
    param_str = None
    if param_rep == ParameterRep.VALUE_OR_STRING_NONE:
        param_str = ControlInterface._param_values_as_str(param)
        param_str = param_str if param_str else 'None'
    elif param_rep == ParameterRep.LABEL_OR_CHOICES:
        param_str = ControlInterface._param_label_choices_as_str(param, verbose, brackets)
    elif param_rep == ParameterRep.VALUE_OR_LABEL_OR_CHOICES:
        param_str = ControlInterface._param_values_as_str(param)
        if not param_str:
            param_str = ControlInterface._param_label_choices_as_str(param, verbose, brackets)
    elif param_rep == ParameterRep.VALUE_OR_EMPTY_STRING:
        param_str = ControlInterface._param_values_as_str(param, brackets)
        if not param_str:
            param_str = ''
    elif param_rep == ParameterRep.ASSIGNMENT_FORM:
        param_str = ControlInterface._param_values_assignment_str(
            param, value_assigned_prefix, value_not_assigned_prefix
        )
        if not param_str:
            param_str = ''
    elif param_rep == ParameterRep.LABEL_FORM:
        param_str = ControlInterface._param_labels_assignment_str(param, value_not_assigned_prefix)
        if not param_str:
            param_str = ''
    return ControlInterface._apply_params_format(param_str, params_format)
replace_control_prose(control, param_dict, params_format=None, param_rep=<ParameterRep.VALUE_OR_LABEL_OR_CHOICES: 3>, show_value_warnings=False, value_assigned_prefix=None, value_not_assigned_prefix=None) staticmethod ¤

Replace the control prose according to set_param.

Source code in trestle/core/control_interface.py
@staticmethod
def replace_control_prose(
    control: cat.Control,
    param_dict: Dict[str, common.Parameter],
    params_format: Optional[str] = None,
    param_rep: ParameterRep = ParameterRep.VALUE_OR_LABEL_OR_CHOICES,
    show_value_warnings: bool = False,
    value_assigned_prefix: Optional[str] = None,
    value_not_assigned_prefix: Optional[str] = None
) -> None:
    """Replace the control prose according to set_param."""
    # first replace all choices that reference parameters
    # note that in ASSIGNMENT_FORM each choice with a parameter will end up as [Assignment: value]
    for param in as_list(control.params):
        ControlInterface._replace_param_choices(
            param,
            param_dict,
            params_format,
            param_rep,
            show_value_warnings,
            value_assigned_prefix,
            value_not_assigned_prefix
        )
    for part in as_list(control.parts):
        if part.prose is not None:
            fixed_prose = ControlInterface._replace_params(
                part.prose,
                param_dict,
                params_format,
                param_rep,
                show_value_warnings,
                value_assigned_prefix,
                value_not_assigned_prefix
            )
            # change the prose in the control itself
            part.prose = fixed_prose
        for prt in as_list(part.parts):
            ControlInterface._replace_part_prose(
                control,
                prt,
                param_dict,
                params_format,
                param_rep,
                show_value_warnings,
                value_assigned_prefix,
                value_not_assigned_prefix
            )
setparam_to_param(param_id, set_param) staticmethod ¤

Convert setparameter to parameter.

Parameters:

Name Type Description Default
param_id str

the id of the parameter

required
set_param prof.SetParameter

the set_parameter from a profile

required

Returns:

Type Description
common.Parameter

a Parameter with param_id and content from the SetParameter

Source code in trestle/core/control_interface.py
@staticmethod
def setparam_to_param(param_id: str, set_param: prof.SetParameter) -> common.Parameter:
    """
    Convert setparameter to parameter.

    Args:
        param_id: the id of the parameter
        set_param: the set_parameter from a profile

    Returns:
        a Parameter with param_id and content from the SetParameter
    """
    return common.Parameter(
        id=param_id, values=set_param.values, select=set_param.select, label=set_param.label, props=set_param.props
    )
strip_to_make_ncname(label) staticmethod ¤

Strip chars to conform with NCNAME regex.

Source code in trestle/core/control_interface.py
@staticmethod
def strip_to_make_ncname(label: str) -> str:
    """Strip chars to conform with NCNAME regex."""
    orig_label = label
    # make sure first char is allowed
    while label and label[0] not in const.NCNAME_UTF8_FIRST_CHAR_OPTIONS:
        label = label[1:]
    new_label = label[:1]
    # now check remaining chars
    if len(label) > 1:
        for ii in range(1, len(label)):
            if label[ii] in const.NCNAME_UTF8_OTHER_CHAR_OPTIONS:
                new_label += label[ii]
    # do final check to confirm it is NCNAME
    match = re.search(const.NCNAME_REGEX, new_label)
    if not match:
        raise TrestleError(f'Unable to convert label {orig_label} to NCNAME format.')
    return new_label
uniquify_set_params(set_params) staticmethod ¤

Remove items with same param_id with priority to later items.

Source code in trestle/core/control_interface.py
@staticmethod
def uniquify_set_params(set_params: Optional[List[TypeWithParamId]]) -> List[TypeWithParamId]:
    """Remove items with same param_id with priority to later items."""
    found_ids: Set[str] = set()
    unique_list: List[TypeWithParamId] = []
    for set_param in reversed(as_list(set_params)):
        if set_param.param_id not in found_ids:
            unique_list.append(set_param)
            found_ids.add(set_param.param_id)
    return list(reversed(unique_list))

ParameterRep (Enum) ¤

Enum for ways to represent a parameter.

Source code in trestle/core/control_interface.py
class ParameterRep(Enum):
    """Enum for ways to represent a parameter."""

    LEAVE_MOUSTACHE = 0
    VALUE_OR_STRING_NONE = 1
    LABEL_OR_CHOICES = 2
    VALUE_OR_LABEL_OR_CHOICES = 3
    VALUE_OR_EMPTY_STRING = 4
    ASSIGNMENT_FORM = 5
    LABEL_FORM = 6
ASSIGNMENT_FORM ¤
LABEL_FORM ¤
LABEL_OR_CHOICES ¤
LEAVE_MOUSTACHE ¤
VALUE_OR_EMPTY_STRING ¤
VALUE_OR_LABEL_OR_CHOICES ¤
VALUE_OR_STRING_NONE ¤

PartInfo dataclass ¤

Class to capture control part info needed in markdown.

Source code in trestle/core/control_interface.py
@dataclass
class PartInfo:
    """Class to capture control part info needed in markdown."""

    name: str
    prose: str
    smt_part: str = ''
    props: Optional[List[common.Property]] = None
    parts: Optional[List[PartInfo]] = None

    def to_dicts(self, part_id_map: Dict[str, str]) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
        """Convert the part info to a dict or list of props."""
        prop_list = []
        part = {}
        # if it has a part name then it is a part with prose
        if self.name:
            part['name'] = part_id_map.get(self.name, self.name)
            if self.prose:
                part['prose'] = self.prose
            if self.parts:
                all_subparts = []
                for subpart in self.parts:
                    subpart_dict, _ = subpart.to_dicts(part_id_map)
                    all_subparts.append(subpart_dict)
                part['parts'] = all_subparts

        # otherwise it is a list of props
        else:
            for prop in as_list(self.props):
                prop_d = {'name': prop.name, 'value': prop.value}
                if prop.ns:
                    prop_d['ns'] = str(prop.ns)
                if self.smt_part:
                    prop_d['smt-part'] = part_id_map.get(self.smt_part, self.smt_part)
                prop_list.append(prop_d)
        return part, prop_list
name: str dataclass-field ¤
parts: Optional[List[trestle.core.control_interface.PartInfo]] dataclass-field ¤
props: Optional[List[trestle.oscal.common.Property]] dataclass-field ¤
prose: str dataclass-field ¤
smt_part: str dataclass-field ¤
Methods¤
__eq__(self, other) special ¤
__init__(self, name, prose, smt_part='', props=None, parts=None) special ¤
__repr__(self) special ¤
to_dicts(self, part_id_map) ¤

Convert the part info to a dict or list of props.

Source code in trestle/core/control_interface.py
def to_dicts(self, part_id_map: Dict[str, str]) -> Tuple[Dict[str, Any], List[Dict[str, Any]]]:
    """Convert the part info to a dict or list of props."""
    prop_list = []
    part = {}
    # if it has a part name then it is a part with prose
    if self.name:
        part['name'] = part_id_map.get(self.name, self.name)
        if self.prose:
            part['prose'] = self.prose
        if self.parts:
            all_subparts = []
            for subpart in self.parts:
                subpart_dict, _ = subpart.to_dicts(part_id_map)
                all_subparts.append(subpart_dict)
            part['parts'] = all_subparts

    # otherwise it is a list of props
    else:
        for prop in as_list(self.props):
            prop_d = {'name': prop.name, 'value': prop.value}
            if prop.ns:
                prop_d['ns'] = str(prop.ns)
            if self.smt_part:
                prop_d['smt-part'] = part_id_map.get(self.smt_part, self.smt_part)
            prop_list.append(prop_d)
    return part, prop_list

handler: python