prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Maintain coherence between keycomments and the metadict
  See #2748 
  
  This is probably best implemented by adding the functionality to our `MetaDict` object or something, so that we don't have to do it manually everywhere.
  
  --- 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/util/metadata.py`
  Content:
  ```
  1 """
  2 This module provides a generalized dictionary class that deals with header
  3 parsing and normalization.
  4 """
  5 from collections import OrderedDict
  6 
  7 __all__ = ['MetaDict']
  8 
  9 
  10 class MetaDict(OrderedDict):
  11     """
  12     A class to hold metadata associated with a `sunpy.map.Map
  13     <sunpy.map.map_factory.MapFactory.__call__>` derivative.
  14 
  15     This class handles everything in lower case. This allows case
  16     insensitive indexing.
  17     """
  18 
  19     def __init__(self, *args):
  20         """
  21         Creates a new MapHeader instance.
  22         """
  23         # Store all keys as upper-case to allow for case-insensitive indexing
  24         # OrderedDict can be instantiated from a list of lists or a tuple of tuples
  25         tags = dict()
  26         if args:
  27             args = list(args)
  28             adict = args[0]
  29             if isinstance(adict, list) or isinstance(adict, tuple):
  30                 tags = OrderedDict((k.upper(), v) for k, v in adict)
  31             elif isinstance(adict, dict):
  32                 tags = OrderedDict((k.upper(), v) for k, v in adict.items())
  33             else:
  34                 raise TypeError("Can not create a MetaDict from this type input")
  35             args[0] = tags
  36 
  37         super().__init__(*args)
  38 
  39     def __contains__(self, key):
  40         """
  41         Override ``__contains__``.
  42         """
  43         return OrderedDict.__contains__(self, key.lower())
  44 
  45     def __getitem__(self, key):
  46         """
  47         Override ``[]`` indexing.
  48         """
  49         return OrderedDict.__getitem__(self, key.lower())
  50 
  51     def __setitem__(self, key, value):
  52         """
  53         Override ``[]`` indexing.
  54         """
  55         return OrderedDict.__setitem__(self, key.lower(), value)
  56 
  57     def get(self, key, default=None):
  58         """
  59         Override ``.get()`` indexing.
  60         """
  61         return OrderedDict.get(self, key.lower(), default)
  62 
  63     def has_key(self, key):
  64         """
  65         Override ``.has_key()`` to perform case-insensitively.
  66         """
  67         return key.lower() in self
  68 
  69     def pop(self, key, default=None):
  70         """
  71         Override ``.pop()`` to perform case-insensitively.
  72         """
  73         return OrderedDict.pop(self, key.lower(), default)
  74 
  75     def update(self, d2):
  76         """
  77         Override ``.update()`` to perform case-insensitively.
  78         """
  79         return OrderedDict.update(self, OrderedDict((k.lower(), v) for k, v in d2.items()))
  80 
  81     def setdefault(self, key, default=None):
  82         """
  83         Override ``.setdefault()`` to perform case-insensitively.
  84         """
  85         return OrderedDict.setdefault(self, key.lower(), default)
  86
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/sunpy/util/metadata.py b/sunpy/util/metadata.py
  --- a/sunpy/util/metadata.py
  +++ b/sunpy/util/metadata.py
  @@ -1,6 +1,6 @@
   """
   This module provides a generalized dictionary class that deals with header
  -parsing and normalization.
  +parsing, normalization, and maintaining coherence between keys and keycomments.
   """
   from collections import OrderedDict
   
  @@ -14,28 +14,67 @@
   
       This class handles everything in lower case. This allows case
       insensitive indexing.
  +
  +    If the key 'keycomments' exists, its value must be a dictionary mapping
  +    keys in the `MetaDict` to their comments. The casing of keys in the
  +    keycomments dictionary is not significant. If a key is removed from the
  +    `MetaDict`, it will also be removed from the keycomments dictionary.
  +    Additionally, any extraneous keycomments will be removed when the
  +    `MetaDict` is instantiated.
       """
   
       def __init__(self, *args):
           """
  -        Creates a new MapHeader instance.
  +        Creates a new MetaDict instance.
           """
  -        # Store all keys as upper-case to allow for case-insensitive indexing
  +        # Store all keys as lower-case to allow for case-insensitive indexing
           # OrderedDict can be instantiated from a list of lists or a tuple of tuples
           tags = dict()
           if args:
               args = list(args)
               adict = args[0]
               if isinstance(adict, list) or isinstance(adict, tuple):
  -                tags = OrderedDict((k.upper(), v) for k, v in adict)
  +                tags = OrderedDict((k.lower(), v) for k, v in adict)
               elif isinstance(adict, dict):
  -                tags = OrderedDict((k.upper(), v) for k, v in adict.items())
  +                tags = OrderedDict((k.lower(), v) for k, v in adict.items())
               else:
                   raise TypeError("Can not create a MetaDict from this type input")
               args[0] = tags
   
           super().__init__(*args)
   
  +        # Use `copy=True` to avoid mutating the caller's keycomments
  +        # dictionary (if they provided one).
  +        self._prune_keycomments(copy=True)
  +
  +    def _prune_keycomments(self, copy=False):
  +        """
  +        Remove keycomments for keys that are not contained in the MetaDict.
  +
  +        Parameters
  +        ----------
  +        copy : `bool`, optional
  +            Make a copy of the current keycomments dict before removing keys.
  +        """
  +        if 'keycomments' not in self:
  +            return
  +
  +        keycomments = self['keycomments']
  +
  +        if not isinstance(keycomments, dict):
  +            raise TypeError(
  +                "'keycomments' key must have a value of type `dict`. Found "
  +                "the following type: %r" % type(keycomments))
  +
  +        if copy:
  +            keycomments = keycomments.copy()
  +
  +        for key in list(keycomments.keys()):
  +            if key not in self:
  +                del keycomments[key]
  +
  +        self['keycomments'] = keycomments
  +
       def __contains__(self, key):
           """
           Override ``__contains__``.
  @@ -54,6 +93,15 @@
           """
           return OrderedDict.__setitem__(self, key.lower(), value)
   
  +    # Note: `OrderedDict.popitem()` does not need to be overridden to prune
  +    # keycomments because it calls `__delitem__` internally.
  +    def __delitem__(self, key):
  +        """
  +        Override ``del dict[key]`` key deletion.
  +        """
  +        OrderedDict.__delitem__(self, key.lower())
  +        self._prune_keycomments()
  +
       def get(self, key, default=None):
           """
           Override ``.get()`` indexing.
  @@ -70,7 +118,11 @@
           """
           Override ``.pop()`` to perform case-insensitively.
           """
  -        return OrderedDict.pop(self, key.lower(), default)
  +        has_key = key in self
  +        result = OrderedDict.pop(self, key.lower(), default)
  +        if has_key:
  +            self._prune_keycomments()
  +        return result
   
       def update(self, d2):
           """
  
verification_info: {"golden_diff": "diff --git a/sunpy/util/metadata.py b/sunpy/util/metadata.py\n--- a/sunpy/util/metadata.py\n+++ b/sunpy/util/metadata.py\n@@ -1,6 +1,6 @@\n \"\"\"\n This module provides a generalized dictionary class that deals with header\n-parsing and normalization.\n+parsing, normalization, and maintaining coherence between keys and keycomments.\n \"\"\"\n from collections import OrderedDict\n \n@@ -14,28 +14,67 @@\n \n     This class handles everything in lower case. This allows case\n     insensitive indexing.\n+\n+    If the key 'keycomments' exists, its value must be a dictionary mapping\n+    keys in the `MetaDict` to their comments. The casing of keys in the\n+    keycomments dictionary is not significant. If a key is removed from the\n+    `MetaDict`, it will also be removed from the keycomments dictionary.\n+    Additionally, any extraneous keycomments will be removed when the\n+    `MetaDict` is instantiated.\n     \"\"\"\n \n     def __init__(self, *args):\n         \"\"\"\n-        Creates a new MapHeader instance.\n+        Creates a new MetaDict instance.\n         \"\"\"\n-        # Store all keys as upper-case to allow for case-insensitive indexing\n+        # Store all keys as lower-case to allow for case-insensitive indexing\n         # OrderedDict can be instantiated from a list of lists or a tuple of tuples\n         tags = dict()\n         if args:\n             args = list(args)\n             adict = args[0]\n             if isinstance(adict, list) or isinstance(adict, tuple):\n-                tags = OrderedDict((k.upper(), v) for k, v in adict)\n+                tags = OrderedDict((k.lower(), v) for k, v in adict)\n             elif isinstance(adict, dict):\n-                tags = OrderedDict((k.upper(), v) for k, v in adict.items())\n+                tags = OrderedDict((k.lower(), v) for k, v in adict.items())\n             else:\n                 raise TypeError(\"Can not create a MetaDict from this type input\")\n             args[0] = tags\n \n         super().__init__(*args)\n \n+        # Use `copy=True` to avoid mutating the caller's keycomments\n+        # dictionary (if they provided one).\n+        self._prune_keycomments(copy=True)\n+\n+    def _prune_keycomments(self, copy=False):\n+        \"\"\"\n+        Remove keycomments for keys that are not contained in the MetaDict.\n+\n+        Parameters\n+        ----------\n+        copy : `bool`, optional\n+            Make a copy of the current keycomments dict before removing keys.\n+        \"\"\"\n+        if 'keycomments' not in self:\n+            return\n+\n+        keycomments = self['keycomments']\n+\n+        if not isinstance(keycomments, dict):\n+            raise TypeError(\n+                \"'keycomments' key must have a value of type `dict`. Found \"\n+                \"the following type: %r\" % type(keycomments))\n+\n+        if copy:\n+            keycomments = keycomments.copy()\n+\n+        for key in list(keycomments.keys()):\n+            if key not in self:\n+                del keycomments[key]\n+\n+        self['keycomments'] = keycomments\n+\n     def __contains__(self, key):\n         \"\"\"\n         Override ``__contains__``.\n@@ -54,6 +93,15 @@\n         \"\"\"\n         return OrderedDict.__setitem__(self, key.lower(), value)\n \n+    # Note: `OrderedDict.popitem()` does not need to be overridden to prune\n+    # keycomments because it calls `__delitem__` internally.\n+    def __delitem__(self, key):\n+        \"\"\"\n+        Override ``del dict[key]`` key deletion.\n+        \"\"\"\n+        OrderedDict.__delitem__(self, key.lower())\n+        self._prune_keycomments()\n+\n     def get(self, key, default=None):\n         \"\"\"\n         Override ``.get()`` indexing.\n@@ -70,7 +118,11 @@\n         \"\"\"\n         Override ``.pop()`` to perform case-insensitively.\n         \"\"\"\n-        return OrderedDict.pop(self, key.lower(), default)\n+        has_key = key in self\n+        result = OrderedDict.pop(self, key.lower(), default)\n+        if has_key:\n+            self._prune_keycomments()\n+        return result\n \n     def update(self, d2):\n         \"\"\"\n", "issue": "Maintain coherence between keycomments and the metadict\nSee #2748 \r\n\r\nThis is probably best implemented by adding the functionality to our `MetaDict` object or something, so that we don't have to do it manually everywhere.\n", "before_files": [{"content": "\"\"\"\nThis module provides a generalized dictionary class that deals with header\nparsing and normalization.\n\"\"\"\nfrom collections import OrderedDict\n\n__all__ = ['MetaDict']\n\n\nclass MetaDict(OrderedDict):\n    \"\"\"\n    A class to hold metadata associated with a `sunpy.map.Map\n    <sunpy.map.map_factory.MapFactory.__call__>` derivative.\n\n    This class handles everything in lower case. This allows case\n    insensitive indexing.\n    \"\"\"\n\n    def __init__(self, *args):\n        \"\"\"\n        Creates a new MapHeader instance.\n        \"\"\"\n        # Store all keys as upper-case to allow for case-insensitive indexing\n        # OrderedDict can be instantiated from a list of lists or a tuple of tuples\n        tags = dict()\n        if args:\n            args = list(args)\n            adict = args[0]\n            if isinstance(adict, list) or isinstance(adict, tuple):\n                tags = OrderedDict((k.upper(), v) for k, v in adict)\n            elif isinstance(adict, dict):\n                tags = OrderedDict((k.upper(), v) for k, v in adict.items())\n            else:\n                raise TypeError(\"Can not create a MetaDict from this type input\")\n            args[0] = tags\n\n        super().__init__(*args)\n\n    def __contains__(self, key):\n        \"\"\"\n        Override ``__contains__``.\n        \"\"\"\n        return OrderedDict.__contains__(self, key.lower())\n\n    def __getitem__(self, key):\n        \"\"\"\n        Override ``[]`` indexing.\n        \"\"\"\n        return OrderedDict.__getitem__(self, key.lower())\n\n    def __setitem__(self, key, value):\n        \"\"\"\n        Override ``[]`` indexing.\n        \"\"\"\n        return OrderedDict.__setitem__(self, key.lower(), value)\n\n    def get(self, key, default=None):\n        \"\"\"\n        Override ``.get()`` indexing.\n        \"\"\"\n        return OrderedDict.get(self, key.lower(), default)\n\n    def has_key(self, key):\n        \"\"\"\n        Override ``.has_key()`` to perform case-insensitively.\n        \"\"\"\n        return key.lower() in self\n\n    def pop(self, key, default=None):\n        \"\"\"\n        Override ``.pop()`` to perform case-insensitively.\n        \"\"\"\n        return OrderedDict.pop(self, key.lower(), default)\n\n    def update(self, d2):\n        \"\"\"\n        Override ``.update()`` to perform case-insensitively.\n        \"\"\"\n        return OrderedDict.update(self, OrderedDict((k.lower(), v) for k, v in d2.items()))\n\n    def setdefault(self, key, default=None):\n        \"\"\"\n        Override ``.setdefault()`` to perform case-insensitively.\n        \"\"\"\n        return OrderedDict.setdefault(self, key.lower(), default)\n", "path": "sunpy/util/metadata.py"}], "after_files": [{"content": "\"\"\"\nThis module provides a generalized dictionary class that deals with header\nparsing, normalization, and maintaining coherence between keys and keycomments.\n\"\"\"\nfrom collections import OrderedDict\n\n__all__ = ['MetaDict']\n\n\nclass MetaDict(OrderedDict):\n    \"\"\"\n    A class to hold metadata associated with a `sunpy.map.Map\n    <sunpy.map.map_factory.MapFactory.__call__>` derivative.\n\n    This class handles everything in lower case. This allows case\n    insensitive indexing.\n\n    If the key 'keycomments' exists, its value must be a dictionary mapping\n    keys in the `MetaDict` to their comments. The casing of keys in the\n    keycomments dictionary is not significant. If a key is removed from the\n    `MetaDict`, it will also be removed from the keycomments dictionary.\n    Additionally, any extraneous keycomments will be removed when the\n    `MetaDict` is instantiated.\n    \"\"\"\n\n    def __init__(self, *args):\n        \"\"\"\n        Creates a new MetaDict instance.\n        \"\"\"\n        # Store all keys as lower-case to allow for case-insensitive indexing\n        # OrderedDict can be instantiated from a list of lists or a tuple of tuples\n        tags = dict()\n        if args:\n            args = list(args)\n            adict = args[0]\n            if isinstance(adict, list) or isinstance(adict, tuple):\n                tags = OrderedDict((k.lower(), v) for k, v in adict)\n            elif isinstance(adict, dict):\n                tags = OrderedDict((k.lower(), v) for k, v in adict.items())\n            else:\n                raise TypeError(\"Can not create a MetaDict from this type input\")\n            args[0] = tags\n\n        super().__init__(*args)\n\n        # Use `copy=True` to avoid mutating the caller's keycomments\n        # dictionary (if they provided one).\n        self._prune_keycomments(copy=True)\n\n    def _prune_keycomments(self, copy=False):\n        \"\"\"\n        Remove keycomments for keys that are not contained in the MetaDict.\n\n        Parameters\n        ----------\n        copy : `bool`, optional\n            Make a copy of the current keycomments dict before removing keys.\n        \"\"\"\n        if 'keycomments' not in self:\n            return\n\n        keycomments = self['keycomments']\n\n        if not isinstance(keycomments, dict):\n            raise TypeError(\n                \"'keycomments' key must have a value of type `dict`. Found \"\n                \"the following type: %r\" % type(keycomments))\n\n        if copy:\n            keycomments = keycomments.copy()\n\n        for key in list(keycomments.keys()):\n            if key not in self:\n                del keycomments[key]\n\n        self['keycomments'] = keycomments\n\n    def __contains__(self, key):\n        \"\"\"\n        Override ``__contains__``.\n        \"\"\"\n        return OrderedDict.__contains__(self, key.lower())\n\n    def __getitem__(self, key):\n        \"\"\"\n        Override ``[]`` indexing.\n        \"\"\"\n        return OrderedDict.__getitem__(self, key.lower())\n\n    def __setitem__(self, key, value):\n        \"\"\"\n        Override ``[]`` indexing.\n        \"\"\"\n        return OrderedDict.__setitem__(self, key.lower(), value)\n\n    # Note: `OrderedDict.popitem()` does not need to be overridden to prune\n    # keycomments because it calls `__delitem__` internally.\n    def __delitem__(self, key):\n        \"\"\"\n        Override ``del dict[key]`` key deletion.\n        \"\"\"\n        OrderedDict.__delitem__(self, key.lower())\n        self._prune_keycomments()\n\n    def get(self, key, default=None):\n        \"\"\"\n        Override ``.get()`` indexing.\n        \"\"\"\n        return OrderedDict.get(self, key.lower(), default)\n\n    def has_key(self, key):\n        \"\"\"\n        Override ``.has_key()`` to perform case-insensitively.\n        \"\"\"\n        return key.lower() in self\n\n    def pop(self, key, default=None):\n        \"\"\"\n        Override ``.pop()`` to perform case-insensitively.\n        \"\"\"\n        has_key = key in self\n        result = OrderedDict.pop(self, key.lower(), default)\n        if has_key:\n            self._prune_keycomments()\n        return result\n\n    def update(self, d2):\n        \"\"\"\n        Override ``.update()`` to perform case-insensitively.\n        \"\"\"\n        return OrderedDict.update(self, OrderedDict((k.lower(), v) for k, v in d2.items()))\n\n    def setdefault(self, key, default=None):\n        \"\"\"\n        Override ``.setdefault()`` to perform case-insensitively.\n        \"\"\"\n        return OrderedDict.setdefault(self, key.lower(), default)\n", "path": "sunpy/util/metadata.py"}]}