class_docstr: Base class for representing metrics.
  
  The `fetch_trial_data` method is the essential method to override when
  subclassing, which specifies how to retrieve a Metric, for a given trial.
  
  A Metric must return a Data object, which requires (at minimum) the following:
      https://ax.dev/api/_modules/ax/core/data.html#Data.required_columns
  
  Attributes:
      lower_is_better: Flag for metrics which should be minimized.
      properties: Properties specific to a particular metric.
class_docstr_tok: ["Base","class","for","representing","metrics",".","The","`","fetch_trial_data","`","method","is","the","essential","method","to","override","when","subclassing",",","which","specifies","how","to","retrieve","a","Metric",",","for","a","given","trial",".","A","Metric","must","return","a","Data","object",",","which","requires","(","at","minimum",")","the","following",":","https",":","\/\/ax.dev\/api\/_modules\/ax\/core\/data.html","#","Data.required_columns","Attributes",":","lower_is_better",":","Flag","for","metrics","which","should","be","minimized",".","properties",":","Properties","specific","to","a","particular","metric","."]
docstr: Fetch or lookup (with fallback to fetching) data for given metrics,
  depending on whether they are available while running. Return a tuple
  containing the data, along with a boolean that will be True if new
  data was fetched, and False if all data was looked up from cache.
  
  If metric is available while running, its data can change (and therefore
  we should always re-fetch it). If metric is available only upon trial
  completion, its data does not change, so we can look up that data on
  the experiment and only fetch the data that is not already attached to
  the experiment.
  
  NOTE: If fetching data for a metrics class that is only available upon
  trial completion, data fetched in this function (data that was not yet
  available on experiment) will be attached to experiment.
docstr_tok: ["Fetch","or","lookup","(","with","fallback","to","fetching",")","data","for","given","metrics",",","depending","on","whether","they","are","available","while","running",".","Return","a","tuple","containing","the","data",",","along","with","a","boolean","that","will","be","True","if","new","data","was","fetched",",","and","False","if","all","data","was","looked","up","from","cache",".","If","metric","is","available","while","running",",","its","data","can","change","(","and","therefore","we","should","always","re-fetch","it",")",".","If","metric","is","available","only","upon","trial","completion",",","its","data","does","not","change",",","so","we","can","look","up","that","data","on","the","experiment","and","only","fetch","the","data","that","is","not","already","attached","to","the","experiment",".","NOTE",":","If","fetching","data","for","a","metrics","class","that","is","only","available","upon","trial","completion",",","data","fetched","in","this","function","(","data","that","was","not","yet","available","on","experiment",")","will","be","attached","to","experiment","."]
code: def fetch_data_prefer_lookup(
      self,
      experiment: core.experiment.Experiment,
      metrics: List[Metric],
      trials: Optional[List[core.base_trial.BaseTrial]] = None,
      **kwargs: Any,
  ) -> Tuple[Dict[int, Dict[str, MetricFetchResult]], bool]:
      """Fetch or lookup (with fallback to fetching) data for given metrics,
      depending on whether they are available while running. Return a tuple
      containing the data, along with a boolean that will be True if new
      data was fetched, and False if all data was looked up from cache.
  
      If metric is available while running, its data can change (and therefore
      we should always re-fetch it). If metric is available only upon trial
      completion, its data does not change, so we can look up that data on
      the experiment and only fetch the data that is not already attached to
      the experiment.
  
      NOTE: If fetching data for a metrics class that is only available upon
      trial completion, data fetched in this function (data that was not yet
      available on experiment) will be attached to experiment.
      """
      # If this metric is available while trial is running, just default to
      # `fetch_experiment_data_multi`.
      if self.is_available_while_running():
          fetched_data = self.bulk_fetch_experiment_data(
              experiment=experiment,
              metrics=metrics,
              trials=trials,
              **kwargs,
          )
          return fetched_data, True
  
      # If this metric is available only upon trial completion, look up data
      # on experiment and only fetch data that is not already cached.
      completed_trials = (
          experiment.completed_trials
          if trials is None
          else [t for t in trials if t.status.is_completed]
      )
  
      if not completed_trials:
          return {}, False
  
      trials_results = {}
      contains_new_data = False
  
      # TODO: Refactor this to not fetch data for each trial individually.
      # Since we use `bulk_fetch_experiment_data`, we should be able to
      # first identify trial + metric combos to fetch, then fetch them all
      # at once.
      for trial in completed_trials:
          cached_trial_data = experiment.lookup_data_for_trial(
              trial_index=trial.index,
          )[0]
  
          cached_metric_names = cached_trial_data.metric_names
          metrics_to_fetch = [
              m for m in metrics if m.name not in cached_metric_names
          ]
          if not metrics_to_fetch:
              # If all needed data fetched from cache, no need to fetch any other data
              # for trial.
              trials_results[trial.index] = self._wrap_trial_data_multi(
                  data=cached_trial_data
              )
              continue
  
          try:
              fetched_trial_data = self.bulk_fetch_experiment_data(
                  experiment=experiment,
                  metrics=metrics_to_fetch,
                  trials=[trial],
                  **kwargs,
              )[trial.index]
  
              contains_new_data = any(
                  result.is_ok()
                  for result in fetched_trial_data.values()
              )
          except NotImplementedError:
              # Metric does not implement fetching logic and only uses lookup.
              # TODO: This is only useful for base `Metric` –– all other metrics
              # do implement fetching logic. Should this exist then or is it only
              # adding complexity?
              fetched_trial_data = {}
  
          trials_results[trial.index] = {
              **self._wrap_trial_data_multi(data=cached_trial_data),
              **fetched_trial_data,
          }
  
      results = {
          trial_index: {
              metric_name: results
              for metric_name, results in results_by_metric_name.items()
              # We subset the metrics because cached results might have more
              # metrics than requested in arguments passed to this method.
              if metric_name in [metric.name for metric in metrics]
          }
          for trial_index, results_by_metric_name in trials_results.items()
      }
      return results, contains_new_data
  
imports: from __future__ import annotations
  import traceback
  import warnings
  from dataclasses import dataclass
  from functools import reduce
  from logging import Logger
  from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type, TYPE_CHECKING
  from ax.core.data import Data
  from ax.utils.common.base import SortableBase
  from ax.utils.common.logger import get_logger
  from ax.utils.common.result import Err, Ok, Result, UnwrapError
  from ax.utils.common.serialization import SerializationMixin
label_desc: Use image node_id 11 for calling the Metric obj's underlying member method code with example usage: obj.fetch_data_prefer_lookup(experiment, metrics, trials) and returns: results,  contains_new_data, fetched_data,  True, dict,  False