def get_value_for_annotation_extension( message_or_descriptor: proto_utils.MessageOrDescriptorBase, extension_field: descriptor.FieldDescriptor) -> Optional[Any]: """Returns the value associated with the annotation extension field. Args: message_or_descriptor: A protobuf DescriptorProto, Message, or DescriptorBase to examine. extension_field: A FieldDescriptor describing the annotation whose value to retrieve. Returns: The value associated with extension_field, if one exists. Otherwise returns None. Raises: ValueError: Unable to retrieve Options for type: <type>. """ options = get_options(message_or_descriptor) if proto_utils.field_is_repeated(extension_field): return options.Extensions[extension_field] if options.HasExtension(extension_field): return options.Extensions[extension_field] return None
def _print_message_field(self, field_name: str, field: descriptor.FieldDescriptor, value: Any): """Prints singular and repeated fields from a message.""" self.generator.add_field(field_name) if proto_utils.field_is_repeated(field): self._print_list(cast(List[Any], value), self._print) else: self._print(cast(message.Message, value))
def _print_primitive_field(self, field_name: str, field: descriptor.FieldDescriptor, value: Any) -> None: """Prints the primitive field. Args: field_name: The name of the field. field: The FielDescriptor whose contents to print. value: The value present at field to print. """ if proto_utils.field_is_repeated(field): string_values = [] elements = [] extensions_found = False nonnull_values_found = False for primitive in value: wrapper = self.primitive_handler.primitive_wrapper_from_primitive( primitive) string_values.append(wrapper.json_value()) elements.append(wrapper.get_element()) nonnull_values_found = nonnull_values_found or wrapper.has_value( ) extensions_found = extensions_found or wrapper.has_element() # print string primitive representations if nonnull_values_found: self.generator.add_field(field_name) self._print_list(string_values, self.generator.push) # print Element representations if extensions_found: if nonnull_values_found: self.generator.push(',') self.generator.add_newline() self.generator.add_field(f'_{field_name}') self._print_list(elements, self._print) else: # Singular field # TODO: Detect ReferenceId using an annotation if (self.json_format == _FhirJsonFormat.ANALYTIC and field.message_type.name == 'ReferenceId'): str_value = proto_utils.get_value_at_field(value, 'value') self.generator.add_field(field_name, f'"{str_value}"') else: # Wrap and print primitive value and (optionally), its element wrapper = self.primitive_handler.primitive_wrapper_from_primitive( value) if wrapper.has_value(): self.generator.add_field(field_name, wrapper.json_value()) if wrapper.has_element( ) and self.json_format == _FhirJsonFormat.PURE: if wrapper.has_value(): self.generator.push(',') self.generator.add_newline() self.generator.add_field(f'_{field_name}') self._print(wrapper.get_element())
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 _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)
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)