Skip to content

Commit 73ff71e

Browse files
JayOfferdahlaeltanawy
authored andcommitted
BUG: Allow IOErrors when attempting to retrieve default client encoding. (pandas-dev#21531)
1 parent 0ba7b16 commit 73ff71e

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

doc/source/whatsnew/v0.24.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,7 @@ I/O
749749
- :func:`read_sas()` will parse numbers in sas7bdat-files that have width less than 8 bytes correctly. (:issue:`21616`)
750750
- :func:`read_sas()` will correctly parse sas7bdat files with many columns (:issue:`22628`)
751751
- :func:`read_sas()` will correctly parse sas7bdat files with data page types having also bit 7 set (so page type is 128 + 256 = 384) (:issue:`16615`)
752+
- Bug in :meth:`detect_client_encoding` where potential ``IOError`` goes unhandled when importing in a mod_wsgi process due to restricted access to stdout. (:issue:`21552`)
752753

753754
Plotting
754755
^^^^^^^^

pandas/io/formats/console.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ def detect_console_encoding():
2121
encoding = None
2222
try:
2323
encoding = sys.stdout.encoding or sys.stdin.encoding
24-
except AttributeError:
24+
except (AttributeError, IOError):
2525
pass
2626

2727
# try again for something better
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pytest
2+
3+
from pandas.io.formats.console import detect_console_encoding
4+
5+
6+
class MockEncoding(object): # TODO(py27): replace with mock
7+
"""
8+
Used to add a side effect when accessing the 'encoding' property. If the
9+
side effect is a str in nature, the value will be returned. Otherwise, the
10+
side effect should be an exception that will be raised.
11+
"""
12+
def __init__(self, encoding):
13+
super(MockEncoding, self).__init__()
14+
self.val = encoding
15+
16+
@property
17+
def encoding(self):
18+
return self.raise_or_return(self.val)
19+
20+
@staticmethod
21+
def raise_or_return(val):
22+
if isinstance(val, str):
23+
return val
24+
else:
25+
raise val
26+
27+
28+
@pytest.mark.parametrize('empty,filled', [
29+
['stdin', 'stdout'],
30+
['stdout', 'stdin']
31+
])
32+
def test_detect_console_encoding_from_stdout_stdin(monkeypatch, empty, filled):
33+
# Ensures that when sys.stdout.encoding or sys.stdin.encoding is used when
34+
# they have values filled.
35+
# GH 21552
36+
with monkeypatch.context() as context:
37+
context.setattr('sys.{}'.format(empty), MockEncoding(''))
38+
context.setattr('sys.{}'.format(filled), MockEncoding(filled))
39+
assert detect_console_encoding() == filled
40+
41+
42+
@pytest.mark.parametrize('encoding', [
43+
AttributeError,
44+
IOError,
45+
'ascii'
46+
])
47+
def test_detect_console_encoding_fallback_to_locale(monkeypatch, encoding):
48+
# GH 21552
49+
with monkeypatch.context() as context:
50+
context.setattr('locale.getpreferredencoding', lambda: 'foo')
51+
context.setattr('sys.stdout', MockEncoding(encoding))
52+
assert detect_console_encoding() == 'foo'
53+
54+
55+
@pytest.mark.parametrize('std,locale', [
56+
['ascii', 'ascii'],
57+
['ascii', Exception],
58+
[AttributeError, 'ascii'],
59+
[AttributeError, Exception],
60+
[IOError, 'ascii'],
61+
[IOError, Exception]
62+
])
63+
def test_detect_console_encoding_fallback_to_default(monkeypatch, std, locale):
64+
# When both the stdout/stdin encoding and locale preferred encoding checks
65+
# fail (or return 'ascii', we should default to the sys default encoding.
66+
# GH 21552
67+
with monkeypatch.context() as context:
68+
context.setattr(
69+
'locale.getpreferredencoding',
70+
lambda: MockEncoding.raise_or_return(locale)
71+
)
72+
context.setattr('sys.stdout', MockEncoding(std))
73+
context.setattr('sys.getdefaultencoding', lambda: 'sysDefaultEncoding')
74+
assert detect_console_encoding() == 'sysDefaultEncoding'

0 commit comments

Comments
 (0)