docstr: Fetch artifact (file) from `self._artifact_path` on Anyscale cluster
  head node.
  
  Note, an implementation detail here is that by the time this function is called,
  the artifact file is already present in s3 bucket by the name of
  `self._USER_GENERATED_ARTIFACT`. This is because, the uploading to s3 portion is
  done by `_anyscale_job_wrapper`.
  
  The fetched artifact will be placed under `self._DEFAULT_ARTIFACTS_DIR`,
  which will ultimately show up in buildkite Artifacts UI tab.
  The fetched file will have the same filename and extension as the one
  on Anyscale cluster head node (same as `self._artifact_path`).
docstr_tok: ["Fetch","artifact","(","file",")","from","`","self._artifact_path","`","on","Anyscale","cluster","head","node",".","Note",",","an","implementation","detail","here","is","that","by","the","time","this","function","is","called",",","the","artifact","file","is","already","present","in","s3","bucket","by","the","name","of","`","self._USER_GENERATED_ARTIFACT","`",".","This","is","because",",","the","uploading","to","s3","portion","is","done","by","`","_anyscale_job_wrapper","`",".","The","fetched","artifact","will","be","placed","under","`","self._DEFAULT_ARTIFACTS_DIR","`",",","which","will","ultimately","show","up","in","buildkite","Artifacts","UI","tab",".","The","fetched","file","will","have","the","same","filename","and","extension","as","the","one","on","Anyscale","cluster","head","node","(","same","as","`","self._artifact_path","`",")","."]
code: def fetch_artifact(self):
      """Fetch artifact (file) from `self._artifact_path` on Anyscale cluster
      head node.
  
      Note, an implementation detail here is that by the time this function is called,
      the artifact file is already present in s3 bucket by the name of
      `self._USER_GENERATED_ARTIFACT`. This is because, the uploading to s3 portion is
      done by `_anyscale_job_wrapper`.
  
      The fetched artifact will be placed under `self._DEFAULT_ARTIFACTS_DIR`,
      which will ultimately show up in buildkite Artifacts UI tab.
      The fetched file will have the same filename and extension as the one
      on Anyscale cluster head node (same as `self._artifact_path`).
      """
      if not self._artifact_uploaded:
          raise FetchResultError(
              "Could not fetch artifact from session as they "
              "were either not generated or not uploaded."
          )
      # first make sure that `self._DEFAULT_ARTIFACTS_DIR` exists.
      if not os.path.exists(self._DEFAULT_ARTIFACTS_DIR):
          os.makedirs(self._DEFAULT_ARTIFACTS_DIR, 0o755)
  
      # we use the same artifact file name and extension specified by user
      # and put it under `self._DEFAULT_ARTIFACTS_DIR`.
      artifact_file_name = os.path.basename(self._artifact_path)
      self.file_manager.download_from_cloud(
          join_cloud_storage_paths(
              self.path_in_bucket, self._USER_GENERATED_ARTIFACT
          ),
          os.path.join(self._DEFAULT_ARTIFACTS_DIR, artifact_file_name),
      )
  
imports: import json
  import os
  import re
  import tempfile
  import shlex
  from typing import TYPE_CHECKING, Any, Dict, Optional, List
  from ray_release.cluster_manager.cluster_manager import ClusterManager
  from ray_release.command_runner.job_runner import JobRunner
  from ray_release.exception import TestCommandTimeout, TestCommandError, JobOutOfRetriesError, PrepareCommandError, PrepareCommandTimeout, JobBrokenError, FetchResultError, JobTerminatedBeforeStartError, JobNoLogsError, JobTerminatedError
  from ray_release.file_manager.job_file_manager import JobFileManager
  from ray_release.job_manager import AnyscaleJobManager
  from ray_release.logger import logger
  from ray_release.util import join_cloud_storage_paths, generate_tmp_cloud_storage_path, get_anyscale_sdk, S3_CLOUD_STORAGE
label_desc: Use image node_id 12 for calling the AnyscaleJobRunner obj's underlying member method code with example usage: obj.fetch_artifact() without return types