class_docstr: Specifies the request expected by the process route.
  
  Note that all underscores in field names are replaced by dashes in the JSON.
class_docstr_tok: ["Specifies","the","request","expected","by","the","process","route",".","Note","that","all","underscores","in","field","names","are","replaced","by","dashes","in","the","JSON","."]
docstr: Downloads specified data and returns an error message if any.
docstr_tok: ["Downloads","specified","data","and","returns","an","error","message","if","any","."]
code: def download_if_needed(self) -> str:
      """Downloads specified data and returns an error message if any."""
      is_valid, err_message = self.validate()
      if not is_valid:
          return err_message
      if (
          not self.force_refresh
          and self.market_data_exists_locally()
          and self.portfolio_exists_locally()
      ):
          return ""
      market_data_path = self.market_data_local_path()
      self._ensure_base_path_exists(market_data_path)
      self._download_from_gcs(self.market_data_file, market_data_path)
      portfolio_data_path = self.portfolio_local_path()
      self._ensure_base_path_exists(portfolio_data_path)
      self._download_from_gcs(self.portfolio_file, portfolio_data_path)
      return ""
  
imports: import os
  from os import path
  import time
  from typing import Dict, List, Tuple, Union
  from common import datatypes
  import dataclasses
  import flask
  import zmq
  from google.cloud import storage
label_desc: Use image node_id 8 for calling the JobRequest obj's underlying member method code with example usage: obj.download_if_needed() and returns: str, err_message, str