code: def debounce(interval_s, keyed_by=None):
      
      def wrapper(func):
          timers = {}
          lock = threading.Lock()
  
          @functools.wraps(func)
          def debounced(*args, **kwargs):
              call_args = inspect.getcallargs(func, *args, **kwargs)
              key = call_args[keyed_by] if keyed_by else None
  
              def run():
                  with lock:
                      del timers[key]
                  return func(*args, **kwargs)
  
              with lock:
                  old_timer = timers.get(key)
                  if old_timer:
                      old_timer.cancel()
  
                  timer = threading.Timer(interval_s, run)
                  timers[key] = timer
                  timer.start()
          return debounced
      return wrapper
docs: Debounce calls to this function until interval_s seconds have passed.