Repository: astropy/astropy

Issue:
`support_nddata` errors at runtime if `data` is the first kwarg only argument
### Description

This doesn't work (even though arguably it should):
```python
>>>  @support_nddata
.... def put_nddata_here3(*, meta, data):
....     print(data)
```
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nix/store/n2hmfmwh05bkcv5ki3vqv17dipf698l1-python3-3.11.9-env/lib/python3.11/site-packages/astropy/nddata/decorators.py", line 292, in support_nddata
    return support_nddata_decorator(_func)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/n2hmfmwh05bkcv5ki3vqv17dipf698l1-python3-3.11.9-env/lib/python3.11/site-packages/astropy/nddata/decorators.py", line 174, in support_nddata_decorator
    raise ValueError(
ValueError: Can only wrap functions whose first positional argument is `data`
```

This however doesn't raise the same error, but instead errors at runtime:

```python
>>>  @support_nddata
.... def put_nddata_here(*, data):
....     print(data)
>>>  put_nddata_here(data=np.array(0))
```
```
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/nix/store/n2hmfmwh05bkcv5ki3vqv17dipf698l1-python3-3.11.9-env/lib/python3.11/site-packages/astropy/nddata/decorators.py", line 182, in wrapper
    bound_args = signature(func).bind(data, *args, **kwargs)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/h723hb9m43lybmvfxkk6n7j4v664qy7b-python3-3.11.9/lib/python3.11/inspect.py", line 3195, in bind
    return self._bind(args, kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/nix/store/h723hb9m43lybmvfxkk6n7j4v664qy7b-python3-3.11.9/lib/python3.11/inspect.py", line 3121, in _bind
    raise TypeError(
TypeError: too many positional arguments

Discussion:

Patch:
diff --git a/.github/workflows/check_milestone.yml b/.github/workflows/check_milestone.yml
index 22b1ad41df..a8c10a4b38 100644
--- a/.github/workflows/check_milestone.yml
+++ b/.github/workflows/check_milestone.yml
@@ -2,7 +2,7 @@ name: Check PR milestone
 
 on:
   pull_request:
-    types: [milestoned, demilestoned]
+    types: [synchronize, milestoned, demilestoned]
 
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
diff --git a/astropy/cosmology/_io/mapping.py b/astropy/cosmology/_io/mapping.py
index 2e09a1c8e5..c7da6ef260 100644
--- a/astropy/cosmology/_io/mapping.py
+++ b/astropy/cosmology/_io/mapping.py
@@ -38,8 +38,7 @@ identical to the |Planck18| cosmology from which it was generated.
 
     >>> cosmo = Cosmology.from_format(cm, format="mapping")
     >>> cosmo
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                  Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
 How did |Cosmology.from_format| know to return an instance of the |FlatLambdaCDM| class?
 The mapping object has a field ``cosmology`` which can be either the string name of the
@@ -57,22 +56,20 @@ argument to |Cosmology.from_format|.
     >>> del cm["cosmology"]  # remove cosmology class
 
     >>> Cosmology.from_format(cm, cosmology="FlatLambdaCDM")
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                  Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
 To the second point, we can use specific cosmology class to parse the data.
 
     >>> from astropy.cosmology import FlatLambdaCDM
     >>> FlatLambdaCDM.from_format(cm)
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                  Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
 Also, the class' default parameter values are used to fill in any information missing in
 the data. For example, if ``Tcmb0`` is missing, the default value of 0.0 K is used.
 
     >>> del cm["Tcmb0"]  # show FlatLambdaCDM provides default
     >>> FlatLambdaCDM.from_format(cm)
-    FlatLambdaCDM(name="Planck18", H0=..., Tcmb0=0.0 K, ...)
+    FlatLambdaCDM(name='Planck18', H0=..., Tcmb0=<Quantity 0. K>, ...)
 
 If instead of *missing* information, there is *extra* information, there are a few
 options. The first is to use the ``move_to_meta`` keyword argument to move fields that
@@ -81,7 +78,7 @@ are not in the Cosmology constructor to the Cosmology's metadata.
     >>> cm2 = cm | {"extra": 42, "cosmology": "FlatLambdaCDM"}
     >>> cosmo = Cosmology.from_format(cm2, move_to_meta=True)
     >>> cosmo.meta
-    OrderedDict([('extra', 42), ...])
+    {'extra': 42, ...}
 
 Alternatively, the ``rename`` keyword argument can be used to rename keys in the mapping
 to fields of the |Cosmology|. This is crucial when the mapping has keys that are not
@@ -93,8 +90,7 @@ valid arguments to the |Cosmology| constructor.
 
     >>> rename = {'cosmo_cls': 'cosmology', 'cosmo_name': 'name'}
     >>> Cosmology.from_format(cm3, rename=rename)
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                    Tcmb0=0.0 K, Neff=3.046, m_nu=None, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 0. K>, Neff=3.046, m_nu=None, Ob0=0.04897)
 
 Let's take a closer look at |Cosmology.to_format|, because there a lot of options, to
 tailor the output to specific needs.
@@ -103,11 +99,7 @@ The dictionary type may be changed with the ``cls`` keyword argument:
 
     >>> from collections import OrderedDict
     >>> Planck18.to_format('mapping', cls=OrderedDict)
-    OrderedDict([('cosmology', <class 'astropy.cosmology...FlatLambdaCDM'>),
-        ('name', 'Planck18'), ('H0', <Quantity 67.66 km / (Mpc s)>),
-        ('Om0', 0.30966), ('Tcmb0', <Quantity 2.7255 K>), ('Neff', 3.046),
-        ('m_nu', <Quantity [0.  , 0.  , 0.06] eV>), ('Ob0', 0.04897),
-        ('meta', ...
+    OrderedDict({'cosmology': <class 'astropy.cosmology.flrw.lambdacdm.FlatLambdaCDM'>, 'name': 'Planck18', 'H0': <Quantity 67.66 km / (Mpc s)>, 'Om0': 0.30966, 'Tcmb0': <Quantity 2.7255 K>, 'Neff': 3.046, 'm_nu': <Quantity [0.  , 0.  , 0.06] eV>, 'Ob0': 0.04897, 'meta': ...
 
 Sometimes it is more useful to have the name of the cosmology class, not the type
 itself. The keyword argument ``cosmology_as_str`` may be used:
@@ -235,31 +227,27 @@ def from_mapping(
 
         >>> cosmo = Cosmology.from_format(cm, format="mapping")
         >>> cosmo
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     The ``cosmology`` field can be omitted if the cosmology class (or its string name)
     is passed as the ``cosmology`` keyword argument to |Cosmology.from_format|.
 
         >>> del cm["cosmology"]  # remove cosmology class
         >>> Cosmology.from_format(cm, cosmology="FlatLambdaCDM")
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     Alternatively, specific cosmology classes can be used to parse the data.
 
         >>> from astropy.cosmology import FlatLambdaCDM
         >>> FlatLambdaCDM.from_format(cm)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     When using a specific cosmology class, the class' default parameter values are used
     to fill in any missing information.
 
         >>> del cm["Tcmb0"]  # show FlatLambdaCDM provides default
         >>> FlatLambdaCDM.from_format(cm)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=0.0 K, Neff=3.046, m_nu=None, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 0. K>, Neff=3.046, m_nu=None, Ob0=0.04897)
 
     The ``move_to_meta`` keyword argument can be used to move fields that are not in the
     Cosmology constructor to the Cosmology's metadata. This is useful when the
@@ -268,7 +256,7 @@ def from_mapping(
         >>> cm2 = cm | {"extra": 42, "cosmology": "FlatLambdaCDM"}
         >>> cosmo = Cosmology.from_format(cm2, move_to_meta=True)
         >>> cosmo.meta
-        OrderedDict([('extra', 42), ...])
+        {'extra': 42, ...}
 
     The ``rename`` keyword argument can be used to rename keys in the mapping to fields
     of the |Cosmology|. This is crucial when the mapping has keys that are not valid
@@ -280,8 +268,7 @@ def from_mapping(
 
         >>> rename = {'cosmo_cls': 'cosmology', 'cosmo_name': 'name'}
         >>> Cosmology.from_format(cm3, rename=rename)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=0.0 K, Neff=3.046, m_nu=None, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 0. K>, Neff=3.046, m_nu=None, Ob0=0.04897)
     """
     # Rename keys, if given a ``renames`` dict.
     # Also, make a copy of the mapping, so we can pop from it.
@@ -371,11 +358,7 @@ def to_mapping(
 
         >>> from collections import OrderedDict
         >>> Planck18.to_format('mapping', cls=OrderedDict)
-        OrderedDict([('cosmology', <class 'astropy.cosmology...FlatLambdaCDM'>),
-          ('name', 'Planck18'), ('H0', <Quantity 67.66 km / (Mpc s)>),
-          ('Om0', 0.30966), ('Tcmb0', <Quantity 2.7255 K>), ('Neff', 3.046),
-          ('m_nu', <Quantity [0.  , 0.  , 0.06] eV>), ('Ob0', 0.04897),
-          ('meta', ...
+        OrderedDict({'cosmology': <class 'astropy.cosmology.flrw.lambdacdm.FlatLambdaCDM'>, 'name': 'Planck18', 'H0': <Quantity 67.66 km / (Mpc s)>, 'Om0': 0.30966, 'Tcmb0': <Quantity 2.7255 K>, 'Neff': 3.046, 'm_nu': <Quantity [0.  , 0.  , 0.06] eV>, 'Ob0': 0.04897, 'meta': {'Oc0': 0.2607, 'n': 0.9665, 'sigma8': 0.8102, 'tau': 0.0561, 'z_reion': <Quantity 7.82 redshift>, 't0': <Quantity 13.787 Gyr>, 'reference': 'Planck Collaboration 2018, 2020, A&A, 641, A6  (Paper VI), Table 2 (TT, TE, EE + lowE + lensing + BAO)'}})
 
     Sometimes it is more useful to have the name of the cosmology class, not
     the type itself. The keyword argument ``cosmology_as_str`` may be used:
diff --git a/astropy/cosmology/_io/row.py b/astropy/cosmology/_io/row.py
index d3ffbff28e..8862cef4d6 100644
--- a/astropy/cosmology/_io/row.py
+++ b/astropy/cosmology/_io/row.py
@@ -18,15 +18,14 @@ The cosmological class and other metadata, e.g. a paper reference, are in
 the Table's metadata.
 
     >>> cr.meta
-    OrderedDict([('Oc0', 0.2607), ('n', 0.9665), ...])
+    {'Oc0': 0.2607, 'n': 0.9665, ...}
 
 Now this row can be used to load a new cosmological instance identical
 to the ``Planck18`` cosmology from which it was generated.
 
     >>> cosmo = Cosmology.from_format(cr, format="astropy.row")
     >>> cosmo
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                    Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
 For more information on the argument options, see :ref:`cosmology_io_builtin-table`.
 """
@@ -105,8 +104,7 @@ def from_row(
 
         >>> cosmo = Cosmology.from_format(cr, format="astropy.row")
         >>> cosmo
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     The ``cosmology`` information (column or metadata) may be omitted if the cosmology
     class (or its string name) is passed as the ``cosmology`` keyword argument to
@@ -114,23 +112,20 @@ def from_row(
 
         >>> del cr.columns["cosmology"]  # remove cosmology from metadata
         >>> Cosmology.from_format(cr, cosmology="FlatLambdaCDM")
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     Alternatively, specific cosmology classes can be used to parse the data.
 
         >>> from astropy.cosmology import FlatLambdaCDM
         >>> FlatLambdaCDM.from_format(cr)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     When using a specific cosmology class, the class' default parameter values are used
     to fill in any missing information.
 
         >>> del cr.columns["Tcmb0"]  # show FlatLambdaCDM provides default
         >>> FlatLambdaCDM.from_format(cr)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=0.0 K, Neff=3.046, m_nu=None, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 0. K>, Neff=3.046, m_nu=None, Ob0=0.04897)
 
     If a `~astropy.table.Row` object has columns that do not match the fields of the
     `~astropy.cosmology.Cosmology` class, they can be mapped using the ``rename``
@@ -229,7 +224,7 @@ def to_row(
     Table's metadata.
 
         >>> cr.meta
-        OrderedDict([('Oc0', 0.2607), ('n', 0.9665), ...])
+        {'Oc0': 0.2607, 'n': 0.9665, ...}
 
     To move the cosmology class from a column to the Table's metadata, set the
     ``cosmology_in_meta`` argument to `True`:
diff --git a/astropy/cosmology/_io/table.py b/astropy/cosmology/_io/table.py
index 058aa4c3e5..42e3d3d041 100644
--- a/astropy/cosmology/_io/table.py
+++ b/astropy/cosmology/_io/table.py
@@ -23,7 +23,7 @@ The cosmological class and other metadata, e.g. a paper reference, are in the Ta
 metadata.
 
     >>> ct.meta
-    OrderedDict([..., ('cosmology', 'FlatLambdaCDM')])
+    {..., 'cosmology': 'FlatLambdaCDM'}
 
 
 Cosmology supports the astropy Table-like protocol (see :ref:`Table-like Objects`) to
@@ -79,8 +79,7 @@ it was generated.
 
     >>> cosmo = Cosmology.from_format(ct, format="astropy.table")
     >>> cosmo
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                    Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
     >>> cosmo == Planck18
     True
 
@@ -90,23 +89,20 @@ The ``cosmology`` information (row or metadata) may be omitted if the cosmology
 
     >>> del ct.meta["cosmology"]  # remove cosmology from metadata
     >>> Cosmology.from_format(ct, cosmology="FlatLambdaCDM")
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                    Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+    FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
 Alternatively, specific cosmology classes can be used to parse the data.
 
     >>> from astropy.cosmology import FlatLambdaCDM
     >>> FlatLambdaCDM.from_format(ct)
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                    Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+     FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
 When using a specific cosmology class, the class' default parameter values are used to
 fill in any missing information.
 
     >>> del ct["Tcmb0"]  # show FlatLambdaCDM provides default
     >>> FlatLambdaCDM.from_format(ct)
-    FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                    Tcmb0=0.0 K, Neff=3.046, m_nu=None, Ob0=0.04897)
+     FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 0. K>, Neff=3.046, m_nu=None, Ob0=0.04897)
 
 For tables with multiple rows of cosmological parameters, the ``index`` argument is
 needed to select the correct row. The index can be an integer for the row number or, if
@@ -237,8 +233,7 @@ def from_table(
 
         >>> cosmo = Cosmology.from_format(ct, format="astropy.table")
         >>> cosmo
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     The ``cosmology`` information (column or metadata) may be omitted if the cosmology
     class (or its string name) is passed as the ``cosmology`` keyword argument to
@@ -246,23 +241,20 @@ def from_table(
 
         >>> del ct.meta["cosmology"]  # remove cosmology from metadata
         >>> Cosmology.from_format(ct, cosmology="FlatLambdaCDM")
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+        FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     Alternatively, specific cosmology classes can be used to parse the data.
 
         >>> from astropy.cosmology import FlatLambdaCDM
         >>> FlatLambdaCDM.from_format(ct)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=2.7255 K, Neff=3.046, m_nu=[0. 0. 0.06] eV, Ob0=0.04897)
+         FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 2.7255 K>, Neff=3.046, m_nu=<Quantity [0.  , 0.  , 0.06] eV>, Ob0=0.04897)
 
     When using a specific cosmology class, the class' default parameter values are used
     to fill in any missing information.
 
         >>> del ct["Tcmb0"]  # show FlatLambdaCDM provides default
         >>> FlatLambdaCDM.from_format(ct)
-        FlatLambdaCDM(name="Planck18", H0=67.66 km / (Mpc s), Om0=0.30966,
-                      Tcmb0=0.0 K, Neff=3.046, m_nu=None, Ob0=0.04897)
+         FlatLambdaCDM(name='Planck18', H0=<Quantity 67.66 km / (Mpc s)>, Om0=0.30966, Tcmb0=<Quantity 0. K>, Neff=3.046, m_nu=None, Ob0=0.04897)
 
     For tables with multiple rows of cosmological parameters, the ``index`` argument is
     needed to select the correct row. The index can be an integer for the row number or,
@@ -406,7 +398,7 @@ def to_table(
     the Table's metadata.
 
         >>> ct.meta
-        OrderedDict([..., ('cosmology', 'FlatLambdaCDM')])
+        {..., 'cosmology': 'FlatLambdaCDM'}
 
     To move the cosmology class from the metadata to a Table column, set the
     ``cosmology_in_meta`` argument to `False`:
diff --git a/astropy/nddata/decorators.py b/astropy/nddata/decorators.py
index a8a20f13e6..91d84d2208 100644
--- a/astropy/nddata/decorators.py
+++ b/astropy/nddata/decorators.py
@@ -145,41 +145,22 @@ def support_nddata(
     all_returns = returns + keeps
 
     def support_nddata_decorator(func):
-        # Find out args and kwargs
-        func_args, func_kwargs = [], []
-        sig = signature(func).parameters
-        for param_name, param in sig.items():
+        func_sig = signature(func)
+        func_parameters = func_sig.parameters
+        for param in func_parameters.values():
             if param.kind in (param.VAR_POSITIONAL, param.VAR_KEYWORD):
                 raise ValueError("func may not have *args or **kwargs.")
-            try:
-                if param.default == param.empty:
-                    func_args.append(param_name)
-                else:
-                    func_kwargs.append(param_name)
-            # The comparison to param.empty may fail if the default is a
-            # numpy array or something similar. So if the comparison fails then
-            # it's quite obvious that there was a default and it should be
-            # appended to the "func_kwargs".
-            except ValueError as exc:
-                if (
-                    "The truth value of an array with more than one element "
-                    "is ambiguous." in str(exc)
-                ):
-                    func_kwargs.append(param_name)
-                else:
-                    raise
-
-        # First argument should be data
-        if not func_args or func_args[0] != attr_arg_map.get("data", "data"):
+
+        data_arg_name = attr_arg_map.get("data", "data")
+        if data_arg_name not in func_parameters.keys():
             raise ValueError(
-                "Can only wrap functions whose first positional "
-                "argument is `{}`"
-                "".format(attr_arg_map.get("data", "data"))
+                f"Can only wrap a function with a {data_arg_name} argument."
             )
 
         @wraps(func)
-        def wrapper(data, *args, **kwargs):
-            bound_args = signature(func).bind(data, *args, **kwargs)
+        def wrapper(*args, **kwargs):
+            bound_args = func_sig.bind_partial(*args, **kwargs)
+            data = bound_args.arguments[data_arg_name]
             unpack = isinstance(data, accepts)
             input_data = data
             ignored = []
@@ -187,7 +168,6 @@ def support_nddata(
                 raise TypeError(
                     f"Only NDData sub-classes that inherit from {accepts.__name__}"
                     " can be used by this function"
-                    ""
                 )
 
             # If data is an NDData instance, we can try and find properties
@@ -208,7 +188,7 @@ def support_nddata(
                         continue
                     # Warn if the property is set but not used by the function.
                     propmatch = attr_arg_map.get(prop, prop)
-                    if propmatch not in func_kwargs:
+                    if propmatch not in func_parameters:
                         ignored.append(prop)
                         continue
 
@@ -228,13 +208,23 @@ def support_nddata(
                         # indistinguishable from an explicitly passed kwarg
                         # and it won't notice that and use the attribute of the
                         # NDData.
-                        if propmatch in func_args or (
-                            propmatch in func_kwargs
-                            and (
+                        warn_conflict = False
+                        try:
+                            if (
                                 bound_args.arguments[propmatch]
-                                is not sig[propmatch].default
-                            )
-                        ):
+                                is not func_parameters[propmatch].default
+                            ):
+                                warn_conflict = True
+                        except ValueError as exc:
+                            if (
+                                "The truth value of an array with more than one element "
+                                "is ambiguous." in str(exc)
+                            ):
+                                warn_conflict = False
+                            else:
+                                raise
+
+                        if warn_conflict:
                             warnings.warn(
                                 "Property {} has been passed explicitly and "
                                 "as an NDData property{}, using explicitly "
@@ -246,9 +236,9 @@ def support_nddata(
                             )
                             continue
                     # Otherwise use the property as input for the function.
-                    kwargs[propmatch] = value
+                    bound_args.arguments[propmatch] = value
                 # Finally, replace data by the data attribute
-                data = data.data
+                bound_args.arguments[data_arg_name] = data.data
 
                 if ignored:
                     warnings.warn(
@@ -258,7 +248,10 @@ def support_nddata(
                         AstropyUserWarning,
                     )
 
-            result = func(data, *args, **kwargs)
+            # Need to apply defaults so that any positional only arguments are
+            # passed as positional properly
+            bound_args.apply_defaults()
+            result = func(*bound_args.args, **bound_args.kwargs)
 
             if unpack and repack:
                 # If there are multiple required returned arguments make sure
diff --git a/astropy/table/table.py b/astropy/table/table.py
index adf76f6f9b..f3937c8da2 100644
--- a/astropy/table/table.py
+++ b/astropy/table/table.py
@@ -93,6 +93,8 @@ __doctest_skip__ = [
     "Table.convert_unicode_to_bytestring",
 ]
 
+__doctest_requires__ = {("Table.from_pandas", "Table.to_pandas"): ["pandas"]}
+
 _pprint_docs = """
     {__doc__}
 
@@ -406,7 +408,7 @@ class TableAttribute(MetaAttribute):
       >>> t.identifier
       10
       >>> t.meta
-      OrderedDict([('__attributes__', {'identifier': 10})])
+      {'__attributes__': {'identifier': 10}}
     """
 
 
diff --git a/astropy/units/__init__.py b/astropy/units/__init__.py
index 318d47e71e..3bef9ecf22 100644
--- a/astropy/units/__init__.py
+++ b/astropy/units/__init__.py
@@ -54,7 +54,6 @@ __all__ += astrophys.__all__
 __all__ += physical.__all__
 __all__ += misc.__all__
 __all__ += photometric.__all__
-__all__ += structured.__all__
 __all__ += equivalencies.__all__
 __all__ += function.__all__
 # We sort `__all__` for the docs. We use `globals` to avoid confusing any static
diff --git a/astropy/visualization/interval.py b/astropy/visualization/interval.py
index e947a9df9a..18b165b95c 100644
--- a/astropy/visualization/interval.py
+++ b/astropy/visualization/interval.py
@@ -210,8 +210,6 @@ class ZScaleInterval(BaseInterval):
     """
     Interval based on IRAF's zscale.
 
-    https://iraf.net/forum/viewtopic.php?showtopic=134139
-
     Original implementation:
     https://github.com/spacetelescope/stsci.numdisplay/blob/master/lib/stsci/numdisplay/zscale.py
 
diff --git a/docs/changes/nddata/17281.bugfix.rst b/docs/changes/nddata/17281.bugfix.rst
new file mode 100644
index 0000000000..a226c98a86
--- /dev/null
+++ b/docs/changes/nddata/17281.bugfix.rst
@@ -0,0 +1,1 @@
+Add support for positional only and keyword only arguments when using the ``support_nddata`` decorator.
diff --git a/docs/index_getting_started.rst b/docs/index_getting_started.rst
index 47b8f260b3..a7dcd0fe5d 100644
--- a/docs/index_getting_started.rst
+++ b/docs/index_getting_started.rst
@@ -4,11 +4,12 @@
 Getting Started
 ***************
 
+:ref:`whatsnew-7.1`
+
 .. toctree::
    :maxdepth: 1
 
    install
-   whatsnew/7.1
    importing_astropy
    Example Gallery <generated/examples/index>
    Tutorials <https://learn.astropy.org/>
diff --git a/docs/stats/index.rst b/docs/stats/index.rst
index 041bc3f589..bc1170d8c5 100644
--- a/docs/stats/index.rst
+++ b/docs/stats/index.rst
@@ -145,7 +145,8 @@ listed below.
    robust.rst
    circ.rst
    ripley.rst
-   ../visualization/histogram.rst
+
+Also see :ref:`astropy-visualization-hist`.
 
 
 Constants
diff --git a/docs/utils/index.rst b/docs/utils/index.rst
index e596f749d5..5a95ed439a 100644
--- a/docs/utils/index.rst
+++ b/docs/utils/index.rst
@@ -24,10 +24,11 @@ The exceptions are below:
 .. toctree::
    :maxdepth: 1
 
-   iers
    data
    masked/index
 
+:ref:`utils-iers`
+
 .. note:: The ``astropy.utils.compat`` subpackage is not included in this
     documentation. It contains utility modules for compatibility with
     older/newer versions of python and numpy, as well as including some
diff --git a/docs/whatsnew/7.0.rst b/docs/whatsnew/7.0.rst
index 53220f0f74..594d75cd3d 100644
--- a/docs/whatsnew/7.0.rst
+++ b/docs/whatsnew/7.0.rst
@@ -1,4 +1,4 @@
 :orphan:
 
 `What's New in Astropy 7.0?
-<https://docs.astropy.org/en/v7.0.0/whatsnew/7.0.html>`__
+<https://docs.astropy.org/en/v7.0.x/whatsnew/7.0.html>`__
diff --git a/docs/whatsnew/index.rst b/docs/whatsnew/index.rst
index 3ca7caf0fa..104685b96a 100644
--- a/docs/whatsnew/index.rst
+++ b/docs/whatsnew/index.rst
@@ -15,7 +15,7 @@ functionality highlighted in these pages.
 
    7.1
 
-* `What's New in Astropy 7.0? <https://docs.astropy.org/en/v6.1.x/whatsnew/7.0.html>`__
+* `What's New in Astropy 7.0? <https://docs.astropy.org/en/v7.0.x/whatsnew/7.0.html>`__
 * `What's New in Astropy 6.1? <https://docs.astropy.org/en/v6.1.x/whatsnew/6.1.html>`__
 * `What's New in Astropy 6.0? <https://docs.astropy.org/en/v6.0.0/whatsnew/6.0.html>`__
 * `What's New in Astropy 5.3? <https://docs.astropy.org/en/v5.3/whatsnew/5.3.html>`__
diff --git a/pyproject.toml b/pyproject.toml
index c83842098a..07422ef25b 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -221,17 +221,16 @@ filterwarnings = [
     "error",
     "ignore:unclosed <socket:ResourceWarning",
     "ignore:unclosed <ssl.SSLSocket:ResourceWarning",
-    "ignore:matplotlibrc text\\.usetex:UserWarning:matplotlib",
 ]
 doctest_norecursedirs = [
     "*/setup_package.py",
     "*/tests/command.py",
 ]
 doctest_subpackage_requires = [
-    "astropy/cosmology/_io/mapping.py = python<3.12",  # PYTHON_LT_3_12 (PR 14784)
-    "astropy/cosmology/_io/row.py = python<3.12",  # PYTHON_LT_3_12 (PR 14784)
-    "astropy/cosmology/_io/table.py = python<3.12",  # PYTHON_LT_3_12 (PR 14784)
-    "astropy/table/table.py = python<3.12",  # PYTHON_LT_3_12 (PR 14784)
+    "astropy/cosmology/_io/mapping.py = python>=3.12",  # not PYTHON_LT_3_12 (PR 17277)
+    "astropy/cosmology/_io/row.py = python>=3.12",  # not PYTHON_LT_3_12 (PR 17277)
+    "astropy/cosmology/_io/table.py = python>=3.12",  # not PYTHON_LT_3_12 (PR 17277)
+    "astropy/table/table.py = python>=3.12",  # not PYTHON_LT_3_12 (PR 17277)
     "astropy/table/mixins/dask.py = dask",
     "docs/* = numpy>=2",   # not NUMPY_LT_2_0 (PR 15065)
     "astropy/stats/info_theory.py = numpy>=2",  # not NUMPY_LT_2_0 (PR 15065)


Test patch:
diff --git a/astropy/nddata/tests/test_decorators.py b/astropy/nddata/tests/test_decorators.py
index 6ff5284483..d104930b41 100644
--- a/astropy/nddata/tests/test_decorators.py
+++ b/astropy/nddata/tests/test_decorators.py
@@ -58,6 +58,129 @@ def test_pass_nddata():
     assert unit_out is unit_in
 
 
+@pytest.mark.parametrize(
+    "func",
+    (
+        lambda *, data, wcs, unit=None: (data, wcs, unit),
+        lambda *, wcs=None, data, unit=None: (data, wcs, unit),
+    ),
+)
+def test_pass_nddata_kwarg_only(func):
+    wrapped_function = support_nddata(func)
+
+    data_in = np.array([1, 2, 3])
+    wcs_in = WCS(naxis=1)
+    unit_in = u.Jy
+
+    nddata_in = NDData(data_in, wcs=wcs_in, unit=unit_in)
+
+    data_out, wcs_out, unit_out = wrapped_function(data=nddata_in)
+
+    assert data_out is data_in
+    assert wcs_out is wcs_in
+    assert unit_out is unit_in
+
+
+@pytest.mark.parametrize(
+    "func, call_type",
+    (
+        pytest.param(
+            lambda data, wcs=None, mask=None, /, *, unit=None: (data, wcs, unit, mask),
+            "data_as_pos",
+            id="data_wcs_mask_pos-only",
+        ),
+        pytest.param(
+            lambda data, wcs=None, /, mask=None, *, unit: (data, wcs, unit, mask),
+            "data_as_pos",
+            id="data_wcs_pos-only",
+        ),
+        pytest.param(
+            lambda wcs=None, /, data=None, mask=None, *, unit: (data, wcs, unit, mask),
+            "data_as_kw",
+            id="data_pos-or-kw",
+        ),
+        pytest.param(
+            lambda wcs=None, /, mask=None, *, data, unit: (data, wcs, unit, mask),
+            "data_as_kw",
+            id="data_kw-only",
+        ),
+    ),
+)
+def test_pass_nddata_constrained_signature(func, call_type):
+    wrapped_function = support_nddata(func)
+
+    data_in = np.array([1, 2, 3])
+    wcs_in = WCS(naxis=1)
+    unit_in = u.Jy
+    mask_in = np.array([True, False, False])
+
+    nddata_in = NDData(data_in, wcs=wcs_in, unit=unit_in, mask=mask_in)
+
+    if call_type == "data_as_pos":
+        args = (nddata_in,)
+        kwargs = {}
+    elif call_type == "data_as_kw":
+        args = ()
+        kwargs = {"data": nddata_in}
+
+    data_out, wcs_out, unit_out, mask_out = wrapped_function(*args, **kwargs)
+
+    assert data_out is data_in
+    assert wcs_out is wcs_in
+    assert unit_out is unit_in
+    assert mask_out is mask_in
+
+    nddata2 = NDData(data_in, unit=unit_in, mask=mask_in)
+
+    if call_type == "data_as_pos":
+        args = (nddata2,)
+        kwargs = {}
+    elif call_type == "data_as_kw":
+        args = ()
+        kwargs = {"data": nddata2}
+
+    data_out, wcs_out, unit_out, mask_out = wrapped_function(*args, **kwargs)
+
+    assert data_out is data_in
+    assert wcs_out is None
+    assert unit_out is unit_in
+    assert mask_out is mask_in
+
+    if call_type == "data_as_pos":
+        args = (nddata2, wcs_in)
+        kwargs = {}
+    elif call_type == "data_as_kw":
+        args = (wcs_in,)
+        kwargs = {"data": nddata2}
+
+    data_out, wcs_out, unit_out, mask_out = wrapped_function(*args, **kwargs)
+
+    assert data_out is data_in
+    assert wcs_out is wcs_in
+    assert unit_out is unit_in
+    assert mask_out is mask_in
+
+    if call_type == "data_as_pos":
+        args = (nddata_in, wcs_in)
+        kwargs = {}
+    elif call_type == "data_as_kw":
+        args = (wcs_in,)
+        kwargs = {"data": nddata_in}
+
+    with pytest.warns(
+        AstropyUserWarning,
+        match=(
+            "Property wcs has been passed explicitly and as "
+            "an NDData property, using explicitly specified value"
+        ),
+    ):
+        data_out, wcs_out, unit_out, mask_out = wrapped_function(*args, **kwargs)
+    assert data_out is data_in
+    assert wcs_out is wcs_in
+    assert unit_out is unit_in
+    assert mask_out is mask_in
+
+
 def test_pass_nddata_and_explicit():
     data_in = np.array([1, 2, 3])
     wcs_in = WCS(naxis=1)
@@ -103,39 +226,18 @@ def test_pass_nddata_ignored():
     assert unit_out is unit_in
 
 
-def test_incorrect_first_argument():
-    with pytest.raises(ValueError) as exc:
-
-        @support_nddata
-        def wrapped_function_2(something, wcs=None, unit=None):
-            pass
-
-    assert (
-        exc.value.args[0]
-        == "Can only wrap functions whose first positional argument is `data`"
-    )
-
-    with pytest.raises(ValueError) as exc:
-
-        @support_nddata
-        def wrapped_function_3(something, data, wcs=None, unit=None):
-            pass
-
-    assert (
-        exc.value.args[0]
-        == "Can only wrap functions whose first positional argument is `data`"
-    )
-
-    with pytest.raises(ValueError) as exc:
-
-        @support_nddata
-        def wrapped_function_4(wcs=None, unit=None):
-            pass
-
-    assert (
-        exc.value.args[0]
-        == "Can only wrap functions whose first positional argument is `data`"
-    )
+@pytest.mark.parametrize(
+    "func",
+    (
+        lambda something, wcs=None, unit=None: None,
+        lambda wcs=None, unit=None: None,
+    ),
+)
+def test_incorrect_first_argument(func):
+    with pytest.raises(
+        ValueError, match="Can only wrap a function with a data argument"
+    ):
+        support_nddata(func)
 
 
 def test_wrap_function_no_kwargs():