Ejemplo n.º 1
0
  def testSetInParentOrAdd_withSingularPrimitive_raises(self):
    """Test set_in_parent_or_add with singular proto primitive."""
    boolean = datatypes_pb2.Boolean()
    with self.assertRaises(ValueError) as ve:
      proto_utils.set_in_parent_or_add(boolean, "value")

    self.assertIsInstance(ve.exception, ValueError)
Ejemplo n.º 2
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)
Ejemplo n.º 3
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)
Ejemplo n.º 4
0
  def testSetInParentOrAdd_withSingularComposite_returnsMessage(self):
    """Test set_in_parent_or_add with a singlular composite field."""
    patient = patient_pb2.Patient()
    self.assertFalse(proto_utils.field_is_set(patient, "active"))

    active_set_in_parent = proto_utils.set_in_parent_or_add(patient, "active")
    self.assertTrue(proto_utils.field_is_set(patient, "active"))
    self.assertFalse(active_set_in_parent.value)
    self.assertFalse(patient.active.value)

    active_set_in_parent.value = True
    self.assertTrue(active_set_in_parent.value)
    self.assertTrue(patient.active.value)
Ejemplo n.º 5
0
  def testSetInParentOrAdd_withRepeatedComposite_returnsMessage(self):
    """Test set_in_parent_or_add with repeated composite field."""
    patient = patient_pb2.Patient()
    self.assertFalse(proto_utils.field_is_set(patient, "name"))

    name_set_in_parent = proto_utils.set_in_parent_or_add(patient, "name")
    self.assertTrue(proto_utils.field_is_set(patient, "name"))
    self.assertEmpty(name_set_in_parent.text.value)
    self.assertEmpty(patient.name[0].text.value)

    name_set_in_parent.text.value = "Foo"
    self.assertEqual(name_set_in_parent.text.value, "Foo")
    self.assertEqual(patient.name[0].text.value, "Foo")
Ejemplo n.º 6
0
def _add_fields_to_extension(msg: message.Message, extension: message.Message):
    """Adds the fields from message to extension."""
    for field in msg.DESCRIPTOR.fields:
        _verify_field_is_proto_message_type(field)

        # Add submessages to nested extensions; singular fields have a length of 1
        for i in range(proto_utils.field_content_length(msg, field)):
            child_extension = proto_utils.set_in_parent_or_add(
                extension, 'extension')
            cast(Any,
                 child_extension).url.value = get_inlined_extension_url(field)
            value = proto_utils.get_value_at_field_index(msg, field, i)
            _add_value_to_extension(
                value, child_extension,
                annotation_utils.is_choice_type_field(field))
Ejemplo n.º 7
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)
Ejemplo n.º 8
0
 def _merge_contained_resource(self, json_value: Dict[str, Any],
                               target: message.Message):
     """Merges json_value into a contained resource field within target."""
     # We handle contained resources in a special way, because despite internally
     # being a oneof, it is not actually a chosen-type in FHIR. The JSON field
     # name is just 'resource', which doesn't give any clues about which field in
     # the oneof to set. Instead, we need to inspect the JSON input to determine
     # its type. Then, merge into that specific fields in the resource oneof.
     resource_type = json_value.get('resourceType')
     contained_field = self._resource_type_mapping.get(resource_type)
     if contained_field is None:
         raise ValueError('Unable to retrieve ContainedResource field for '
                          f'{resource_type}.')
     contained_message = proto_utils.set_in_parent_or_add(
         target, contained_field)
     self._merge_message(json_value, contained_message)
Ejemplo n.º 9
0
    def _merge_choice_field(self, json_value: Any,
                            choice_field: descriptor.FieldDescriptor,
                            field_name: str, parent: message.Message):
        """Creates a Message based on the choice_field Descriptor and json_value.

    The resulting message is merged into parent.

    Args:
      json_value: The JSON value to merge into a message of the type described
        by choice_field.
      choice_field: The field descriptor of the FHIR choice type on parent.
      field_name: The nested field name of the choice type, e.g.: _valueBoolean.
      parent: The parent Message to merge into.
    """
        choice_field_name = _get_choice_field_name(choice_field, field_name)
        choice_field_map = _get_field_map(choice_field.message_type)
        choice_value_field = choice_field_map.get(choice_field_name)
        if choice_value_field is None:
            raise ValueError(f'Cannot find {choice_field_name!r} on '
                             f'{choice_field.full_name}')

        choice_message = proto_utils.set_in_parent_or_add(parent, choice_field)
        self._merge_field(json_value, choice_value_field, choice_message)
Ejemplo n.º 10
0
    def _merge_field(self, json_value: Any, field: descriptor.FieldDescriptor,
                     parent: message.Message):
        """Merges the json_value into the provided field of the parent Message.

    Args:
      json_value: The JSON value to set.
      field: The FieldDescriptor of the field to set in parent.
      parent: The parent Message to set the value on.

    Raises:
      ValueError: In the event that a non-primitive field has already been set.
      ValueError: In the event that a oneof field has already been set.
    """
        # If the field is non-primitive, ensure that it hasn't been set yet. Note
        # that we allow primitive types to be set already, because FHIR represents
        # extensions to primitives as separate, subsequent JSON elements, with the
        # field prepended by an underscore
        if (not annotation_utils.is_primitive_type(field.message_type)
                and proto_utils.field_is_set(parent, field)):
            raise ValueError('Target field {} is already set.'.format(
                field.full_name))

        # Consider it an error if a oneof field is already set.
        #
        # Exception: When a primitive in a choice type has a value and an extension,
        # it will get set twice, once by the value (e.g. valueString) and once by an
        # extension (e.g., _valueString)
        if field.containing_oneof is not None:
            oneof_field = parent.DESCRIPTOR.oneofs_by_name[
                field.containing_oneof.name]
            if (annotation_utils.is_primitive_type(field.message_type)
                    and oneof_field.full_name == field.full_name):
                raise ValueError(
                    'Cannot set field {} since oneof field {} is already set.'.
                    format(field.full_name, oneof_field.full_name))

        # Ensure that repeated fields get proper list assignment
        existing_field_size = proto_utils.field_content_length(parent, field)
        if proto_utils.field_is_repeated(field):
            if not isinstance(json_value, list):
                raise ValueError(
                    f'Attempted to merge a repeated field, {field.name}, a json_value '
                    f'with type {type(json_value)} instead of a list.')

            if existing_field_size != 0 and existing_field_size != len(
                    json_value):
                raise ValueError(
                    'Repeated primitive list length does not match extension list for '
                    'field: {field.full_name!r}.')

        # Set the JSON values, taking care to clear the PRIMITIVE_HAS_NO_VALUE_URL
        # if we've already visited the field before.
        json_value = (json_value if proto_utils.field_is_repeated(field) else
                      [json_value])
        for (i, value) in enumerate(json_value):
            parsed_value = self._parse_field_value(field, value)
            if existing_field_size > 0:
                field_value = proto_utils.get_value_at_field_index(
                    parent, field, i)
                field_value.MergeFrom(parsed_value)
                extensions.clear_fhir_extensions_with_url(
                    field_value, extensions.PRIMITIVE_HAS_NO_VALUE_URL)
            else:
                field_value = proto_utils.set_in_parent_or_add(parent, field)
                field_value.MergeFrom(parsed_value)
Ejemplo n.º 11
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)
Ejemplo n.º 12
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)