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")
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])
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)
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)
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)
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)
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)
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)
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)
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 _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)
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.')
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)
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)
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