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
def testCopyCommonField_notPresentInBothMessages_raisesException(self): """Tests copy_common_field with an invalid descriptor raises.""" first_patient = patient_pb2.Patient( active=datatypes_pb2.Boolean(value=True)) second_patient = patient_pb2.Patient( active=datatypes_pb2.Boolean(value=False)) with self.assertRaises(ValueError) as ve: proto_utils.copy_common_field(first_patient, second_patient, "value") self.assertIsInstance(ve.exception, ValueError)
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 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)
def testCopyCommonField_differentMessageTypes_succeeds(self): """Tests that copy_common_field succeeds on a single Message field.""" string_value = datatypes_pb2.String(id=datatypes_pb2.String(value="foo")) boolean_value = datatypes_pb2.Boolean(id=datatypes_pb2.String(value="bar")) # Before copy self.assertEqual(string_value.id.value, "foo") self.assertEqual(boolean_value.id.value, "bar") proto_utils.copy_common_field(string_value, boolean_value, "id") # After copy self.assertEqual(string_value.id.value, "foo") self.assertEqual(boolean_value.id.value, "foo")
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 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 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 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 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