Exemple #1
0
 def testIsPrimitiveType_withPrimitives_returnsTrue(self):
     """Test is_primitive_type functionality on primitive input."""
     boolean = datatypes_pb2.Boolean()
     code = datatypes_pb2.Code()
     self.assertTrue(annotation_utils.is_primitive_type(boolean))
     self.assertTrue(annotation_utils.is_primitive_type(boolean.DESCRIPTOR))
     self.assertTrue(annotation_utils.is_primitive_type(code))
     self.assertTrue(annotation_utils.is_primitive_type(code.DESCRIPTOR))
Exemple #2
0
 def testIsPrimitiveType_withPatient_returnsFalse(self):
   """Test is_primitive_type functionality on non-primitive input."""
   patient = patient_pb2.Patient()
   patient_descriptor_proto = self._descriptor_proto_for_descriptor(
       patient.DESCRIPTOR)
   self.assertFalse(annotation_utils.is_primitive_type(patient))
   self.assertFalse(annotation_utils.is_primitive_type(patient.DESCRIPTOR))
   self.assertFalse(
       annotation_utils.is_primitive_type(patient_descriptor_proto))
Exemple #3
0
    def _print_message(self, msg: message.Message) -> None:
        """Prints the representation of message."""
        self.generator.open_json_object()

        # Add the resource type preamble if necessary
        set_fields = msg.ListFields()
        if (annotation_utils.is_resource(msg)
                and self.json_format == _FhirJsonFormat.PURE):
            self.generator.add_field('resourceType',
                                     f'"{msg.DESCRIPTOR.name}"')
            if set_fields:
                self.generator.push(',')
                self.generator.add_newline()

        # Print all fields
        for (i, (set_field, value)) in enumerate(set_fields):
            if (annotation_utils.is_choice_type_field(set_field)
                    and self.json_format == _FhirJsonFormat.PURE):
                self._print_choice_field(set_field.json_name, set_field, value)
            elif annotation_utils.is_primitive_type(set_field.message_type):
                self._print_primitive_field(set_field.json_name, set_field,
                                            value)
            else:
                self._print_message_field(set_field.json_name, set_field,
                                          value)

            if i < (len(set_fields) - 1):
                self.generator.push(',')
                self.generator.add_newline()

        self.generator.close_json_object()
Exemple #4
0
    def _print_choice_field(self, field_name: str,
                            field: descriptor.FieldDescriptor,
                            choice_container: message.Message) -> None:
        """Prints a FHIR choice field.

    This field is expected to have one valid oneof set.

    Args:
      field_name: The name of the field.
      field: The FieldDescriptor whose contents to print.
      choice_container: The value present at field, which should be a oneof with
        a single value set.
    """
        if len(choice_container.DESCRIPTOR.oneofs) != 1:
            raise ValueError(f'Invalid value for choice field {field_name}: '
                             f'{choice_container}.')
        oneof_group = choice_container.DESCRIPTOR.oneofs[0]
        set_oneof_name = choice_container.WhichOneof(oneof_group.name)
        if set_oneof_name is None:
            raise ValueError('Oneof not set on choice type: '
                             f'{choice_container.DESCRIPTOR.full_name}.')
        value_field = choice_container.DESCRIPTOR.fields_by_name[
            set_oneof_name]
        oneof_field_name = value_field.json_name
        oneof_field_name = oneof_field_name[0].upper() + oneof_field_name[1:]

        value = proto_utils.get_value_at_field(choice_container, value_field)
        if annotation_utils.is_primitive_type(value_field.message_type):
            self._print_primitive_field(field_name + oneof_field_name,
                                        value_field, value)
        else:
            self._print_message_field(field_name + oneof_field_name,
                                      value_field, value)
def _is_only_primitive(name: str,
                       msg_descriptor: descriptor.Descriptor) -> bool:
    """Returns if the message specified is only a primitive and nothing else."""
    return (
        annotation_utils.is_primitive_type(msg_descriptor)
        # TODO: Remove "ReferenceId" handling once ReferenceId
        # is no longer (erroneously) marked as a primitive.
        and name != 'ReferenceId'
        # STU3 profiles of codes are marked as primitive types.
        and not fhir_types.is_profile_of_code(msg_descriptor))
Exemple #6
0
    def print(self, msg: message.Message) -> str:
        """Returns the contents of message as a FHIR JSON string."""
        self.generator.clear()

        # If provided a primitive message, simply wrap and return the JSON value
        if annotation_utils.is_primitive_type(msg):
            wrapper = self.primitive_handler.primitive_wrapper_from_primitive(
                msg)
            return wrapper.json_value()

        self._print(msg)
        return self.generator.dump()
Exemple #7
0
    def merge_value(self, json_value: Any, target: message.Message):
        """Merges the provided json_value into the target Message.

    Args:
      json_value: A Python-native representation of JSON data.
      target: The target Message to merge the JSON data into.
    """
        target_descriptor = target.DESCRIPTOR
        if annotation_utils.is_primitive_type(target_descriptor):
            if isinstance(json_value, dict):
                # This is a primitive type extension. Merge the extension fields into
                # the empty target proto, and tag it as having no value.
                self._merge_message(json_value, target)

                extension_field = target_descriptor.fields_by_name.get(
                    'extension')
                if extension_field is None:
                    raise ValueError(
                        "Invalid primitive. No 'extension' field exists on "
                        f"{target_descriptor.full_name}.")
                primitive_has_no_value = extensions.create_primitive_has_no_value(
                    extension_field.message_type)
                proto_utils.append_value_at_field(target, extension_field,
                                                  primitive_has_no_value)
            else:
                wrapper = self.primitive_handler.primitive_wrapper_from_json_value(
                    json_value,
                    type(target),
                    default_timezone=self.default_timezone)
                wrapper.merge_into(target)
        elif annotation_utils.is_reference(target_descriptor):
            self._merge_message(json_value, target)
            references.split_if_relative_reference(target)
        else:
            if isinstance(json_value, dict):
                # The provided value must be another FHIR element
                self._merge_message(json_value, target)
            elif isinstance(json_value,
                            (tuple, list)) and len(json_value) == 1:
                # Check if the target field is non-repeated, and we're trying to
                # populate it with a single-element array. This is considered valid, and
                # occurs when a profiled resource reduces the size of a repeated FHIR
                # field to a maximum of 1.
                self._merge_message(json_value[0], target)
            else:
                raise ValueError(
                    'Expected a JSON object for field of type: {}.'.format(
                        target_descriptor.full_name))
  def primitive_wrapper_from_primitive(
      self,
      primitive_message: message.Message,
  ) -> _primitive_wrappers.PrimitiveWrapper:
    """See json_format PrimitiveHandler.primitive_wrapper_from_primitive."""
    if not annotation_utils.is_primitive_type(primitive_message):
      raise ValueError(
          f'Not a primitive type: {primitive_message.DESCRIPTOR.full_name!r}.')

    wrapper_cls = self.get_primitive_wrapper_cls_for_primitive_cls(
        type(primitive_message))
    wrapper_context = _primitive_wrappers.Context(
        separator_stride_cls=fhirproto_extensions_pb2
        .Base64BinarySeparatorStride,
        code_cls=self.code_cls,
        default_timezone=_primitive_time_utils.SIMPLE_ZULU)
    return wrapper_cls.from_primitive(primitive_message, wrapper_context)
Exemple #9
0
def _get_field_map(
        desc: descriptor.Descriptor) -> Dict[str, descriptor.FieldDescriptor]:
    """Returns a mapping between field name and FieldDescriptor.

  Note that for FHIR "Choice" types, field names of primitive extensions are
  prepended with a leading underscore, e.g.: value + _boolean = _valueBoolean.

  Args:
    desc: The Descriptor of the Message whose mapping to return.

  Returns:
    A mapping between the field name and its corresponding FieldDescriptor.
  """
    # Early exit if we've already constructed the field mapping
    with _field_map_memos_cv:
        if desc in _field_map_memos:
            return _field_map_memos[desc]

    # Build field mapping
    field_map = {}
    for field in desc.fields:
        if (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE
                and annotation_utils.is_choice_type_field(field)):
            inner_map = _get_field_map(field.message_type)
            for (child_field_name, _) in inner_map.items():
                choice_field_name = _get_nested_choice_field_name(
                    field, child_field_name)
                field_map[choice_field_name] = field
        else:
            field_map[field.json_name] = field

            # FHIR JSON represents extensions to primitive fields as separate
            # standalone JSON objects, keyed by '_' + field_name
            if (field.type == descriptor.FieldDescriptor.TYPE_MESSAGE and
                    annotation_utils.is_primitive_type(field.message_type)):
                field_map['_' + field.json_name] = field

    # Cache result
    with _field_map_memos_cv:
        _field_map_memos[desc] = field_map
        return _field_map_memos[desc]
  def primitive_wrapper_from_json_value(
      self,
      json_value: Optional[Any],
      primitive_cls: Type[message.Message],
      *,
      default_timezone: str = _primitive_time_utils.SIMPLE_ZULU
  ) -> _primitive_wrappers.PrimitiveWrapper:
    """See json_format PrimitiveHandler.primitive_wrapper_from_json_value."""
    if not annotation_utils.is_primitive_type(primitive_cls.DESCRIPTOR):
      raise ValueError(
          f'Not a primitive type: {primitive_cls.DESCRIPTOR.full_name!r}.')

    wrapper_cls = self.get_primitive_wrapper_cls_for_primitive_cls(
        primitive_cls)
    wrapper_context = _primitive_wrappers.Context(
        separator_stride_cls=fhirproto_extensions_pb2
        .Base64BinarySeparatorStride,
        code_cls=self.code_cls,
        default_timezone=default_timezone)
    return wrapper_cls.from_json_value(json_value, primitive_cls,
                                       wrapper_context)
Exemple #11
0
def _validate_fhir_constraints(
        msg: message.Message, base_name: str,
        primitive_handler_: primitive_handler.PrimitiveHandler):
    """Iterates over fields of the provided message and validates constraints.

  Args:
    msg: The message to validate.
    base_name: The root message name for recursive validation of nested message
      fields.
    primitive_handler_: Responsible for returning PrimitiveWrappers.

  Raises:
    fhir_errors.InvalidFhirError: In the event that a field is found to be
    violating FHIR constraints or a required oneof is not set.
  """
    if annotation_utils.is_primitive_type(msg):
        # Validation is implicitly done on the primitive type during wrapping
        _ = primitive_handler_.primitive_wrapper_from_primitive(msg)
        return

    if proto_utils.is_message_type(msg, any_pb2.Any):
        # We do not validate "Any" constrained resources.
        # TODO: Potentially unpack the correct type and validate?
        return

    # Enumerate and validate fields of the message
    for field in msg.DESCRIPTOR.fields:
        field_name = f'{base_name}.{field.json_name}'
        _validate_field(msg, field, field_name, primitive_handler_)

    # Also verify that oneof fields are set. Note that optional choice-types
    # should have the containing message unset - if the containing message is set,
    # it should have a value set as well
    for oneof in msg.DESCRIPTOR.oneofs:
        if (msg.WhichOneof(oneof.name) is None
                and not oneof.GetOptions().HasExtension(
                    annotations_pb2.fhir_oneof_is_optional)):
            raise fhir_errors.InvalidFhirError(
                f'Empty oneof: `{oneof.full_name}`.')
Exemple #12
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)