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))
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))
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()
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))
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()
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)
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)
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}`.')
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)