rsalmei / alive-progress

A new kind of Progress Bar, with real-time throughput, ETA, and very cool animations!
MIT License
5.49k stars 207 forks source link

OSError: [Errno 25] Inappropriate ioctl for device when running in pytest #115

Closed Saif807380 closed 2 years ago

Saif807380 commented 2 years ago

A project of mine uses alive-progress. I updated from 1.6.2 to 2.1.0 and the above error was raised when I ran my tests. I tried running the test in different terminals - iterm2, vscode and pycharm but got the same exception. Below is the stack trace for the same.

tests/test_base.py::test_pipeline_with_default_reader
  /Users/saifkazi/Desktop/preprocessy/venv/lib/python3.9/site-packages/_pytest/threadexception.py:75: PytestUnhandledThreadExceptionWarning: Exception in thread Thread-1

  Traceback (most recent call last):
    File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/threading.py", line 954, in _bootstrap_inner
      self.run()
    File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/threading.py", line 892, in run
      self._target(*self._args, **self._kwargs)
    File "/Users/saifkazi/Desktop/preprocessy/venv/lib/python3.9/site-packages/alive_progress/core/progress.py", line 112, in run
      alive_repr(next(spinner_player))
    File "/Users/saifkazi/Desktop/preprocessy/venv/lib/python3.9/site-packages/alive_progress/core/progress.py", line 123, in alive_repr
      run.last_len = print_cells(fragments, term.cols(), run.last_len, _term=term)
    File "/Users/saifkazi/Desktop/preprocessy/venv/lib/python3.9/site-packages/alive_progress/utils/terminal/tty.py", line 32, in cols
      return os.get_terminal_size()[0]
  OSError: [Errno 25] Inappropriate ioctl for device

The code that uses alive-progress

def process(self):
        """Method that executes the pipeline sequentially."""
        self.print_info()
        with alive_bar(
            len(self.steps),
            title="Pipeline Stages",
            enrich_print=False,
            force_tty=True,
        ) as bar:
            print("\nProcessing...\n")
            for step in self.steps:
                step(self.params)
                print(
                    f"==> Completed Stage: {stringcase.sentencecase(step.__name__)}\n"
                )
                bar()
        print(
            Fore.GREEN + "\nPipeline Completed Successfully\n" + Style.RESET_ALL
        )

The test for above

def test_pipeline_with_default_reader():
    df = pd.DataFrame({"A": np.arange(1, 100), "B": np.arange(1, 100)})
    _ = df.to_csv("./datasets/configs/dataset.csv", index=False)

    params = {
        "col_1": "A",
        "col_2": "B",
        "test_size": 0.2,
    }

    pipeline = BasePipeline(
        train_df_path="./datasets/configs/dataset.csv",
        steps=[times_two, squared, split],
        params=params,
    )
    pipeline.process()

    assert "train_df" in pipeline.params.keys()

Version Numbers

Python - 3.8, 3.9 alive-progress - 2.1.0

From what I could debug, this has something to do with utils/terminal/tty.py.

rsalmei commented 2 years ago

Hey @Saif807380,

Actually, you can see in the last log line that is Python framework that is returning an error...

    File "/Users/saifkazi/Desktop/preprocessy/venv/lib/python3.9/site-packages/alive_progress/utils/terminal/tty.py", line 32, in cols
      return os.get_terminal_size()[0]
  OSError: [Errno 25] Inappropriate ioctl for device

In this tty.py file of mine, I just try to get the terminal size... Where are you running this? Can you please try to run this?

import os
os.get_terminal_size()
Saif807380 commented 2 years ago

Below is the output when I ran this in iterm2

Screen Shot 2021-10-26 at 23 20 35

And this is the output in vscode's terminal

Screen Shot 2021-10-26 at 23 22 39
Saif807380 commented 2 years ago

I replaced os.get_terminal_size() in tty.py with shutil.get_terminal_size() and everything worked perfectly. Since shutil is wrapper on os it must be handling exceptions underneath.

rsalmei commented 2 years ago

Yes, the shutil one will always work, since it is just a wrapper over the os one, which includes a try/except to just return 80 cols in case of error. But I use the raw one, which has a much smaller overhead.

rsalmei commented 2 years ago

How are you starting your project? You certainly are in a different environment...

Saif807380 commented 2 years ago

I run everything inside the virtual environment. I tried out different terminal applications -

And since my project uses GitHub actions for running test, it too failed with the same error.

Saif807380 commented 2 years ago

For testing, I am using pytest and run the tests by running pytest locally.

rsalmei commented 2 years ago

Ohh yes, forgot you was saying your tests were not working!

Well, these new terminal abstractions came exactly to fix a bug in 2.0, in which the terminal ANSI Escape Codes were being disabled based only on the terminal report (sys.stdout.isatty()), so a force_tty param wasn't being taken into account... Your tests should probably work on 2.0.

But on 2.1 where this was fixed, that force_tty will actually enable the full feature set of alive_progress, including the background thread and print/logging hooks, which may not be a good idea in a pytest environment...

Could you please include that snippet on the first line of your test_pipeline_with_default_reader?

import os
os.get_terminal_size()

I think the test will break on the first line, without ever getting to alive_progres...

Saif807380 commented 2 years ago

Yes, the test broke on the first line itself as you mentioned. Is there a workaround to this? Setting force_tty = None, fixes the running of tests. But are there any side effects to doing so? Right now with force_tty at its default value, satisfies all my use cases, but is there some feature that I'll miss out on by doing this?

rsalmei commented 2 years ago

Nice, as I thought... And great that it works with the default value of force_tty. But unfortunately I don't know of any fix for that. I even tried to search about it, but couldn't find anything relevant...

No, not at all! You won't miss out anything! This param exists just as a workaround for terminals that do not report themselves as "interactive", such as Pycharm's terminal and Jupyter Notebook. So, if you're running your software anywhere else, it won't make any difference 👍

I'll close this ticket than, but feel free to reply if you want.