Skip to content

Preserve falsy values (0, False) in jira and latex exports#643

Open
koriyoshi2041 wants to merge 3 commits into
jazzband:masterfrom
koriyoshi2041:fix/preserve-falsy-values-jira-latex
Open

Preserve falsy values (0, False) in jira and latex exports#643
koriyoshi2041 wants to merge 3 commits into
jazzband:masterfrom
koriyoshi2041:fix/preserve-falsy-values-jira-latex

Conversation

@koriyoshi2041

Copy link
Copy Markdown

The JIRA and LaTeX serializers decide whether a cell is empty with a plain truthiness test:

# _jira.py
delimiter.join([str(item) if item else " " for item in row])
# _latex.py
new_row = [cls._escape_tex_reserved_symbols(str(item)) if item else "" for item in row]

So 0, 0.0, and False — all valid data — are treated as missing and exported as blank cells, silently losing data. For example tablib.Dataset([0, False, "c"]).jira produced | | |c| instead of |0|False|c|.

The HTML format already does this correctly with str(item) if item is not None else "", so "blank only for missing values" is the intended contract. This changes both serializers to test item not in (None, ""), which keeps the existing None/empty-string behaviour (still rendered blank) while preserving real falsy values.

Added test_jira_export_falsy_values and test_latex_export_falsy_values; the existing *_none_* tests still pass. pytest -k "jira or latex" is green (12 passed).

The jira and latex serializers used a truthiness test (`if item`) to decide
whether a cell is empty, so 0, 0.0, and False were exported as blank cells and
silently lost. Only None and empty strings should render as blank, which is
what the html format already does (`if item is not None`). Check
`item not in (None, '')` instead so real falsy values are kept.

@hugovk hugovk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also changes the display of other falsy things.

With this script:

import tablib

# The fix itself: 0 and False are real values.
print(repr(tablib.Dataset([0, False, 'c']).jira))

# Finding 1: change is broader than 0/False — falsy containers/bytes now
# render their reprs instead of blank cells (jira and latex alike).
print(repr(tablib.Dataset([[], {}, 'c']).jira))

# Finding 2: b'' hits a bytes-vs-str comparison in `item not in (None, '')`
# — raises BytesWarning under -bb (master rendered a blank cell).
print(repr(tablib.Dataset([b'', 'c']).jira))

main:

'| | |c|'
'| | |c|'
'| |c|'

PR:

'|0|False|c|'
'|[]|{}|c|'
"|b''|c|"

And if we run with bytes warnings: python -b repro.py:

main:

'| | |c|'
'| | |c|'
'| |c|'

PR:

'|0|False|c|'
'|[]|{}|c|'
/Users/hugo/github/tablib/src/tablib/formats/_jira.py:38: BytesWarning: Comparison between bytes and string
  delimiter.join([str(item) if item not in (None, '') else ' ' for item in row]),
/Users/hugo/github/tablib/src/tablib/formats/_jira.py:38: BytesWarning: str() on a bytes instance
  delimiter.join([str(item) if item not in (None, '') else ' ' for item in row]),
"|b''|c|"

Comment thread src/tablib/formats/_latex.py Outdated
"""

new_row = [cls._escape_tex_reserved_symbols(str(item)) if item else ''
new_row = [cls._escape_tex_reserved_symbols(str(item)) if item not in (None, '') else ''

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can simplify this:

Suggested change
new_row = [cls._escape_tex_reserved_symbols(str(item)) if item not in (None, '') else ''
new_row = ['' if item is None else cls._escape_tex_reserved_symbols(str(item))

Comment thread src/tablib/formats/_jira.py Outdated
return '{}{}{}'.format(
delimiter,
delimiter.join([str(item) if item else ' ' for item in row]),
delimiter.join([str(item) if item not in (None, '') else ' ' for item in row]),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
delimiter.join([str(item) if item not in (None, '') else ' ' for item in row]),
delimiter.join([' ' if item in (None, '') else str(item) for item in row]),

@hugovk hugovk added the changelog: Fixed For any bug fixes label Jul 8, 2026
@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.28%. Comparing base (7503651) to head (2db3ddc).
⚠️ Report is 5 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #643      +/-   ##
==========================================
+ Coverage   93.14%   93.28%   +0.13%     
==========================================
  Files          29       29              
  Lines        3226     3293      +67     
==========================================
+ Hits         3005     3072      +67     
  Misses        221      221              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread src/tablib/formats/_jira.py Outdated
)

@classmethod
def _is_empty_value(cls, item):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this to utils.py and deduplicate.

Comment thread src/tablib/formats/_jira.py Outdated
Comment on lines +46 to +49
if isinstance(item, (str, bytes, bytearray)):
return len(item) == 0
if isinstance(item, (list, tuple, dict, set, frozenset)):
return len(item) == 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a single:

if isinstance(item, (str, bytes, bytearray, list, tuple, dict, set, frozenset)) and len(item) == 0:
    return True

Comment thread src/tablib/formats/_jira.py Outdated
delimiter
)

@classmethod

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cls is never used, can remove @classmethod and cls at the call site.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog: Fixed For any bug fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants