def _set_handled_properties_on_asset(properties, asset):  # pylint: disable=invalid-name
    '''
    Extracts all handled properties from the bag (except for identification, asset class and self/external calibration)
    and inserts them in the properties list of the asset.
    '''
    handled_properties = []

    # These could technically be handled by the _set_unhandled_properties_on_asset,
    # but in the future we should be able to easily add some parsing validation to
    # these so that only string values are allowed, not nested JSON objects, which
    # is a valid case for all unhandled properties.
    _append_property_if_exists(properties, handled_properties,
                               apm_constants.ASSET_CLASSS_KEY)
    _append_property_if_exists(properties, handled_properties,
                               apm_constants.ASSET_NAME_KEY)
    _append_property_if_exists(properties, handled_properties,
                               apm_constants.FIRMWARE_VERSION_KEY)
    _append_property_if_exists(properties, handled_properties,
                               apm_constants.HARDWRE_VERSION_KEY)
    _append_property_if_exists(properties, handled_properties,
                               apm_constants.VISA_RESOURCE_NAME_KEY)

    # This will only be a valid boolean if at least one of SupportsSelfCalibration
    # and SupportsExternalCalibration are valid booleans.
    supports_any_calibration = None

    supports_self_calibration = properties.pop(
        _camelcase(apm_constants.SUPPORTS_SELF_CALIBRATION_KEY), None)
    if _isbool(supports_self_calibration):
        supports_any_calibration = supports_self_calibration
        handled_properties.append(
            apm_messages.Property(apm_constants.SUPPORTS_SELF_CALIBRATION_KEY,
                                  supports_self_calibration))

    supports_external_calibration = properties.pop(
        _camelcase(apm_constants.SUPPORTS_EXTERNAL_CALIBRATION_KEY), None)
    if _isbool(supports_external_calibration):
        supports_any_calibration = supports_any_calibration or supports_external_calibration
        handled_properties.append(
            apm_messages.Property(
                apm_constants.SUPPORTS_EXTERNAL_CALIBRATION_KEY,
                supports_external_calibration))

    if _isbool(supports_any_calibration):
        handled_properties.append(
            apm_messages.Property(
                apm_constants.SUPPORTS_ANY_CALIBRATION_MESSAGE_KEY,
                supports_any_calibration))

    if handled_properties:
        asset.properties.extend(handled_properties)
def _set_self_calibration_properties_on_asset(properties, asset):  # pylint: disable=invalid-name
    '''
    Extracts the self calibration property from the bag and inserts the calibration properties
    in the properties list of the asset.

    Properties include:
    - calibration date
    - limited
    - operator

    If the calibration date is invalid or if it does not exist this is a NoOp.
    '''
    self_calibration_properties = []

    self_calibration = properties.pop(
        _camelcase(apm_constants.SELF_CALIBRATION_KEY), None)
    if not _isdict(self_calibration):
        return

    date_str = self_calibration.pop(
        _camelcase(apm_constants.CALIBARTION_DATE_KEY), None)
    date = _parse_date(date_str)
    if not date:
        return

    self_calibration_properties.append(
        apm_messages.Property(apm_constants.SELF_CALIBRATION_DATE_MESSAGE_KEY,
                              date.isoformat()))

    limited = self_calibration.pop(_camelcase(apm_constants.IS_LIMITED_KEY),
                                   None)
    if _isbool(limited):
        self_calibration_properties.append(
            apm_messages.Property(
                apm_constants.SELF_CALIBRATION_IS_LIMITED_MESSAGE_KEY,
                limited))

    _append_operator_properties_if_exists(
        self_calibration, self_calibration_properties,
        apm_constants.SELF_CALIBRATION_OPERATOR_DISPLAY_NAME_MESSAGE_KEY,
        apm_constants.SELF_CALIBRATION_OPERATOR_USER_ID_MESSAGE_KEY)

    asset.properties.extend(self_calibration_properties)
def _append_property_with_message_key_if_exists(properties, result_properties,
                                                property_key,
                                                property_message_key):  # pylint: disable=invalid-name
    '''
    Extracts the property with the given key from the bag and appends it to the result properties list
    with the message key.

    If the key does not exists this is a NoOp.
    '''
    property_value = properties.pop(_camelcase(property_key), None)
    if property_value:
        result_properties.append(
            apm_messages.Property(property_message_key,
                                  _to_str(property_value)))
def _set_unhandled_properties_on_asset(properties, asset):  # pylint: disable=invalid-name
    '''
    Takes all properties from the bag and inserts them in the properties list of the asset.
    '''
    unhandled_properties = []

    _remove_reserved_properties(properties)

    for property_key in properties:
        # This can't be a pop(), because it will raise a `dictionary changed size during iteration` exception
        property_value = properties.get(property_key, None)
        unhandled_properties.append(
            apm_messages.Property(_pascalcase(property_key),
                                  _to_str(property_value)))

    asset.properties.extend(unhandled_properties)
def _set_external_calibration_properties_on_asset(properties, asset):  # pylint: disable=invalid-name
    '''
    Extracts the external calibration property from the bag and inserts the calibration properties
    in the properties list of the asset.

    Properties include:
    - calibration date
    - next recommended date
    - recommended interval
    - limited
    - comments
    - checksum
    - operator

    If either one of the calibration date, recommended interval or next recommended date
    are invalid or if they do not exist in the bag this is a NoOp.
    '''
    external_calibration_properties = []

    external_calibration = properties.pop(
        _camelcase(apm_constants.EXTERNAL_CALIBRATION_KEY), None)
    if not _isdict(external_calibration):
        return

    date_str = external_calibration.pop(
        _camelcase(apm_constants.CALIBARTION_DATE_KEY), None)
    date = _parse_date(date_str)
    interval = external_calibration.pop(
        _camelcase(apm_constants.RECOMMENDED_INTERVAL_KEY), None)
    if not date or not _is_interval_valid(interval):
        return

    next_date = date + relativedelta(months=+interval)

    external_calibration_properties.extend([
        apm_messages.Property(
            apm_constants.EXTERNAL_CALIBRATION_DATE_MESSAGE_KEY,
            date.isoformat()),
        apm_messages.Property(
            apm_constants.
            EXTERNAL_CALIBRATION_RECOMMENDED_INTERVAL_MESSAGE_KEY,
            _to_str(interval)),
        apm_messages.Property(
            apm_constants.
            EXTERNAL_CALIBRATION_NEXT_RECOMMENDED_DATE_MESSAGE_KEY,
            next_date.isoformat())
    ])

    _append_property_with_message_key_if_exists(
        external_calibration, external_calibration_properties,
        apm_constants.COMMENTS_KEY,
        apm_constants.EXTERNAL_CALIBRATION_COMMENTS_MESSAGE_KEY)

    _append_property_with_message_key_if_exists(
        external_calibration, external_calibration_properties,
        apm_constants.CHECKSUM_KEY,
        apm_constants.EXTERNAL_CALIBRATION_CHECKSUM_MESSAGE_KEY)

    limited = external_calibration.pop(
        _camelcase(apm_constants.IS_LIMITED_KEY), None)
    if _isbool(limited):
        external_calibration_properties.append(
            apm_messages.Property(
                apm_constants.EXTERNAL_CALIBRATION_IS_LIMITED_MESSAGE_KEY,
                limited))

    supports_limited = external_calibration.pop(
        _camelcase(apm_constants.SUPPORTS_LIMITED_KEY), None)
    if _isbool(supports_limited):
        external_calibration_properties.append(
            apm_messages.Property(
                apm_constants.
                EXTERNAL_CALIBRATION_SUPPORTS_LIMITED_MESSAGE_KEY,
                supports_limited))

    supports_write = external_calibration.pop(
        _camelcase(apm_constants.SUPPORTS_WRITE_KEY), None)
    if _isbool(supports_write):
        external_calibration_properties.append(
            apm_messages.Property(
                apm_constants.EXTERNAL_CALIBRATION_SUPPORTS_WRITE_MESSAGE_KEY,
                supports_write))

    _append_operator_properties_if_exists(
        external_calibration, external_calibration_properties,
        apm_constants.EXTERNAL_CALIBRATION_OPERATOR_DISPLAY_NAME_MESSAGE_KEY,
        apm_constants.EXTERNAL_CALIBRATION_OPERATOR_USER_ID_MESSAGE_KEY)

    asset.properties.extend(external_calibration_properties)