Repository: astropy/astropy

Issue:
KeyError: 'version_1_3_or_later' when parsing certain VOTables
### Description

When parsing VOTables (for instance) VOTables with empty integer literals in some places (e.g., MIN value="" in a VALUES element), astropy crashes with

```
KeyError: 'version_1_3_or_later'
```

### Expected behavior

Well, you *could* argue that at least some such VOTables should be rejected (a NULL value in a MIN, indeed, does not make sense) with a sensible error message; with the patch proposed in the accompanying PR, astropy emits a warning.  I notice in passing that the reproducer table passes stilts votlint.  I also note in passing that against the warning, a value="" is accepted in a PARAM (as it should, as this is the way to express NULLs).

My fix in the accompanying PR just stops the crashing, leading to a workable table.

### How to Reproduce

Try

```python
from astropy import table
table.Table.read("with-empty-min.vot", format="votable")
```

with the table from https://docs.g-vo.org/with-empty-min.vot

### Versions

Both current HEAD and what's in Debian bookworm.


Discussion:
github-actions[bot]: 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](https://docs.astropy.org/en/latest/development/codeguide.html)?
  - [ ] Are tests added/updated as required? If so, do they follow the [Astropy testing guidelines](https://docs.astropy.org/en/latest/development/testguide.html)?
  - [ ] Are docs added/updated as required? If so, do they follow the [Astropy documentation guidelines](https://docs.astropy.org/en/latest/development/docguide.html)?
  - [ ] Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for [rebase](https://docs.astropy.org/en/latest/development/development_details.html#rebase-if-necessary) and [squash](https://docs.astropy.org/en/latest/development/development_details.html#squash-if-necessary).
  - [ ] 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](https://docs.astropy.org/en/latest/development/development_details.html#pre-commit).
  - [ ] 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.
github-actions[bot]: 👋 Thank you for your draft pull request! Do you know that you can use `[ci skip]` or `[skip ci]` in your commit messages to skip running continuous integration tests until you are ready?
pllim: I need to find a way to test this by designing a test that fails on `main` but not in this PR, preferably without introducing yet another data file.

Patch:
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index de825ac072..d7c1d8261f 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -47,8 +47,10 @@ jobs:
       # the build isolation and explicitly install the latest developer version of numpy as well as
       # the latest stable versions of all other build-time dependencies.
       env: |
-        CIBW_BEFORE_BUILD: '${{ ((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build all wheels')) && 'pip install --pre --extra-index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple setuptools setuptools_scm cython numpy>=0.0.dev0 extension-helpers') || '' }}'
+        CIBW_BEFORE_BUILD: '${{ ((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build all wheels')) && 'pip install setuptools setuptools_scm cython numpy extension-helpers') || '' }}'
         CIBW_BUILD_FRONTEND: '${{ ((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build all wheels')) && 'pip; args: --no-build-isolation') || 'build' }}'
+        CIBW_ENVIRONMENT: '${{ ((github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' || contains(github.event.pull_request.labels.*.name, 'Build all wheels')) && 'PIP_PRE=1 PIP_EXTRA_INDEX_URL=https://pypi.anaconda.org/scientific-python-nightly-wheels/simple') || '' }}'
+        CIBW_ALLOW_EMPTY: true
 
       test_extras: test
       # FIXME: we exclude the test_data_out_of_range test since it
@@ -62,16 +64,19 @@ jobs:
         - cp310-manylinux_x86_64
         - cp311-manylinux_x86_64
         - cp312-manylinux_x86_64
+        - cp313-manylinux_x86_64
 
         # Note that following wheels are not currently tested:
 
         - cp310-manylinux_aarch64
         - cp311-manylinux_aarch64
         - cp312-manylinux_aarch64
+        - cp313-manylinux_aarch64
 
         - cp310-musllinux_x86_64
         - cp311-musllinux_x86_64
         - cp312-musllinux_x86_64
+        - cp313-musllinux_x86_64
 
         # MacOS X wheels - as noted in https://github.com/astropy/astropy/pull/12379 we deliberately
         # do not build universal2 wheels.
@@ -79,20 +84,24 @@ jobs:
         - cp310*macosx_x86_64
         - cp311*macosx_x86_64
         - cp312*macosx_x86_64
+        - cp313*macosx_x86_64
 
         - cp310*macosx_arm64
         - cp311*macosx_arm64
         - cp312*macosx_arm64
+        - cp313*macosx_arm64
 
         # Windows wheels
 
         - cp310*win32
         - cp311*win32
         - cp312*win32
+        - cp313*win32
 
         - cp310*win_amd64
         - cp311*win_amd64
         - cp312*win_amd64
+        - cp313*win_amd64
 
     secrets:
       pypi_token: ${{ secrets.pypi_token }}
diff --git a/astropy/io/votable/tree.py b/astropy/io/votable/tree.py
index 036ca8af73..ed85aeffcf 100644
--- a/astropy/io/votable/tree.py
+++ b/astropy/io/votable/tree.py
@@ -1053,7 +1053,7 @@ class Values(Element, _IDProperty):
     @min.setter
     def min(self, min):
         if hasattr(self._field, "converter") and min is not None:
-            self._min = self._field.converter.parse(min)[0]
+            self._min = self._field.converter.parse(min, config=self._config)[0]
         else:
             self._min = min
 
@@ -1089,7 +1089,7 @@ class Values(Element, _IDProperty):
     @max.setter
     def max(self, max):
         if hasattr(self._field, "converter") and max is not None:
-            self._max = self._field.converter.parse(max)[0]
+            self._max = self._field.converter.parse(max, config=self._config)[0]
         else:
             self._max = max
 
diff --git a/docs/changes/io.votable/16830.bugfix.rst b/docs/changes/io.votable/16830.bugfix.rst
new file mode 100644
index 0000000000..d30a2a9ff9
--- /dev/null
+++ b/docs/changes/io.votable/16830.bugfix.rst
@@ -0,0 +1,1 @@
+Fix KeyError when parsing certain VOTables.
diff --git a/tox.ini b/tox.ini
index b3af161492..18532d2fd9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 envlist =
-    py{310,311,312,dev}-test{,-recdeps,-alldeps,-oldestdeps,-devdeps,-devinfra,-predeps,-numpy123,-numpy124,-numpy125,-mpl360}{,-cov}{,-clocale}{,-fitsio}{,-noscipy}
+    py{310,311,312,313,dev}-test{,-recdeps,-alldeps,-oldestdeps,-devdeps,-devinfra,-predeps,-numpy123,-numpy124,-numpy125,-mpl360}{,-cov}{,-clocale}{,-fitsio}{,-noscipy}
     # Only these two exact tox environments have corresponding figure hash files.
     py310-test-image-mpl360-cov
     py310-test-image-mpldev-cov
@@ -18,6 +18,7 @@ setenv =
     cov: CFLAGS = --coverage -fno-inline-functions -O0
     # opt-in faster coverage when available (requires Python >= 3.12)
     py312: COVERAGE_CORE=sysmon
+    py313: COVERAGE_CORE=sysmon
     image: MPLFLAGS = -m "mpl_image_compare" --mpl --mpl-generate-summary=html --mpl-results-path={toxinidir}/results --mpl-hash-library={toxinidir}/astropy/tests/figures/{envname}.json --mpl-baseline-path=https://raw.githubusercontent.com/astropy/astropy-figure-tests/astropy-main/figures/{envname}/ --remote-data -W ignore::DeprecationWarning
     !image: MPLFLAGS =
     clocale: LC_CTYPE = C.ascii


Test patch:
diff --git a/astropy/io/votable/tests/test_tree.py b/astropy/io/votable/tests/test_tree.py
index 7fd771cef7..224d189a33 100644
--- a/astropy/io/votable/tests/test_tree.py
+++ b/astropy/io/votable/tests/test_tree.py
@@ -90,6 +90,31 @@ def test_namespace_warning():
     parse(io.BytesIO(good_namespace_13), verify="exception")
 
 
+def test_votable_values_empty_min_max():
+    """Regression test for https://github.com/astropy/astropy/issues/16825"""
+    with_empty_minmax = b"""<VOTABLE xmlns="http://www.ivoa.net/xml/VOTable/v1.3" version="1.4">
+        <RESOURCE type="results">
+          <TABLE name="main">
+            <PARAM name="break" datatype="int" value=""/>
+          <FIELD ID="hd" datatype="int" name="hd" ucd="meta.id;meta.main">
+            <DESCRIPTION>HD number for this object</DESCRIPTION>
+            <VALUES null="-2147483648">
+              <MIN value=""/>
+              <MAX value=""/>
+            </VALUES>
+          </FIELD>
+          <DATA>
+            <BINARY>
+              <STREAM encoding="base64">AAMNIg==</STREAM>
+            </BINARY>
+          </DATA>
+        </TABLE>
+      </RESOURCE>
+    </VOTABLE>
+    """
+    parse(io.BytesIO(with_empty_minmax), verify="exception")
+
+
 def test_version():
     """
     VOTableFile.__init__ allows versions of '1.1', '1.2', '1.3' and '1.4'.
diff --git a/astropy/modeling/tests/test_parameters.py b/astropy/modeling/tests/test_parameters.py
index 6137977afa..1a5e085736 100644
--- a/astropy/modeling/tests/test_parameters.py
+++ b/astropy/modeling/tests/test_parameters.py
@@ -6,6 +6,7 @@ Tests models.parameters
 
 import functools
 import itertools
+import sys
 import unittest.mock as mk
 
 import numpy as np
@@ -754,6 +755,14 @@ class TestParameters:
         param._internal_value = 4
         assert param._raw_value == 4
 
+    @pytest.mark.xfail(
+        sys.version_info >= (3, 13),
+        reason=(
+            "this test is known-broken in Python 3.13.0rc1 "
+            "and we don't want it to start passing silently. "
+            "Reference https://github.com/astropy/astropy/pull/16659"
+        ),
+    )
     def test__create_value_wrapper(self):
         param = Parameter(name="test", default=[1, 2, 3, 4])