rich icon indicating copy to clipboard operation
rich copied to clipboard

[BUG] `AttributeError` using `Console.capture` when run with `pythonw`

Open Lx opened this issue 2 years ago • 6 comments

Describe the bug

This code, when run using pythonw:

table = Table(box=DOUBLE_EDGE)
printer = Console(force_terminal=False)
with printer.capture() as capture:
    printer.print(table)

fails with this stack trace:

Traceback (most recent call last):
  File "<MY CODE>", line ...
    with printer.capture() as capture:
  File "rich\console.py", line 336, in __exit__
    self._result = self._console.end_capture()
  File "rich\console.py", line 852, in end_capture
    self._exit_buffer()
  File "rich\console.py", line 796, in _exit_buffer
    self._check_buffer()
  File "rich\console.py", line 1979, in _check_buffer
    self.file.fileno() in _STD_STREAMS_OUTPUT
AttributeError: 'NoneType' object has no attribute 'fileno'

My guess is that pythonw causes Console to get some attributes that are usually not null, but in this case, are.

I can probably immediately work around this by instantiating the Console like so:

table = Table(box=DOUBLE_EDGE)
capture = StringIO()
printer = Console(file=capture)
printer.print(table)

but Rich could probably cater for the Console instance having no STDOUT in some cases (such as above).

Platform

  • Windows 10
  • Python v3.10.0

I've avoided including details generated by python -m rich.diagnose because more diagnostic information would be provided using pythonw ...—but of course, since it disables STDOUT, there's no obvious way to grab that info for you. 🤣

Lx avatar Jul 17 '22 07:07 Lx

I don't have a Windows box handy. What happens if you run a script with pythonw that does print("hello world") ?

willmcgugan avatar Jul 19 '22 10:07 willmcgugan

Hi Will. There is no output. I somewhat expected this as I believe pythonw nullifies its STDOUT handle.

Lx avatar Jul 19 '22 12:07 Lx

And is that the behaviour you would expect from Rich? To silently ignore the error?

Could you paste the full traceback?

willmcgugan avatar Jul 21 '22 13:07 willmcgugan

To frame my expected behaviour in a slightly alternative manner:

  • When I instantiate a Console purely for the purpose of capture-ing its output to a variable, I would expect Rich not to fail when STDOUT is redirected to null (which is what pythonw appears to do)—particularly since I would expect STDOUT not to be getting used as part of that capturing operation.

Directly answering your question though: yes, I believe there should be no error.

The traceback above is complete except for my calling code, which is no different from the example given.

Lx avatar Jul 22 '22 14:07 Lx

I see. So the error occurs regardless of if you are printing to stdout or not.

You didn't include the full traceback btw. The error message would help.

willmcgugan avatar Jul 22 '22 15:07 willmcgugan

Yes, the error is occurring despite no intent to print to STDOUT.

Sorry, you're absolutely right: I did fail to paste the actual error message. I've edited that in to the earlier message now, but here's the entire thing from my logging anyway for good measure, in case any of the other bits do actually help:

2022-07-17 15:35:48,860 ERROR    [ClientWorkerThread/ductus.client.gui.eventloopthread.ThreadedEventLoop] exception in asyncio event loop
Traceback (most recent call last):
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\gui\eventloopthread.py", line 54, in __thread_main
    asyncio.run(self.__run_coro_cancellable(), debug=True)
  File "C:\Program Files\Python310\lib\asyncio\runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "C:\Program Files\Python310\lib\asyncio\base_events.py", line 641, in run_until_complete
    return future.result()
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\gui\eventloopthread.py", line 76, in __run_coro_cancellable
    await await_task
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\gui\worker.py", line 85, in __loop_tasks
    await asyncio.gather(
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\gui\worker.py", line 128, in __watch_for_auth_requests
    await self.client.google_auth()
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\client.py", line 119, in google_auth
    creds = await asyncio.to_thread(google_auth_flow)
  File "C:\Program Files\Python310\lib\asyncio\threads.py", line 25, in to_thread
    return await loop.run_in_executor(None, func_call)
  File "C:\Program Files\Python310\lib\concurrent\futures\thread.py", line 52, in run
    result = self.fn(*self.args, **self.kwargs)
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\googleauth.py", line 43, in google_auth_flow
    success_message=_browser_finish_message(),
  File "C:\Users\kate\AppData\Local\Programs\Ductus\ductus\client\googleauth.py", line 75, in _browser_finish_message
    with printer.capture() as capture:
  File "...\rich\console.py", line 336, in __exit__
    self._result = self._console.end_capture()
  File "...\rich\console.py", line 852, in end_capture
    self._exit_buffer()
  File "...\rich\console.py", line 796, in _exit_buffer
    self._check_buffer()
  File "...\rich\console.py", line 1979, in _check_buffer
    self.file.fileno() in _STD_STREAMS_OUTPUT
AttributeError: 'NoneType' object has no attribute 'fileno'

In particular, Console is treating its file attribute as if it will never be None. When run under pythonw however, that's exactly what it's defaulting to. Even so though, in my example, there's no obvious reason for self.file to be getting called.

Lx avatar Jul 23 '22 00:07 Lx

Did I solve your problem?

Why not buy the devs a coffee to say thanks?

github-actions[bot] avatar Sep 08 '22 12:09 github-actions[bot]