nodes: 0, module; 1, function_definition; 2, function_name:validate_sort_fields; 3, parameters; 4, block; 5, identifier:sort_fields; 6, expression_statement; 7, comment:# Allow descending order per field by prefixing with '-'; 8, expression_statement; 9, function_definition; 10, comment:# Split and validate field list; 11, expression_statement; 12, expression_statement; 13, comment:# No descending fields?; 14, if_statement; 15, comment:# Need to provide complex key; 16, class_definition; 17, return_statement; 18, comment:""" Make sure the fields in the given list exist, and return sorting key.
  
          If field names are prefixed with '-', sort order is reversed for that field (descending).
      """; 19, assignment; 20, function_name:sort_order_filter; 21, parameters; 22, block; 23, assignment; 24, call; 25, not_operator; 26, block; 27, identifier:Key; 28, argument_list; 29, block; 30, identifier:Key; 31, identifier:descending; 32, call; 33, identifier:name; 34, expression_statement; 35, if_statement; 36, return_statement; 37, identifier:sort_fields; 38, call; 39, attribute; 40, argument_list; 41, identifier:descending; 42, return_statement; 43, identifier:object; 44, expression_statement; 45, function_definition; 46, function_definition; 47, identifier:set; 48, argument_list; 49, string:"Helper to remove flag and memoize sort order"; 50, call; 51, block; 52, identifier:name; 53, identifier:validate_field_list; 54, argument_list; 55, identifier:log; 56, identifier:debug; 57, binary_operator:"Sorting order is: %s" % ", ".join([('-' if i in descending else '') + i
          for i in sort_fields]); 58, call; 59, string:"Complex sort order key"; 60, function_name:__init__; 61, parameters; 62, block; 63, function_name:__lt__; 64, parameters; 65, block; 66, attribute; 67, argument_list; 68, expression_statement; 69, expression_statement; 70, identifier:sort_fields; 71, keyword_argument; 72, string:"Sorting order is: %s"; 73, call; 74, attribute; 75, argument_list; 76, identifier:self; 77, identifier:obj; 78, list_splat_pattern; 79, expression_statement; 80, expression_statement; 81, identifier:self; 82, identifier:other; 83, expression_statement; 84, for_statement; 85, return_statement; 86, identifier:name; 87, identifier:startswith; 88, string; 89, assignment; 90, call; 91, identifier:name_filter; 92, identifier:sort_order_filter; 93, attribute; 94, argument_list; 95, identifier:operator; 96, identifier:attrgetter; 97, list_splat; 98, identifier:args; 99, string:"Remember object to be compared"; 100, assignment; 101, string:"Compare to other key"; 102, identifier:field; 103, identifier:sort_fields; 104, block; 105, False; 106, string_content:-; 107, identifier:name; 108, subscript; 109, attribute; 110, argument_list; 111, string:", "; 112, identifier:join; 113, list_comprehension; 114, call; 115, attribute; 116, identifier:obj; 117, expression_statement; 118, if_statement; 119, return_statement; 120, identifier:name; 121, slice; 122, identifier:descending; 123, identifier:add; 124, identifier:name; 125, binary_operator:('-' if i in descending else '') + i; 126, for_in_clause; 127, identifier:tuple; 128, argument_list; 129, identifier:self; 130, identifier:obj; 131, assignment; 132, comparison_operator:lhs == rhs; 133, block; 134, conditional_expression:rhs < lhs if field in descending else lhs < rhs; 135, integer:1; 136, parenthesized_expression; 137, identifier:i; 138, identifier:i; 139, identifier:sort_fields; 140, identifier:sort_fields; 141, pattern_list; 142, expression_list; 143, identifier:lhs; 144, identifier:rhs; 145, continue_statement; 146, comparison_operator:rhs < lhs; 147, comparison_operator:field in descending; 148, comparison_operator:lhs < rhs; 149, conditional_expression:'-' if i in descending else ''; 150, identifier:lhs; 151, identifier:rhs; 152, call; 153, call; 154, identifier:rhs; 155, identifier:lhs; 156, identifier:field; 157, identifier:descending; 158, identifier:lhs; 159, identifier:rhs; 160, string; 161, comparison_operator:i in descending; 162, string; 163, identifier:getattr; 164, argument_list; 165, identifier:getattr; 166, argument_list; 167, string_content:-; 168, identifier:i; 169, identifier:descending; 170, attribute; 171, identifier:field; 172, attribute; 173, identifier:field; 174, identifier:self; 175, identifier:obj; 176, identifier:other; 177, identifier:obj
code: def validate_sort_fields(sort_fields):
      """ Make sure the fields in the given list exist, and return sorting key.
  
          If field names are prefixed with '-', sort order is reversed for that field (descending).
      """
      # Allow descending order per field by prefixing with '-'
      descending = set()
      def sort_order_filter(name):
          "Helper to remove flag and memoize sort order"
          if name.startswith('-'):
              name = name[1:]
              descending.add(name)
          return name
  
      # Split and validate field list
      sort_fields = validate_field_list(sort_fields, name_filter=sort_order_filter)
      log.debug("Sorting order is: %s" % ", ".join([('-' if i in descending else '') + i
          for i in sort_fields]))
  
      # No descending fields?
      if not descending:
          return operator.attrgetter(*tuple(sort_fields))
  
      # Need to provide complex key
      class Key(object):
          "Complex sort order key"
          def __init__(self, obj, *args):
              "Remember object to be compared"
              self.obj = obj
          def __lt__(self, other):
              "Compare to other key"
              for field in sort_fields:
                  lhs, rhs = getattr(self.obj, field), getattr(other.obj, field)
                  if lhs == rhs:
                      continue
                  return rhs < lhs if field in descending else lhs < rhs
              return False
  
      return Key