Repository: pytorch/ignite

Issue:
Improve typing in engine
## 🚀 Feature

The following code may be fail if `max_epochs is None`:

https://github.com/pytorch/ignite/blob/a756f9ca956ee0ee72bb61401a87a6eaf249dbba/ignite/engine/engine.py#L839-L842

https://github.com/pytorch/ignite/blob/a756f9ca956ee0ee72bb61401a87a6eaf249dbba/ignite/engine/engine.py#L562-L566

Maybe, we have to add additional check of `max_epochs is not None` and remove `# type: ignore[operator]`.

@gruebel what do you think ?


Discussion:
I took a very long view on the code and I think it is a really good idea to check for `max_epochs is not None`, because right now, there can be edge cases like `max_epochs is None` and `epoch_length is not None` this will result in a Runtime Error in the `_is_done` method.
https://github.com/pytorch/ignite/blob/3affd95f9480b4f6a9070d800d8505bc3e80e0dc/ignite/engine/engine.py#L560-L567

and as long as the `_is_done` check doesn't raise an error here `max_epochs` will also be not `None`
https://github.com/pytorch/ignite/blob/a756f9ca956ee0ee72bb61401a87a6eaf249dbba/ignite/engine/engine.py#L835-L843 
@gruebel thanks for the feedback. Can I assign this issue to you ?
Sure 😄  will make a PR ready today or latest tomorrow.

Resolving patch:
diff --git a/ignite/contrib/handlers/param_scheduler.py b/ignite/contrib/handlers/param_scheduler.py
--- a/ignite/contrib/handlers/param_scheduler.py
+++ b/ignite/contrib/handlers/param_scheduler.py
@@ -65,7 +65,8 @@ def __call__(self, engine: Optional[Engine], name: Optional[str] = None) -> None
         if isinstance(value, list):
             if len(value) != len(self.optimizer_param_groups):
                 raise ValueError(
-                    f"size of value is different than optimizer_param_groups {len(value)} != {len(self.optimizer_param_groups)}"
+                    "size of value is different than optimizer_param_groups "
+                    f"{len(value)} != {len(self.optimizer_param_groups)}"
                 )
 
             for i, param_group in enumerate(self.optimizer_param_groups):
diff --git a/ignite/engine/engine.py b/ignite/engine/engine.py
--- a/ignite/engine/engine.py
+++ b/ignite/engine/engine.py
@@ -561,7 +561,8 @@ def _is_done(state: State) -> bool:
         is_done_iters = state.max_iters is not None and state.iteration >= state.max_iters
         is_done_count = (
             state.epoch_length is not None
-            and state.iteration >= state.epoch_length * state.max_epochs  # type: ignore[operator]
+            and state.max_epochs is not None
+            and state.iteration >= state.epoch_length * state.max_epochs
         )
         is_done_epochs = state.max_epochs is not None and state.epoch >= state.max_epochs
         return is_done_iters or is_done_count or is_done_epochs
@@ -833,12 +834,17 @@ def _run_once_on_dataset(self) -> float:
                     # Should exit while loop if we can not iterate
                     if should_exit:
                         if not self._is_done(self.state):
+                            total_iters = (
+                                self.state.epoch_length * self.state.max_epochs
+                                if self.state.max_epochs is not None
+                                else self.state.max_iters
+                            )
+
                             warnings.warn(
                                 "Data iterator can not provide data anymore but required total number of "
                                 "iterations to run is not reached. "
                                 "Current iteration: {} vs Total iterations to run : {}".format(
-                                    self.state.iteration,
-                                    self.state.epoch_length * self.state.max_epochs,  # type: ignore[operator]
+                                    self.state.iteration, total_iters,
                                 )
                             )
                         break


Test patch: