docs: def wrap_as_node(self, func):
          'wrap a function as a node'
          name = self.get_name(func)
  
          @wraps(func)
          def wrapped(*args, **kwargs):
              'wrapped version of func'
              message = self.get_message_from_call(*args, **kwargs)
              self.logger.info('calling "%s" with %r', name, message)
              result = func(message)
  
              # functions can return multiple values ("emit" multiple times)
              # by yielding instead of returning. Handle this case by making
              # a list of the results and processing them all after the
              # generator successfully exits. If we were to process them as
              # they came out of the generator, we might get a partially
              # processed input sent down the graph. This may be possible in
              # the future via a flag.
              if isinstance(result, GeneratorType):
                  results = [
                      self.wrap_result(name, item)
                      for item in result
                      if item is not NoResult
                  ]
                  self.logger.debug(
                      '%s returned generator yielding %d items', func, len(results)
                  )
  
                  [self.route(name, item) for item in results]
                  return tuple(results)
  
              # the case of a direct return is simpler. wrap, route, and
              # return the value.
              else:
                  if result is NoResult:
                      return result
  
                  result = self.wrap_result(name, result)
                  self.logger.debug(
                      '%s returned single value %s', func, result
                  )
                  self.route(name, result)
                  return result
  
          return wrapped