class_docstr: StorageManager is helper interface for downloading & uploading files to supported remote storage
  Support remote servers: http(s)/S3/GS/Azure/File-System-Folder
  Cache is enabled by default for all downloaded remote urls/files
docstr: Upload local folder recursively to a remote storage, maintaining the sub folder structure
  in the remote storage.
  
  .. note::
  
      If we have a local file `~/folder/sub/file.ext` then
      `StorageManager.upload_folder('~/folder/', 's3://bucket/')`
      will create `s3://bucket/sub/file.ext`
  
  :param str local_folder: Local folder to recursively upload
  :param str remote_url: Target remote storage location, tree structure of `local_folder` will
      be created under the target remote_url. Supports Http/S3/GS/Azure and shared filesystem.
      Example: 's3://bucket/data/'
  :param str match_wildcard: If specified only upload files matching the `match_wildcard`
      Example: `*.json`
      Notice: target file size/date are not checked. Default True, always upload.
      Notice if uploading to http, we will always overwrite the target.
  :return: Newly uploaded remote URL or None on error.
docstr_tok: ["Upload","local","folder","recursively","to","a","remote","storage",",","maintaining","the","sub","folder","structure","in","the","remote","storage",".","..","note",":",":","If","we","have","a","local","file","`","~\/folder\/sub\/file.ext","`","then","`","StorageManager.upload_folder","(","'~\/folder\/","'",",","'s3",":","\/\/bucket\/","'",")","`","will","create","`","s3",":","\/\/bucket\/sub\/file.ext","`",":","param","str","local_folder",":","Local","folder","to","recursively","upload",":","param","str","remote_url",":","Target","remote","storage","location",",","tree","structure","of","`","local_folder","`","will","be","created","under","the","target","remote_url",".","Supports","Http\/S3\/GS\/Azure","and","shared","filesystem",".","Example",":","'s3",":","\/\/bucket\/data\/'",":","param","str","match_wildcard",":","If","specified","only","upload","files","matching","the","`","match_wildcard","`","Example",":","`","*",".json","`","Notice",":","target","file","size\/date","are","not","checked",".","Default","True",",","always","upload",".","Notice","if","uploading","to","http",",","we","will","always","overwrite","the","target",".",":","return",":","Newly","uploaded","remote","URL","or","None","on","error","."]
code: def upload_folder(cls, local_folder, remote_url, match_wildcard=None):
      # type: (str, str, Optional[str]) -> Optional[str]
      """
      Upload local folder recursively to a remote storage, maintaining the sub folder structure
      in the remote storage.
  
      .. note::
  
          If we have a local file `~/folder/sub/file.ext` then
          `StorageManager.upload_folder('~/folder/', 's3://bucket/')`
          will create `s3://bucket/sub/file.ext`
  
      :param str local_folder: Local folder to recursively upload
      :param str remote_url: Target remote storage location, tree structure of `local_folder` will
          be created under the target remote_url. Supports Http/S3/GS/Azure and shared filesystem.
          Example: 's3://bucket/data/'
      :param str match_wildcard: If specified only upload files matching the `match_wildcard`
          Example: `*.json`
          Notice: target file size/date are not checked. Default True, always upload.
          Notice if uploading to http, we will always overwrite the target.
      :return: Newly uploaded remote URL or None on error.
      """
  
      base_logger = LoggerRoot.get_base_logger()
  
      if not Path(local_folder).is_dir():
          base_logger.error(
              "Local folder '{}' does not exist".format(local_folder)
          )
          return
      local_folder = str(Path(local_folder))
      results = []
      helper = StorageHelper.get(remote_url)
      with ThreadPool() as pool:
          for path in Path(local_folder).rglob(match_wildcard or "*"):
              if not path.is_file():
                  continue
              results.append(
                  pool.apply_async(
                      helper.upload,
                      args=(
                          str(path),
                          str(path).replace(local_folder, remote_url),
                      ),
                  )
              )
  
          success = 0
          failed = 0
          for res in results:
              # noinspection PyBroadException
              try:
                  res.get()  # Reraise the exceptions from remote call (if any)
                  success += 1
              except Exception:
                  failed += 1
  
          if failed == 0:
              return remote_url
  
          base_logger.error(
              "Failed uploading {}/{} files from {}".format(
                  failed, success + failed, local_folder
              )
          )
  
imports: import fnmatch
  import shutil
  import tarfile
  from multiprocessing.pool import ThreadPool
  from random import random
  from time import time
  from typing import List, Optional, Union
  from zipfile import ZipFile
  from pathlib2 import Path
  from .cache import CacheManager
  from .callbacks import ProgressReport
  from .helper import StorageHelper
  from .util import encode_string_to_filename, safe_extract, create_zip_directories
  from ..debugging.log import LoggerRoot
  from ..config import deferred_config
label_desc: Use image node_id 6 for calling the StorageManager obj's underlying member method code with example usage: obj.upload_folder(cls, local_folder, remote_url, match_wildcard) and returns: remote_url