Repository: astropy/astropy

Issue:
Remove deprecation warnings from Stretch classes
This is a reminder to remove the deprecation warnings from `Stretch` classes in version 7.0.

They were deprecated in #15538.

CC: @pllim 

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#astropy-documentation-rules-and-guidelines)?
  - [ ] 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/workflow/development_workflow.html#rebase-if-necessary) and [squash](https://docs.astropy.org/en/latest/development/workflow/development_workflow.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/workflow/development_workflow.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?
  - [ ] Is a milestone set? Milestone must be set but we cannot check for it on Actions; do not let the green checkmark fool you.
  - [ ] 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.
pllim: Thanks! Yes, would need a change log under `api` for visualization.

p.s. Right now the labeler has a bug where it applies "visualization" to all PRs but finally one where the label actually is correct. 😆 
pllim: @MridulS no longer has contract with us, so a maintainer might have to take over.
larrybradley: @pllim I can finish this if needed.

Patch:
diff --git a/astropy/io/misc/yaml.py b/astropy/io/misc/yaml.py
index e42abd824a..87a68d05b4 100644
--- a/astropy/io/misc/yaml.py
+++ b/astropy/io/misc/yaml.py
@@ -194,13 +194,13 @@ def _skycoord_constructor(loader, node):
 # Straight from yaml's Representer
 def _complex_representer(self, data):
     if data.imag == 0.0:
-        data = f"{data.real!r}"
+        data = f"{data.real!s}"
     elif data.real == 0.0:
-        data = f"{data.imag!r}j"
+        data = f"{data.imag!s}j"
     elif data.imag > 0:
-        data = f"{data.real!r}+{data.imag!r}j"
+        data = f"{data.real!s}+{data.imag!s}j"
     else:
-        data = f"{data.real!r}{data.imag!r}j"
+        data = f"{data.real!s}{data.imag!s}j"
     return self.represent_scalar("tag:yaml.org,2002:python/complex", data)
 
 
@@ -243,6 +243,30 @@ class AstropyDumper(yaml.SafeDumper):
     def _represent_tuple(self, data):
         return self.represent_sequence("tag:yaml.org,2002:python/tuple", data)
 
+    def represent_float(self, data):
+        # Override to change repr(data) to str(data) since otherwise all the
+        # numpy scalars fail in not NUMPY_LT_2_0.
+        # otherwise, this function is identical to yaml.SafeDumper.represent_float
+        # (as of pyyaml 6.0.1)
+        if data != data or (data == 0.0 and data == 1.0):
+            value = ".nan"
+        elif data == self.inf_value:
+            value = ".inf"
+        elif data == -self.inf_value:
+            value = "-.inf"
+        else:
+            value = str(data).lower()
+            # Note that in some cases `repr(data)` represents a float number
+            # without the decimal parts.  For instance:
+            #   >>> repr(1e17)
+            #   '1e17'
+            # Unfortunately, this is not a valid float representation according
+            # to the definition of the `!!float` tag.  We fix this by adding
+            # '.0' before the 'e' symbol.
+            if "." not in value and "e" in value:
+                value = value.replace("e", ".0e", 1)
+        return self.represent_scalar("tag:yaml.org,2002:float", value)
+
 
 AstropyDumper.add_multi_representer(u.UnitBase, _unit_representer)
 AstropyDumper.add_multi_representer(u.FunctionUnitBase, _unit_representer)
@@ -273,9 +297,7 @@ for np_type in [
         np_type, yaml.representer.SafeRepresenter.represent_int
     )
 for np_type in [np.float16, np.float32, np.float64, np.longdouble]:
-    AstropyDumper.add_representer(
-        np_type, yaml.representer.SafeRepresenter.represent_float
-    )
+    AstropyDumper.add_representer(np_type, AstropyDumper.represent_float)
 for np_type in [complex, np.complex64, np.complex128]:
     AstropyDumper.add_representer(np_type, _complex_representer)
 
diff --git a/astropy/visualization/stretch.py b/astropy/visualization/stretch.py
index 941389c6f0..4e1b3362ac 100644
--- a/astropy/visualization/stretch.py
+++ b/astropy/visualization/stretch.py
@@ -7,8 +7,6 @@ another set of [0:1] values with a transformation.
 
 import numpy as np
 
-from astropy.utils.decorators import deprecated_attribute
-
 from .transform import BaseTransform, CompositeTransform
 
 __all__ = [
@@ -210,8 +208,6 @@ class PowerStretch(BaseStretch):
         than 0.
     """
 
-    power = deprecated_attribute("power", "6.0", alternative="a")
-
     @property
     def _supports_invalid_kw(self):
         return True
@@ -290,8 +286,6 @@ class PowerDistStretch(BaseStretch):
         1000.
     """
 
-    exp = deprecated_attribute("exp", "6.0", alternative="a")
-
     def __init__(self, a=1000.0):
         if a < 0 or a == 1:  # singularity
             raise ValueError("a must be >= 0, but cannot be set to 1")
@@ -329,8 +323,6 @@ class InvertedPowerDistStretch(BaseStretch):
         1000.
     """
 
-    exp = deprecated_attribute("exp", "6.0", alternative="a")
-
     def __init__(self, a=1000.0):
         if a < 0 or a == 1:  # singularity
             raise ValueError("a must be >= 0, but cannot be set to 1")
@@ -385,8 +377,6 @@ class LogStretch(BaseStretch):
         greater than 0.  Default is 1000.
     """
 
-    exp = deprecated_attribute("exp", "6.0", alternative="a")
-
     @property
     def _supports_invalid_kw(self):
         return True
@@ -466,8 +456,6 @@ class InvertedLogStretch(BaseStretch):
         greater than 0.  Default is 1000.
     """
 
-    exp = deprecated_attribute("exp", "6.0", alternative="a")
-
     def __init__(self, a):
         super().__init__()
         if a <= 0:  # singularity
diff --git a/docs/changes/io.misc/16416.bugfix.rst b/docs/changes/io.misc/16416.bugfix.rst
new file mode 100644
index 0000000000..d004cce686
--- /dev/null
+++ b/docs/changes/io.misc/16416.bugfix.rst
@@ -0,0 +1,1 @@
+Fix YAML table serialization compatibility with numpy 2.
diff --git a/docs/changes/visualization/15751.api.rst b/docs/changes/visualization/15751.api.rst
new file mode 100644
index 0000000000..1cd3383169
--- /dev/null
+++ b/docs/changes/visualization/15751.api.rst
@@ -0,0 +1,5 @@
+Removed deprecated ``exp`` attribute in the ``LogStretch``,
+``InvertedLogStretch``, ``PowerDistStretch``, and
+``InvertedPowerDistStretch`` stretch classes, and the ``power``
+attribute in the ``PowerStretch``. Instead, use the ``a`` attribute,
+which matches the input keyword.


Test patch:
diff --git a/astropy/io/misc/tests/test_yaml.py b/astropy/io/misc/tests/test_yaml.py
index 72b35e5d55..39c93aad45 100644
--- a/astropy/io/misc/tests/test_yaml.py
+++ b/astropy/io/misc/tests/test_yaml.py
@@ -9,7 +9,7 @@ from io import StringIO
 import numpy as np
 import pytest
 
-from astropy import units as u
+import astropy.units as u
 from astropy.coordinates import (
     Angle,
     CartesianDifferential,
@@ -29,6 +29,16 @@ from astropy.table import QTable, SerializedColumn
 from astropy.time import Time
 
 
+@pytest.fixture
+def without_legacy_printoptions():
+    # this can be removed when/after reverting
+    # https://github.com/astropy/astropy/pull/15096
+    legacy_val = np.get_printoptions()["legacy"]
+    np.set_printoptions(legacy=False)
+    yield
+    np.set_printoptions(legacy=legacy_val)
+
+
 @pytest.mark.parametrize(
     "c",
     [
@@ -45,6 +55,7 @@ from astropy.time import Time
         np.complex128(1.0 - 2**-52 + 1j * (1.0 - 2**-52)),
     ],
 )
+@pytest.mark.usefixtures("without_legacy_printoptions")
 def test_numpy_types(c):
     cy = load(dump(c))
     assert c == cy
diff --git a/astropy/visualization/tests/test_stretch.py b/astropy/visualization/tests/test_stretch.py
index 05d0c7c423..4cdb1d63b2 100644
--- a/astropy/visualization/tests/test_stretch.py
+++ b/astropy/visualization/tests/test_stretch.py
@@ -4,7 +4,6 @@ import numpy as np
 import pytest
 from numpy.testing import assert_allclose, assert_equal
 
-from astropy.utils.exceptions import AstropyDeprecationWarning
 from astropy.visualization.stretch import (
     AsinhStretch,
     ContrastBiasStretch,
@@ -160,25 +159,3 @@ def test_histeqstretch_invalid():
     result = np.array([0.0, 0.0, 0.25, 0.5, 0.75, 1.0, 1.0])
     assert_equal(HistEqStretch(data)(data), result)
     assert_equal(InvertedHistEqStretch(data)(data), result)
-
-
-def test_deprecated_attrs():
-    match = "The power attribute is deprecated"
-    with pytest.warns(AstropyDeprecationWarning, match=match):
-        stretch = PowerStretch(a=0.5)
-        assert stretch.power == stretch.a
-
-    match = "The exp attribute is deprecated"
-    with pytest.warns(AstropyDeprecationWarning, match=match):
-        stretch = PowerDistStretch(a=0.5)
-        assert stretch.exp == stretch.a
-    with pytest.warns(AstropyDeprecationWarning, match=match):
-        stretch = InvertedPowerDistStretch(a=0.5)
-        assert stretch.exp == stretch.a
-
-    with pytest.warns(AstropyDeprecationWarning, match=match):
-        stretch = LogStretch(a=0.5)
-        assert stretch.exp == stretch.a
-    with pytest.warns(AstropyDeprecationWarning, match=match):
-        stretch = InvertedLogStretch(a=0.5)
-        assert stretch.exp == stretch.a