<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def get_attached_message(self, key, message_type, tags=None, required=False):
 """Returns the message attached to the module under the given key, or None. Module publishers can attach protocol messages to modules at creation time to provide module consumers with additional information, e.g., on module usage or provenance (see see hub.attach_message()). A typical use would be to store a small set of named values with modules of a certain type so that a support library for consumers of such modules can be parametric in those values. This method can also be called on a Module instantiated from a ModuleSpec, then `tags` are set to those used in module instatiation. Args: key: A string with the key of an attached message. message_type: A concrete protocol message class (*not* object) used to parse the attached message from its serialized representation. The message type for a particular key must be advertised with the key. tags: Optional set of strings, specifying the graph variant from which to read the attached message. required: An optional boolean. Setting it true changes the effect of an unknown `key` from returning None to raising a KeyError with text about attached messages. Returns: An instance of `message_type` with the message contents attached to the module, or `None` if `key` is unknown and `required` is False. Raises: KeyError: if `key` is unknown and `required` is True. """

    attached_bytes = self._get_attached_bytes(key, tags)
    if attached_bytes is None:
      if required:
        raise KeyError("No attached message for key '%s' in graph version %s "
                       "of Hub Module" % (key, sorted(tags or [])))
      else:
        return None
    message = message_type()
    message.ParseFromString(attached_bytes)
    return message