Skip to content

Try to improve version checks in VOTable - #18366

Merged
tomdonaldson merged 17 commits into
astropy:mainfrom
timj:u/timj/votable-config
Jul 16, 2025
Merged

tomdonaldson merged 17 commits into
astropy:mainfrom
timj:u/timj/votable-config

Conversation

@timj

@timj timj commented Jun 28, 2025

Copy link
Copy Markdown
Contributor

Description

This pull request is to address some issues I encountered creating a VOTable programmatically.
It doesn't solve all the problems in that I'm still not clear how I'm supposed to instantiate a TimeSys object since it's not clear how I am supposed to be able to get the version compatibility config from a VOTableFile, but it does at least solve part of the problem by allowing TableElement to know its own version from the associated VOTableFile.

Fixes #18356

  • By checking this box, the PR author has requested that maintainers do NOT use the "Squash and Merge" button. Maintainers should respect this when possible; however, the final decision is at the discretion of the maintainer that merges the PR.

@github-actions

Copy link
Copy Markdown
Contributor

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.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@timj
timj force-pushed the u/timj/votable-config branch from 8dafbcd to 236f9ff Compare June 28, 2025 21:11
parameter unchanged.
"""
if config is None:
if hasattr(votable, "_get_version_checks"):

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now that I've made it so that VOTableFile always has the version checks set for it, it's probably better to make this line instead be:

Suggested change
if hasattr(votable, "_get_version_checks"):
if hasattr(votable, "config"):

and then copy the entire config over.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's a good point. I don't have a strong preference, though limiting the scope to the version configs makes it a little more explicit why this is being done and is closest to what was being done before with Field. Copying the whole config could bring in config items from the votable, which is probably not harmful and might even be good in most cases, but it does go beyond what is explicitly needed for this fix.

It's messy that version concerns aren't better encapsulated (away from configs). Without bigger refactoring, it will be hard to make configs really safe during programmatic construction of a votable, i.e., that they meet all the same implicit assumptions as when they are created during parsing. The API allows passing anything in for config in each element constructor.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Ok. I'll leave it with the explicit _get_version_checks call, although I will add "version" field into the dict so that all the version information is present.

@timj

timj commented Jun 28, 2025

Copy link
Copy Markdown
Contributor Author

It occurred to me that it would be much easier if VOTableFile was changed to:

  • Always include the version checks in its internal config (and the version number)
  • Had a public config property

Since then when constructing a VOTable programmatically (and not from parsing an XML file) you can reliably pass in config=votable.config to every constructor that needed a config and things would Just Work. I made that change and added some tests which do now make it clear how to construct a TimeSys without a warning.

@timj
timj force-pushed the u/timj/votable-config branch 3 times, most recently from 6c54d56 to bec861c Compare June 28, 2025 22:33
if element is before:
if getattr(element, attr, None) == ref:
vo_raise(
KeyError,

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.

How did this ordering error not trigger in CI for the past 14 years? 😱

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My working theory is that the code has always raised (since the code assumes an exception class but falls over when it's given a string) but nothing in CI was in fact checking the actual exception class.

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.

Even if not directly this may feed back to the issues that test coverage is not great or not sufficient (#9433 but some others, too); and that we should really switch to mandatory kwargs as much as possible (#17224)

@pllim pllim added this to the v7.2.0 milestone Jun 30, 2025
@pllim pllim added the Bug label Jun 30, 2025
@pllim

pllim commented Jun 30, 2025

Copy link
Copy Markdown
Member

Need change log. Thanks!

@timj

timj commented Jun 30, 2025

Copy link
Copy Markdown
Contributor Author

Will add a changelog once I get some feedback that I'm on the right track.

@pllim
pllim requested review from bsipocz and tomdonaldson June 30, 2025 15:37

@tomdonaldson tomdonaldson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for tackling this @timj. I think this is a very reasonable approach to addressing the missing config values. I appreciate the nice new test and the code cleanup changes you made along the way.

It looks like there are a couple minor CI items (a ruff complaint and needing a changelog over in docs/changes/io.votable). I'll be happy to approve when that's in place. Let us know if we can help with those items.

parameter unchanged.
"""
if config is None:
if hasattr(votable, "_get_version_checks"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

That's a good point. I don't have a strong preference, though limiting the scope to the version configs makes it a little more explicit why this is being done and is closest to what was being done before with Field. Copying the whole config could bring in config items from the votable, which is probably not harmful and might even be good in most cases, but it does go beyond what is explicitly needed for this fix.

It's messy that version concerns aren't better encapsulated (away from configs). Without bigger refactoring, it will be hard to make configs really safe during programmatic construction of a votable, i.e., that they meet all the same implicit assumptions as when they are created during parsing. The API allows passing anything in for config in each element constructor.

@timj
timj force-pushed the u/timj/votable-config branch from d7de74c to f7bf1ed Compare July 11, 2025 19:46
timj added 3 commits July 11, 2025 13:02
This ensures that error messages can report the actual
version without having to work it out from the or_later
values.
@timj

timj commented Jul 11, 2025

Copy link
Copy Markdown
Contributor Author

@tomdonaldson looks like all checks pass now.

@timj

timj commented Jul 11, 2025 •

Copy link
Copy Markdown
Contributor Author

@tomdonaldson whilst I was testing my upstream code, I noticed that Astropy VOTable output fails validation, at least for v1.5. Because the Resource.to_xml() method does:

https://github.com/astropy/astropy/blob/main/astropy/io/votable/tree.py#L4038-L4044

            element_sets = [
                self.coordinate_systems,
                self.time_systems,
                self.params,
                self.infos,
                self.links,
            ]

but the schema requires that INFO comes before COOSYS. This causes stilts votlint to complain:

ERROR (l.8, c.59): cvc-complex-type.2.4.a: Invalid content was found starting with element '{"http://www.ivoa.net/xml/VOTable/v1.3":INFO}'. One of '{"http://www.ivoa.net/xml/VOTable/v1.3":COOSYS, "http://www.ivoa.net/xml/VOTable/v1.3":TIMESYS, "http://www.ivoa.net/xml/VOTable/v1.3":GROUP, "http://www.ivoa.net/xml/VOTable/v1.3":PARAM, "http://www.ivoa.net/xml/VOTable/v1.3":LINK, "http://www.ivoa.net/xml/VOTable/v1.3":TABLE, "http://www.ivoa.net/xml/VOTable/v1.3":RESOURCE, WC[##other:"http://www.ivoa.net/xml/VOTable/v1.3"]}' is expected.

Trivial fix if you want me to add it on this ticket.

@timj

timj commented Jul 11, 2025

Copy link
Copy Markdown
Contributor Author

Looking at the history I think Astropy VOTables with COOSYS and INFO in the RESOURCE block have been failing validation since the beginning of time (I tracked the problem back to at least 2011).

@timj

timj commented Jul 11, 2025

Copy link
Copy Markdown
Contributor Author

Since it was a one line fix I fixed it here.

@timj

timj commented Jul 11, 2025 •

Copy link
Copy Markdown
Contributor Author

Clearly some tests hardwire the wrong order... [fixed]

Without this change the COOSYS was coming before INFO and this
was failing linting because the schema requires INFO to come
first.

ERROR (l.8, c.59): cvc-complex-type.2.4.a: Invalid content was found starting with element '{"http://www.ivoa.net/xml/VOTable/v1.3":INFO}'.
One of '{"http://www.ivoa.net/xml/VOTable/v1.3":COOSYS,
"http://www.ivoa.net/xml/VOTable/v1.3":TIMESYS,
"http://www.ivoa.net/xml/VOTable/v1.3":GROUP,
"http://www.ivoa.net/xml/VOTable/v1.3":PARAM,
"http://www.ivoa.net/xml/VOTable/v1.3":LINK,
"http://www.ivoa.net/xml/VOTable/v1.3":TABLE,
"http://www.ivoa.net/xml/VOTable/v1.3":RESOURCE,
WC[##other:"http://www.ivoa.net/xml/VOTable/v1.3"]}' is expected.
@timj
timj force-pushed the u/timj/votable-config branch from ca8ae4d to ede16c4 Compare July 11, 2025 22:12

@tomdonaldson tomdonaldson left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@timj Nice catch and thanks for the fix. I'm glad you've been pushing on the votable creation and writing aspects of this module, and that (at least so far) the issues have been manageable. Clearly those use cases could use a lot more test coverage.

The INFO elements have been a subject of some confusion for a while. It's always been weird that they have been allowed both at the beginning and end of VOTABLE and RESOURCE, but that in VOTABLE the order of the first one didn't matter. Another area of confusion was that up through v1.3, the usually helpful figures summarizing the elements got the ordering requirement wrong in RESOURCE (subsequently fixed in v1.4).

This all looks good now.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

VOTable serialization is not setting version configuration reliably

4 participants