def validate_primitive_without_value(fhir_primitive: message.Message): """Validates a Message which has the PrimitiveWithoutValue extension. Given that there is a PrimitiveWithoutValue extension present, there must be at least one other extension. Otherwise, there is truly no value set other than id and/or extension (non-value fields). Args: fhir_primitive: The FHIR primitive Message to validate. Raises: fhir_errors.InvalidFhirError: In the event that there is less than one extension present, or there are values set other than id and/or extension. """ name = fhir_primitive.DESCRIPTOR.full_name # There must be at least one other FHIR extension if len(extensions.get_fhir_extensions(fhir_primitive)) < 2: raise fhir_errors.InvalidFhirError( f'{name!r} must have either extensions or a value present.') # ... Else truly no value set other than id and/or extension for field, _ in fhir_primitive.ListFields(): # Iterate set fields only if field.name not in extensions.NON_VALUE_FIELDS: raise fhir_errors.InvalidFhirError( f'{name!r} contains PrimitiveHasNoValue but {field.name!r} is set.' )
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 _GetMethodUrlAndPathParamsNames( self, handler_name: str, args: message.Message, ) -> Tuple[reflection_pb2.ApiMethod, str, Iterable[str]]: path_params = {} # Dict[str, Union[int, str]] if args: for field, value in args.ListFields(): if self.handlers_map.is_endpoint_expecting( handler_name, field.name): path_params[ field.name] = self._CoerceValueToQueryStringType( field, value) url = self.urls.build(handler_name, path_params, force_external=True) method = None for rule in self.handlers_map.iter_rules(): if rule.endpoint == handler_name: method = [m for m in rule.methods if m != "HEAD"][0] if not method: raise RuntimeError("Can't find method for %s" % handler_name) return method, url, list(path_params.keys())
def collect_formatted_strings( proto: message.Message, field_modifier: Callable[[str, str, 'options_pb2.StringFormat.V'], str] = lambda s, p, f: s, path: Tuple[str, ...] = ()) \ -> Iterator[Tuple[str, str, 'options_pb2.StringFormat.V']]: """Iterate recursively through all fields of a proto message to find fields with specific string formats. Args: - proto, the proto message from which to collect strings - field_modifier, a function to modify fields in place in the proto: it is given a string, its path in the proto and its string_format, and should produce another string. If a field in the proto has several string_formats, the modifier is applied once for each string_format, in the order in which they were declared. Default is no-op. - path, a tuple to prepend to all output paths (needed for recursion). Yields: Triples (collected_string, path_in_object, string_format): - collected_string, a string found in a (possibly repeated) field in the proto - path, the path where the string can be found in the proto, as a dot-separated string. For repeated fields, the index is provided. - string_format an options_pb2.StringFormat value that is declared on this field. """ for field_descriptor, value in proto.ListFields(): next_path = path + (field_descriptor.name, ) this_string_formats = \ field_descriptor.GetOptions().Extensions[options_pb2.string_format] if field_descriptor.type == field_descriptor.TYPE_MESSAGE: if field_descriptor.label == field_descriptor.LABEL_REPEATED: try: value.items() # TODO(cyrille): Deal with map types. logging.warning('Ignoring a map field: %s', '.'.join(next_path)) continue except TypeError: pass for index, repeated_value in enumerate(value): yield from collect_formatted_strings( repeated_value, field_modifier, next_path + (str(index), )) else: yield from collect_formatted_strings(value, field_modifier, next_path) continue for string_format in this_string_formats: if field_descriptor.label == field_descriptor.LABEL_REPEATED: for index, repeated_value in enumerate(value): to_yield = repeated_value, '.'.join( next_path + (str(index), )), string_format value[index] = field_modifier(*to_yield) yield to_yield else: to_yield = value, '.'.join(next_path), string_format updated_value = field_modifier(*to_yield) setattr(proto, field_descriptor.name, updated_value) yield to_yield # Pass the updated value to the next iteration of the this_string_formats loop. value = updated_value
def _iter_sensitive_fields(msg: message.Message, field_usages_to_yield: Set[int]) \ -> Iterator[Tuple[message.Message, descriptor.FieldDescriptor]]: """Iterate recursively through all fields of a proto message to find sensitive fields.""" for field_descriptor, value in msg.ListFields(): extensions = field_descriptor.GetOptions().Extensions field_usage = extensions[options_pb2.field_usage] if field_usage in field_usages_to_yield: yield msg, field_descriptor continue # Timestamp are always considered as sensitive. if field_descriptor.message_type == timestamp_pb2.Timestamp.DESCRIPTOR: yield msg, field_descriptor continue if field_descriptor.type == field_descriptor.TYPE_MESSAGE: if hasattr(value, 'DESCRIPTOR'): for field in _iter_sensitive_fields(value, field_usages_to_yield): yield field else: for repeated_value in value: if not hasattr(repeated_value, 'DESCRIPTOR'): # value is actually a ScalarMap. break for field in _iter_sensitive_fields( repeated_value, field_usages_to_yield): yield field
def substitute_runtime_parameter( msg: message.Message, parameter_bindings: Mapping[str, types.Property]) -> None: """Utility function to substitute runtime parameter placeholders with values. Args: msg: The original message to change. Only messages defined under pipeline_pb2 will be supported. Other types will result in no-op. parameter_bindings: A dict of parameter keys to parameter values that will be used to substitute the runtime parameter placeholder. Returns: None """ if not isinstance(msg, message.Message): return # If the message is a pipeline_pb2.Value instance, try to find an substitute # with runtime parameter bindings. if isinstance(msg, pipeline_pb2.Value): value = cast(pipeline_pb2.Value, msg) which = value.WhichOneof('value') if which == 'runtime_parameter': real_value = _get_runtime_parameter_value(value.runtime_parameter, parameter_bindings) if real_value is None: return value.Clear() common_utils.set_metadata_value(metadata_value=value.field_value, value=real_value) if which == 'structural_runtime_parameter': real_value = _get_structural_runtime_parameter_value( value.structural_runtime_parameter, parameter_bindings) if real_value is None: return value.Clear() common_utils.set_metadata_value(metadata_value=value.field_value, value=real_value) return # For other cases, recursively call into sub-messages if any. for field, sub_message in msg.ListFields(): # No-op for non-message types. if field.type != descriptor.FieldDescriptor.TYPE_MESSAGE: continue # Evaluates every map values in a map. elif (field.message_type.has_options and field.message_type.GetOptions().map_entry): for key in sub_message: substitute_runtime_parameter(sub_message[key], parameter_bindings) # Evaluates every entry in a list. elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED: for element in sub_message: substitute_runtime_parameter(element, parameter_bindings) # Evaluates sub-message. else: substitute_runtime_parameter(sub_message, parameter_bindings)
def Recurse(msg: message.Message, prev: Tuple[str, ...]) -> None: fields = sorted(msg.ListFields(), key=lambda field: field[0].name) for field, value in fields: curr = prev + (field.name, ) if field.type == descriptor.FieldDescriptor.TYPE_MESSAGE: Recurse(value, curr) else: result[".".join(curr)] = transform(field, value)
def which_has(msg: Message) -> str: """ Like WhichOneof, but for our fake oneofs using "has_" fields. Returns the non-"has_" field name which is set on `msg`. """ # TODO remove this once we can use oneofs in our protos for field_descriptor, value in msg.ListFields(): if not field_descriptor.name.startswith("has_"): continue if value is True: return field_descriptor.name[4:] raise ValueError( "Invalid {} protobuf: none of the has_ values are set.".format( type(msg).__name__ ) )
def _print_contained_resource(self, contained_resource: message.Message): """Prints the set fields of the contained resource. If the _FhirJsonFormat is set to ANALYTIC, this method only prints the url. Args: contained_resource: The contained resource to iterate over and print. """ for (_, set_field_value) in contained_resource.ListFields(): if self.json_format == _FhirJsonFormat.ANALYTIC: structure_definition_url = ( annotation_utils.get_structure_definition_url( set_field_value)) self.generator.push(f'"{structure_definition_url}"') else: # print the entire contained resource... self._print(set_field_value)
def Canonicalize(message: Message): for (fd, v) in message.ListFields(): if fd.label != fd.LABEL_REPEATED: continue # Order of arguments is fixed. if fd.name == 'arguments': continue for item in v: if isinstance(item, Message): Canonicalize(item) if len(v) == 0: continue if isinstance(v[0], Message): v.sort(key=NameOrSelf) else: v.sort()
def encode_message( msg: Message, parents: List[FieldDescriptor] = []) -> Generator[bytes, None, None]: msg_path = [p.number for p in parents] for fd, val in msg.ListFields(): if fd.type == FieldDescriptor.TYPE_MESSAGE: for d in encode_message(val, parents + [fd]): yield d elif fd.type == FieldDescriptor.TYPE_GROUP: raise NotImplementedError() else: path = msg_path + [fd.number] if fd.label == FieldDescriptor.LABEL_REPEATED: for i, v in enumerate(val): yield encode_scalar(path + [i], fd, v) else: yield encode_scalar(path, fd, val)
def from_message(msg: message.Message, components: Optional[List[Text]] = None) -> pd.DataFrame: """Converts protobuf message to a dataframe. Args: msg: Protobuf message to convert. components: Prefixes for column names. Returns: Pandas dataframe representing given message. """ if components is None: components = [] data = {} for desc, value in msg.ListFields(): if isinstance(value, message.Message): data.update(from_message(value, components + [desc.name])) else: data.update(_get_pretty_value(value, desc, components)) return pd.DataFrame(data=data)
def _(message: Message) -> dict: exprs = getattr(message, 'exprs', None) if exprs is not None: return [expr_to_obj(expr) for expr in to_dict(exprs)] return {f[0].name: to_dict(f[1]) for f in message.ListFields()}