class_docstr: AllDatasetBatchesIterator returns a dict containing batches from all dataloaders.
  When the stopping mechanism is set to ALL_DATASETS_EXHAUSTED, it will skip over the
  finished datasets.
  
  
  This supports three stopping mechanisms:
  1. `ALL_DATASETS_EXHAUSTED`: Iterates till the largest dataset is exhausted,
  while skipping those that are done
  2. `SMALLEST_DATASET_EXHAUSTED`: Stops iteration once the smallest dataset
  has been exhausted
  3. `RESTART_UNTIL_ALL_DATASETS_EXHAUSTED`: Iterates until the largest dataset
  is exhausted, while restarting those that are done
  
  Returns batches of the format: {
      dataloader_1_name: batch_obtained_from_dataloader_1,
      dataloader_2_name: batch_obtained_from_dataloader_2,
  }
  
  Args:
      individual_dataloaders (Mapping[str, Union[DataLoader, Iterable]]): A mapping of DataLoaders or Iterables with dataloader name as key
      and dataloader/iterable object as value.
      iteration_strategy (AllDatasetBatches): A AllDatasetBatches dataclass indicating how the dataloaders are iterated over.
  
  Examples:
      >>> loaders = {'a': torch.utils.data.DataLoader(range(4), batch_size=4),
          'b': torch.utils.data.DataLoader(range(15), batch_size=5)}
      >>> all_dataset_batch_strategy = AllDatasetBatches(
              stopping_mechanism=StoppingMechanism.ALL_DATASETS_EXHAUSTED
          )
      >>> combined_iterator = AllDatasetBatchesIterator(loaders, all_dataset_batch_strategy)
      >>> for item in combined_iterator:
              print(item)
      {'a': tensor([0, 1, 2, 3]), 'b': tensor([0, 1, 2, 3, 4])}
      {'b': tensor([5, 6, 7, 8, 9])}
      {'b': tensor([10, 11, 12, 13, 14])}
code: def __next__(self) -> Dict[str, Any]:
      batch_dict = {}
      for iterator in self.individual_iterators:
          try:
              batch_dict[iterator] = next(
                  self.individual_iterators[iterator]
              )
          except StopIteration:
              if (
                  self.iteration_strategy.stopping_mechanism
                  == StoppingMechanism.SMALLEST_DATASET_EXHAUSTED
              ):
                  raise StopIteration
  
              elif (
                  self.iteration_strategy.stopping_mechanism
                  == StoppingMechanism.RESTART_UNTIL_ALL_DATASETS_EXHAUSTED
              ):
                  if iterator not in self.iterators_finished:
                      self.iterators_finished.append(iterator)
                  if len(self.iterators_finished) == len(
                      self.individual_iterators
                  ):
                      raise StopIteration
                  else:
                      self.individual_iterators[iterator] = iter(
                          self.individual_dataloaders[iterator]
                      )
                      batch_dict[iterator] = next(
                          self.individual_iterators[iterator]
                      )
  
      if len(batch_dict) == 0:
          raise StopIteration
      return batch_dict
  
imports: from __future__ import annotations
  import random
  from abc import abstractmethod
  from dataclasses import dataclass
  from enum import Enum
  from itertools import cycle
  from typing import Any, Dict, Iterable, Iterator, List, Mapping, MutableMapping, Optional, Type, TYPE_CHECKING, Union
  import torch
  import torch.distributed
label_desc: Use image node_id 2 for calling the AllDatasetBatchesIterator obj's underlying member method code with example usage: obj.__next__() and returns: batch_dict