Fix ODS export of boolean values crashing on re-import - #656
Conversation
In Python bool is a subclass of int, so a True/False cell matched the
numbers.Number branch in ODSFormat.dset_sheet and was written with
valuetype="float" and value=True. odfpy serializes that as the string
"True", and on import read_cell runs float("True") for a float cell,
raising ValueError. read_cell already has a boolean value-type reader
that the export side never exercised.
Emit booleans with valuetype="boolean" and the matching booleanvalue so
they round-trip back as Python bool.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #656 +/- ##
==========================================
+ Coverage 93.22% 93.24% +0.02%
==========================================
Files 29 29
Lines 3261 3272 +11
==========================================
+ Hits 3040 3051 +11
Misses 221 221 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
for more information, see https://pre-commit.ci
| self.assertIs(data.dict[0]['flag'], True) | ||
| self.assertIs(data.dict[1]['flag'], False) | ||
| self.assertTrue(data.dict[0]['flag']) | ||
| self.assertFalse(data.dict[1]['flag']) |
There was a problem hiding this comment.
I do not agree with this precommit change, we clearly want to test False/True here, and not the truethy/falsy values. @hugovk, do you know if we can by-pass the automatic change in this particular case?
There was a problem hiding this comment.
This is from teyit, it doesn't have # noqa way to disable per line.
Some options:
-
Flip the order:
self.assertIs(True, data.dict[0]['flag']) -
Rewrite as two checks:
assertIsInstance(flag, bool)andassertTrue(flag) -
It only matches AST constants, use variables:
for index, expected in ((0, True), (1, False)):
self.assertIs(data.dict[index]['flag'], expected)- Exclude teyit for this file in
.pre-commit-config.yaml(which has the bulk of the tests) - Disable teyit entirely
Somewhat related, I use pytest in most other projects. What do you think about using it here, or do you prefer unittest? I'd prefer pytest, but also fine with unittest too.
There was a problem hiding this comment.
I'm not a fan of automated checks, so I wouldn't be opposed to simply disable teyit entirely.
As far as testing is concerned, I've never been convicted about the real added value of pytest. For me it's more a matter of preferences, so when I can stick to the standard lib, I do. But I don't consider this as my project, so feel free to change that if you have the time for.
Use a loop over (index, expected) pairs with assertIs so the test checks the values round-trip as True/False rather than merely truthy or falsy. This also keeps the explicit-boolean intent that teyit would otherwise rewrite to assertTrue/assertFalse, since it only matches bare True/False AST constants.
|
Good point, thanks. The assertion should verify the values come back as real booleans, not merely truthy/falsy (that is the whole point of the fix, since before it they round-tripped as the string for index, expected in ((0, True), (1, False)):
self.assertIs(data.dict[index]['flag'], expected)This keeps the strict |
|
Thanks, but since @hugovk removed the teyit linter, I think you could revert to your original version, sorry! |
With the teyit pre-commit hook gone, the assertion no longer needs the variable-based workaround, so use the clearer explicit assertIs(..., True) and assertIs(..., False) form.
|
Done, reverted to the direct |
What
Exporting a
Datasetthat contains a boolean to ODS and reading it back crashes:In Python
boolis a subclass ofint, so aTrue/Falsecell satisfiesisinstance(col, numbers.Number)inODSFormat.dset_sheetand is written withvaluetype="float"andvalue=True. odfpy serializes that attribute as the string"True", and on importread_cellrunsfloat("True")for afloat-typed cell, raisingValueError.read_cellalready has avalue_type == 'boolean'reader, but the export side never produced a boolean-typed cell, so that path was unreachable for tablib's own output.Fix
Add a
boolbranch before thenumbers.Numberbranch indset_sheetthat writesvaluetype="boolean"with the matchingbooleanvalue, so booleans round-trip back as Pythonbool. Theboolcheck must come first becauseboolis a subclass ofint.Test
Added
ODSTests.test_ods_export_import_boolean, which round-tripsTrueandFalsethrough ODS and asserts they come back as the correct booleans. It fails before the change (ValueError) and passes after. The existing numeric/date round-trip test is unaffected.