Skip to content

Commit

Permalink
Fixes #578 - Do not mutate data in place when applying formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
claudep committed Mar 21, 2024
1 parent 4fd9a68 commit 23e4699
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 18 deletions.
31 changes: 16 additions & 15 deletions src/tablib/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ def append(self, value):
def insert(self, index, value):
self._row.insert(index, value)

def copy(self):
return Row(self._row.copy(), self.tags.copy())

def __contains__(self, item):
return item in self._row

Expand Down Expand Up @@ -270,27 +273,25 @@ def _package(self, dicts=True):

_data = list(self._data)

# Execute formatters
if self._formatters:
for row_i, row in enumerate(_data):
def format_row(row):
# Execute formatters
if self._formatters:
row = row.copy() # To not mutate internal data structure
for col, callback in self._formatters:
try:
if col is None:
for j, c in enumerate(row):
_data[row_i][j] = callback(c)
else:
_data[row_i][col] = callback(row[col])
except IndexError:
raise InvalidDatasetIndex
if col is None:
# Apply formatter to all cells
row = [callback(cell) for cell in row]
else:
row[col] = callback(row[col])
return list(row)

if self.headers:
if dicts:
data = [dict(list(zip(self.headers, data_row))) for data_row in _data]
data = [dict(list(zip(self.headers, format_row(row)))) for row in _data]
else:
data = [list(self.headers)] + list(_data)
data = [list(self.headers)] + [format_row(row) for row in _data]
else:
data = [list(row) for row in _data]

data = [format_row(row) for row in _data]
return data

def _get_headers(self):
Expand Down
12 changes: 9 additions & 3 deletions tests/test_tablib.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,12 +564,18 @@ def test_formatters(self):
"""Confirm formatters are being triggered."""

def _formatter(cell_value):
return str(cell_value).upper()
return str(cell_value)[1:]

self.founders.add_formatter('last_name', _formatter)

for name in [r['last_name'] for r in self.founders.dict]:
self.assertTrue(name.isupper())
expected = [
{'first_name': 'John', 'last_name': 'dams', 'gpa': 90},
{'first_name': 'George', 'last_name': 'ashington', 'gpa': 67},
{'first_name': 'Thomas', 'last_name': 'efferson', 'gpa': 50},
]
self.assertEqual(self.founders.dict, expected)
# Test once more as the result should be the same
self.assertEqual(self.founders.dict, expected)

def test_unicode_renders_markdown_table(self):
# add another entry to test right field width for
Expand Down

0 comments on commit 23e4699

Please sign in to comment.