code: def etag_cache(
      cache: Cache = cache_manager.cache,
      get_last_modified: Callable[..., datetime] | None = None,
      max_age: int | float = app.config["CACHE_DEFAULT_TIMEOUT"],
      raise_for_access: Callable[..., Any] | None = None,
      skip: Callable[..., bool] | None = None,
  ) -> Callable[..., Any]:
      """
      A decorator for caching views and handling etag conditional requests.
  
      The decorator adds headers to GET requests that help with caching: Last-
      Modified, Expires and ETag. It also handles conditional requests, when the
      client send an If-Matches header.
  
      If a cache is set, the decorator will cache GET responses, bypassing the
      dataframe serialization. POST requests will still benefit from the
      dataframe cache for requests that produce the same SQL.
  
      """
  
      def decorator(f: Callable[..., Any]) -> Callable[..., Any]:
          @wraps(f)
          def wrapper(*args: Any, **kwargs: Any) -> Response:
              # Check if the user can access the resource
              if raise_for_access:
                  try:
                      raise_for_access(*args, **kwargs)
                  except Exception:  # pylint: disable=broad-except
                      # If there's no access, bypass the cache and let the function
                      # handle the response.
                      return f(*args, **kwargs)
  
              # for POST requests we can't set cache headers, use the response
              # cache nor use conditional requests; this will still use the
              # dataframe cache in `superset/viz.py`, though.
              if request.method == "POST" or (
                  skip and skip(*args, **kwargs)
              ):
                  return f(*args, **kwargs)
  
              response = None
              try:
                  # build the cache key from the function arguments and any
                  # other additional GET arguments (like `form_data`, eg).
                  key_args = list(args)
                  key_kwargs = kwargs.copy()
                  key_kwargs.update(request.args)
                  cache_key = wrapper.make_cache_key(  # type: ignore
                      f, *key_args, **key_kwargs
                  )
                  response = cache.get(cache_key)
              except Exception:  # pylint: disable=broad-except
                  if app.debug:
                      raise
                  logger.exception(
                      "Exception possibly due to cache backend."
                  )
  
              # Check if the cache is stale. Default the content_changed_time to now
              # if we don't know when it was last modified.
              content_changed_time = datetime.utcnow()
              if get_last_modified:
                  content_changed_time = get_last_modified(
                      *args, **kwargs
                  )
                  if (
                      response
                      and response.last_modified
                      and response.last_modified.timestamp()
                      < content_changed_time.timestamp()
                  ):
                      # Bypass the cache if the response is stale
                      response = None
  
              # if no response was cached, compute it using the wrapped function
              if response is None:
                  response = f(*args, **kwargs)
  
                  # add headers for caching: Last Modified, Expires and ETag
                  # always revalidate the cache if we're checking permissions or
                  # if the response was modified
                  if get_last_modified or raise_for_access:
                      # `Cache-Control: no-cache` asks the browser to always store
                      # the cache, but also must validate it with the server.
                      response.cache_control.no_cache = True
                  else:
                      # `Cache-Control: Public` asks the browser to always store
                      # the cache.
                      response.cache_control.public = True
  
                  response.last_modified = content_changed_time
                  expiration = (
                      max_age or ONE_YEAR
                  )  # max_age=0 also means far future
                  response.expires = response.last_modified + timedelta(
                      seconds=expiration
                  )
                  response.add_etag()
  
                  # if we have a cache, store the response from the request
                  try:
                      cache.set(cache_key, response, timeout=max_age)
                  except Exception:  # pylint: disable=broad-except
                      if app.debug:
                          raise
                      logger.exception(
                          "Exception possibly due to cache backend."
                      )
  
              return response.make_conditional(request)
  
          wrapper.uncached = f  # type: ignore
          wrapper.cache_timeout = max_age  # type: ignore
          wrapper.make_cache_key = cache._memoize_make_cache_key(  # type: ignore # pylint: disable=protected-access
              make_name=None, timeout=max_age
          )
  
          return wrapper
  
      return decorator
  
file_path: superset/superset/utils/cache.py
imports: from __future__ import annotations
  import inspect
  import logging
  from datetime import datetime, timedelta
  from functools import wraps
  from typing import Any, Callable, TYPE_CHECKING
  from flask import current_app, request
  from flask_caching import Cache
  from flask_caching.backends import NullCache
  from werkzeug.wrappers import Response
  from superset import db
  from superset.extensions import cache_manager
  from superset.models.cache import CacheKey
  from superset.utils.core import json_int_dttm_ser
  from superset.utils.hashing import md5_sha_from_dict
label_desc: Use image node_id 4 for calling a global function with example usage: etag_cache(cache, get_last_modified, max_age, raise_for_access, skip) and returns: decorator, wrapper, response, f, f