| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
|
Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.
|
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Summary
A VOTable FIELD declares its datatype explicitly, but reading one into a QTable threw that away for any integer column that also carried a unit: the column was converted to Quantity, which casts to float64, silently changing every value above 2**53. Object identifiers are routinely that large, so the reported Euclid object_id column came back with wrong values and no warning, while the same file read into a plain Table was exact.
TableElement.to_table() now sets the QTable preserve_quantity_dtype attribute added in #20247, so the declared datatype survives the conversion to Quantity. A long column with unit="ct" becomes an integer Quantity in counts rather than a float one, which is the behavior discussed in the issue.
Fixes #17963.
Based on #20247 — the first commit here is that PR, so this one should be merged after it. Only the second commit is new.
Backwards compatibility
Reading a VOTable into a QTable changes the dtype of integer columns that have a unit, from float64 to the declared integer type. That is the bug being fixed, and it is the only value-level change: everything else, including plain Table reads, is untouched. Code that assumed a float column there will now see an integer one — an integer Quantity supports the same operations, but true division and in-place float assignment behave as they do for any integer array.
The other visible change is the extra __attributes__ entry in the meta of every table produced by to_table(), as described above.
AI disclosure
This PR is AI-generated using Claude Opus 5. I have examined the code and fully understand the changes in tree.py and the tests.
Details
Click to expandSet the preserve_quantity_dtype table attribute when converting a VOTable to an astropy table
1. Where the cast happens
io.votable itself was never lossy. TableElement.to_table() returns a Table whose columns hold the exact integer values from the file. The cast happens afterwards, in QTable._convert_col_for_table(), which turns any Column with a unit into a Quantity — and Quantity.__new__ defaults to dtype=np.inexact. Since the reader is registered for Table, the unified I/O layer produces the QTable itself, with out = cls(out, copy=False) in table/connect.py, well after the reader has returned.
That is why the fix cannot be a change to the values or dtypes the reader produces — they are already right. What the reader has to do is tell the eventual QTable conversion to leave the dtype alone, and the only channel that survives from the reader to that conversion is the table meta.
2. Implementation
Four lines in TableElement.to_table(), in astropy/io/votable/tree.py:
preserve_quantity_dtype is the TableAttribute added in #20247; like every TableAttribute its value lives in meta['__attributes__'], which is documented behavior of TableAttribute rather than an internal detail being poked at.
This goes in to_table() rather than in connect.py::read_table_votable() so that it also covers code that goes through the votable API directly:
That is the path pyvo and astroquery use — DALResultsTable.to_qtable() in the issue report — so fixing only the unified-I/O entry point would have left the reporter's actual call broken.
3. Testing
Two tests in astropy/io/votable/tests/test_table.py, built on a VOTABLE_INT_WITH_UNIT sample modeled on the file in the issue: object_id is datatype="long" unit="NA" with a VALUES null, counts is datatype="long" unit="ct", ra is datatype="double" unit="deg".
Two things worth knowing if you touch these tests: reading unit="NA" emits nothing, but writing it back does emit W50: Invalid unit string 'NA', so only the write is wrapped in pytest.warns. And unit="count" is not valid VOUnit — it parses to UnrecognizedUnit, so the sample uses ct.
Also checked by hand against the file from the issue:
astropy/io/votable, table, io/ascii, io/misc, units, utils, plus docs/table and docs/io doctests: 10522 passed, 206 skipped, 29 xfailed.
4. Trade-offs and what is not addressed
Every table from to_table() now carries meta['__attributes__'] = {'preserve_quantity_dtype': True}, whether or not it has an integer column with a unit. This is visible in t.meta and propagates to formats that preserve meta — an ECSV written from a VOTable-read table gains a __attributes__: {preserve_quantity_dtype: true} line. Writing back to VOTable drops it, since TableElement.from_table() reads only the ID/name/ref/ucd/utype/description keys.
The alternative is to set it only when a column actually is an integer with a unit, which keeps meta clean in the common case. I went with the unconditional form because the guarantee comes from the format — VOTable always declares datatype — rather than from the contents of a particular file. Easy to change if reviewers prefer the narrower version.
Not addressed here, from the issue discussion: