@classmethod
  @gen.coroutine
  def _NotifyFollowers(cls, client, viewpoint_id, followers, invalidate, activity_func,
                       inc_badge=False, always_notify=False):
    """Adds a notification for each of the given followers that the specified viewpoint has
    structurally changed. If "invalidate" is a dict, then uses that directly. Otherwise, assumes
    it's a function that takes a follower id and returns the invalidate dict for that follower.
    If "always_notify" is true, then always send notifications, even to removed followers.

    In order to minimize undesirable client artifacts caused by reads of half-committed data,
    we will commit updates in this order:

    1. In parallel:
       a. Create the activity.
       b. Update all Followed records (of all followers in the viewpoint).

    2. In parallel:
       a. Update update_seq in the viewpoint.
       b. Update viewed_seq in the sending follower.

    3. In parallel:
       a. For each follower:
          i. Create notification
          ii. Send alert
    """
    from viewfinder.backend.db.viewpoint import Viewpoint
    from viewfinder.backend.op.alert_manager import AlertManager

    # Get the current operation, which provides the calling user and the op timestamp.
    operation = NotificationManager._GetOperation()

    @gen.coroutine
    def _NotifyOneFollower(viewpoint, seq_num_pair, activity, follower, follower_settings):
      """Creates a notification for the follower and sends an alert if configured to do so."""
      # If follower has been removed, do not send notifications or alerts to it.
      if follower.IsRemoved() and not always_notify:
        return

      # Get the invalidate dict.
      if invalidate is None or isinstance(invalidate, dict):
        foll_invalidate = invalidate
      else:
        foll_invalidate = invalidate(follower.user_id)

      # Don't send alert or increment badge for the user that is creating the activity and
      # sending the NotificationManager.
      is_sending_user = follower.user_id == operation.user_id

      # Create the notification for the follower.
      # Update the Followed index, which orders viewpoints by timestamp of last update.
      notification = yield Notification.CreateForUser(client,
                                                      operation,
                                                      follower.user_id,
                                                      activity.name,
                                                      invalidate=foll_invalidate,
                                                      activity_id=activity.activity_id,
                                                      viewpoint_id=viewpoint_id,
                                                      seq_num_pair=seq_num_pair,
                                                      inc_badge=inc_badge and not is_sending_user)

      if not is_sending_user:
        yield AlertManager.SendFollowerAlert(client,
                                             follower.user_id,
                                             notification.badge,
                                             viewpoint,
                                             follower,
                                             follower_settings,
                                             activity)

    # We want a locked viewpoint while updating its sequence numbers and the corresponding Followed rows.
    # Locking also prevents race conditions where new followers are added during iteration.
    Viewpoint.AssertViewpointLockAcquired(viewpoint_id)

    # Get affected viewpoint and the follower sending the notification, if it's available.
    viewpoint = yield gen.Task(Viewpoint.Query, client, viewpoint_id, None)
    sending_follower = next((follower for follower in followers if follower.user_id == operation.user_id), None)

    # Update the viewpoint and follower sequence numbers, but do not commit until after Followed
    # records are updated (since we're updating the viewpoint's "last_updated" at that time anyway).
    viewpoint.update_seq += 1
    if sending_follower is not None:
      sending_follower.viewed_seq += 1
      seq_num_pair = (viewpoint.update_seq, sending_follower.viewed_seq)
    else:
      seq_num_pair = (viewpoint.update_seq, None)

    # Create the activity.
    activity_task = activity_func(client, operation.user_id, viewpoint_id, viewpoint.update_seq)

    # Get account settings for each follower in order to determine what level of alerts they'd like.
    follower_keys = [AccountSettings.ConstructKey(follower.user_id) for follower in followers]
    settings_task = gen.Task(AccountSettings.BatchQuery, client, follower_keys, None, must_exist=False)

    # Update all Followed records.
    followed_task = gen.Multi([gen.Task(Followed.UpdateDateUpdated,
                                        client,
                                        follower.user_id,
                                        viewpoint_id,
                                        viewpoint.last_updated,
                                        operation.timestamp)
                               for follower in followers])

    activity, all_follower_settings, _ = yield [activity_task, settings_task, followed_task]

    # Now that the Followed records have been updated, update the viewpoint's "last_updated" attribute.
    # This must be done afterwards so that the previous value of last_updated is known, even if the
    # operation fails and restarts.
    if operation.timestamp > viewpoint.last_updated:
      viewpoint.last_updated = operation.timestamp

    # Commit changes to update_seq and the sending follower's viewed_seq.
    yield [gen.Task(viewpoint.Update, client),
           gen.Task(sending_follower.Update, client) if sending_follower is not None else util.GenConstant(None)]

    # Visit each follower and generate notifications and alerts for it.
    yield [_NotifyOneFollower(viewpoint, seq_num_pair, activity, follower, follower_settings)
           for follower, follower_settings in zip(followers, all_follower_settings)]
dataset/ETHPy150Open viewfinderco/viewfinder/backend/op/notification_manager.py/NotificationManager._NotifyFollowers/original