docs: async def serialize_property(value: 'Input[Any]',
                               deps: List['Resource'],
                               input_transformer: Optional[Callable[[str], str]] = None) -> Any:
      """
      Serializes a single Input into a form suitable for remoting to the engine, awaiting
      any futures required to do so.
      """
      if isinstance(value, list):
          props = []
          for elem in value:
              props.append(await serialize_property(elem, deps, input_transformer))
  
          return props
  
      if known_types.is_custom_resource(value):
          deps.append(value)
          return await serialize_property(value.id, deps, input_transformer)
  
      if known_types.is_asset(value):
          # Serializing an asset requires the use of a magical signature key, since otherwise it would look
          # like any old weakly typed object/map when received by the other side of the RPC boundary.
          obj = {
              _special_sig_key: _special_asset_sig
          }
  
          if hasattr(value, "path"):
              obj["path"] = await serialize_property(value.path, deps, input_transformer)
          elif hasattr(value, "text"):
              obj["text"] = await serialize_property(value.text, deps, input_transformer)
          elif hasattr(value, "uri"):
              obj["uri"] = await serialize_property(value.uri, deps, input_transformer)
          else:
              raise AssertionError(f"unknown asset type: {value}")
  
          return obj
  
      if known_types.is_archive(value):
          # Serializing an archive requires the use of a magical signature key, since otherwise it would look
          # like any old weakly typed object/map when received by the other side of the RPC boundary.
          obj = {
              _special_sig_key: _special_archive_sig
          }
  
          if hasattr(value, "assets"):
              obj["assets"] = await serialize_property(value.assets, deps, input_transformer)
          elif hasattr(value, "path"):
              obj["path"] = await serialize_property(value.path, deps, input_transformer)
          elif hasattr(value, "uri"):
              obj["uri"] = await serialize_property(value.uri, deps, input_transformer)
          else:
              raise AssertionError(f"unknown archive type: {value}")
  
          return obj
  
      if inspect.isawaitable(value):
          # Coroutines and Futures are both awaitable. Coroutines need to be scheduled.
          # asyncio.ensure_future returns futures verbatim while converting coroutines into
          # futures by arranging for the execution on the event loop.
          #
          # The returned future can then be awaited to yield a value, which we'll continue
          # serializing.
          future_return = await asyncio.ensure_future(value)
          return await serialize_property(future_return, deps, input_transformer)
  
      if known_types.is_output(value):
          deps.extend(value.resources())
  
          # When serializing an Output, we will either serialize it as its resolved value or the "unknown value"
          # sentinel. We will do the former for all outputs created directly by user code (such outputs always
          # resolve isKnown to true) and for any resource outputs that were resolved with known values.
          is_known = await value._is_known
          value = await serialize_property(value.future(), deps, input_transformer)
          return value if is_known else UNKNOWN
  
      if isinstance(value, dict):
          obj = {}
          for k, v in value.items():
              transformed_key = k
              if input_transformer is not None:
                  transformed_key = input_transformer(k)
                  log.debug(f"transforming input property: {k} -> {transformed_key}")
              obj[transformed_key] = await serialize_property(v, deps, input_transformer)
  
          return obj
  
      return value