docs: def is_invalid(self):
          """Check if this RefPkg is invalid.
  
          Valid means that it contains a properly named manifest, and
          each of the files described in the manifest exists and has the
          proper MD5 hashsum.
  
          If the Refpkg is valid, is_invalid returns False.  Otherwise it
          returns a nonempty string describing the error.
          """
          # Manifest file contains the proper keys
          for k in ['metadata', 'files', 'md5']:
              if not(k in self.contents):
                  return "Manifest file missing key %s" % k
              if not(isinstance(self.contents[k], dict)):
                  return "Key %s in manifest did not refer to a dictionary" % k
  
          if not('rollback' in self.contents):
              return "Manifest file missing key rollback"
          if not(isinstance(self.contents['rollback'], dict)) and self.contents[
                  "rollback"] is not None:
              return ("Key rollback in manifest did not refer to a "
                      "dictionary or None, found %s") % str(self.contents['rollback'])
  
          if not('rollforward' in self.contents):
              return "Manifest file missing key rollforward"
  
          if self.contents['rollforward'] is not None:
              if not(isinstance(self.contents['rollforward'], list)):
                  return "Key rollforward was not a list, found %s" % str(
                      self.contents['rollforward'])
              elif len(self.contents['rollforward']) != 2:
                  return "Key rollforward had wrong length, found %d" % \
                      len(self.contents['rollforward'])
              elif not is_string(self.contents['rollforward'][0]):
                  print(type(self.contents['rollforward'][0]))
                  return "Key rollforward's first entry was not a string, found %s" % \
                      str(self.contents['rollforward'][0])
              elif not(isinstance(self.contents['rollforward'][1], dict)):
                  return "Key rollforward's second entry was not a dict, found %s" % \
                      str(self.contents['rollforward'][1])
  
          if not("log" in self.contents):
              return "Manifest file missing key 'log'"
  
          if not(isinstance(self.contents['log'], list)):
              return "Key 'log' in manifest did not refer to a list"
  
          # MD5 keys and filenames are in one to one correspondence
          if self.contents['files'].keys() != self.contents[
                  'md5'].keys():
              return ("Files and MD5 sums in manifest do not "
                      "match (files: %s, MD5 sums: %s)") % \
                  (list(self.contents['files'].keys()),
                   list(self.contents['md5'].keys()))
  
          # All files in the manifest exist and match the MD5 sums
          for key, filename in self.contents['files'].items():
              # we don't need to explicitly check for existence;
              # calculate_resource_md5 will open the file for us.
              expected_md5 = self.resource_md5(key)
              found_md5 = self.calculate_resource_md5(key)
              if found_md5 != expected_md5:
                  return ("File %s referred to by key %s did "
                          "not match its MD5 sum (found: %s, expected %s)") % \
                      (filename, key, found_md5, expected_md5)
          return False