code: def _decorator_factory(
      level: LogStatsLevel,
      reduce_function: Callable,
      input_name: Optional[str],
      output_name: Optional[str],
      group_by: Optional[str],
      cumulative: bool = False,
  ) -> Callable:
      """
      Aggregation decorator factory
  
      :param level: The aggregation level that this decoration refers to
      :param reduce_function: The statistics calculation to be used
      :param input_name: Specifies the event attribute to be passed to the aggregation_function
                         (in case the event accepts more than one argument)
      :param output_name: Specify the name of the output, which is necessary when using multiple aggregations on the
                          same attribute.
      :param group_by: If there are multiple groups defined for an event, per default the statistics
                       is collected at the 'cell' level. This option allows to project the statistics
                       onto a single group (e.g. for inventory statistics we might define location and product as
                       groups, but prefer to monitor statistics grouped only per product, regardless of location
                       and vice versa)
      :param cumulative: Enable cumulative statistics
  
      :return: The decorator function
      """
  
      def decorator(func_obj) -> Callable:
          """
  
          @param func_obj: The actual function to be decorated
          @return: The unmodified function object (required by the Python decorator semantics)
          """
          # attach a dictionary that maps the aggregation level to the statistics calculation info
          input_to_reducers = getattr(func_obj, level.name, None)
  
          # create the aggregation dictionary if it does not exist (this will be the case for the first decorator)
          if not input_to_reducers:
              input_to_reducers = defaultdict(list)
              setattr(func_obj, level.name, input_to_reducers)
  
          # check if the input_name is valid
          _check_input_name(level, func_obj, input_name)
  
          # check if the output_name is valid
          _output_name = _check_output_name(
              input_name=input_name,
              output_name=output_name,
              input_to_reducers=input_to_reducers,
          )
  
          # add the new aggregation to the dictionary
          input_to_reducers[input_name].append(
              (reduce_function, _output_name, group_by, cumulative)
          )
  
          return func_obj
  
      return decorator
  
file_path: maze/maze/core/log_stats/event_decorators.py
imports: import inspect
  from collections import defaultdict
  from typing import Callable, Optional, List, Dict
  from maze.core.log_stats.log_stats import LogStatsLevel
label_desc: Use image node_id 4 for calling a global function with example usage: _decorator_factory(level, reduce_function, input_name, output_name, group_by, cumulative) and returns: decorator, func_obj