docs: async def serialize_properties(inputs: 'Inputs',
                                 property_deps: Dict[str, List['Resource']],
                                 input_transformer: Optional[Callable[[str], str]] = None) -> struct_pb2.Struct:
      """
      Serializes an arbitrary Input bag into a Protobuf structure, keeping track of the list
      of dependent resources in the `deps` list. Serializing properties is inherently async
      because it awaits any futures that are contained transitively within the input bag.
      """
      struct = struct_pb2.Struct()
      for k, v in inputs.items():
          deps = []
          result = await serialize_property(v, deps, input_transformer)
          # We treat properties that serialize to None as if they don't exist.
          if result is not None:
              # While serializing to a pb struct, we must "translate" all key names to be what the engine is going to
              # expect. Resources provide the "transform" function for doing this.
              translated_name = k
              if input_transformer is not None:
                  translated_name = input_transformer(k)
                  log.debug(f"top-level input property translated: {k} -> {translated_name}")
              # pylint: disable=unsupported-assignment-operation
              struct[translated_name] = result
              property_deps[translated_name] = deps
  
      return struct