docs: def wait_until(obj, att, desired, callback=None, interval=5, attempts=0,
          verbose=False, verbose_atts=None):
      """
      When changing the state of an object, it will commonly be in a transitional
      state until the change is complete. This will reload the object every
      `interval` seconds, and check its `att` attribute until the `desired` value
      is reached, or until the maximum number of attempts is reached. The updated
      object is returned. It is up to the calling program to check the returned
      object to make sure that it successfully reached the desired state.
  
      Once the desired value of the attribute is reached, the method returns. If
      not, it will re-try until the attribute's value matches one of the
      `desired` values. By default (attempts=0) it will loop infinitely until the
      attribute reaches the desired value. You can optionally limit the number of
      times that the object is reloaded by passing a positive value to
      `attempts`. If the attribute has not reached the desired value by then, the
      method will exit.
  
      If `verbose` is True, each attempt will print out the current value of the
      watched attribute and the time that has elapsed since the original request.
      Also, if `verbose_atts` is specified, the values of those attributes will
      also be output. If `verbose` is False, then `verbose_atts` has no effect.
  
      Note that `desired` can be a list of values; if the attribute becomes equal
      to any of those values, this will succeed. For example, when creating a new
      cloud server, it will initially have a status of 'BUILD', and you can't
      work with it until its status is 'ACTIVE'. However, there might be a
      problem with the build process, and the server will change to a status of
      'ERROR'. So for this case you need to set the `desired` parameter to
      `['ACTIVE', 'ERROR']`. If you simply pass 'ACTIVE' as the desired state,
      this will loop indefinitely if a build fails, as the server will never
      reach a status of 'ACTIVE'.
  
      Since this process of waiting can take a potentially long time, and will
      block your program's execution until the desired state of the object is
      reached, you may specify a callback function. The callback can be any
      callable that accepts a single parameter; the parameter it receives will be
      either the updated object (success), or None (failure). If a callback is
      specified, the program will return immediately after spawning the wait
      process in a separate thread.
      """
      if callback:
          waiter = _WaitThread(obj=obj, att=att, desired=desired, callback=callback,
                  interval=interval, attempts=attempts, verbose=verbose,
                  verbose_atts=verbose_atts)
          waiter.start()
          return waiter
      else:
          return _wait_until(obj=obj, att=att, desired=desired, callback=None,
                  interval=interval, attempts=attempts, verbose=verbose,
                  verbose_atts=verbose_atts)