We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
improve gzipped-FITS files detection
### Describe the feature

Currently the file detection for gzipped-FITS files relies on checking if they are named in the `filename.fits.gz` format. However since this condition isn't a necessity for gzipped files, this approach misses those gzipped-FITS files which do not end with the `.gz` extension.

### Proposed solution

Use magic bytes / file signatures to check whether the file is gzipped or not. If it is, decompress and read its first few bytes to determine whether it's a FITS file.

--- 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/file_tools.py`
Content:
```
1 """
2 This module provides a generic file reader.
3 """
4 import os
5 import re
6 import pathlib
7 
8 try:
9     from . import _fits as fits
10 except ImportError:
11     fits = None
12 
13 try:
14     from . import jp2
15 except ImportError:
16     jp2 = None
17 
18 try:
19     from . import ana
20 except ImportError:
21     ana = None
22 
23 
24 __all__ = ['read_file', 'read_file_header', 'write_file', 'detect_filetype']
25 
26 # File formats supported by SunPy
27 _known_extensions = {
28     ('fts', 'fits'): 'fits',
29     ('jp2', 'j2k', 'jpc', 'jpt'): 'jp2',
30     ('fz', 'f0'): 'ana'
31 }
32 
33 
34 # Define a dict which raises a custom error message if the value is None
35 class Readers(dict):
36     def __init__(self, *args):
37         dict.__init__(self, *args)
38 
39     def __getitem__(self, key):
40         val = dict.__getitem__(self, key)
41         if val is None:
42             raise ReaderError(f"The Reader sunpy.io.{key} is not available, "
43                               "please check that you have the required dependencies "
44                               "installed.")
45         return val
46 
47 
48 # Map the readers
49 _readers = Readers({
50     'fits': fits,
51     'jp2': jp2,
52     'ana': ana
53 })
54 
55 
56 def read_file(filepath, filetype=None, **kwargs):
57     """
58     Automatically determine the filetype and read the file.
59 
60     Parameters
61     ----------
62     filepath : `str`, path-like
63         The file to be read.
64     filetype : `str`, optional
65         Supported reader or extension to manually specify the filetype.
66         Supported readers are ('jp2', 'fits', 'ana')
67     memmap : `bool`, optional
68         Should memory mapping be used, i.e. keep data on disk rather than in RAM.
69         This is currently only supported by the FITS reader.
70     **kwargs : `dict`
71         All extra keyword arguments are passed to ``.read`` for the file specific reader.
72 
73     Returns
74     -------
75     pairs : `list`
76         A list of (data, header) tuples.
77     """
78     # Convert Path objects to strings as the filepath can also be a URL
79     filepath = str(filepath)
80     # Use the explicitly passed filetype
81     if filetype is not None:
82         return _readers[filetype].read(filepath, **kwargs)
83 
84     # Go through the known extensions
85     for extension, readername in _known_extensions.items():
86         if filepath.endswith(extension) or filetype in extension:
87             return _readers[readername].read(filepath, **kwargs)
88 
89     # If filetype is not apparent from the extension, attempt to detect it
90     readername = _detect_filetype(filepath)
91     return _readers[readername].read(filepath, **kwargs)
92 
93 
94 def read_file_header(filepath, filetype=None, **kwargs):
95     """
96     Reads the header from a given file.
97 
98     This should always return a instance of `~sunpy.io.header.FileHeader`.
99 
100     Parameters
101     ----------
102     filepath : `str`
103         The file from which the header is to be read.
104     filetype : `str`
105         Supported reader or extension to manually specify the filetype.
106         Supported readers are ('jp2', 'fits').
107     **kwargs : `dict`
108         All extra keyword arguments are passed to ``.get_header`` for the file specific reader.
109 
110     Returns
111     -------
112     headers : `list`
113         A list of headers.
114     """
115     # Use the explicitly passed filetype
116     if filetype is not None:
117         return _readers[filetype].get_header(filepath, **kwargs)
118 
119     # Go through the known extensions
120     for extension, readername in _known_extensions.items():
121         if filepath.endswith(extension) or filetype in extension:
122             return _readers[readername].get_header(filepath, **kwargs)
123 
124     # If filetype is not apparent from the extension, attempt to detect it
125     readername = _detect_filetype(filepath)
126     return _readers[readername].get_header(filepath, **kwargs)
127 
128 
129 def write_file(fname, data, header, filetype='auto', **kwargs):
130     """
131     Write a file from a data & header pair using one of the defined file types.
132 
133     Parameters
134     ----------
135     fname : `str`
136         Filename of file to save.
137     data : `numpy.ndarray`
138         Data to save to a fits file.
139     header : `collections.OrderedDict`
140         Meta data to save with the data.
141     filetype : `str`, {'auto', 'fits', 'jp2'}, optional
142         Filetype to save if ``auto`` the  filename extension will
143         be detected, else specify a supported file extension.
144     **kwargs : `dict`
145         All extra keyword arguments are passed to ``.write`` for the file specific reader.
146 
147     Notes
148     -----
149     * This routine currently only supports saving a single HDU.
150     """
151     if filetype == 'auto':
152         # Get the extension without the leading dot
153         filetype = pathlib.Path(fname).suffix[1:]
154 
155     for extension, readername in _known_extensions.items():
156         if filetype in extension:
157             return _readers[readername].write(fname, data, header, **kwargs)
158 
159     # Nothing has matched, report an error
160     raise ValueError(f"The filetype provided ({filetype}) is not supported")
161 
162 
163 def _detect_filetype(filepath):
164     """
165     Attempts to determine the type of data contained in a file and returns
166     the filetype if the available readers exist within sunpy.io
167 
168     Parameters
169     ----------
170     filepath : `str`
171         Where the file is.
172 
173     Returns
174     -------
175     filetype : `str`
176         The type of file.
177     """
178 
179     if detect_filetype(filepath) in _readers.keys():
180         return detect_filetype(filepath)
181 
182     # Raise an error if an unsupported filetype is encountered
183     raise UnrecognizedFileTypeError("The requested filetype is not currently "
184                                     "supported by SunPy.")
185 
186 
187 def detect_filetype(filepath):
188     """
189     Attempts to determine the type of file a given filepath is.
190 
191     Parameters
192     ----------
193     filepath : `str`
194         Where the file is.
195 
196     Returns
197     -------
198     filetype : `str`
199         The type of file.
200     """
201 
202     # Open file and read in first two lines
203     with open(filepath, 'rb') as fp:
204         line1 = fp.readline()
205         line2 = fp.readline()
206         # Some FITS files do not have line breaks at the end of header cards.
207         fp.seek(0)
208         first80 = fp.read(80)
209         # first 8 bytes of netcdf4/hdf5 to determine filetype as have same sequence
210         fp.seek(0)
211         first_8bytes = fp.read(8)
212         # first 4 bytes of CDF
213         fp.seek(0)
214         cdf_magic_number = fp.read(4).hex()
215 
216     # FITS
217     # Check the extensions to see if it is a gzipped FITS file
218     filepath_rest_ext1, ext1 = os.path.splitext(filepath)
219     _, ext2 = os.path.splitext(filepath_rest_ext1)
220 
221     gzip_extensions = [".gz"]
222     fits_extensions = [".fts", ".fit", ".fits"]
223     if (ext1 in gzip_extensions and ext2 in fits_extensions):
224         return 'fits'
225 
226     # Check for "KEY_WORD  =" at beginning of file
227     match = re.match(br"[A-Z0-9_]{0,8} *=", first80)
228     if match is not None:
229         return 'fits'
230 
231     # JPEG 2000
232     # Checks for one of two signatures found at beginning of all JP2 files.
233     # Adapted from ExifTool
234     # [1] https://www.sno.phy.queensu.ca/~phil/exiftool/
235     # [2] http://www.hlevkin.com/Standards/fcd15444-2.pdf
236     # [3] http://www.hlevkin.com/Standards/fcd15444-1.pdf
237     jp2_signatures = [b"\x00\x00\x00\x0cjP  \x0d\x0a\x87\x0a",
238                       b"\x00\x00\x00\x0cjP\x1a\x1a\x0d\x0a\x87\x0a"]
239     for sig in jp2_signatures:
240         if line1 + line2 == sig:
241             return 'jp2'
242 
243     # netcdf4 and hdf5 files
244     if first_8bytes == b'\x89HDF\r\n\x1a\n':
245         return 'hdf5'
246 
247     if cdf_magic_number in ['cdf30001', 'cdf26002', '0000ffff']:
248         return 'cdf'
249 
250     # Raise an error if an unsupported filetype is encountered
251     raise UnrecognizedFileTypeError("The requested filetype is not currently "
252                                     "supported by SunPy.")
253 
254 
255 class UnrecognizedFileTypeError(OSError):
256     """
257     Exception to raise when an unknown file type is encountered.
258     """
259 
260 
261 class ReaderError(ImportError):
262     """
263     Exception to raise when a reader errors.
264     """
265 
266 
267 class InvalidJPEG2000FileExtension(OSError):
268     """
269     Exception to raise when an invalid JPEG2000 file type is encountered.
270     """
271
```
--- 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/file_tools.py b/sunpy/io/file_tools.py
--- a/sunpy/io/file_tools.py
+++ b/sunpy/io/file_tools.py
@@ -1,8 +1,8 @@
 """
 This module provides a generic file reader.
 """
-import os
 import re
+import gzip
 import pathlib
 
 try:
@@ -214,14 +214,11 @@
         cdf_magic_number = fp.read(4).hex()
 
     # FITS
-    # Check the extensions to see if it is a gzipped FITS file
-    filepath_rest_ext1, ext1 = os.path.splitext(filepath)
-    _, ext2 = os.path.splitext(filepath_rest_ext1)
-
-    gzip_extensions = [".gz"]
-    fits_extensions = [".fts", ".fit", ".fits"]
-    if (ext1 in gzip_extensions and ext2 in fits_extensions):
-        return 'fits'
+    # Checks for gzip signature.
+    # If found, decompresses first few bytes and checks for FITS
+    if first80[:3] == b"\x1f\x8b\x08":
+        with gzip.open(filepath, 'rb') as fp:
+            first80 = fp.read(80)
 
     # Check for "KEY_WORD  =" at beginning of file
     match = re.match(br"[A-Z0-9_]{0,8} *=", first80)

{"golden_diff": "diff --git a/sunpy/io/file_tools.py b/sunpy/io/file_tools.py\n--- a/sunpy/io/file_tools.py\n+++ b/sunpy/io/file_tools.py\n@@ -1,8 +1,8 @@\n \"\"\"\n This module provides a generic file reader.\n \"\"\"\n-import os\n import re\n+import gzip\n import pathlib\n \n try:\n@@ -214,14 +214,11 @@\n         cdf_magic_number = fp.read(4).hex()\n \n     # FITS\n-    # Check the extensions to see if it is a gzipped FITS file\n-    filepath_rest_ext1, ext1 = os.path.splitext(filepath)\n-    _, ext2 = os.path.splitext(filepath_rest_ext1)\n-\n-    gzip_extensions = [\".gz\"]\n-    fits_extensions = [\".fts\", \".fit\", \".fits\"]\n-    if (ext1 in gzip_extensions and ext2 in fits_extensions):\n-        return 'fits'\n+    # Checks for gzip signature.\n+    # If found, decompresses first few bytes and checks for FITS\n+    if first80[:3] == b\"\\x1f\\x8b\\x08\":\n+        with gzip.open(filepath, 'rb') as fp:\n+            first80 = fp.read(80)\n \n     # Check for \"KEY_WORD  =\" at beginning of file\n     match = re.match(br\"[A-Z0-9_]{0,8} *=\", first80)\n", "issue": "improve gzipped-FITS files detection\n### Describe the feature\n\nCurrently the file detection for gzipped-FITS files relies on checking if they are named in the `filename.fits.gz` format. However since this condition isn't a necessity for gzipped files, this approach misses those gzipped-FITS files which do not end with the `.gz` extension.\n\n### Proposed solution\n\nUse magic bytes / file signatures to check whether the file is gzipped or not. If it is, decompress and read its first few bytes to determine whether it's a FITS file.\n", "before_files": [{"content": "\"\"\"\nThis module provides a generic file reader.\n\"\"\"\nimport os\nimport re\nimport pathlib\n\ntry:\n    from . import _fits as fits\nexcept ImportError:\n    fits = None\n\ntry:\n    from . import jp2\nexcept ImportError:\n    jp2 = None\n\ntry:\n    from . import ana\nexcept ImportError:\n    ana = None\n\n\n__all__ = ['read_file', 'read_file_header', 'write_file', 'detect_filetype']\n\n# File formats supported by SunPy\n_known_extensions = {\n    ('fts', 'fits'): 'fits',\n    ('jp2', 'j2k', 'jpc', 'jpt'): 'jp2',\n    ('fz', 'f0'): 'ana'\n}\n\n\n# Define a dict which raises a custom error message if the value is None\nclass Readers(dict):\n    def __init__(self, *args):\n        dict.__init__(self, *args)\n\n    def __getitem__(self, key):\n        val = dict.__getitem__(self, key)\n        if val is None:\n            raise ReaderError(f\"The Reader sunpy.io.{key} is not available, \"\n                              \"please check that you have the required dependencies \"\n                              \"installed.\")\n        return val\n\n\n# Map the readers\n_readers = Readers({\n    'fits': fits,\n    'jp2': jp2,\n    'ana': ana\n})\n\n\ndef read_file(filepath, filetype=None, **kwargs):\n    \"\"\"\n    Automatically determine the filetype and read the file.\n\n    Parameters\n    ----------\n    filepath : `str`, path-like\n        The file to be read.\n    filetype : `str`, optional\n        Supported reader or extension to manually specify the filetype.\n        Supported readers are ('jp2', 'fits', 'ana')\n    memmap : `bool`, optional\n        Should memory mapping be used, i.e. keep data on disk rather than in RAM.\n        This is currently only supported by the FITS reader.\n    **kwargs : `dict`\n        All extra keyword arguments are passed to ``.read`` for the file specific reader.\n\n    Returns\n    -------\n    pairs : `list`\n        A list of (data, header) tuples.\n    \"\"\"\n    # Convert Path objects to strings as the filepath can also be a URL\n    filepath = str(filepath)\n    # Use the explicitly passed filetype\n    if filetype is not None:\n        return _readers[filetype].read(filepath, **kwargs)\n\n    # Go through the known extensions\n    for extension, readername in _known_extensions.items():\n        if filepath.endswith(extension) or filetype in extension:\n            return _readers[readername].read(filepath, **kwargs)\n\n    # If filetype is not apparent from the extension, attempt to detect it\n    readername = _detect_filetype(filepath)\n    return _readers[readername].read(filepath, **kwargs)\n\n\ndef read_file_header(filepath, filetype=None, **kwargs):\n    \"\"\"\n    Reads the header from a given file.\n\n    This should always return a instance of `~sunpy.io.header.FileHeader`.\n\n    Parameters\n    ----------\n    filepath : `str`\n        The file from which the header is to be read.\n    filetype : `str`\n        Supported reader or extension to manually specify the filetype.\n        Supported readers are ('jp2', 'fits').\n    **kwargs : `dict`\n        All extra keyword arguments are passed to ``.get_header`` for the file specific reader.\n\n    Returns\n    -------\n    headers : `list`\n        A list of headers.\n    \"\"\"\n    # Use the explicitly passed filetype\n    if filetype is not None:\n        return _readers[filetype].get_header(filepath, **kwargs)\n\n    # Go through the known extensions\n    for extension, readername in _known_extensions.items():\n        if filepath.endswith(extension) or filetype in extension:\n            return _readers[readername].get_header(filepath, **kwargs)\n\n    # If filetype is not apparent from the extension, attempt to detect it\n    readername = _detect_filetype(filepath)\n    return _readers[readername].get_header(filepath, **kwargs)\n\n\ndef write_file(fname, data, header, filetype='auto', **kwargs):\n    \"\"\"\n    Write a file from a data & header pair using one of the defined file types.\n\n    Parameters\n    ----------\n    fname : `str`\n        Filename of file to save.\n    data : `numpy.ndarray`\n        Data to save to a fits file.\n    header : `collections.OrderedDict`\n        Meta data to save with the data.\n    filetype : `str`, {'auto', 'fits', 'jp2'}, optional\n        Filetype to save if ``auto`` the  filename extension will\n        be detected, else specify a supported file extension.\n    **kwargs : `dict`\n        All extra keyword arguments are passed to ``.write`` for the file specific reader.\n\n    Notes\n    -----\n    * This routine currently only supports saving a single HDU.\n    \"\"\"\n    if filetype == 'auto':\n        # Get the extension without the leading dot\n        filetype = pathlib.Path(fname).suffix[1:]\n\n    for extension, readername in _known_extensions.items():\n        if filetype in extension:\n            return _readers[readername].write(fname, data, header, **kwargs)\n\n    # Nothing has matched, report an error\n    raise ValueError(f\"The filetype provided ({filetype}) is not supported\")\n\n\ndef _detect_filetype(filepath):\n    \"\"\"\n    Attempts to determine the type of data contained in a file and returns\n    the filetype if the available readers exist within sunpy.io\n\n    Parameters\n    ----------\n    filepath : `str`\n        Where the file is.\n\n    Returns\n    -------\n    filetype : `str`\n        The type of file.\n    \"\"\"\n\n    if detect_filetype(filepath) in _readers.keys():\n        return detect_filetype(filepath)\n\n    # Raise an error if an unsupported filetype is encountered\n    raise UnrecognizedFileTypeError(\"The requested filetype is not currently \"\n                                    \"supported by SunPy.\")\n\n\ndef detect_filetype(filepath):\n    \"\"\"\n    Attempts to determine the type of file a given filepath is.\n\n    Parameters\n    ----------\n    filepath : `str`\n        Where the file is.\n\n    Returns\n    -------\n    filetype : `str`\n        The type of file.\n    \"\"\"\n\n    # Open file and read in first two lines\n    with open(filepath, 'rb') as fp:\n        line1 = fp.readline()\n        line2 = fp.readline()\n        # Some FITS files do not have line breaks at the end of header cards.\n        fp.seek(0)\n        first80 = fp.read(80)\n        # first 8 bytes of netcdf4/hdf5 to determine filetype as have same sequence\n        fp.seek(0)\n        first_8bytes = fp.read(8)\n        # first 4 bytes of CDF\n        fp.seek(0)\n        cdf_magic_number = fp.read(4).hex()\n\n    # FITS\n    # Check the extensions to see if it is a gzipped FITS file\n    filepath_rest_ext1, ext1 = os.path.splitext(filepath)\n    _, ext2 = os.path.splitext(filepath_rest_ext1)\n\n    gzip_extensions = [\".gz\"]\n    fits_extensions = [\".fts\", \".fit\", \".fits\"]\n    if (ext1 in gzip_extensions and ext2 in fits_extensions):\n        return 'fits'\n\n    # Check for \"KEY_WORD  =\" at beginning of file\n    match = re.match(br\"[A-Z0-9_]{0,8} *=\", first80)\n    if match is not None:\n        return 'fits'\n\n    # JPEG 2000\n    # Checks for one of two signatures found at beginning of all JP2 files.\n    # Adapted from ExifTool\n    # [1] https://www.sno.phy.queensu.ca/~phil/exiftool/\n    # [2] http://www.hlevkin.com/Standards/fcd15444-2.pdf\n    # [3] http://www.hlevkin.com/Standards/fcd15444-1.pdf\n    jp2_signatures = [b\"\\x00\\x00\\x00\\x0cjP  \\x0d\\x0a\\x87\\x0a\",\n                      b\"\\x00\\x00\\x00\\x0cjP\\x1a\\x1a\\x0d\\x0a\\x87\\x0a\"]\n    for sig in jp2_signatures:\n        if line1 + line2 == sig:\n            return 'jp2'\n\n    # netcdf4 and hdf5 files\n    if first_8bytes == b'\\x89HDF\\r\\n\\x1a\\n':\n        return 'hdf5'\n\n    if cdf_magic_number in ['cdf30001', 'cdf26002', '0000ffff']:\n        return 'cdf'\n\n    # Raise an error if an unsupported filetype is encountered\n    raise UnrecognizedFileTypeError(\"The requested filetype is not currently \"\n                                    \"supported by SunPy.\")\n\n\nclass UnrecognizedFileTypeError(OSError):\n    \"\"\"\n    Exception to raise when an unknown file type is encountered.\n    \"\"\"\n\n\nclass ReaderError(ImportError):\n    \"\"\"\n    Exception to raise when a reader errors.\n    \"\"\"\n\n\nclass InvalidJPEG2000FileExtension(OSError):\n    \"\"\"\n    Exception to raise when an invalid JPEG2000 file type is encountered.\n    \"\"\"\n", "path": "sunpy/io/file_tools.py"}], "after_files": [{"content": "\"\"\"\nThis module provides a generic file reader.\n\"\"\"\nimport re\nimport gzip\nimport pathlib\n\ntry:\n    from . import _fits as fits\nexcept ImportError:\n    fits = None\n\ntry:\n    from . import jp2\nexcept ImportError:\n    jp2 = None\n\ntry:\n    from . import ana\nexcept ImportError:\n    ana = None\n\n\n__all__ = ['read_file', 'read_file_header', 'write_file', 'detect_filetype']\n\n# File formats supported by SunPy\n_known_extensions = {\n    ('fts', 'fits'): 'fits',\n    ('jp2', 'j2k', 'jpc', 'jpt'): 'jp2',\n    ('fz', 'f0'): 'ana'\n}\n\n\n# Define a dict which raises a custom error message if the value is None\nclass Readers(dict):\n    def __init__(self, *args):\n        dict.__init__(self, *args)\n\n    def __getitem__(self, key):\n        val = dict.__getitem__(self, key)\n        if val is None:\n            raise ReaderError(f\"The Reader sunpy.io.{key} is not available, \"\n                              \"please check that you have the required dependencies \"\n                              \"installed.\")\n        return val\n\n\n# Map the readers\n_readers = Readers({\n    'fits': fits,\n    'jp2': jp2,\n    'ana': ana\n})\n\n\ndef read_file(filepath, filetype=None, **kwargs):\n    \"\"\"\n    Automatically determine the filetype and read the file.\n\n    Parameters\n    ----------\n    filepath : `str`, path-like\n        The file to be read.\n    filetype : `str`, optional\n        Supported reader or extension to manually specify the filetype.\n        Supported readers are ('jp2', 'fits', 'ana')\n    memmap : `bool`, optional\n        Should memory mapping be used, i.e. keep data on disk rather than in RAM.\n        This is currently only supported by the FITS reader.\n    **kwargs : `dict`\n        All extra keyword arguments are passed to ``.read`` for the file specific reader.\n\n    Returns\n    -------\n    pairs : `list`\n        A list of (data, header) tuples.\n    \"\"\"\n    # Convert Path objects to strings as the filepath can also be a URL\n    filepath = str(filepath)\n    # Use the explicitly passed filetype\n    if filetype is not None:\n        return _readers[filetype].read(filepath, **kwargs)\n\n    # Go through the known extensions\n    for extension, readername in _known_extensions.items():\n        if filepath.endswith(extension) or filetype in extension:\n            return _readers[readername].read(filepath, **kwargs)\n\n    # If filetype is not apparent from the extension, attempt to detect it\n    readername = _detect_filetype(filepath)\n    return _readers[readername].read(filepath, **kwargs)\n\n\ndef read_file_header(filepath, filetype=None, **kwargs):\n    \"\"\"\n    Reads the header from a given file.\n\n    This should always return a instance of `~sunpy.io.header.FileHeader`.\n\n    Parameters\n    ----------\n    filepath : `str`\n        The file from which the header is to be read.\n    filetype : `str`\n        Supported reader or extension to manually specify the filetype.\n        Supported readers are ('jp2', 'fits').\n    **kwargs : `dict`\n        All extra keyword arguments are passed to ``.get_header`` for the file specific reader.\n\n    Returns\n    -------\n    headers : `list`\n        A list of headers.\n    \"\"\"\n    # Use the explicitly passed filetype\n    if filetype is not None:\n        return _readers[filetype].get_header(filepath, **kwargs)\n\n    # Go through the known extensions\n    for extension, readername in _known_extensions.items():\n        if filepath.endswith(extension) or filetype in extension:\n            return _readers[readername].get_header(filepath, **kwargs)\n\n    # If filetype is not apparent from the extension, attempt to detect it\n    readername = _detect_filetype(filepath)\n    return _readers[readername].get_header(filepath, **kwargs)\n\n\ndef write_file(fname, data, header, filetype='auto', **kwargs):\n    \"\"\"\n    Write a file from a data & header pair using one of the defined file types.\n\n    Parameters\n    ----------\n    fname : `str`\n        Filename of file to save.\n    data : `numpy.ndarray`\n        Data to save to a fits file.\n    header : `collections.OrderedDict`\n        Meta data to save with the data.\n    filetype : `str`, {'auto', 'fits', 'jp2'}, optional\n        Filetype to save if ``auto`` the  filename extension will\n        be detected, else specify a supported file extension.\n    **kwargs : `dict`\n        All extra keyword arguments are passed to ``.write`` for the file specific reader.\n\n    Notes\n    -----\n    * This routine currently only supports saving a single HDU.\n    \"\"\"\n    if filetype == 'auto':\n        # Get the extension without the leading dot\n        filetype = pathlib.Path(fname).suffix[1:]\n\n    for extension, readername in _known_extensions.items():\n        if filetype in extension:\n            return _readers[readername].write(fname, data, header, **kwargs)\n\n    # Nothing has matched, report an error\n    raise ValueError(f\"The filetype provided ({filetype}) is not supported\")\n\n\ndef _detect_filetype(filepath):\n    \"\"\"\n    Attempts to determine the type of data contained in a file and returns\n    the filetype if the available readers exist within sunpy.io\n\n    Parameters\n    ----------\n    filepath : `str`\n        Where the file is.\n\n    Returns\n    -------\n    filetype : `str`\n        The type of file.\n    \"\"\"\n\n    if detect_filetype(filepath) in _readers.keys():\n        return detect_filetype(filepath)\n\n    # Raise an error if an unsupported filetype is encountered\n    raise UnrecognizedFileTypeError(\"The requested filetype is not currently \"\n                                    \"supported by SunPy.\")\n\n\ndef detect_filetype(filepath):\n    \"\"\"\n    Attempts to determine the type of file a given filepath is.\n\n    Parameters\n    ----------\n    filepath : `str`\n        Where the file is.\n\n    Returns\n    -------\n    filetype : `str`\n        The type of file.\n    \"\"\"\n\n    # Open file and read in first two lines\n    with open(filepath, 'rb') as fp:\n        line1 = fp.readline()\n        line2 = fp.readline()\n        # Some FITS files do not have line breaks at the end of header cards.\n        fp.seek(0)\n        first80 = fp.read(80)\n        # first 8 bytes of netcdf4/hdf5 to determine filetype as have same sequence\n        fp.seek(0)\n        first_8bytes = fp.read(8)\n        # first 4 bytes of CDF\n        fp.seek(0)\n        cdf_magic_number = fp.read(4).hex()\n\n    # FITS\n    # Checks for gzip signature.\n    # If found, decompresses first few bytes and checks for FITS\n    if first80[:3] == b\"\\x1f\\x8b\\x08\":\n        with gzip.open(filepath, 'rb') as fp:\n            first80 = fp.read(80)\n\n    # Check for \"KEY_WORD  =\" at beginning of file\n    match = re.match(br\"[A-Z0-9_]{0,8} *=\", first80)\n    if match is not None:\n        return 'fits'\n\n    # JPEG 2000\n    # Checks for one of two signatures found at beginning of all JP2 files.\n    # Adapted from ExifTool\n    # [1] https://www.sno.phy.queensu.ca/~phil/exiftool/\n    # [2] http://www.hlevkin.com/Standards/fcd15444-2.pdf\n    # [3] http://www.hlevkin.com/Standards/fcd15444-1.pdf\n    jp2_signatures = [b\"\\x00\\x00\\x00\\x0cjP  \\x0d\\x0a\\x87\\x0a\",\n                      b\"\\x00\\x00\\x00\\x0cjP\\x1a\\x1a\\x0d\\x0a\\x87\\x0a\"]\n    for sig in jp2_signatures:\n        if line1 + line2 == sig:\n            return 'jp2'\n\n    # netcdf4 and hdf5 files\n    if first_8bytes == b'\\x89HDF\\r\\n\\x1a\\n':\n        return 'hdf5'\n\n    if cdf_magic_number in ['cdf30001', 'cdf26002', '0000ffff']:\n        return 'cdf'\n\n    # Raise an error if an unsupported filetype is encountered\n    raise UnrecognizedFileTypeError(\"The requested filetype is not currently \"\n                                    \"supported by SunPy.\")\n\n\nclass UnrecognizedFileTypeError(OSError):\n    \"\"\"\n    Exception to raise when an unknown file type is encountered.\n    \"\"\"\n\n\nclass ReaderError(ImportError):\n    \"\"\"\n    Exception to raise when a reader errors.\n    \"\"\"\n\n\nclass InvalidJPEG2000FileExtension(OSError):\n    \"\"\"\n    Exception to raise when an invalid JPEG2000 file type is encountered.\n    \"\"\"\n", "path": "sunpy/io/file_tools.py"}]}