Esempio n. 1
0
  def testSetValueAtField_singlePrimitiveValue_setsValue(self):
    """Test set_value_at_field with a singular primitive type."""
    arbitrary_string = datatypes_pb2.String(value="foo")

    self.assertEqual(arbitrary_string.value, "foo")
    proto_utils.set_value_at_field(arbitrary_string, "value", "bar")
    self.assertEqual(arbitrary_string.value, "bar")
Esempio n. 2
0
  def testSetValueAtField_singleCompositeValue_setsValue(self):
    """Test set_value_at_field with a singular compositie type."""
    patient = patient_pb2.Patient(active=datatypes_pb2.Boolean(value=False))

    self.assertFalse(patient.active.value)
    proto_utils.set_value_at_field(patient, "active",
                                   datatypes_pb2.Boolean(value=True))
    self.assertTrue(patient.active.value)
 def _set_primitive_has_no_value_extension(
         self, primitive: message.Message) -> None:
     """Sets the PrimitiveHasNoValue FHIR extension on the primitive."""
     extensions_field = primitive.DESCRIPTOR.fields_by_name['extension']
     primitive_has_no_value = extensions.create_primitive_has_no_value(
         extensions_field.message_type)
     proto_utils.set_value_at_field(primitive, 'extension',
                                    [primitive_has_no_value])
Esempio n. 4
0
def clear_fhir_extensions_with_url(msg: message.Message, url: str):
    """Removes FHIR extensions that have the provided url."""
    fhir_extensions = get_fhir_extensions(msg)
    updated_fhir_extensions = [
        extension for extension in fhir_extensions
        if cast(Any, extension).url.value != url
    ]
    proto_utils.set_value_at_field(msg, 'extension', updated_fhir_extensions)
Esempio n. 5
0
def copy_coding(source: message.Message, target: message.Message):
    """Copies all fields from source to target "Coding" messages.

  Args:
    source: The FHIR coding instance to copy from.
    target: The FHIR coding instance to copy to.

  Raises:
    InvalidFhirError: In the event that source or target is not a type/profile
    of Coding.
  """
    if not fhir_types.is_type_or_profile_of_coding(source.DESCRIPTOR):
        raise fhir_errors.InvalidFhirError(
            f'Source: {source.DESCRIPTOR.full_name} '
            'is not a type or profile of Coding.')

    if not fhir_types.is_type_or_profile_of_coding(target.DESCRIPTOR):
        raise fhir_errors.InvalidFhirError(
            f'Target: {target.DESCRIPTOR.full_name} '
            'is not a type or profile of Coding.')

    if proto_utils.are_same_message_type(source.DESCRIPTOR, target.DESCRIPTOR):
        target.CopyFrom(source)
        return

    # Copy fields present in both profiled and unprofiled codings.
    proto_utils.copy_common_field(source, target, 'id')
    proto_utils.copy_common_field(source, target, 'extension')
    proto_utils.copy_common_field(source, target, 'version')
    proto_utils.copy_common_field(source, target, 'display')
    proto_utils.copy_common_field(source, target, 'user_selected')

    # Copy the "code" field from source to target
    source_code = proto_utils.get_value_at_field(source, 'code')
    copy_code(source_code, proto_utils.set_in_parent_or_add(target, 'code'))

    target_system_field = target.DESCRIPTOR.fields_by_name.get('system')

    # TODO: This will fail if there is a target system field,
    # *and* a source system field, since in this case the source code will not
    # contain the system information, the containing Coding would.  In general,
    # it's not quite right to get the system from Code, since unprofiled codes
    # don't contain system information.  In practice, this isn't a problem,
    # because the only kind of profiled Codings we currently support are
    # Codings with typed Codes (which contain source information) but this is
    # not neccessary according to FHIR spec.
    if target_system_field is not None:
        source_system_str = get_system_for_code(source_code)
        target_system_uri = proto_utils.set_in_parent_or_add(
            target, target_system_field)
        proto_utils.set_value_at_field(target_system_uri, 'value',
                                       source_system_str)
Esempio n. 6
0
def populate_typed_reference_id(reference_id: message.Message, resource_id: str,
                                version: Optional[str]):
  """Sets the resource_id and optionally, version, on the reference."""
  reference_id_value_field = reference_id.DESCRIPTOR.fields_by_name['value']
  proto_utils.set_value_at_field(reference_id, reference_id_value_field,
                                 resource_id)
  if version is not None:
    history_field = reference_id.DESCRIPTOR.fields_by_name.get('history')
    if history_field is None:
      raise ValueError('Not a valid ReferenceId message: '
                       f"{reference_id.DESCRIPTOR.full_name}. Field 'history' "
                       'does not exist.')
    history = proto_utils.set_in_parent_or_add(reference_id, history_field)
    history_value_field = history.DESCRIPTOR.fields_by_name['value']
    proto_utils.set_value_at_field(history, history_value_field, version)
Esempio n. 7
0
  def testSetValueAtField_repeatedCompositeValue_setsList(self):
    """Test set_value_at_field with a repeated composite type."""
    old_names = [
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="A")),
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="B")),
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="C")),
    ]
    patient = patient_pb2.Patient(name=old_names)
    self.assertEqual(patient.name[:], old_names)

    new_names = [
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="Foo")),
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="Bar")),
        datatypes_pb2.HumanName(text=datatypes_pb2.String(value="Bats")),
    ]
    proto_utils.set_value_at_field(patient, "name", new_names)
    self.assertEqual(patient.name[:], new_names)
Esempio n. 8
0
def copy_coding(source: message.Message, target: message.Message):
    """Copies all fields from source to target "Coding" messages.

  Args:
    source: The FHIR coding instance to copy from.
    target: The FHIR coding instance to copy to.

  Raises:
    InvalidFhirError: In the event that source or target is not a type/profile
    of Coding.
  """
    if not fhir_types.is_type_or_profile_of_coding(source.DESCRIPTOR):
        raise fhir_errors.InvalidFhirError(
            f'Source: {source.DESCRIPTOR.full_name} '
            'is not a type or profile of Coding.')

    if not fhir_types.is_type_or_profile_of_coding(target.DESCRIPTOR):
        raise fhir_errors.InvalidFhirError(
            f'Target: {target.DESCRIPTOR.full_name} '
            'is not a type or profile of Coding.')

    if proto_utils.are_same_message_type(source.DESCRIPTOR, target.DESCRIPTOR):
        target.CopyFrom(source)
        return

    # Copy fields present in both profiled and unprofiled codings.
    proto_utils.copy_common_field(source, target, 'id')
    proto_utils.copy_common_field(source, target, 'extension')
    proto_utils.copy_common_field(source, target, 'version')
    proto_utils.copy_common_field(source, target, 'display')
    proto_utils.copy_common_field(source, target, 'user_selected')

    # Copy the "code" field from source to target
    source_code = proto_utils.get_value_at_field(source, 'code')
    copy_code(source_code, proto_utils.set_in_parent_or_add(target, 'code'))

    target_system_field = target.DESCRIPTOR.fields_by_name.get('system')
    if target_system_field is not None:
        source_system_str = get_system_for_code(source_code)
        target_system_uri = proto_utils.set_in_parent_or_add(
            target, target_system_field)
        proto_utils.set_value_at_field(target_system_uri, 'value',
                                       source_system_str)
Esempio n. 9
0
def _add_value_to_extension(msg: message.Message, extension: message.Message,
                            is_choice_type: bool):
    """Adds the fields from msg to a generic Extension.

  Attempts are first made to set the "value" field of the generic Extension
  based on the type of field set on message. If this fails, checks are made
  against the generic Code and Coding types, and finally we fall back to adding
  the message's fields as sub-extensions.

  Args:
    msg: The message whose values to add to extension.
    extension: The generic Extension to populate.
    is_choice_type: Whether or not the provided message represents a "choice"
    type.
  """
    if is_choice_type:
        oneofs = msg.DESCRIPTOR.oneofs
        if not oneofs:
            raise fhir_errors.InvalidFhirError(
                f'Choice type is missing a oneof: {msg.DESCRIPTOR.full_name}')
        value_field_name = msg.WhichOneof(oneofs[0].name)
        if value_field_name is None:
            raise ValueError('Choice type has no value set: '
                             f'{msg.DESCRIPTOR.full_name}')
        value_field = msg.DESCRIPTOR.fields_by_name[value_field_name]
        _verify_field_is_proto_message_type(value_field)
        _add_value_to_extension(
            proto_utils.get_value_at_field(msg, value_field), extension, False)
    else:
        # Try to set the message directly as a datatype value on the extension.
        # E.g., put the message of type Boolean into the value.boolean field
        value_field_mapping = _get_value_field_mapping_for_extension(extension)
        value_field = value_field_mapping.get(msg.DESCRIPTOR.full_name)
        if value_field is not None:
            proto_utils.set_value_at_field(
                cast(Any, extension).value, value_field, msg)
        elif annotation_utils.has_fhir_valueset_url(msg):
            codes.copy_code(msg, cast(Any, extension).value.code)
        elif fhir_types.is_type_or_profile_of_coding(msg):
            codes.copy_coding(msg, cast(Any, extension).value.coding)
        else:  # Fall back to adding individual fields as sub-extensions
            _add_fields_to_extension(msg, extension)
Esempio n. 10
0
    def _print_reference(self, reference: message.Message):
        """Standardizes and prints the provided reference.

    Note that "standardization" in this case refers to un-typing the typed-
    reference prior to printing.

    Args:
      reference: The reference to print.
    """
        set_oneof = reference.WhichOneof('reference')
        if set_oneof is None or set_oneof == 'uri':  # Reference is already standard
            self._print_message(reference)
        else:
            new_reference = copy.copy(reference)

            # Setting the new URI field will overwrite the oneof
            new_uri = proto_utils.get_value_at_field(new_reference, 'uri')
            proto_utils.set_value_at_field(
                new_uri, 'value', references.reference_to_string(reference))
            self._print_message(new_reference)
Esempio n. 11
0
def split_if_relative_reference(reference: message.Message):
    """Splits relative references into their components.

  For example, a reference with the uri set to "Patient/ABCD" will be changed
  into a reference with the patientId set to "ABCD".

  Args:
    reference: The FHIR reference to potentially split.
  """
    _validate_reference(reference)
    uri_field = reference.DESCRIPTOR.fields_by_name.get('uri')
    if not proto_utils.field_is_set(reference, uri_field):
        return  # No URI to split

    uri = proto_utils.get_value_at_field(reference, uri_field)

    # We're permissive about various full URL schemes. If we're able to find a
    # match, the URI is valid as-is.
    url_match = re.fullmatch(_URL_REFERENCE_PATTERN, uri.value)
    if url_match is not None:
        return  # URI is valid

    internal_match = re.fullmatch(_INTERNAL_REFERENCE_PATTERN, uri.value)
    if internal_match is not None:
        # Note that we make the reference_id off of the reference before adding it,
        # since adding the reference_id would destroy the uri field, as they are
        # both in the same oneof. This allows us to copy fields from uri to
        # reference_id without making an extra copy.
        reference_id_field = get_reference_id_field_for_resource(
            reference, internal_match.group('resource_type'))
        reference_id = proto_utils.create_message_from_descriptor(
            reference_id_field.message_type)
        populate_typed_reference_id(reference_id,
                                    internal_match.group('resource_id'),
                                    internal_match.group('version'))

        proto_utils.copy_common_field(uri, reference_id, 'id')
        proto_utils.copy_common_field(uri, reference_id, 'extension')
        proto_utils.set_value_at_field(reference, reference_id_field,
                                       reference_id)
        return

    fragment_match = re.fullmatch(_FRAGMENT_REFERENCE_PATTERN, uri.value)
    if fragment_match is not None:
        # Note that we make the fragment off of the reference before adding it,
        # since adding the fragment would destroy the uri field, as they are both in
        # the same oneof. This allows us to copy fields from uri to fragment without
        # making an extra copy.
        fragment_field = reference.DESCRIPTOR.fields_by_name['fragment']
        fragment = proto_utils.create_message_from_descriptor(
            fragment_field.message_type)

        value_field = fragment.DESCRIPTOR.fields_by_name['value']
        proto_utils.set_value_at_field(fragment, value_field, uri.value[1:])
        proto_utils.copy_common_field(uri, fragment, 'id')
        proto_utils.copy_common_field(uri, fragment, 'extension')
        proto_utils.set_value_at_field(reference, fragment_field, fragment)
        return

    raise ValueError(f'String {uri.value!r} cannot be parsed as a reference.')
Esempio n. 12
0
    def _print_reference(self, reference: message.Message) -> None:
        """Standardizes and prints the provided reference.

    Note that "standardization" in the case of PURE FHIR JSON refers to
    un-typing the typed-reference prior to printing.

    Args:
      reference: The reference to print.
    """
        set_oneof = reference.WhichOneof('reference')
        if (self.json_format == _FhirJsonFormat.PURE and set_oneof is not None
                and set_oneof != 'uri'):
            # In pure FHIR mode, we have to serialize structured references
            # into FHIR uri strings.
            standardized_reference = copy.copy(reference)

            # Setting the new URI field will overwrite the original oneof
            new_uri = proto_utils.get_value_at_field(standardized_reference,
                                                     'uri')
            proto_utils.set_value_at_field(
                new_uri, 'value', references.reference_to_string(reference))
            self._print_message(standardized_reference)
        else:
            self._print_message(reference)
Esempio n. 13
0
def copy_code(source: message.Message, target: message.Message):
    """Adds all fields from source to target.

  Args:
    source: The FHIR Code instance to copy from.
    target: The target FHIR Code instance to copy to.
  """
    if not fhir_types.is_type_or_profile_of_code(source.DESCRIPTOR):
        raise fhir_errors.InvalidFhirError(
            f'Source: {source.DESCRIPTOR.full_name} '
            'is not type or profile of Code.')

    if not fhir_types.is_type_or_profile_of_code(target.DESCRIPTOR):
        raise fhir_errors.InvalidFhirError(
            f'Target: {target.DESCRIPTOR.full_name} '
            'is not type or profile of Code.')

    if proto_utils.are_same_message_type(source.DESCRIPTOR, target.DESCRIPTOR):
        target.CopyFrom(source)
        return

    source_value_field = source.DESCRIPTOR.fields_by_name.get('value')
    target_value_field = target.DESCRIPTOR.fields_by_name.get('value')
    if source_value_field is None or target_value_field is None:
        raise fhir_errors.InvalidFhirError(
            'Unable to copy code from '
            f'{source.DESCRIPTOR.full_name} '
            f'to {target.DESCRIPTOR.full_name}.')

    proto_utils.copy_common_field(source, target, 'id')
    proto_utils.copy_common_field(source, target, 'extension')

    # Handle specialized codes
    if (source_value_field.type not in _CODE_TYPES
            or target_value_field.type not in _CODE_TYPES):
        raise ValueError(
            f'Unable to copy from {source.DESCRIPTOR.full_name} '
            f'to {target.DESCRIPTOR.full_name}. Must have a field '
            'of TYPE_ENUM or TYPE_STRING.')

    source_value = proto_utils.get_value_at_field(source, source_value_field)
    if source_value_field.type == target_value_field.type:
        # Perform a simple assignment if value_field types are equivalent
        proto_utils.set_value_at_field(target, target_value_field,
                                       source_value)
    else:
        # Otherwise, we need to transform the value prior to assignment...
        if source_value_field.type == descriptor.FieldDescriptor.TYPE_STRING:
            source_enum_value = code_string_to_enum_value_descriptor(
                source_value, target_value_field.enum_type)
            proto_utils.set_value_at_field(target, target_value_field,
                                           source_enum_value.number)
        elif source_value_field.type == descriptor.FieldDescriptor.TYPE_ENUM:
            source_string_value = enum_value_descriptor_to_code_string(
                source_value_field.enum_type.values_by_number[source_value])
            proto_utils.set_value_at_field(target, target_value_field,
                                           source_string_value)
        else:  # Should never hit
            raise ValueError('Unexpected generic value field type: '
                             f'{source_value_field.type}. Must be a field of '
                             'TYPE_ENUM or TYPE_STRING in order to copy.')
Esempio n. 14
0
def add_extension_to_message(extension: message.Message, msg: message.Message):
    """Recursively parses extension and adds to message.

  Args:
    extension: The FHIR extension to serialize and add.
    msg: The message to add the extension onto

  Raises:
    InvalidFhirError: In the event that a value is set on the extension, but the
    corresponding message field to copy it to is repeated (extension values are
    singular only).
  """
    desc = msg.DESCRIPTOR
    fields_by_url = {
        get_inlined_extension_url(field): field
        for field in desc.fields if field.name != 'id'
    }

    # Copy the id field if present
    id_field = desc.fields_by_name.get('id')
    if proto_utils.field_is_set(extension, id_field):
        proto_utils.set_value_at_field(msg, id_field, cast(Any, extension).id)

    # Handle simple extensions (only one value present)
    if proto_utils.field_is_set(extension, 'value'):
        if len(fields_by_url) != 1:
            raise fhir_errors.InvalidFhirError(
                f'Expected a single field, found {len(fields_by_url)}; '
                f'{desc.full_name} is an invalid extension type.')

        field = list(fields_by_url.items())[0][1]
        if proto_utils.field_is_repeated(field):
            raise fhir_errors.InvalidFhirError(
                f'Expected {field.full_name} to be a singular field. '
                f'{desc.full_name} is an invalid extension type.')
        _add_extension_value_to_message(extension, msg, field)
        return

    # Else, iterate through all child extensions...
    child_extensions = proto_utils.get_value_at_field(extension, 'extension')
    for child_extension in child_extensions:
        field = fields_by_url.get(child_extension.url.value)
        if field is None:
            raise ValueError(f'Message of type: {desc.full_name} has no field '
                             f'with name: {child_extension.url.value}.')

        # Simple value type on child_extension...
        if proto_utils.field_is_set(child_extension, 'value'):
            _add_extension_value_to_message(child_extension, msg, field)
            continue

        # Recurse for nested composite messages...
        if not proto_utils.field_is_repeated(field):
            if proto_utils.field_is_set(msg, field):
                raise ValueError(
                    f'Field: {field.full_name} is already set on message: '
                    f'{desc.full_name}.')

            if proto_utils.field_content_length(child_extension,
                                                'extension') > 1:
                raise ValueError(
                    f'Cardinality mismatch between field: {field.full_name} and '
                    f'extension: {desc.full_name}.')

        child_message = proto_utils.set_in_parent_or_add(msg, field)
        add_extension_to_message(child_extension, child_message)
Esempio n. 15
0
def _add_extension_value_to_message(extension: message.Message,
                                    msg: message.Message,
                                    message_field: descriptor.FieldDescriptor):
    """Serialize the provided extension and add it to the message.

  Args:
    extension: The FHIR extension to serialize.
    msg: The message to add the serialized extension to.
    message_field: The field on the message to set.

  Raises:
    InvalidFhirError: In the event that the field to be set is not a singular
    message type, or if the provided extension is not singular (has nested
    extensions).
  """
    if message_field.type != descriptor.FieldDescriptor.TYPE_MESSAGE:
        raise fhir_errors.InvalidFhirError(
            f'{msg.DESCRIPTOR.full_name} is not a FHIR extension type.')

    extension_field = extension.DESCRIPTOR.fields_by_name['extension']
    if proto_utils.field_content_length(extension, extension_field) > 0:
        raise fhir_errors.InvalidFhirError(
            'No child extensions should be set on '
            f'{extension.DESCRIPTOR.full_name}.')

    value_field = _get_populated_extension_value_field(extension)

    # If a choice type, need to assign the extension value to the correct field.
    if annotation_utils.is_choice_type_field(message_field):
        choice_message = proto_utils.get_value_at_field(msg, message_field)
        choice_descriptor = choice_message.DESCRIPTOR

        for choice_field in choice_descriptor.fields:
            if (value_field.message_type.full_name ==
                    choice_field.message_type.full_name):
                _add_extension_value_to_message(extension, choice_message,
                                                choice_field)
                return

        raise ValueError(
            f'No field on Choice Type {choice_descriptor.full_name} '
            f'for extension {extension.DESCRIPTOR.full_name}.')

    # If the target message is a bound Code type, we need to convert the generic
    # Code field from the extension into the target typed Code.
    if annotation_utils.has_fhir_valueset_url(message_field.message_type):
        typed_code = proto_utils.set_in_parent_or_add(msg, message_field)
        codes.copy_code(cast(Any, extension).value.code, typed_code)
        return

    # If the target message is bound to a Coding type, we must convert the generic
    # Coding field from the extension into the target typed Coding.
    if fhir_types.is_type_or_profile_of_coding(message_field.message_type):
        typed_coding = proto_utils.set_in_parent_or_add(msg, message_field)
        codes.copy_coding(cast(Any, extension).value.coding, typed_coding)
        return

    # Value types must match
    if not proto_utils.are_same_message_type(value_field.message_type,
                                             message_field.message_type):
        raise ValueError(
            'Missing expected value of type '
            f'{message_field.message_type.full_name} in extension '
            f'{extension.DESCRIPTOR.full_name}.')

    value = proto_utils.get_value_at_field(
        cast(Any, extension).value, value_field)
    if proto_utils.field_is_repeated(message_field):
        proto_utils.append_value_at_field(msg, message_field, value)
    else:
        proto_utils.set_value_at_field(msg, message_field, value)
Esempio n. 16
0
def split_if_relative_reference(reference: message.Message):
  """If possible, parses a `Reference` `uri` into more structured fields.

  This is only possible for two forms of reference uris:
  * Relative references of the form $TYPE/$ID, e.g., "Patient/1234"
    In this case, this will be parsed to a proto of the form:
    {patient_id: {value: "1234"}}
  * Fragments of the form "#$FRAGMENT", e.g., "#vs1".  In this case, this would
    be parsed into a proto of the form:
    {fragment: {value: "vs1"} }

  If the reference URI matches one of these schemas, the `uri` field will be
  cleared, and the appropriate structured fields set. Otherwise, the reference
  will be unchanged.

  Args:
    reference: The FHIR reference to potentially split.

  Raises:
    ValueError: If the message is not a valid FHIR Reference proto.
  """
  _validate_reference(reference)
  uri_field = reference.DESCRIPTOR.fields_by_name.get('uri')
  if not proto_utils.field_is_set(reference, uri_field):
    return  # No URI to split

  uri = proto_utils.get_value_at_field(reference, uri_field)

  internal_match = re.fullmatch(_INTERNAL_REFERENCE_PATTERN, uri.value)
  if internal_match is not None:
    # Note that we make the reference_id off of the reference before adding it,
    # since adding the reference_id would destroy the uri field, as they are
    # both in the same oneof. This allows us to copy fields from uri to
    # reference_id without making an extra copy.
    reference_id_field = get_reference_id_field_for_resource(
        reference, internal_match.group('resource_type'))
    reference_id = proto_utils.create_message_from_descriptor(
        reference_id_field.message_type)
    populate_typed_reference_id(reference_id,
                                internal_match.group('resource_id'),
                                internal_match.group('version'))

    proto_utils.copy_common_field(uri, reference_id, 'id')
    proto_utils.copy_common_field(uri, reference_id, 'extension')
    proto_utils.set_value_at_field(reference, reference_id_field, reference_id)
    return

  fragment_match = re.fullmatch(_FRAGMENT_REFERENCE_PATTERN, uri.value)
  if fragment_match is not None:
    # Note that we make the fragment off of the reference before adding it,
    # since adding the fragment would destroy the uri field, as they are both in
    # the same oneof. This allows us to copy fields from uri to fragment without
    # making an extra copy.
    fragment_field = reference.DESCRIPTOR.fields_by_name['fragment']
    fragment = proto_utils.create_message_from_descriptor(
        fragment_field.message_type)

    value_field = fragment.DESCRIPTOR.fields_by_name['value']
    proto_utils.set_value_at_field(fragment, value_field, uri.value[1:])
    proto_utils.copy_common_field(uri, fragment, 'id')
    proto_utils.copy_common_field(uri, fragment, 'extension')
    proto_utils.set_value_at_field(reference, fragment_field, fragment)
    return