Beispiel #1
0
def reference_to_string(reference: message.Message) -> str:
    """Returns a reference URI for a typed reference message."""
    _validate_reference(reference)

    # Early-exit if URI or fragment is set
    if proto_utils.field_is_set(reference, 'uri'):
        uri = proto_utils.get_value_at_field(reference, 'uri')
        return uri.value
    elif proto_utils.field_is_set(reference, 'fragment'):
        fragment = proto_utils.get_value_at_field(reference, 'fragment')
        return f'#{fragment.value}'

    set_oneof = reference.WhichOneof('reference')
    if set_oneof is None:
        raise ValueError(
            f'Reference is not set on: {reference.DESCRIPTOR.name}.')

    # Convert to CamelCase
    prefix = path_utils.snake_case_to_camel_case(set_oneof, upper=True)

    # Cull trailing 'Id'
    if prefix.endswith('Id'):
        prefix = prefix[:-2]

    reference_id = proto_utils.get_value_at_field(reference, set_oneof)
    reference_string = f'{prefix}/{reference_id.value}'
    if proto_utils.field_is_set(reference_id, 'history'):
        reference_string += f'/_history/{reference_id.history.value}'
    return reference_string
Beispiel #2
0
def _validate_period(period: message.Message, base_name: str):
    """Validates that a timelike period has a valid start and end value."""
    if not (proto_utils.field_is_set(period, 'start')
            and proto_utils.field_is_set(period, 'end')):
        return  # Early exit; either start or end field is not present

    # Check whether start time is greater than end time. Note that, if it is,
    # that's not necessarily invalid, since the precisions can be different. So we
    # need to compare the end time at the upper bound of the end element.
    #
    # Example: If the start time is "Tuesday at noon", and the end time is "some
    # time Tuesday", this is valid even though the timestamp used for "some time
    # Tuesday" is Tuesday 00:00, since the precision for the start is higher than
    # the end.
    #
    # Also note the GetUpperBoundFromTimelikeElement is always greater than the
    # time itself by exactly one time unit, and hence start needs to be strictly
    # less than the upper bound of end, so as not to allow ranges like [Tuesday,
    # Monday] to be valid.
    start: message.Message = proto_utils.get_value_at_field(period, 'start')
    end: message.Message = proto_utils.get_value_at_field(period, 'end')
    end_precision = proto_utils.get_value_at_field(end, 'precision')

    start_dt_value = _primitive_time_utils.get_date_time_value(start)
    end_upper_bound = _primitive_time_utils.get_upper_bound(
        end, _primitive_time_utils.DateTimePrecision(end_precision))
    if start_dt_value >= end_upper_bound:
        raise fhir_errors.InvalidFhirError(
            f'`{base_name}` start time is later than end time.')
Beispiel #3
0
def add_message_to_extension(msg: message.Message,
                             extension: message.Message) -> None:
    """Adds the contents of msg to extension.

  Args:
    msg: A FHIR profile of Extension, whose contents should be added to the
      generic extension.
    extension: The generic Extension to populate.
  """
    if not fhir_types.is_profile_of_extension(msg):
        raise ValueError(f'Message: {msg.DESCRIPTOR.full_name} is not a valid '
                         'FHIR Extension profile.')

    if not fhir_types.is_extension(extension):
        raise ValueError(
            f'Extension: {extension.DESCRIPTOR.full_name} is not a '
            'valid FHIR Extension.')

    cast(Any,
         extension).url.value = annotation_utils.get_structure_definition_url(
             msg.DESCRIPTOR)

    # Copy over the id field if present
    if proto_utils.field_is_set(msg, 'id'):
        proto_utils.copy_common_field(msg, extension, 'id')

    # Copy the vlaue fields from message into the extension
    value_fields = [
        field for field in msg.DESCRIPTOR.fields
        if field.name not in NON_VALUE_FIELDS
    ]
    if not value_fields:
        raise ValueError(
            f'Extension has no value fields: {msg.DESCRIPTOR.name}.')

    # Add fields to the extension. If there is a single value field, a simple
    # value assignment will suffice. Otherwise, we need to loop over all fields
    # and add them as child extensions
    if (len(value_fields) == 1
            and not proto_utils.field_is_repeated(value_fields[0])):
        value_field = value_fields[0]
        _verify_field_is_proto_message_type(value_field)
        if proto_utils.field_is_set(msg, value_field):
            value = proto_utils.get_value_at_field(msg, value_field)
            _add_value_to_extension(
                value, extension,
                annotation_utils.is_choice_type_field(value_field))
        else:
            # TODO: Invalid FHIR; throw an error here?
            pass
    else:  # Add child extensions...
        _add_fields_to_extension(msg, extension)
Beispiel #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)
Beispiel #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")
    def testSplitIfRelativeReference_withFragmentReference_succeeds(self):
        ref = datatypes_pb2.Reference(uri=datatypes_pb2.String(value='#org-1'))

        uri_field = ref.DESCRIPTOR.fields_by_name['uri']
        fragment_field = ref.DESCRIPTOR.fields_by_name['fragment']
        self.assertTrue(proto_utils.field_is_set(ref, uri_field))
        self.assertFalse(proto_utils.field_is_set(ref, fragment_field))

        references.split_if_relative_reference(ref)

        self.assertFalse(proto_utils.field_is_set(ref, uri_field))
        self.assertTrue(proto_utils.field_is_set(ref, fragment_field))
        self.assertEqual(proto_utils.get_value_at_field(ref, fragment_field),
                         datatypes_pb2.String(value='org-1'))
 def get_element(self) -> Optional[message.Message]:
     """Return Element of the primitive. Xhtml cannot have extensions."""
     if not proto_utils.field_is_set(self.wrapped, 'id'):
         return None
     element = type(self.wrapped)()
     proto_utils.copy_common_field(self.wrapped, element, 'id')
     return element
Beispiel #8
0
def _validate_field(msg: message.Message, field: descriptor.FieldDescriptor,
                    field_name: str,
                    primitive_handler_: primitive_handler.PrimitiveHandler):
    """Validates that required fields are set, and performs basic temporal checks.

  Args:
    msg: The Message that the field belongs to.
    field: The FieldDescriptor of the field to examine.
    field_name: The name of the field.
    primitive_handler_: Responsible for returning PrimitiveWrappers.

  Raises:
    fhir_errors.InvalidFhirError: In the event that a required field is not set
    or if temporal requirements are not met.
  """
    if annotation_utils.field_is_required(
            field) and not proto_utils.field_is_set(msg, field):
        raise fhir_errors.InvalidFhirError(
            f'Required field `{field.full_name}` is missing.')

    if annotation_utils.is_reference(field.message_type):
        _validate_reference_field(msg, field)
        return

    if field.type == descriptor.FieldDescriptor.TYPE_MESSAGE:
        # Returns the first value only for a singular field
        for i in range(proto_utils.field_content_length(msg, field)):
            submessage = proto_utils.get_value_at_field_index(msg, field, i)
            _validate_fhir_constraints(submessage, field_name,
                                       primitive_handler_)

            # Run extra validation for some types, until FHIRPath validation covers
            # these as well
            if fhir_types.is_period(submessage):
                _validate_period(submessage, field_name)
Beispiel #9
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.')
  def testSplitIfRelativeReference_withRelativeReference_succeeds(self):
    ref = datatypes_pb2.Reference(
        uri=datatypes_pb2.String(value='Practitioner/example'),
        display=datatypes_pb2.String(value='Dr Adam Careful'))

    uri_field = ref.DESCRIPTOR.fields_by_name['uri']
    practitioner_id_field = ref.DESCRIPTOR.fields_by_name['practitioner_id']
    self.assertTrue(proto_utils.field_is_set(ref, uri_field))
    self.assertFalse(proto_utils.field_is_set(ref, practitioner_id_field))

    references.split_if_relative_reference(ref)

    self.assertFalse(proto_utils.field_is_set(ref, uri_field))
    self.assertTrue(proto_utils.field_is_set(ref, practitioner_id_field))
    self.assertEqual(
        proto_utils.get_value_at_field(ref, practitioner_id_field),
        datatypes_pb2.ReferenceId(value='example'))
Beispiel #11
0
  def get_element(self) -> Optional[message.Message]:
    """Returns the raw Element underlying the wrapped primitive.

    Note that conversion-only extensions are removed prior to returning.
    """
    if not (proto_utils.field_is_set(self.wrapped, 'id') or
            proto_utils.field_is_set(self.wrapped, 'extension')):
      return None  # Early-exit if we can't populate an Element

    element = proto_utils.create_message_from_descriptor(
        self.wrapped.DESCRIPTOR)
    if proto_utils.field_is_set(self.wrapped, 'id'):
      proto_utils.copy_common_field(self.wrapped, element, 'id')

    extensions_list = cast(List[Any],
                           extensions.get_fhir_extensions(self.wrapped))
    for extension in extensions_list:
      if extension.url.value not in extensions.CONVERSION_ONLY_EXTENSION_URLS:
        proto_utils.append_value_at_field(element, 'extension', extension)

    return element
    def has_element(self) -> bool:
        """Returns True if the wrapped primitive has data in its `Element` fields.

    This method checks whether or not the wrapped primitive contains data in
    fields defined on the root Element type, like `id` or `extension`.
    """
        if proto_utils.field_is_set(self.wrapped, 'id'):
            return True

        # Return True if there exists an extension on the wrapped primitive whose
        # structure definition URL is something other than a conversion-only URL
        extensions_list = cast(List[Any],
                               extensions.get_fhir_extensions(self.wrapped))
        for extension in extensions_list:
            if extension.url.value not in extensions.CONVERSION_ONLY_EXTENSION_URLS:
                return True
        return False
Beispiel #13
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)
Beispiel #14
0
 def testFieldIsSet_withUnsetField_returnsFalse(self):
   """Test field_is_set with an unset field."""
   default_patient = patient_pb2.Patient()  # Leave active unset
   self.assertFalse(proto_utils.field_is_set(default_patient, "active"))
Beispiel #15
0
 def testFieldIsSet_withSetField_returnsTrue(self):
   """Test field_is_set with a set field."""
   patient = patient_pb2.Patient(active=datatypes_pb2.Boolean(value=True))
   self.assertTrue(proto_utils.field_is_set(patient, "active"))
 def has_element(self) -> bool:
     """Returns True if the Xhtml primitive has an 'id' field."""
     return proto_utils.field_is_set(self.wrapped, 'id')
Beispiel #17
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)
Beispiel #18
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