docs: def apply_config_changes(before_editing, data, kvpairs):
      """Applies config changes specified as a list of key-value pairs.
  
      Keys are interpreted as dotted paths into the configuration data structure. Except for paths beginning with
      `postgresql.parameters` where rest of the path is used directly to allow for PostgreSQL GUCs containing dots.
      Values are interpreted as YAML values.
  
      :param before_editing: human representation before editing
      :param data: configuration datastructure
      :param kvpairs: list of strings containing key value pairs separated by =
      :returns tuple of human readable and parsed datastructure after changes
      """
      changed_data = copy.deepcopy(data)
  
      def set_path_value(config, path, value, prefix=()):
          # Postgresql GUCs can't be nested, but can contain dots so we re-flatten the structure for this case
          if prefix == ('postgresql', 'parameters'):
              path = ['.'.join(path)]
  
          key = path[0]
          if len(path) == 1:
              if value is None:
                  config.pop(key, None)
              else:
                  config[key] = value
          else:
              if not isinstance(config.get(key), dict):
                  config[key] = {}
              set_path_value(config[key], path[1:], value, prefix + (key,))
              if config[key] == {}:
                  del config[key]
  
      for pair in kvpairs:
          if not pair or "=" not in pair:
              raise PatroniCtlException("Invalid parameter setting {0}".format(pair))
          key_path, value = pair.split("=", 1)
          set_path_value(changed_data, key_path.strip().split("."), yaml.safe_load(value))
  
      return format_config_for_editing(changed_data), changed_data