We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Example of reading data/header with io.fits.read is broken
In the docs, we have [the following example of how to read the data and header directly from a file using `sunpy.io.fits.read`](https://docs.sunpy.org/en/stable/code_ref/map.html#fixing-map-metadata),

```python
[data,header] = sunpy.io.fits.read(filename)
```

There are a few problems here.

1. If you have 2 HDUs, this returns the first to `data` and the second to `header`, obviously not the intended behavior.
2. You can use the `hdus` kwarg to select only a specific HDU but it turns out this is broken because the rest of the function expects an `HDUList` and after we slice it, we don't cast it back to an `HDUList` so everything after this explodes in fun and unexpected ways.
3. Even accounting for the first two problems, this function returns a list of data,header pairs, not a list of data and headers so you get a `ValueError` when trying to return this to a tuple as above. Fixing items 1 and 2, this example could be amended to,

```python
data, header = sunpy.io.fits.read(filename)[0]
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sunpy/io/fits.py`
Content:
```
1 """
2 This module provides a FITS file reader.
3 
4 Notes
5 -----
6 
7 1. FITS files allow comments to be attached to every value in the header.
8    This is implemented in this module as a KEYCOMMENTS dictionary in the
9    sunpy header. To add a comment to the file on write, add a comment to this
10    dictionary with the same name as a key in the header (upcased).
11 
12 2. Due to the way `~astropy.io.fits` works with images, the header dictionary may
13    differ depending on whether is accessed before or after the fits[0].data
14    is requested. If the header is read before the data then the original
15    header will be returned. If the header is read after the data has been
16    accessed then the data will have been scaled and a modified header
17    reflecting these changes will be returned: BITPIX may differ and
18    BSCALE and B_ZERO may be dropped in the modified version.
19 
20 3. The verify('silentfix+warn') call attempts to handle violations of the FITS
21    standard. For example, ``nan`` values will be converted to "nan" strings.
22    Attempting to cast a `astropy.io.fits.Header` to a dictionary while it contains
23    invalid header tags will result in an error so verifying it early on
24    makes the header easier to work with later.
25 """
26 import os
27 import re
28 import sys
29 import warnings
30 import traceback
31 import collections
32 
33 from astropy.io import fits
34 
35 from sunpy.io.header import FileHeader
36 from sunpy.util.exceptions import SunpyUserWarning
37 
38 __all__ = ['header_to_fits', 'read', 'get_header', 'write', 'extract_waveunit']
39 
40 HDPair = collections.namedtuple('HDPair', ['data', 'header'])
41 
42 
43 def read(filepath, hdus=None, memmap=None, **kwargs):
44     """
45     Read a fits file.
46 
47     Parameters
48     ----------
49     filepath : `str`
50         The fits file to be read.
51     hdu: `int` or iterable
52         The HDU indexes to read from the file.
53 
54     Returns
55     -------
56     pairs : `list`
57         A list of (data, header) tuples
58 
59     Notes
60     -----
61     This routine reads all the HDU's in a fits file and returns a list of the
62     data and a FileHeader instance for each one.
63 
64     Also all comments in the original file are concatenated into a single
65     "comment" key in the returned FileHeader.
66     """
67     with fits.open(filepath, ignore_blank=True, memmap=memmap) as hdulist:
68         if hdus is not None:
69             if isinstance(hdus, int):
70                 hdulist = hdulist[hdus]
71             elif isinstance(hdus, collections.Iterable):
72                 hdulist = [hdulist[i] for i in hdus]
73 
74         hdulist.verify('silentfix+warn')
75 
76         headers = get_header(hdulist)
77         pairs = []
78 
79         for i, (hdu, header) in enumerate(zip(hdulist, headers)):
80             try:
81                 pairs.append(HDPair(hdu.data, header))
82             except (KeyError, ValueError) as e:
83                 message = f"Error when reading HDU {i}. Skipping.\n"
84                 for line in traceback.format_tb(sys.exc_info()[2]):
85                     message += line
86                     message += '\n'
87                 message += repr(e)
88                 warnings.warn(message, SunpyUserWarning)
89 
90     return pairs
91 
92 
93 def get_header(afile):
94     """
95     Read a fits file and return just the headers for all HDU's. In each header,
96     the key WAVEUNIT denotes the wavelength unit which is used to describe the
97     value of the key "WAVELNTH".
98 
99     Parameters
100     ----------
101     afile : `str` or `astropy.io.fits.HDUList`
102         The file to be read, or HDUList to process.
103 
104     Returns
105     -------
106     headers : `list`
107         A list of `sunpy.io.helper.FileHeader` headers.
108     """
109     if isinstance(afile, fits.HDUList):
110         hdulist = afile
111         close = False
112     else:
113         hdulist = fits.open(afile, ignore_blank=True)
114         hdulist.verify('silentfix')
115         close = True
116 
117     try:
118         headers = []
119         for hdu in hdulist:
120             try:
121                 comment = "".join(hdu.header['COMMENT']).strip()
122             except KeyError:
123                 comment = ""
124             try:
125                 history = "".join(hdu.header['HISTORY']).strip()
126             except KeyError:
127                 history = ""
128 
129             header = FileHeader(hdu.header)
130             header['COMMENT'] = comment
131             header['HISTORY'] = history
132 
133             # Strip out KEYCOMMENTS to a dict, the hard way
134             keydict = {}
135             for card in hdu.header.cards:
136                 if card.comment != '':
137                     keydict.update({card.keyword: card.comment})
138             header['KEYCOMMENTS'] = keydict
139             header['WAVEUNIT'] = extract_waveunit(header)
140 
141             headers.append(header)
142     finally:
143         if close:
144             hdulist.close()
145     return headers
146 
147 
148 def write(fname, data, header, hdu_type=None, **kwargs):
149     """
150     Take a data header pair and write a FITS file.
151 
152     Parameters
153     ----------
154     fname : `str`
155         File name, with extension.
156     data : `numpy.ndarray`
157         n-dimensional data array.
158     header : `dict`
159         A header dictionary.
160     hdu_type: `None` or `~astropy.io.fits.CompImageHDU`
161         `None` will return a normal FITS files.
162         `~astropy.io.fits.CompImageHDU` will rice compress the FITS file.
163     kwargs :
164         Additional keyword arguments are given to
165         `~astropy.io.fits.HDUList.writeto`.
166     """
167     # Copy header so the one in memory is left alone while changing it for
168     # write.
169     header = header.copy()
170 
171     fits_header = header_to_fits(header)
172 
173     if isinstance(fname, str):
174         fname = os.path.expanduser(fname)
175 
176     fitskwargs = {'output_verify': 'fix'}
177     fitskwargs.update(kwargs)
178 
179     if not hdu_type:
180         hdu_type = fits.PrimaryHDU
181 
182     hdu = hdu_type(data=data, header=fits_header)
183 
184     if not isinstance(hdu, fits.PrimaryHDU):
185         hdul = fits.HDUList([fits.PrimaryHDU(), hdu])
186     else:
187         hdul = fits.HDUList([hdu])
188 
189     hdul.writeto(fname, **fitskwargs)
190 
191 
192 def header_to_fits(header):
193     """
194     Convert a header dict to a `~astropy.fits.Header`.
195     """
196     # Copy the header to avoid modifying it in place
197     header = header.copy()
198     # The comments need to be added to the header separately from the normal
199     # kwargs. Find and deal with them:
200     fits_header = fits.Header()
201     # Check Header
202     key_comments = header.pop('KEYCOMMENTS', False)
203 
204     for k, v in header.items():
205         # Drop any keys that have non-ascii characters
206         if not fits.Card._ascii_text_re.match(str(v)):
207             warnings.warn(f'The meta key {k} is not valid ascii, dropping from the FITS header',
208                           SunpyUserWarning)
209             continue
210         # Drop any keys which are too long to save into FITS
211         if len(k) > 8:
212             warnings.warn(f"The meta key {k} is too long, dropping from the FITS header "
213                           "(maximum allowed key length is 8 characters).",
214                           SunpyUserWarning)
215             continue
216 
217         if k.upper() in ('COMMENT', 'HV_COMMENT'):
218             comments = str(v).split('\n')
219             for com in comments:
220                 fits_header.add_comment(com)
221         elif k.upper() == 'HISTORY':
222             hists = str(v).split('\n')
223             for hist in hists:
224                 fits_header.add_history(hist)
225         elif isinstance(v, fits.header._HeaderCommentaryCards):
226             if k != '':
227                 fits_header.append(fits.Card(k, str(v).split('\n')))
228         else:
229             # For some horrific reason, we save a list to the wavelnth key in
230             # sources/rhessi.py. This is the least invasive fix for that stupidity.
231             if isinstance(v, list):
232                 v = str(v)
233             fits_header.append(fits.Card(k, v))
234 
235     if isinstance(key_comments, dict):
236         for k, v in key_comments.items():
237             # Check that the Card for the comment exists before trying to write to it.
238             if k in fits_header:
239                 fits_header.comments[k] = v
240     elif key_comments:
241 
242         raise TypeError("KEYCOMMENTS must be a dictionary")
243 
244     return fits_header
245 
246 
247 def extract_waveunit(header):
248     """
249     Attempt to read the wavelength unit from a given FITS header.
250 
251     Parameters
252     ----------
253     header : `sunpy.io.header.FileHeader`
254         One `~sunpy.io.header.FileHeader` instance which was created by
255         reading a FITS file. For example, `sunpy.io.fits.get_header` returns a list of
256         such instances.
257 
258     Returns
259     -------
260     waveunit : `str`
261         The wavelength unit that could be found or ``None`` otherwise.
262 
263     Examples
264     --------
265     The goal of this function is to return a string that can be used in
266     conjunction with the astropy.units module so that the return value can be
267     directly passed to `astropy.units.Unit`.
268 
269     >>> import astropy.units
270     >>> header = {'WAVEUNIT': 'Angstrom', 'KEYCOMMENTS': {}}
271     >>> waveunit = extract_waveunit(header)
272     >>> if waveunit is not None:
273     ...     unit = astropy.units.Unit(waveunit)
274     """
275     # algorithm: try the following procedures in the following order and return
276     # as soon as a waveunit could be detected
277     # 1. read header('WAVEUNIT'). If None, go to step 2.
278     # 1.1 -9 -> 'nm'
279     # 1.2 -10 -> 'angstrom'
280     # 1.3 0 -> go to step 2
281     # 1.4 if neither of the above, return the value itself in lowercase
282     # 2. parse waveunit_comment
283     # 2.1 'in meters' -> 'm'
284     # 3. parse wavelnth_comment
285     # 3.1 "[$UNIT] ..." -> $UNIT
286     # 3.2 "Observed wavelength ($UNIT)" -> $UNIT
287     def parse_waveunit_comment(waveunit_comment):
288         if waveunit_comment == 'in meters':
289             return 'm'
290 
291     waveunit_comment = header['KEYCOMMENTS'].get('WAVEUNIT')
292     wavelnth_comment = header['KEYCOMMENTS'].get('WAVELNTH')
293     waveunit = header.get('WAVEUNIT')
294     if waveunit is not None:
295         metre_submultiples = {
296             0: parse_waveunit_comment(waveunit_comment),
297             -1: 'dm',
298             -2: 'cm',
299             -3: 'mm',
300             -6: 'um',
301             -9: 'nm',
302             -10: 'angstrom',
303             -12: 'pm',
304             -15: 'fm',
305             -18: 'am',
306             -21: 'zm',
307             -24: 'ym'}
308         waveunit = metre_submultiples.get(waveunit, str(waveunit).lower())
309     elif waveunit_comment is not None:
310         waveunit = parse_waveunit_comment(waveunit_comment)
311     elif wavelnth_comment is not None:
312         # supported formats (where $UNIT is the unit like "nm" or "Angstrom"):
313         #   "Observed wavelength ($UNIT)"
314         #   "[$UNIT] ..."
315         parentheses_pattern = r'Observed wavelength \((\w+?)\)$'
316         brackets_pattern = r'^\[(\w+?)\]'
317         for pattern in [parentheses_pattern, brackets_pattern]:
318             m = re.search(pattern, wavelnth_comment)
319             if m is not None:
320                 waveunit = m.group(1)
321                 break
322     if waveunit == '':
323         return None  # To fix problems associated with HMI FITS.
324     return waveunit
325
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sunpy/io/fits.py b/sunpy/io/fits.py
--- a/sunpy/io/fits.py
+++ b/sunpy/io/fits.py
@@ -48,7 +48,7 @@
     ----------
     filepath : `str`
         The fits file to be read.
-    hdu: `int` or iterable
+    hdus: `int` or iterable
         The HDU indexes to read from the file.
 
     Returns
@@ -71,7 +71,9 @@
             elif isinstance(hdus, collections.Iterable):
                 hdulist = [hdulist[i] for i in hdus]
 
-        hdulist.verify('silentfix+warn')
+        hdulist = fits.hdu.HDUList(hdulist)
+        for h in hdulist:
+            h.verify('silentfix+warn')
 
         headers = get_header(hdulist)
         pairs = []

{"golden_diff": "diff --git a/sunpy/io/fits.py b/sunpy/io/fits.py\n--- a/sunpy/io/fits.py\n+++ b/sunpy/io/fits.py\n@@ -48,7 +48,7 @@\n     ----------\n     filepath : `str`\n         The fits file to be read.\n-    hdu: `int` or iterable\n+    hdus: `int` or iterable\n         The HDU indexes to read from the file.\n \n     Returns\n@@ -71,7 +71,9 @@\n             elif isinstance(hdus, collections.Iterable):\n                 hdulist = [hdulist[i] for i in hdus]\n \n-        hdulist.verify('silentfix+warn')\n+        hdulist = fits.hdu.HDUList(hdulist)\n+        for h in hdulist:\n+            h.verify('silentfix+warn')\n \n         headers = get_header(hdulist)\n         pairs = []\n", "issue": "Example of reading data/header with io.fits.read is broken\nIn the docs, we have [the following example of how to read the data and header directly from a file using `sunpy.io.fits.read`](https://docs.sunpy.org/en/stable/code_ref/map.html#fixing-map-metadata),\r\n\r\n```python\r\n[data,header] = sunpy.io.fits.read(filename)\r\n```\r\n\r\nThere are a few problems here.\r\n\r\n1. If you have 2 HDUs, this returns the first to `data` and the second to `header`, obviously not the intended behavior.\r\n2. You can use the `hdus` kwarg to select only a specific HDU but it turns out this is broken because the rest of the function expects an `HDUList` and after we slice it, we don't cast it back to an `HDUList` so everything after this explodes in fun and unexpected ways.\r\n3. Even accounting for the first two problems, this function returns a list of data,header pairs, not a list of data and headers so you get a `ValueError` when trying to return this to a tuple as above. Fixing items 1 and 2, this example could be amended to,\r\n\r\n```python\r\ndata, header = sunpy.io.fits.read(filename)[0]\r\n```\n", "before_files": [{"content": "\"\"\"\nThis module provides a FITS file reader.\n\nNotes\n-----\n\n1. FITS files allow comments to be attached to every value in the header.\n   This is implemented in this module as a KEYCOMMENTS dictionary in the\n   sunpy header. To add a comment to the file on write, add a comment to this\n   dictionary with the same name as a key in the header (upcased).\n\n2. Due to the way `~astropy.io.fits` works with images, the header dictionary may\n   differ depending on whether is accessed before or after the fits[0].data\n   is requested. If the header is read before the data then the original\n   header will be returned. If the header is read after the data has been\n   accessed then the data will have been scaled and a modified header\n   reflecting these changes will be returned: BITPIX may differ and\n   BSCALE and B_ZERO may be dropped in the modified version.\n\n3. The verify('silentfix+warn') call attempts to handle violations of the FITS\n   standard. For example, ``nan`` values will be converted to \"nan\" strings.\n   Attempting to cast a `astropy.io.fits.Header` to a dictionary while it contains\n   invalid header tags will result in an error so verifying it early on\n   makes the header easier to work with later.\n\"\"\"\nimport os\nimport re\nimport sys\nimport warnings\nimport traceback\nimport collections\n\nfrom astropy.io import fits\n\nfrom sunpy.io.header import FileHeader\nfrom sunpy.util.exceptions import SunpyUserWarning\n\n__all__ = ['header_to_fits', 'read', 'get_header', 'write', 'extract_waveunit']\n\nHDPair = collections.namedtuple('HDPair', ['data', 'header'])\n\n\ndef read(filepath, hdus=None, memmap=None, **kwargs):\n    \"\"\"\n    Read a fits file.\n\n    Parameters\n    ----------\n    filepath : `str`\n        The fits file to be read.\n    hdu: `int` or iterable\n        The HDU indexes to read from the file.\n\n    Returns\n    -------\n    pairs : `list`\n        A list of (data, header) tuples\n\n    Notes\n    -----\n    This routine reads all the HDU's in a fits file and returns a list of the\n    data and a FileHeader instance for each one.\n\n    Also all comments in the original file are concatenated into a single\n    \"comment\" key in the returned FileHeader.\n    \"\"\"\n    with fits.open(filepath, ignore_blank=True, memmap=memmap) as hdulist:\n        if hdus is not None:\n            if isinstance(hdus, int):\n                hdulist = hdulist[hdus]\n            elif isinstance(hdus, collections.Iterable):\n                hdulist = [hdulist[i] for i in hdus]\n\n        hdulist.verify('silentfix+warn')\n\n        headers = get_header(hdulist)\n        pairs = []\n\n        for i, (hdu, header) in enumerate(zip(hdulist, headers)):\n            try:\n                pairs.append(HDPair(hdu.data, header))\n            except (KeyError, ValueError) as e:\n                message = f\"Error when reading HDU {i}. Skipping.\\n\"\n                for line in traceback.format_tb(sys.exc_info()[2]):\n                    message += line\n                    message += '\\n'\n                message += repr(e)\n                warnings.warn(message, SunpyUserWarning)\n\n    return pairs\n\n\ndef get_header(afile):\n    \"\"\"\n    Read a fits file and return just the headers for all HDU's. In each header,\n    the key WAVEUNIT denotes the wavelength unit which is used to describe the\n    value of the key \"WAVELNTH\".\n\n    Parameters\n    ----------\n    afile : `str` or `astropy.io.fits.HDUList`\n        The file to be read, or HDUList to process.\n\n    Returns\n    -------\n    headers : `list`\n        A list of `sunpy.io.helper.FileHeader` headers.\n    \"\"\"\n    if isinstance(afile, fits.HDUList):\n        hdulist = afile\n        close = False\n    else:\n        hdulist = fits.open(afile, ignore_blank=True)\n        hdulist.verify('silentfix')\n        close = True\n\n    try:\n        headers = []\n        for hdu in hdulist:\n            try:\n                comment = \"\".join(hdu.header['COMMENT']).strip()\n            except KeyError:\n                comment = \"\"\n            try:\n                history = \"\".join(hdu.header['HISTORY']).strip()\n            except KeyError:\n                history = \"\"\n\n            header = FileHeader(hdu.header)\n            header['COMMENT'] = comment\n            header['HISTORY'] = history\n\n            # Strip out KEYCOMMENTS to a dict, the hard way\n            keydict = {}\n            for card in hdu.header.cards:\n                if card.comment != '':\n                    keydict.update({card.keyword: card.comment})\n            header['KEYCOMMENTS'] = keydict\n            header['WAVEUNIT'] = extract_waveunit(header)\n\n            headers.append(header)\n    finally:\n        if close:\n            hdulist.close()\n    return headers\n\n\ndef write(fname, data, header, hdu_type=None, **kwargs):\n    \"\"\"\n    Take a data header pair and write a FITS file.\n\n    Parameters\n    ----------\n    fname : `str`\n        File name, with extension.\n    data : `numpy.ndarray`\n        n-dimensional data array.\n    header : `dict`\n        A header dictionary.\n    hdu_type: `None` or `~astropy.io.fits.CompImageHDU`\n        `None` will return a normal FITS files.\n        `~astropy.io.fits.CompImageHDU` will rice compress the FITS file.\n    kwargs :\n        Additional keyword arguments are given to\n        `~astropy.io.fits.HDUList.writeto`.\n    \"\"\"\n    # Copy header so the one in memory is left alone while changing it for\n    # write.\n    header = header.copy()\n\n    fits_header = header_to_fits(header)\n\n    if isinstance(fname, str):\n        fname = os.path.expanduser(fname)\n\n    fitskwargs = {'output_verify': 'fix'}\n    fitskwargs.update(kwargs)\n\n    if not hdu_type:\n        hdu_type = fits.PrimaryHDU\n\n    hdu = hdu_type(data=data, header=fits_header)\n\n    if not isinstance(hdu, fits.PrimaryHDU):\n        hdul = fits.HDUList([fits.PrimaryHDU(), hdu])\n    else:\n        hdul = fits.HDUList([hdu])\n\n    hdul.writeto(fname, **fitskwargs)\n\n\ndef header_to_fits(header):\n    \"\"\"\n    Convert a header dict to a `~astropy.fits.Header`.\n    \"\"\"\n    # Copy the header to avoid modifying it in place\n    header = header.copy()\n    # The comments need to be added to the header separately from the normal\n    # kwargs. Find and deal with them:\n    fits_header = fits.Header()\n    # Check Header\n    key_comments = header.pop('KEYCOMMENTS', False)\n\n    for k, v in header.items():\n        # Drop any keys that have non-ascii characters\n        if not fits.Card._ascii_text_re.match(str(v)):\n            warnings.warn(f'The meta key {k} is not valid ascii, dropping from the FITS header',\n                          SunpyUserWarning)\n            continue\n        # Drop any keys which are too long to save into FITS\n        if len(k) > 8:\n            warnings.warn(f\"The meta key {k} is too long, dropping from the FITS header \"\n                          \"(maximum allowed key length is 8 characters).\",\n                          SunpyUserWarning)\n            continue\n\n        if k.upper() in ('COMMENT', 'HV_COMMENT'):\n            comments = str(v).split('\\n')\n            for com in comments:\n                fits_header.add_comment(com)\n        elif k.upper() == 'HISTORY':\n            hists = str(v).split('\\n')\n            for hist in hists:\n                fits_header.add_history(hist)\n        elif isinstance(v, fits.header._HeaderCommentaryCards):\n            if k != '':\n                fits_header.append(fits.Card(k, str(v).split('\\n')))\n        else:\n            # For some horrific reason, we save a list to the wavelnth key in\n            # sources/rhessi.py. This is the least invasive fix for that stupidity.\n            if isinstance(v, list):\n                v = str(v)\n            fits_header.append(fits.Card(k, v))\n\n    if isinstance(key_comments, dict):\n        for k, v in key_comments.items():\n            # Check that the Card for the comment exists before trying to write to it.\n            if k in fits_header:\n                fits_header.comments[k] = v\n    elif key_comments:\n\n        raise TypeError(\"KEYCOMMENTS must be a dictionary\")\n\n    return fits_header\n\n\ndef extract_waveunit(header):\n    \"\"\"\n    Attempt to read the wavelength unit from a given FITS header.\n\n    Parameters\n    ----------\n    header : `sunpy.io.header.FileHeader`\n        One `~sunpy.io.header.FileHeader` instance which was created by\n        reading a FITS file. For example, `sunpy.io.fits.get_header` returns a list of\n        such instances.\n\n    Returns\n    -------\n    waveunit : `str`\n        The wavelength unit that could be found or ``None`` otherwise.\n\n    Examples\n    --------\n    The goal of this function is to return a string that can be used in\n    conjunction with the astropy.units module so that the return value can be\n    directly passed to `astropy.units.Unit`.\n\n    >>> import astropy.units\n    >>> header = {'WAVEUNIT': 'Angstrom', 'KEYCOMMENTS': {}}\n    >>> waveunit = extract_waveunit(header)\n    >>> if waveunit is not None:\n    ...     unit = astropy.units.Unit(waveunit)\n    \"\"\"\n    # algorithm: try the following procedures in the following order and return\n    # as soon as a waveunit could be detected\n    # 1. read header('WAVEUNIT'). If None, go to step 2.\n    # 1.1 -9 -> 'nm'\n    # 1.2 -10 -> 'angstrom'\n    # 1.3 0 -> go to step 2\n    # 1.4 if neither of the above, return the value itself in lowercase\n    # 2. parse waveunit_comment\n    # 2.1 'in meters' -> 'm'\n    # 3. parse wavelnth_comment\n    # 3.1 \"[$UNIT] ...\" -> $UNIT\n    # 3.2 \"Observed wavelength ($UNIT)\" -> $UNIT\n    def parse_waveunit_comment(waveunit_comment):\n        if waveunit_comment == 'in meters':\n            return 'm'\n\n    waveunit_comment = header['KEYCOMMENTS'].get('WAVEUNIT')\n    wavelnth_comment = header['KEYCOMMENTS'].get('WAVELNTH')\n    waveunit = header.get('WAVEUNIT')\n    if waveunit is not None:\n        metre_submultiples = {\n            0: parse_waveunit_comment(waveunit_comment),\n            -1: 'dm',\n            -2: 'cm',\n            -3: 'mm',\n            -6: 'um',\n            -9: 'nm',\n            -10: 'angstrom',\n            -12: 'pm',\n            -15: 'fm',\n            -18: 'am',\n            -21: 'zm',\n            -24: 'ym'}\n        waveunit = metre_submultiples.get(waveunit, str(waveunit).lower())\n    elif waveunit_comment is not None:\n        waveunit = parse_waveunit_comment(waveunit_comment)\n    elif wavelnth_comment is not None:\n        # supported formats (where $UNIT is the unit like \"nm\" or \"Angstrom\"):\n        #   \"Observed wavelength ($UNIT)\"\n        #   \"[$UNIT] ...\"\n        parentheses_pattern = r'Observed wavelength \\((\\w+?)\\)$'\n        brackets_pattern = r'^\\[(\\w+?)\\]'\n        for pattern in [parentheses_pattern, brackets_pattern]:\n            m = re.search(pattern, wavelnth_comment)\n            if m is not None:\n                waveunit = m.group(1)\n                break\n    if waveunit == '':\n        return None  # To fix problems associated with HMI FITS.\n    return waveunit\n", "path": "sunpy/io/fits.py"}], "after_files": [{"content": "\"\"\"\nThis module provides a FITS file reader.\n\nNotes\n-----\n\n1. FITS files allow comments to be attached to every value in the header.\n   This is implemented in this module as a KEYCOMMENTS dictionary in the\n   sunpy header. To add a comment to the file on write, add a comment to this\n   dictionary with the same name as a key in the header (upcased).\n\n2. Due to the way `~astropy.io.fits` works with images, the header dictionary may\n   differ depending on whether is accessed before or after the fits[0].data\n   is requested. If the header is read before the data then the original\n   header will be returned. If the header is read after the data has been\n   accessed then the data will have been scaled and a modified header\n   reflecting these changes will be returned: BITPIX may differ and\n   BSCALE and B_ZERO may be dropped in the modified version.\n\n3. The verify('silentfix+warn') call attempts to handle violations of the FITS\n   standard. For example, ``nan`` values will be converted to \"nan\" strings.\n   Attempting to cast a `astropy.io.fits.Header` to a dictionary while it contains\n   invalid header tags will result in an error so verifying it early on\n   makes the header easier to work with later.\n\"\"\"\nimport os\nimport re\nimport sys\nimport warnings\nimport traceback\nimport collections\n\nfrom astropy.io import fits\n\nfrom sunpy.io.header import FileHeader\nfrom sunpy.util.exceptions import SunpyUserWarning\n\n__all__ = ['header_to_fits', 'read', 'get_header', 'write', 'extract_waveunit']\n\nHDPair = collections.namedtuple('HDPair', ['data', 'header'])\n\n\ndef read(filepath, hdus=None, memmap=None, **kwargs):\n    \"\"\"\n    Read a fits file.\n\n    Parameters\n    ----------\n    filepath : `str`\n        The fits file to be read.\n    hdus: `int` or iterable\n        The HDU indexes to read from the file.\n\n    Returns\n    -------\n    pairs : `list`\n        A list of (data, header) tuples\n\n    Notes\n    -----\n    This routine reads all the HDU's in a fits file and returns a list of the\n    data and a FileHeader instance for each one.\n\n    Also all comments in the original file are concatenated into a single\n    \"comment\" key in the returned FileHeader.\n    \"\"\"\n    with fits.open(filepath, ignore_blank=True, memmap=memmap) as hdulist:\n        if hdus is not None:\n            if isinstance(hdus, int):\n                hdulist = hdulist[hdus]\n            elif isinstance(hdus, collections.Iterable):\n                hdulist = [hdulist[i] for i in hdus]\n\n        hdulist = fits.hdu.HDUList(hdulist)\n        for h in hdulist:\n            h.verify('silentfix+warn')\n\n        headers = get_header(hdulist)\n        pairs = []\n\n        for i, (hdu, header) in enumerate(zip(hdulist, headers)):\n            try:\n                pairs.append(HDPair(hdu.data, header))\n            except (KeyError, ValueError) as e:\n                message = f\"Error when reading HDU {i}. Skipping.\\n\"\n                for line in traceback.format_tb(sys.exc_info()[2]):\n                    message += line\n                    message += '\\n'\n                message += repr(e)\n                warnings.warn(message, SunpyUserWarning)\n\n    return pairs\n\n\ndef get_header(afile):\n    \"\"\"\n    Read a fits file and return just the headers for all HDU's. In each header,\n    the key WAVEUNIT denotes the wavelength unit which is used to describe the\n    value of the key \"WAVELNTH\".\n\n    Parameters\n    ----------\n    afile : `str` or `astropy.io.fits.HDUList`\n        The file to be read, or HDUList to process.\n\n    Returns\n    -------\n    headers : `list`\n        A list of `sunpy.io.helper.FileHeader` headers.\n    \"\"\"\n    if isinstance(afile, fits.HDUList):\n        hdulist = afile\n        close = False\n    else:\n        hdulist = fits.open(afile, ignore_blank=True)\n        hdulist.verify('silentfix')\n        close = True\n\n    try:\n        headers = []\n        for hdu in hdulist:\n            try:\n                comment = \"\".join(hdu.header['COMMENT']).strip()\n            except KeyError:\n                comment = \"\"\n            try:\n                history = \"\".join(hdu.header['HISTORY']).strip()\n            except KeyError:\n                history = \"\"\n\n            header = FileHeader(hdu.header)\n            header['COMMENT'] = comment\n            header['HISTORY'] = history\n\n            # Strip out KEYCOMMENTS to a dict, the hard way\n            keydict = {}\n            for card in hdu.header.cards:\n                if card.comment != '':\n                    keydict.update({card.keyword: card.comment})\n            header['KEYCOMMENTS'] = keydict\n            header['WAVEUNIT'] = extract_waveunit(header)\n\n            headers.append(header)\n    finally:\n        if close:\n            hdulist.close()\n    return headers\n\n\ndef write(fname, data, header, hdu_type=None, **kwargs):\n    \"\"\"\n    Take a data header pair and write a FITS file.\n\n    Parameters\n    ----------\n    fname : `str`\n        File name, with extension.\n    data : `numpy.ndarray`\n        n-dimensional data array.\n    header : `dict`\n        A header dictionary.\n    hdu_type: `None` or `~astropy.io.fits.CompImageHDU`\n        `None` will return a normal FITS files.\n        `~astropy.io.fits.CompImageHDU` will rice compress the FITS file.\n    kwargs :\n        Additional keyword arguments are given to\n        `~astropy.io.fits.HDUList.writeto`.\n    \"\"\"\n    # Copy header so the one in memory is left alone while changing it for\n    # write.\n    header = header.copy()\n\n    fits_header = header_to_fits(header)\n\n    if isinstance(fname, str):\n        fname = os.path.expanduser(fname)\n\n    fitskwargs = {'output_verify': 'fix'}\n    fitskwargs.update(kwargs)\n\n    if not hdu_type:\n        hdu_type = fits.PrimaryHDU\n\n    hdu = hdu_type(data=data, header=fits_header)\n\n    if not isinstance(hdu, fits.PrimaryHDU):\n        hdul = fits.HDUList([fits.PrimaryHDU(), hdu])\n    else:\n        hdul = fits.HDUList([hdu])\n\n    hdul.writeto(fname, **fitskwargs)\n\n\ndef header_to_fits(header):\n    \"\"\"\n    Convert a header dict to a `~astropy.fits.Header`.\n    \"\"\"\n    # Copy the header to avoid modifying it in place\n    header = header.copy()\n    # The comments need to be added to the header separately from the normal\n    # kwargs. Find and deal with them:\n    fits_header = fits.Header()\n    # Check Header\n    key_comments = header.pop('KEYCOMMENTS', False)\n\n    for k, v in header.items():\n        # Drop any keys that have non-ascii characters\n        if not fits.Card._ascii_text_re.match(str(v)):\n            warnings.warn(f'The meta key {k} is not valid ascii, dropping from the FITS header',\n                          SunpyUserWarning)\n            continue\n        # Drop any keys which are too long to save into FITS\n        if len(k) > 8:\n            warnings.warn(f\"The meta key {k} is too long, dropping from the FITS header \"\n                          \"(maximum allowed key length is 8 characters).\",\n                          SunpyUserWarning)\n            continue\n\n        if k.upper() in ('COMMENT', 'HV_COMMENT'):\n            comments = str(v).split('\\n')\n            for com in comments:\n                fits_header.add_comment(com)\n        elif k.upper() == 'HISTORY':\n            hists = str(v).split('\\n')\n            for hist in hists:\n                fits_header.add_history(hist)\n        elif isinstance(v, fits.header._HeaderCommentaryCards):\n            if k != '':\n                fits_header.append(fits.Card(k, str(v).split('\\n')))\n        else:\n            # For some horrific reason, we save a list to the wavelnth key in\n            # sources/rhessi.py. This is the least invasive fix for that stupidity.\n            if isinstance(v, list):\n                v = str(v)\n            fits_header.append(fits.Card(k, v))\n\n    if isinstance(key_comments, dict):\n        for k, v in key_comments.items():\n            # Check that the Card for the comment exists before trying to write to it.\n            if k in fits_header:\n                fits_header.comments[k] = v\n    elif key_comments:\n\n        raise TypeError(\"KEYCOMMENTS must be a dictionary\")\n\n    return fits_header\n\n\ndef extract_waveunit(header):\n    \"\"\"\n    Attempt to read the wavelength unit from a given FITS header.\n\n    Parameters\n    ----------\n    header : `sunpy.io.header.FileHeader`\n        One `~sunpy.io.header.FileHeader` instance which was created by\n        reading a FITS file. For example, `sunpy.io.fits.get_header` returns a list of\n        such instances.\n\n    Returns\n    -------\n    waveunit : `str`\n        The wavelength unit that could be found or ``None`` otherwise.\n\n    Examples\n    --------\n    The goal of this function is to return a string that can be used in\n    conjunction with the astropy.units module so that the return value can be\n    directly passed to `astropy.units.Unit`.\n\n    >>> import astropy.units\n    >>> header = {'WAVEUNIT': 'Angstrom', 'KEYCOMMENTS': {}}\n    >>> waveunit = extract_waveunit(header)\n    >>> if waveunit is not None:\n    ...     unit = astropy.units.Unit(waveunit)\n    \"\"\"\n    # algorithm: try the following procedures in the following order and return\n    # as soon as a waveunit could be detected\n    # 1. read header('WAVEUNIT'). If None, go to step 2.\n    # 1.1 -9 -> 'nm'\n    # 1.2 -10 -> 'angstrom'\n    # 1.3 0 -> go to step 2\n    # 1.4 if neither of the above, return the value itself in lowercase\n    # 2. parse waveunit_comment\n    # 2.1 'in meters' -> 'm'\n    # 3. parse wavelnth_comment\n    # 3.1 \"[$UNIT] ...\" -> $UNIT\n    # 3.2 \"Observed wavelength ($UNIT)\" -> $UNIT\n    def parse_waveunit_comment(waveunit_comment):\n        if waveunit_comment == 'in meters':\n            return 'm'\n\n    waveunit_comment = header['KEYCOMMENTS'].get('WAVEUNIT')\n    wavelnth_comment = header['KEYCOMMENTS'].get('WAVELNTH')\n    waveunit = header.get('WAVEUNIT')\n    if waveunit is not None:\n        metre_submultiples = {\n            0: parse_waveunit_comment(waveunit_comment),\n            -1: 'dm',\n            -2: 'cm',\n            -3: 'mm',\n            -6: 'um',\n            -9: 'nm',\n            -10: 'angstrom',\n            -12: 'pm',\n            -15: 'fm',\n            -18: 'am',\n            -21: 'zm',\n            -24: 'ym'}\n        waveunit = metre_submultiples.get(waveunit, str(waveunit).lower())\n    elif waveunit_comment is not None:\n        waveunit = parse_waveunit_comment(waveunit_comment)\n    elif wavelnth_comment is not None:\n        # supported formats (where $UNIT is the unit like \"nm\" or \"Angstrom\"):\n        #   \"Observed wavelength ($UNIT)\"\n        #   \"[$UNIT] ...\"\n        parentheses_pattern = r'Observed wavelength \\((\\w+?)\\)$'\n        brackets_pattern = r'^\\[(\\w+?)\\]'\n        for pattern in [parentheses_pattern, brackets_pattern]:\n            m = re.search(pattern, wavelnth_comment)\n            if m is not None:\n                waveunit = m.group(1)\n                break\n    if waveunit == '':\n        return None  # To fix problems associated with HMI FITS.\n    return waveunit\n", "path": "sunpy/io/fits.py"}]}