Repository: sunpy/sunpy

Issue:
Support the use of SunPyBaseCoordinateFrame outside of core library
### Describe the feature

* It would be useful to be able to use `SunPyBaseCoordinateFrame` outside of sunpy but currently doing so is alkward as the `frame_to_wcs` and `wcs_to_frame` mappings are hardcode to the current frames / wcs' and raise an error you you try to do so
* The name clearly implies this is SunPy specific but there a lot of boiler plate code that would have to be copied to provide the same thing.

At the moment to make this work you have to make sure what ever frames are added are added before the sunpy function e.g.

`astropy.wcs.utils.FRAME_WCS_MAPPINGS.insert(1, [my_wcs_to_frame])`

### Proposed solution

Don't raise an error the if the frame is a subclass of `SunPyBaseCoordinateFrame` but not one of the frame implemented in sunpy return `None` 

Specifically L198 raise an error as xcoord and xcoord are undefined
https://github.com/sunpy/sunpy/blob/2281a2198997e8671efd48dcb531b07a98b86ddf/sunpy/coordinates/wcs_utils.py#L173-L198

A simple solution would be to an a final else clause to this if Elias
```python
        elif isinstance(frame, HeliographicStonyhurst):
            xcoord = 'HGLN' + '-' + projection
            ycoord = 'HGLT' + '-' + projection
            wcs.wcs.cunit = ['deg', 'deg']
        else
            return None

```

Discussion:
ayshih: Hmm, I'm inclined to categorize this PR as a bug fix (as opposed to a feature) and backport it

Patch:
diff --git a/changelog/6978.doc.rst b/changelog/6978.doc.rst
deleted file mode 100644
index 0253847f4..000000000
--- a/changelog/6978.doc.rst
+++ /dev/null
@@ -1,1 +0,0 @@
-Added a how-to guide for manipulating grid lines on `~sunpy.map.GenericMap`.
diff --git a/changelog/7421.deprecation.rst b/changelog/7421.deprecation.rst
deleted file mode 100644
index 2db7345f0..000000000
--- a/changelog/7421.deprecation.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Deprecated all positional arguments in :meth:`sunpy.map.GenericMap.plot` method.
-The ``annotate``, ``axes``, ``title``, ``clip_interval`` arguments should be passed as keyword arguments (e.g., ``..., title=True, ...``) instead.
diff --git a/changelog/7491.doc.rst b/changelog/7491.doc.rst
deleted file mode 100644
index d3054d372..000000000
--- a/changelog/7491.doc.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Add an example gallery entry demonstrating how to use the coordinates framework to compute intersections
-between instrument lines of sight and a simulation domain.
diff --git a/changelog/7594.bugfix.rst b/changelog/7594.bugfix.rst
new file mode 100644
index 000000000..804869e71
--- /dev/null
+++ b/changelog/7594.bugfix.rst
@@ -0,0 +1,1 @@
+Fixed a bug that interfered with :func:`astropy.wcs.utils.celestial_frame_to_wcs` when working with a custom subclass of :class:`~sunpy.coordinates.frames.SunPyBaseCoordinateFrame`.
diff --git a/docs/how_to/index.rst b/docs/how_to/index.rst
index f2246e992..707cd744d 100644
--- a/docs/how_to/index.rst
+++ b/docs/how_to/index.rst
@@ -18,7 +18,6 @@ If you're starting fresh you might want to check out the :ref:`sunpy-tutorial-in
    create_custom_map
    create_custom_timeseries
    fix_map_metadata
-   manipulate_grid_lines
    parse_time
    remote_data_manager
    search_multiple_wavelengths
diff --git a/docs/how_to/manipulate_grid_lines.rst b/docs/how_to/manipulate_grid_lines.rst
deleted file mode 100644
index ed8228ee1..000000000
--- a/docs/how_to/manipulate_grid_lines.rst
+++ /dev/null
@@ -1,93 +0,0 @@
-.. _how-to-manipulate-grid-lines-in-image-plots:
-
-*****************************************
-Manipulate grids lines when plotting Maps
-*****************************************
-
-Underneath the hood, `sunpy.map.GenericMap.plot` uses `~astropy.visualization.wcsaxes.WCSAxes` to have to ability to plot images in world coordinates.
-This means that sometimes the standard matplotlib methods to manipulate grid lines may not work as expected.
-So you have to access the `~astropy.visualization.wcsaxes.WCSAxes` object to manipulate the grid lines.
-
-`Astropy have a guide on how to manipulate grid lines in WCSAxes <https://docs.astropy.org/en/stable/visualization/wcsaxes/index.html>`__.
-Here we provide a solar example of how to manipulate grid lines when plotting a `~sunpy.map.GenericMap`.
-
-Turning off the Helioprojective grid
-====================================
-
-By default `sunpy.map.GenericMap.plot` draws a grid tracing the lines of helioprojective latitude and longitude, as below:
-
-.. plot::
-    :include-source:
-    :context: close-figs
-
-    import matplotlib.pyplot as plt
-    import astropy.units as u
-
-    import sunpy.map
-    from sunpy.data.sample import HMI_LOS_IMAGE
-
-    smap = sunpy.map.Map(HMI_LOS_IMAGE)
-
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection=smap)
-
-    smap.plot(axes=ax)
-
-    plt.show()
-
-In some cases it may be desirable to remove this grid.
-Since the underlying axes is a `~astropy.visualization.wcsaxes.WCSAxes`, you will need to access the ``grid`` method on the axes' object ``coord`` attribute:
-
-.. plot::
-    :include-source:
-    :context: close-figs
-
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection=smap)
-
-    smap.plot(axes=ax)
-
-    ax.coords[0].grid(draw_grid=False)  # Disable grid for 1st axis
-    ax.coords[1].grid(draw_grid=False)  # Disable grid for 2nd axis
-
-    plt.show()
-
-Changing the appearance of Helioprojective grid
-===============================================
-
-As the Helioprojective grid is applied by  `~astropy.visualization.wcsaxes.WCSAxes`, if you want to change the appeerance of the grid lines, you will need to access the ``grid`` method on the axes' object ``coord`` attribute and pass the desired keywords to it:
-
-.. plot::
-    :include-source:
-    :context: close-figs
-
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection=smap)
-
-    smap.plot(axes=ax)
-
-    ax.coords[0].grid(color='yellow', linestyle='solid', alpha=0.5)
-    ax.coords[1].grid(color='red', linestyle='dotted', alpha=0.5)
-
-    plt.show()
-
-Changing the appearance of Heliographic grid
-============================================
-
-`sunpy.map.GenericMap.draw_grid` allows users to overlay a Heliographic grid on their solar image plot.
-This method does not explicitly provide many options for the appearance of the grid lines.
-Instead it accepts keyword arguments and transparently passes them onto the underlying infrastructure.
-Therefore to change the width of the lines to say, 0.25, and their style to say, dotted, do the following:
-
-.. plot::
-    :include-source:
-    :context: close-figs
-
-    fig = plt.figure()
-    ax = fig.add_subplot(111, projection=smap)
-
-    smap.plot(axes=ax)
-
-    smap.draw_grid(axes=ax, linewidth=0.25, linestyle="dotted")
-
-    plt.show()
diff --git a/docs/whatsnew/6.0.rst b/docs/whatsnew/6.0.rst
index dcee02c76..1874d14bd 100644
--- a/docs/whatsnew/6.0.rst
+++ b/docs/whatsnew/6.0.rst
@@ -82,9 +82,3 @@ However, we have now fixed the sanitization to be more conservative, to follow t
 This now does not replace periods, does not change letter case and do not leave Unicode characters decomposed.
 An example of this is that the filename ``ä`` will now be replaced with ``ä`` instead of being left out of the filename.
 Another example is that the filename "aia.lev1.171A_2020_06_07T06_33_09.35Z.image_lev1.fits" used to be replaced with "aia_lev1_171a_2020_06_07t06_33_09_35z_image_lev1.fits" and now will not be.
-
-Deprecate positional arguments in :meth:`sunpy.map.GenericMap.plot`
-===================================================================
-
-The arguments for :meth:`sunpy.map.GenericMap.plot` have been changed to being keyword only.
-Pass them as keyword arguments (e.g., ``..., title=True, ...``) instead.
diff --git a/examples/units_and_coordinates/los_simulation_box_intersection.py b/examples/units_and_coordinates/los_simulation_box_intersection.py
deleted file mode 100644
index d89072a9a..000000000
--- a/examples/units_and_coordinates/los_simulation_box_intersection.py
+++ /dev/null
@@ -1,219 +0,0 @@
-"""
-============================================================================================
-Understanding Observer Orientation Relative to a Simulated Volume with `astropy.coordinates`
-============================================================================================
-
-This example demonstrates how to use the `astropy.coordinates` framework, combined with the solar
-coordinate frameworks provided by ``sunpy``, to find the intersection of a set of lines of sight
-defined by an observer position with a simulated volume represented by a Cartesian box.
-"""
-# sphinx_gallery_thumbnail_number = 4
-
-import itertools
-
-import matplotlib.pyplot as plt
-import numpy as np
-
-import astropy.time
-import astropy.units as u
-from astropy.coordinates import SkyCoord
-
-import sunpy.map
-import sunpy.sun.constants
-from sunpy.coordinates import Heliocentric, Helioprojective
-
-#############################################################################################
-# First, we need to define the orientation of our box and the corresponding coordinate frame.
-# We do this by specifying a coordinate in the Heliographic Stonyhurst frame (HGS) and then
-# using that coordinate to define the coordinate frame of the simulation box in a
-# Heliocentric Cartesian (HCC) frame. For more information on all of these frames, see
-# `Thompson (2006) <https://ui.adsabs.harvard.edu/abs/2006A%26A...449..791T/abstract>`__.
-
-hcc_orientation = SkyCoord(lon=0*u.deg, lat=20*u.deg,
-                           radius=sunpy.sun.constants.radius,
-                           frame='heliographic_stonyhurst')
-date = astropy.time.Time('2020-01-01')
-frame_hcc = Heliocentric(observer=hcc_orientation, obstime=date)
-
-#############################################################################################
-# Next, we need to define a coordinate frame for our synthetic spacecraft.
-# This could be something like SDO or *Solar Orbiter*, but in this case we'll just choose an
-# *interesting* orientation at 1 AU. As before, we first need to define the observer position
-# in HGS which we will then use to define the Helioprojective frame of our observer.
-
-observer = SkyCoord(lon=45*u.deg, lat=10*u.deg, radius=1*u.AU, frame='heliographic_stonyhurst')
-frame_hpc = Helioprojective(observer=observer, obstime=date)
-
-#############################################################################################
-# Using our coordinate frame for synthetic observer, we can then create an empty map that
-# represents our fake observation. We'll create a function to do this since we'll need to do
-# it a few times. Note that we are using the center coordinate at the bottom boundary of our
-# simulation box as the reference coordinate such that field-of-view of our synthetic
-# simulation is centered on this point.
-
-def make_fake_map(ref_coord):
-    instrument_data = np.nan * np.ones((25, 25))
-    instrument_header = sunpy.map.make_fitswcs_header(instrument_data,
-                                                      ref_coord,
-                                                      scale=u.Quantity([20, 20])*u.arcsec/u.pix)
-    return sunpy.map.Map(instrument_data, instrument_header)
-instrument_map = make_fake_map(hcc_orientation.transform_to(frame_hpc))
-
-#############################################################################################
-# Now that we have our map representing our fake observation, we can get the coordinates, in
-# pixel space, of the center of each pixel. These will be our lines of sight.
-
-map_indices = sunpy.map.all_pixel_indices_from_map(instrument_map).value.astype(int)
-map_indices = map_indices.reshape((2, map_indices.shape[1]*map_indices.shape[2]))
-
-#############################################################################################
-# We can then use the WCS of the map to find the associate world coordinate for each pixel coordinate.
-# Note that by default, the "z" or *distance* coordinate in the HPC frame is assumed to lie on the solar surface.
-# As such, for each LOS, we will add a z coordinate that spans from 99% to 101% of the observer radius.
-# This gives us a reasonable range of distances that will intersect our simulation box.
-
-lines_of_sight = []
-distance = np.linspace(0.99, 1.01, 10000)*observer.radius
-for indices in map_indices.T:
-    coord = instrument_map.wcs.pixel_to_world(*indices)
-    lines_of_sight.append(SkyCoord(Tx=coord.Tx, Ty=coord.Ty, distance=distance, frame=coord.frame))
-
-#############################################################################################
-# We can do a simple visualization of all of our LOS on top of our synthetic map
-
-fig = plt.figure()
-ax = fig.add_subplot(projection=instrument_map)
-instrument_map.plot(axes=ax, title=False)
-instrument_map.draw_grid(axes=ax, color='k')
-for los in lines_of_sight:
-    ax.plot_coord(los[0], color='C0', marker='.', ls='', markersize=1,)
-
-#############################################################################################
-# The next step is to define our simulation box. We'll choose somewhat arbitrary dimensions
-# for our box and then compute the offsets of each of the corners from the center of the box.
-
-box_dimensions = u.Quantity([100,100,200])*u.Mm
-corners = list(itertools.product(box_dimensions[0]/2*[-1,1],
-                                 box_dimensions[1]/2*[-1,1],
-                                 box_dimensions[2]/2*[-1,1]))
-
-#############################################################################################
-# We then define the edges of the box by considering all possible combinations of two corners
-# and keeping only those combinations who vary along a single dimension.
-
-edges = []
-for possible_edges in itertools.combinations(corners,2):
-    diff_edges = u.Quantity(possible_edges[0])-u.Quantity(possible_edges[1])
-    if np.count_nonzero(diff_edges) == 1:
-        edges.append(possible_edges)
-
-#############################################################################################
-# Finally, we can define the edge coordinates of the box by first creating a coordinate to
-# represent the origin. This is easily computed from our point that defined the orientation
-# since this is the point at which the box is tangent to the solar surface.
-
-box_origin = hcc_orientation.transform_to(frame_hcc)
-box_origin = SkyCoord(x=box_origin.x,
-                      y=box_origin.y,
-                      z=box_origin.z+box_dimensions[2]/2,
-                      frame=box_origin.frame)
-
-#############################################################################################
-# Using that origin, we can compute the coordinates of all edges.
-
-edge_coords = []
-for edge in edges:
-    edge_coords.append(SkyCoord(x=box_origin.x+u.Quantity([edge[0][0],edge[1][0]]),
-                                y=box_origin.y+u.Quantity([edge[0][1],edge[1][1]]),
-                                z=box_origin.z+u.Quantity([edge[0][2],edge[1][2]]),
-                                frame=box_origin.frame))
-
-#############################################################################################
-# Let's overlay the simulation box on top of our fake map we created earlier to see if things
-# look right.
-
-fig = plt.figure()
-ax = fig.add_subplot(projection=instrument_map)
-instrument_map.plot(axes=ax, title=False)
-instrument_map.draw_grid(axes=ax, color='k')
-for edge in edge_coords:
-    ax.plot_coord(edge, color='k', ls='-', marker='')
-ax.plot_coord(box_origin, color='r', marker='x')
-ax.plot_coord(hcc_orientation, color='b', marker='x')
-
-#############################################################################################
-# Let's combine all of these pieces by plotting the lines of sight and the simulation box on
-# a single plot. We'll also overlay the pixel grid of our fake image.
-
-fig = plt.figure()
-ax = fig.add_subplot(projection=instrument_map)
-instrument_map.plot(axes=ax, title=False)
-instrument_map.draw_grid(axes=ax, color='k')
-ax.plot_coord(box_origin, marker='x', color='r', ls='', label='Simulation Box Center')
-ax.plot_coord(hcc_orientation, color='b', marker='x', ls='', label='Simulation Box Bottom')
-for i,edge in enumerate(edge_coords):
-    ax.plot_coord(edge, color='k', ls='-', label='Simulation Box' if i==0 else None)
-# Plot the pixel center positions
-for i,los in enumerate(lines_of_sight):
-    ax.plot_coord(los[0], color='C0', marker='.', label='LOS' if i==0 else None, markersize=1)
-# Plot the edges of the pixel grid
-xpix_edges = np.array(range(int(instrument_map.dimensions.x.value)+1))-0.5
-ypix_edges = np.array(range(int(instrument_map.dimensions.y.value)+1))-0.5
-ax.vlines(x=xpix_edges,
-          ymin=ypix_edges[0],
-          ymax=ypix_edges[-1],
-          color='k', ls='--', lw=.5, label='pixel grid')
-ax.hlines(y=ypix_edges,
-          xmin=xpix_edges[0],
-          xmax=xpix_edges[-1],
-          color='k', ls='--', lw=.5,)
-ax.legend(loc=2, frameon=False)
-ax.set_xlim(-10, 35)
-ax.set_ylim(-10, 35)
-
-#############################################################################################
-# Finally, we have all of the pieces of information we need to understand whether a given LOS
-# intersects the simulation box. First, we define a function that takes in the edge coordinates
-# of our box and a LOS coordinate and returns to us a boolean mask of where that LOS coordinate
-# falls in the box.
-
-def is_coord_in_box(box_edges, coord):
-    box_edges = SkyCoord(box_edges)
-    coord_hcc = coord.transform_to(box_edges.frame)
-    in_x = np.logical_and(coord_hcc.x<box_edges.x.max(), coord_hcc.x>box_edges.x.min())
-    in_y = np.logical_and(coord_hcc.y<box_edges.y.max(), coord_hcc.y>box_edges.y.min())
-    in_z = np.logical_and(coord_hcc.z<box_edges.z.max(), coord_hcc.z>box_edges.z.min())
-    return np.all([in_x, in_y, in_z], axis=0)
-
-
-#############################################################################################
-# Next we define another map, using a different orientation from our observer, so we can look
-# at the intersection of the box and the many LOS from a different viewing angle.
-
-new_obs = SkyCoord(lon=25*u.deg, lat=0*u.deg, radius=1*u.AU, frame='heliographic_stonyhurst')
-earth_map = make_fake_map(
-    hcc_orientation.transform_to(Helioprojective(observer=new_obs, obstime=date))
-)
-
-#############################################################################################
-# Finally, we'll create another visualization that combines the simulation box with the lines
-# of sight and additional highlighting that shows *where* the LOS intersect the box.
-
-fig = plt.figure()
-ax = fig.add_subplot(projection=earth_map)
-earth_map.plot(axes=ax, title=False)
-earth_map.draw_grid(axes=ax, color='k')
-for los in lines_of_sight:
-    ax.plot_coord(los, color='C0', marker='', ls='-', alpha=0.2)
-for los in lines_of_sight:
-    inside_box = is_coord_in_box(edge_coords, los)
-    if inside_box.any():
-        ax.plot_coord(los[inside_box], color='C1', marker='', ls='-')
-for edge in edge_coords:
-    ax.plot_coord(edge, color='k', ls='-')
-ax.plot_coord(box_origin, marker='x', color='r')
-ax.plot_coord(hcc_orientation, color='b', marker='x', ls='')
-ax.set_xlim(-10, 35)
-ax.set_ylim(-10, 35)
-
-plt.show()
diff --git a/sunpy/coordinates/wcs_utils.py b/sunpy/coordinates/wcs_utils.py
index 087644364..c2ffcce22 100644
--- a/sunpy/coordinates/wcs_utils.py
+++ b/sunpy/coordinates/wcs_utils.py
@@ -191,6 +191,9 @@ def solar_frame_to_wcs_mapping(frame, projection='TAN'):
             xcoord = 'HGLN' + '-' + projection
             ycoord = 'HGLT' + '-' + projection
             wcs.wcs.cunit = ['deg', 'deg']
+        else:
+            # A subclass not supported by the core library
+            return None
 
     else:
         return None
diff --git a/sunpy/map/mapbase.py b/sunpy/map/mapbase.py
index 9b335a14f..d0906984d 100644
--- a/sunpy/map/mapbase.py
+++ b/sunpy/map/mapbase.py
@@ -50,7 +50,6 @@ from sunpy.util.decorators import (
     add_common_docstring,
     cached_property_based_on,
     check_arithmetic_compatibility,
-    deprecate_positional_args_since,
 )
 from sunpy.util.exceptions import warn_metadata, warn_user
 from sunpy.util.functools import seconddispatch
@@ -2440,9 +2439,8 @@ class GenericMap(NDData):
 
         return figure
 
-    @deprecate_positional_args_since(since="6.0.0")
     @u.quantity_input
-    def plot(self, *, annotate=True, axes=None, title=True, autoalign=False,
+    def plot(self, annotate=True, axes=None, title=True, autoalign=False,
              clip_interval: u.percent = None, **imshow_kwargs):
         """
         Plots the map object using matplotlib, in a method equivalent
@@ -2495,7 +2493,6 @@ class GenericMap(NDData):
         :meth:`~sunpy.coordinates.Helioprojective.assume_spherical_screen` context
         manager may be appropriate.
         """
-        # Todo: remove this when deprecate_positional_args_since is removed
         # Users sometimes assume that the first argument is `axes` instead of `annotate`
         if not isinstance(annotate, bool):
             raise TypeError("You have provided a non-boolean value for the `annotate` parameter. "
diff --git a/sunpy/util/decorators.py b/sunpy/util/decorators.py
index 21e0e2fbb..10906419c 100644
--- a/sunpy/util/decorators.py
+++ b/sunpy/util/decorators.py
@@ -265,10 +265,9 @@ class add_common_docstring:
         return func
 
 
-def deprecate_positional_args_since(func=None, *, since):
+def deprecate_positional_args_since(since, keyword_only=False):
     """
-    Decorator for methods that issues warnings for positional arguments.
-
+    Decorator for methods that issues warnings for positional arguments
     Using the keyword-only argument syntax in pep 3102, arguments after the
     * will issue a warning when passed as a positional argument.
 
@@ -277,20 +276,30 @@ def deprecate_positional_args_since(func=None, *, since):
 
     Parameters
     ----------
-    func : callable, default=None
-        Function to check arguments on.
     since : str
-        The version since when positional arguments will result in error.
-
-    Notes
-    -----
-    Taken from from `scikit-learn <https://github.com/scikit-learn/scikit-learn/blob/main/sklearn/utils/validation.py#L1271>`__.
-    Licensed under the BSD, see "licenses/SCIKIT-LEARN.rst".
+        Parameter denoting last supported version.
     """
-    def _inner_deprecate_positional_args(f):
+    def deprecate_positional_args(f):
+        """
+
+
+        Parameters
+        ----------
+        f : function
+            Function to check arguments on.
+
+        References
+        ----------
+        Taken from from `scikit-learn <https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/utils/validation.py#L1271>`__.
+        Licensed under the BSD, see "licenses/SCIKIT-LEARN.rst".
+        """
+        nonlocal keyword_only
+
         sig = signature(f)
         kwonly_args = []
         all_args = []
+        keyword_only = keyword_only or tuple()
+
         for name, param in sig.parameters.items():
             if param.kind == Parameter.POSITIONAL_OR_KEYWORD:
                 all_args.append(name)
@@ -300,31 +309,24 @@ def deprecate_positional_args_since(func=None, *, since):
         @wraps(f)
         def inner_f(*args, **kwargs):
             extra_args = len(args) - len(all_args)
-            if extra_args <= 0:
-                return f(*args, **kwargs)
-
-            # extra_args > 0
-            args_msg = [
-                f"{name}={arg}"
-                for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:])
-            ]
-            args_msg = ", ".join(args_msg)
-            warn_deprecated(
-
-                    f"Pass {args_msg} as keyword args. From version "
-                    f"{since} passing these as positional arguments "
-                    "will result in an error"
-
-            )
-            kwargs.update(zip(sig.parameters, args))
+            if extra_args > 0:
+                for name, arg in zip(kwonly_args[:extra_args], args[-extra_args:]):
+                    if name in keyword_only:
+                        raise TypeError(f"{name} must be specified as a keyword argument.")
+
+                # ignore first 'self' argument for instance methods
+                args_msg = [f'{name}={arg}'
+                            for name, arg in zip(kwonly_args[:extra_args],
+                                                 args[-extra_args:])]
+                last_supported_version = ".".join(map(str, get_removal_version(since)))
+                warn_deprecated(f"Pass {', '.join(args_msg)} as keyword args. "
+                                f"From version {last_supported_version} "
+                                "passing these as positional arguments will result in an error.")
+            kwargs.update({k: arg for k, arg in zip(sig.parameters, args)})
             return f(**kwargs)
-
         return inner_f
+    return deprecate_positional_args
 
-    if func is not None:
-        return _inner_deprecate_positional_args(func)
-
-    return _inner_deprecate_positional_args
 
 _NOT_FOUND = object()
 


Test patch:
diff --git a/sunpy/coordinates/tests/test_wcs_utils.py b/sunpy/coordinates/tests/test_wcs_utils.py
index 1a8e9ff20..dacdc942f 100644
--- a/sunpy/coordinates/tests/test_wcs_utils.py
+++ b/sunpy/coordinates/tests/test_wcs_utils.py
@@ -15,6 +15,7 @@ from sunpy.coordinates.frames import (
     HeliographicCarrington,
     HeliographicStonyhurst,
     Helioprojective,
+    SunPyBaseCoordinateFrame,
 )
 from sunpy.coordinates.wcs_utils import (
     _set_wcs_aux_obs_coord,
@@ -379,3 +380,11 @@ def test_observer_hgln_crln_priority():
     # Note: don't test whether crlt or hglt is used---according to
     # _set_wcs_aux_obs_coord, those are expected to always be the same and so
     # the same one is always used
+
+
+def test_sunpybaseframe_external():
+    class MyFrame(SunPyBaseCoordinateFrame):
+        pass
+
+    out = solar_frame_to_wcs_mapping(MyFrame())
+    assert out is None
diff --git a/sunpy/map/tests/test_mapbase.py b/sunpy/map/tests/test_mapbase.py
index 9763bf20e..78ece0a84 100644
--- a/sunpy/map/tests/test_mapbase.py
+++ b/sunpy/map/tests/test_mapbase.py
@@ -32,7 +32,7 @@ from sunpy.map.sources import AIAMap
 from sunpy.tests.helpers import figure_test
 from sunpy.time import parse_time
 from sunpy.util import SunpyUserWarning
-from sunpy.util.exceptions import SunpyDeprecationWarning, SunpyMetadataWarning
+from sunpy.util.exceptions import SunpyMetadataWarning
 from sunpy.util.metadata import ModifiedItem
 from sunpy.util.util import fix_duplicate_notes
 from .conftest import make_simple_map
@@ -1774,14 +1774,7 @@ def test_only_cd():
     np.testing.assert_allclose(cd_map.rotation_matrix, np.array([[3/5, -4/5], [5/13, 12/13]]))
 
 
-def test_plot_deprecated_positional_args(aia171_test_map):
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass annotate=True as keyword args"):
-        aia171_test_map.plot(True)
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass annotate=interpolation as keyword args."):
-        with pytest.raises(TypeError, match="non-boolean value"):
-            aia171_test_map.plot('interpolation')
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass annotate=interpolation, axes=True as keyword args."):
-        with pytest.raises(TypeError, match="non-boolean value"):
-            aia171_test_map.plot('interpolation', True)
+def test_plot_annotate_nonboolean(aia171_test_map):
+    ax = plt.subplot(projection=aia171_test_map)
+    with pytest.raises(TypeError, match="non-boolean value"):
+        aia171_test_map.plot(ax)
diff --git a/sunpy/map/tests/test_reproject_to.py b/sunpy/map/tests/test_reproject_to.py
index b01215025..0f3885e71 100644
--- a/sunpy/map/tests/test_reproject_to.py
+++ b/sunpy/map/tests/test_reproject_to.py
@@ -121,7 +121,6 @@ def test_invalid_inputs(aia171_test_map, hpc_header):
     with pytest.raises(ValueError, match="The specified algorithm must be one of"):
         aia171_test_map.reproject_to(hpc_header, algorithm='something')
 
-
 def test_rsun_mismatch_warning(aia171_test_map, hpc_header):
     with pytest.warns(SunpyUserWarning, match="rsun mismatch detected: "):
         # Modifying the `hpc_header` rsun value to create a mismatch
diff --git a/sunpy/util/tests/test_decorators.py b/sunpy/util/tests/test_decorators.py
index aa4424a45..48b8115c5 100644
--- a/sunpy/util/tests/test_decorators.py
+++ b/sunpy/util/tests/test_decorators.py
@@ -2,9 +2,7 @@ import warnings
 
 import pytest
 
-import astropy.units as u
-
-from sunpy.util.decorators import deprecate_positional_args_since, deprecated, get_removal_version
+from sunpy.util.decorators import deprecated, get_removal_version
 from sunpy.util.exceptions import SunpyDeprecationWarning, SunpyPendingDeprecationWarning
 
 
@@ -40,45 +38,3 @@ def test_deprecated_warning_message(since, pending, warning, message, warning_me
     with pytest.warns(warning, match=warning_message):
         warnings.simplefilter('always')
         foo()
-
-def test_deprecate_positional_args_warns_for_function_version():
-    @deprecate_positional_args_since(since="0.26")
-    def f1(a, *, b):
-        pass
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"From version 0.26 passing these as positional"):
-        f1(1, 2)
-
-def test_deprecate_positional_args_warns_quantity_input():
-    # It has to be this order otherwise, it will not work
-    @deprecate_positional_args_since(since="0.26")
-    @u.quantity_input
-    def f1(a, *, b: u.percent = None):
-        pass
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"From version 0.26 passing these as positional"):
-        f1(1, 2 * u.percent)
-
-
-def test_deprecate_positional_args_warns_for_class():
-
-    class A1:
-        @deprecate_positional_args_since(since="0.26")
-        def __init__(self, a, b, *, c=1, d=1):
-            pass
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass c=3 as keyword args"):
-        A1(1, 2, 3)
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass c=3, d=4 as keyword args"):
-        A1(1, 2, 3, 4)
-    class A2:
-        @deprecate_positional_args_since(since="0.26")
-        def __init__(self, a=1, b=1, *, c=1, d=1):
-            pass
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass c=3 as keyword args"):
-        A2(1, 2, 3)
-
-    with pytest.warns(SunpyDeprecationWarning, match=r"Pass c=3, d=4 as keyword args"):
-        A2(1, 2, 3, 4)