class_docstr: Downloader from AWS S3 buckets
      
class_docstr_tok: ["Downloader","from","AWS","S3","buckets"]
docstr: Get key information via GET so we can get error_code if any
  
  The problem with bucket.get_key is that it uses HEAD request.
  With that request response header has only the status (e.g. 400)
  but not a specific error_code.  That makes it impossible to properly
  react on failed requests (wait? re-auth?).
  
  Yarik found no easy way in boto to reissue the request with GET,
  so this code is lobotomized version of _get_key_internal but with GET
  instead of HEAD and thus providing body into error handling.
docstr_tok: ["Get","key","information","via","GET","so","we","can","get","error_code","if","any","The","problem","with","bucket.get_key","is","that","it","uses","HEAD","request",".","With","that","request","response","header","has","only","the","status","(","e.g",".","400",")","but","not","a","specific","error_code",".","That","makes","it","impossible","to","properly","react","on","failed","requests","(","wait","?","re-auth","?",")",".","Yarik","found","no","easy","way","in","boto","to","reissue","the","request","with","GET",",","so","this","code","is","lobotomized","version","of","_get_key_internal","but","with","GET","instead","of","HEAD","and","thus","providing","body","into","error","handling","."]
code: def _get_key_via_get(self, key_name, version_id=None, headers=None):
      """Get key information via GET so we can get error_code if any
  
      The problem with bucket.get_key is that it uses HEAD request.
      With that request response header has only the status (e.g. 400)
      but not a specific error_code.  That makes it impossible to properly
      react on failed requests (wait? re-auth?).
  
      Yarik found no easy way in boto to reissue the request with GET,
      so this code is lobotomized version of _get_key_internal but with GET
      instead of HEAD and thus providing body into error handling.
      """
      query_args_l = []
      if version_id:
          query_args_l.append("versionId=%s" % version_id)
      query_args = "&".join(query_args_l) or None
      bucket = self._bucket
      headers = headers or {}
      headers["Range"] = "bytes=0-0"
      response = bucket.connection.make_request(
          "GET",
          bucket.name,
          key_name,
          headers=headers,
          query_args=query_args,
      )
      body = response.read()
      if response.status // 100 == 2:
          # it was all good
          k = bucket.key_class(bucket)
          provider = bucket.connection.provider
          k.metadata = boto.utils.get_aws_metadata(
              response.msg, provider
          )
          for field in Key.base_fields:
              k.__dict__[
                  field.lower().replace("-", "_")
              ] = response.getheader(field)
          crange, crange_size = (
              response.getheader("content-range"),
              None,
          )
          if crange:
              # should look like 'bytes 0-0/50993'
              if not crange.startswith("bytes 0-0/"):
                  # we will just spit out original exception and be done -- we have tried!
                  raise ValueError(
                      "Got content-range %s which I do not know how to handle to "
                      "get the full size" % repr(crange)
                  )
              crange_size = int(crange.split("/", 1)[-1])
              k.size = crange_size
          # the following machinations are a workaround to the fact that
          # apache/fastcgi omits the content-length header on HEAD
          # requests when the content-length is zero.
          # See http://goo.gl/0Tdax for more details.
          if response.status != 206:  # partial content
              # assume full return of 0 bytes etc
              clen_size = int(response.getheader("content-length"), 0)
  
              if crange_size is not None:
                  if crange_size != clen_size:
                      raise ValueError(
                          "We got content-length %d and size from content-range %d - they do "
                          "not match",
                          clen_size,
                          crange_size,
                      )
              k.size = clen_size
          k.name = key_name
          k.handle_version_headers(response)
          k.handle_encryption_headers(response)
          k.handle_restore_headers(response)
          k.handle_storage_class_header(response)
          k.handle_addl_headers(response.getheaders())
          return k
      else:
          if response.status == 404:
              return None
          else:
              raise bucket.connection.provider.storage_response_error(
                  response.status, response.reason, body
              )
  
imports: import re
  from urllib.parse import urlsplit, unquote
  from ..utils import auto_repr, ensure_dict_from_str
  from ..dochelpers import borrowkwargs
  from ..support.network import get_url_straight_filename, iso8601_to_epoch, rfc2822_to_epoch
  from .base import Authenticator
  from .base import BaseDownloader, DownloaderSession
  from ..support.exceptions import AccessPermissionExpiredError, CapturedException, TargetFileAbsent
  from ..support.s3 import Key, OrdinaryCallingFormat, S3ResponseError, boto, get_bucket, try_multiple_dec_s3
  from ..support.status import FileStatus
  import logging
  from logging import getLogger
label_desc: Use image node_id 8 for calling the S3Downloader obj's underlying member method code with example usage: obj._get_key_via_get(key_name, version_id, headers) and returns: k, None