rich
                                
                                 rich copied to clipboard
                                
                                    rich copied to clipboard
                            
                            
                            
                        [BUG] UnicodeEncodeError on Windows with ruler.
Describe the bug
The issue started appearing in my test suite, and I could boil it down to this reproducible example. I am using rich 12.5.1.
# Content of script.py.
from rich.console import Console
if __name__ == '__main__':
    console = Console()
    console.rule()  # or console.print("─")
# Content of run.py.
import subprocess
result = subprocess.run(("python", "rich_script.py"), check=True)
and then I am executing pytest run.py, which results in
Click to expand
================================================= test session starts =================================================
platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: C:\Users\TobiasR\git\pytask, configfile: tox.ini
plugins: anyio-3.6.1, cov-3.0.0, forked-1.4.0, xdist-2.5.0
collected 0 items / 2 errors
======================================================= ERRORS ========================================================
_______________________________________________ ERROR collecting run.py _______________________________________________
run.py:3: in <module>
    result = subprocess.run(("python", "rich_script.py"), check=True)
C:\tools\miniconda3\envs\pytask\lib\subprocess.py:524: in run
    raise CalledProcessError(retcode, process.args,
E   subprocess.CalledProcessError: Command '('python', 'rich_script.py')' returned non-zero exit status 1.
--------------------------------------------------- Captured stderr ---------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\TobiasR\git\pytask\rich_script.py", line 6, in <module>
    console.rule()
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 1553, in rule
    self.print(rule)
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 1646, in print
    with self:
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 848, in __exit__
    self._exit_buffer()
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 806, in _exit_buffer
    self._check_buffer()
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 1999, in _check_buffer
    legacy_windows_render(
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\_windows_renderer.py", line 17, in legacy_windows_render
    term.write_styled(text, style)
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\_win32_console.py", line 442, in write_styled
    self.write_text(text)
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\_win32_console.py", line 403, in write_text
    self.write(text)
  File "C:\tools\miniconda3\envs\pytask\lib\encodings\cp1252.py", line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-78: character maps to <undefined>
_______________________________________________ ERROR collecting run.py _______________________________________________
run.py:3: in <module>
    result = subprocess.run(("python", "rich_script.py"), check=True)
C:\tools\miniconda3\envs\pytask\lib\subprocess.py:524: in run
    raise CalledProcessError(retcode, process.args,
E   subprocess.CalledProcessError: Command '('python', 'rich_script.py')' returned non-zero exit status 1.
--------------------------------------------------- Captured stderr ---------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\TobiasR\git\pytask\rich_script.py", line 6, in <module>
    console.rule()
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 1553, in rule
    self.print(rule)
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 1646, in print
    with self:
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 848, in __exit__
    self._exit_buffer()
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 806, in _exit_buffer
    self._check_buffer()
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\console.py", line 1999, in _check_buffer
    legacy_windows_render(
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\_windows_renderer.py", line 17, in legacy_windows_render
    term.write_styled(text, style)
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\_win32_console.py", line 442, in write_styled
    self.write_text(text)
  File "C:\tools\miniconda3\envs\pytask\lib\site-packages\rich\_win32_console.py", line 403, in write_text
    self.write(text)
  File "C:\tools\miniconda3\envs\pytask\lib\encodings\cp1252.py", line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode characters in position 0-78: character maps to <undefined>
=============================================== short test summary info ===============================================
ERROR run.py - subprocess.CalledProcessError: Command '('python', 'rich_script.py')' returned non-zero exit status 1.
ERROR run.py - subprocess.CalledProcessError: Command '('python', 'rich_script.py')' returned non-zero exit status 1.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 2 errors during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
================================================== 2 errors in 0.72s ==================================================
The error does not occur when I
- switch to rich 12.4.4 or ...
- set PYTHONIOENCODING to utf-8 or ...
- execute python run.py
Platform
Click to expand
The issue occurred only on Windows and I am running Powershell Core embedded in Windows Terminal.
python -m rich.diagnose
pip freeze | grep rich
╭───────────────────────── <class 'rich.console.Console'> ─────────────────────────╮
│ A high level console interface.                                                  │
│                                                                                  │
│ ╭──────────────────────────────────────────────────────────────────────────────╮ │
│ │ <console width=120 ColorSystem.TRUECOLOR>                                    │ │
│ ╰──────────────────────────────────────────────────────────────────────────────╯ │
│                                                                                  │
│     color_system = 'truecolor'                                                   │
│         encoding = 'utf-8'                                                       │
│             file = <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'> │
│           height = 30                                                            │
│    is_alt_screen = False                                                         │
│ is_dumb_terminal = False                                                         │
│   is_interactive = True                                                          │
│       is_jupyter = False                                                         │
│      is_terminal = True                                                          │
│   legacy_windows = False                                                         │
│         no_color = False                                                         │
│          options = ConsoleOptions(                                               │
│                        size=ConsoleDimensions(width=120, height=30),             │
│                        legacy_windows=False,                                     │
│                        min_width=1,                                              │
│                        max_width=120,                                            │
│                        is_terminal=True,                                         │
│                        encoding='utf-8',                                         │
│                        max_height=30,                                            │
│                        justify=None,                                             │
│                        overflow=None,                                            │
│                        no_wrap=False,                                            │
│                        highlight=None,                                           │
│                        markup=None,                                              │
│                        height=None                                               │
│                    )                                                             │
│            quiet = False                                                         │
│           record = False                                                         │
│         safe_box = True                                                          │
│             size = ConsoleDimensions(width=120, height=30)                       │
│        soft_wrap = False                                                         │
│           stderr = False                                                         │
│            style = None                                                          │
│         tab_size = 8                                                             │
│            width = 120                                                           │
╰──────────────────────────────────────────────────────────────────────────────────╯
╭── <class 'rich._windows.WindowsConsoleFeatures'> ───╮
│ Windows features available.                         │
│                                                     │
│ ╭─────────────────────────────────────────────────╮ │
│ │ WindowsConsoleFeatures(vt=True, truecolor=True) │ │
│ ╰─────────────────────────────────────────────────╯ │
│                                                     │
│ truecolor = True                                    │
│        vt = True                                    │
╰─────────────────────────────────────────────────────╯
╭────── Environment Variables ───────╮
│ {                                  │
│     'TERM': None,                  │
│     'COLORTERM': None,             │
│     'CLICOLOR': None,              │
│     'NO_COLOR': None,              │
│     'TERM_PROGRAM': None,          │
│     'COLUMNS': None,               │
│     'LINES': None,                 │
│     'JUPYTER_COLUMNS': None,       │
│     'JUPYTER_LINES': None,         │
│     'JPY_PARENT_PID': None,        │
│     'VSCODE_VERBOSE_LOGGING': None │
│ }                                  │
╰────────────────────────────────────╯
platform="Windows"
Not the first time we've been bitten by Windows CP-1252 encoding.
The solution may be to force utf8
I can't reproduce this. Running the same Python version, Rich version, tried legacy/non-legacy terminals, tried forcing the encoding to cp1252 and utf-8, tried with an without running in a subprocess. Every time the rule renders without an error. I'm not sure where else the difference could lie 🤔
It's interesting that you've supplied the example as a subprocess - is that because you can only reproduce the issue when you run it in a subprocess?
I noticed in the "Platform" information you shared, legacy_windows = False, yet the code path which raises the error implies that legacy_windows = True. I wonder if for some reason, the console reports legacy Windows in the subprocess but not in the parent process? Regardless, I can't reproduce even when I force legacy and cp1252 on my end, so I'm stumped at the moment!
Also, comparing the version that works with you with the latest version, I can't see any relevant (encoding-related) changes at all: https://github.com/Textualize/rich/compare/v12.4.4...v12.5.1
Yes, this error only occurs when I am using pytest to execute the subprocess script with pytest run.py. python run.py works. So, for me, it seemed like pytest influences which console settings are detected in the subprocess.
I also had it after an update in my windows tests in github https://github.com/InfrastructureAsCode-ch/nornir_rich/runs/7483512482?check_suite_focus=true
I am using PYTHONIOENCODING: utf-8 at the moment and it works fine but for me on windows pytest . worked well. So I have the problam only in the workflow on GitHub
For tests, you probably want to set legacy_windows to True or False, so you aren't at the mercy of the environment. Ditto with the other parameters that auto-detect.
I am seeing this in a azure pipelines run of a conda-forge feedstock. Should I open another issue? Relevant bits:
Python 3.8, rich 12.6.0-pyhd8ed1ab_0 from conda-forge.
ConsoleOptions
From this log
| | render_options = ConsoleOptions(                                        | |
| |                      size=ConsoleDimensions(width=79, height=25),       | |
| |                      legacy_windows=True,                               | |
| |                      min_width=1,                                       | |
| |                      max_width=79,                                      | |
| |                      is_terminal=False,                                 | |
| |                      encoding='cp1252',                                 | |
| |                      max_height=25,                                     | |
| |                      justify=None,                                      | |
| |                      overflow=None,                                     | |
| |                      no_wrap=None,                                      | |
| |                      highlight=None,                                    | |
| |                      markup=None,                                       | |
| |                      height=None                                        | |
| |                  )                                                      | |
when rendering this string
text = '.                                                  \u2554\u2584\u2593\u2593\u2593\u2593\u2584'
This is still an issue. In CP1252 encoding (which is what the :! operator in nvim uses) console.print(Rule()) and console.rule() throw an error when no title is provided. They work fine with a title provided.