docs: def source(self, func, name=None):
          """
          Declare a source stream that introduces tuples into the application.
  
          Typically used to create a stream of tuple from an external source,
          such as a sensor or reading from an external system.
  
          Tuples are obtained from an iterator obtained from the passed iterable
          or callable that returns an iterable.
  
          Each tuple that is not None from the iterator is present on the returned stream.
  
          Each tuple is a Python object and must be picklable to allow execution of the application
          to be distributed across available resources in the Streams instance.
  
          If the iterator's ``__iter__`` or ``__next__`` block then shutdown,
          checkpointing or consistent region processing may be delayed.
          Having ``__next__`` return ``None`` (no available tuples) or tuples
          to submit will allow such processing to proceed.
  
          A shutdown ``threading.Event`` is available through
          :py:func:`streamsx.ec.shutdown` which becomes set when a shutdown
          of the processing element has been requested. This event my be waited
          on to perform a sleep that will terminate upon shutdown.
  
          Args:
              func(callable): An iterable or a zero-argument callable that returns an iterable of tuples.
              name(str): Name of the stream, defaults to a generated name.
  
          Exceptions raised by ``func`` or its iterator will cause
          its processing element will terminate. 
  
          If ``func`` is a callable object then it may suppress exceptions
          by return a true value from its ``__exit__`` method.
  
          Suppressing an exception raised by ``func.__iter__`` causes the
          source to be empty, no tuples are submitted to the stream.
  
          Suppressing an exception raised by ``__next__`` on the iterator
          results in no tuples being submitted for that call to ``__next__``.
          Processing continues with calls to ``__next__`` to fetch subsequent tuples.
  
          Returns:
              Stream: A stream whose tuples are the result of the iterable obtained from `func`.
          """
          _name = name
          if inspect.isroutine(func):
              pass
          elif callable(func):
              pass
          else:
              if _name is None:
                  _name = type(func).__name__
              func = streamsx.topology.runtime._IterableInstance(func)
  
          sl = _SourceLocation(_source_info(), "source")
          _name = self.graph._requested_name(_name, action='source', func=func)
          # source is always stateful
          op = self.graph.addOperator(self.opnamespace+"::Source", func, name=_name, sl=sl)
          op._layout(kind='Source', name=_name, orig_name=name)
          oport = op.addOutputPort(name=_name)
          return Stream(self, oport)._make_placeable()