docs: def AddAttribute(self, attribute, value=None, age=None):
      """Add an additional attribute to this object.
  
      If value is None, attribute is expected to be already initialized with a
      value. For example:
  
      fd.AddAttribute(fd.Schema.CONTAINS("some data"))
  
      Args:
         attribute: The attribute name or an RDFValue derived from the attribute.
         value: The value the attribute will be set to.
         age: Age (timestamp) of the attribute. If None, current time is used.
  
      Raises:
         IOError: If this object is read only.
      """
      if "w" not in self.mode:
        raise IOError("Writing attribute %s to read only object." % attribute)
  
      if value is None:
        value = attribute
        attribute = value.attribute_instance
  
      # Check if this object should be locked in order to add the attribute.
      # NOTE: We don't care about locking when doing blind writes.
      if self.mode != "w" and attribute.lock_protected and not self.transaction:
        raise IOError("Object must be locked to write attribute %s." % attribute)
  
      self._CheckAttribute(attribute, value)
      # Does this represent a new version?
      if attribute.versioned:
        if attribute.creates_new_object_version:
          self._new_version = True
  
        # Update the time of this new attribute.
        if age:
          value.age = age
        else:
          value.age = rdfvalue.RDFDatetime.Now()
  
      # Non-versioned attributes always replace previous versions and get written
      # at the earliest timestamp (so they appear in all objects).
      else:
        self._to_delete.add(attribute)
        self.synced_attributes.pop(attribute, None)
        self.new_attributes.pop(attribute, None)
        value.age = 0
  
      self._AddAttributeToCache(attribute, value, self.new_attributes)
      self._dirty = True