docs: def needed_inputs(self):
          """ List all the needed inputs of a configured engine
  
          >>> engine = Engine("op1", "op2")
          >>> engine.op1.setup(in_name="in", out_name="middle", required=False)
          >>> engine.op2.setup(in_name="middle", out_name="out")
          >>> engine.op1.append(lambda x:x+2)
          >>> engine.op2.append(lambda x:x*2)
          >>> engine.op1.select('<lambda>')
          >>> list(engine.needed_inputs())
          ['in']
  
          But now if we unactivate the first component:
  
          >>> engine.op1.clear_selections()
          >>> list(engine.needed_inputs())
          ['middle']
  
          More complex example:
  
          >>> engine = Engine("op1", "op2")
          >>> engine.op1.setup(in_name="in", out_name="middle")
          >>> engine.op2.setup(in_name=["middle", "in2"], out_name="out")
          >>> engine.op1.append(lambda x:x+2)
          >>> engine.op2.append(lambda x, y:x*y)
          >>> engine.needed_inputs() == {'in', 'in2'}
          True
  
          Note that by default the needed input is 'input':
          
          >>> engine = Engine("op1", "op2")
          >>> engine.op1.append(lambda x:x+2)
          >>> engine.op2.append(lambda x:x*2)
          >>> list(engine.needed_inputs())
          ['input']
          """
          needed = set()
          available = set()       # set of available data
          for bnum, block in enumerate(self):
              if not block.selected():    # if the block will not be used
                  continue
              if block.in_name is not None:
                  for in_name in block.in_name:
                      if not in_name in available:
                          needed.add(in_name)
              elif bnum == 0:
                  # if the first block
                  needed.add(Engine.DEFAULT_IN_NAME)
              # register the output
              available.add(block.out_name)
          return needed