Skip to content

Commit

Permalink
Safely perform _cellrepr on list objects, since list objects can be c…
Browse files Browse the repository at this point in the history
…ell values (#7)

in a DataFrame. Deal with regression where float precision is mangled
during round-trip testing, by using repr() on float values and str()
on other values.

Fixes #6.
  • Loading branch information
robin900 committed Jul 27, 2017
1 parent f41a0a1 commit 00d2de4
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 6 deletions.
7 changes: 5 additions & 2 deletions gspread_dataframe.py
Expand Up @@ -48,9 +48,12 @@ def _cellrepr(value, allow_formulas):
to be interpreted as formulas; otherwise, escape
them with an apostrophe to avoid formula interpretation.
"""
if pd.isnull(value):
if pd.isnull(value) is True:
return ""
value = str(value)
if isinstance(value, float):
value = repr(value)
else:
value = str(value)
if (not allow_formulas) and value.startswith('='):
value = "'%s" % value
return value
Expand Down
4 changes: 2 additions & 2 deletions tests/cell_feed.xml
Expand Up @@ -142,10 +142,10 @@
<updated>2017-03-28T13:38:41.662Z</updated>
<category scheme="http://schemas.google.com/spreadsheets/2006" term="http://schemas.google.com/spreadsheets/2006#cell"/>
<title type="text">C2</title>
<content type="text">1.113</content>
<content type="text">[1, 2, 3]</content>
<link href="https://spreadsheets.google.com/feeds/cells/XXXXXXX/od6/private/full/R2C3" rel="self" type="application/atom+xml"/>
<link href="https://spreadsheets.google.com/feeds/cells/XXXXXXX/od6/private/full/R2C3/7ujti" rel="edit" type="application/atom+xml"/>
<ns2:cell col="3" inputValue="1.113" numericValue="1.113" row="2">1.113</ns2:cell>
<ns2:cell col="3" inputValue="[1, 2, 3]" row="2">[1, 2, 3]</ns2:cell>
</entry>
<entry>
<id>https://spreadsheets.google.com/feeds/cells/XXXXXXX/od6/private/full/R2C4</id>
Expand Down
29 changes: 29 additions & 0 deletions tests/gspread_dataframe_test.py
Expand Up @@ -126,6 +126,26 @@ def test_parse_dates_custom_parser(self):
df = get_as_dataframe(self.sheet, parse_dates=[4], date_parser=lambda x: datetime.strptime(x, '%Y-%m-%d'))
self.assertEqual(df['Date Column'][0], datetime(2017,3,4))


_original_mock_failure_message = Mock._format_mock_failure_message

def _format_mock_failure_message(self, args, kwargs):
message = 'Expected call: %s\nActual call: %s'
expected_string = self._format_mock_call_signature(args, kwargs)
call_args = self.call_args
if len(call_args) == 3:
call_args = call_args[1:]
actual_string = self._format_mock_call_signature(*call_args)
msg = (message % (expected_string, actual_string))
if len(call_args[0]) > 1 and isinstance(call_args[0][1], (str, bytes)) and len(args) > 1 and isinstance(args[1], (str, bytes)) and call_args[0][1] != args[1]:
import difflib
sm = difflib.SequenceMatcher(None, call_args[0][1], args[1])
m = sm.find_longest_match(0, len(call_args[0][1]), 0, len(args[1]))
msg += "; diff: at index %d, expected %s -- actual %s" % (m.a+m.size, call_args[0][1][m.a+m.size-40:m.a+m.size+40], args[1][m.b+m.size-40:m.b+m.size+40])
return msg

Mock._format_mock_failure_message = _format_mock_failure_message

class TestWorksheetWrites(unittest.TestCase):

def setUp(self):
Expand All @@ -140,3 +160,12 @@ def test_write_basic(self):
self.sheet.resize.assert_called_once_with(10, 10)
self.sheet.client.post_cells.assert_called_once()
self.sheet.client.post_cells.assert_called_once_with(self.sheet, POST_CELLS_EXPECTED)

def test_write_list_value_to_cell(self):
df = get_as_dataframe(self.sheet)
df = df.set_value(0, 'Numeric Column', [1,2,3])
set_with_dataframe(self.sheet, df, resize=True)
self.sheet.resize.assert_called_once_with(10, 10)
self.sheet.client.post_cells.assert_called_once()
self.sheet.client.post_cells.assert_called_once_with(self.sheet, POST_CELLS_EXPECTED)

2 changes: 1 addition & 1 deletion tests/mock_worksheet.py
Expand Up @@ -9,7 +9,7 @@

def contents_of_file(filename, et_parse=True):
with open(os.path.join(os.path.dirname(__file__), filename), 'r') as f:
return ET.fromstring(f.read()) if et_parse else f.read()
return ET.fromstring(f.read().strip()) if et_parse else f.read().strip()

CELL_FEED = contents_of_file('cell_feed.xml')
WORKSHEET_FEED = contents_of_file('worksheet_feed.xml')
Expand Down
3 changes: 2 additions & 1 deletion tests/post_cells_expected.xml

Large diffs are not rendered by default.

0 comments on commit 00d2de4

Please sign in to comment.