Query the Forest 2.0 server for a list of underlying QPU devices.

    NOTE: These can't directly be used to manufacture pyQuil Device objects, but this gives a list
          of legal values that can be supplied to list_lattices to filter its (potentially very
          noisy) output.

    :return: A list of device names.

def list_devices(connection: ForestConnection = None):
    """
    Query the Forest 2.0 server for a list of underlying QPU devices.

    NOTE: These can't directly be used to manufacture pyQuil Device objects, but this gives a list
          of legal values that can be supplied to list_lattices to filter its (potentially very
          noisy) output.

    :return: A list of device names.
    """
    # For the record, the dictionary stored in "devices" that we're getting back is keyed on device
    # names and has this structure in its values:
    #
    # {
    #   "is_online":   a boolean indicating the availability of the device,
    #   "is_retuning": a boolean indicating whether the device is busy retuning,
    #   "specs":       a Specs object describing the entire device, serialized as a dictionary,
    #   "isa":         an ISA object describing the entire device, serialized as a dictionary,
    #   "noise_model": a NoiseModel object describing the entire device, serialized as a dictionary
    # }
    if connection is None:
        connection = ForestConnection()

    session = connection.session
    url = connection.forest_cloud_endpoint + '/devices'
    return sorted(get_json(session, url)["devices"].keys())