nodes: 0, module; 1, function_definition; 2, function_name:find_range; 3, parameters; 4, block; 5, identifier:values; 6, default_parameter; 7, expression_statement; 8, try_statement; 9, identifier:soft_range; 10, list; 11, comment:"""
      Safely finds either the numerical min and max of
      a set of values, falling back to the first and
      the last value in the sorted list of values.
      """; 12, block; 13, except_clause; 14, expression_statement; 15, expression_statement; 16, if_statement; 17, if_statement; 18, return_statement; 19, block; 20, assignment; 21, assignment; 22, call; 23, block; 24, comparison_operator:values.dtype.kind == 'M'; 25, block; 26, expression_list; 27, try_statement; 28, identifier:values; 29, call; 30, identifier:values; 31, conditional_expression:np.squeeze(values) if len(values.shape) > 1 else values; 32, identifier:len; 33, argument_list; 34, expression_statement; 35, attribute; 36, string; 37, return_statement; 38, call; 39, call; 40, block; 41, except_clause; 42, attribute; 43, argument_list; 44, call; 45, comparison_operator:len(values.shape) > 1; 46, identifier:values; 47, identifier:soft_range; 48, assignment; 49, attribute; 50, identifier:kind; 51, string_content:M; 52, expression_list; 53, attribute; 54, argument_list; 55, attribute; 56, argument_list; 57, expression_statement; 58, return_statement; 59, block; 60, identifier:np; 61, identifier:array; 62, identifier:values; 63, attribute; 64, argument_list; 65, call; 66, integer:1; 67, identifier:values; 68, call; 69, identifier:values; 70, identifier:dtype; 71, call; 72, call; 73, identifier:np; 74, identifier:nanmin; 75, identifier:values; 76, identifier:np; 77, identifier:nanmax; 78, identifier:values; 79, assignment; 80, tuple; 81, return_statement; 82, identifier:np; 83, identifier:squeeze; 84, identifier:values; 85, identifier:len; 86, argument_list; 87, attribute; 88, argument_list; 89, attribute; 90, argument_list; 91, attribute; 92, argument_list; 93, identifier:values; 94, call; 95, subscript; 96, subscript; 97, tuple; 98, attribute; 99, identifier:np; 100, identifier:concatenate; 101, list; 102, identifier:values; 103, identifier:min; 104, identifier:values; 105, identifier:max; 106, identifier:sorted; 107, argument_list; 108, identifier:values; 109, integer:0; 110, identifier:values; 111, unary_operator; 112, None; 113, None; 114, identifier:values; 115, identifier:shape; 116, identifier:values; 117, identifier:soft_range; 118, identifier:values; 119, integer:1
code: def find_range(values, soft_range=[]):
      """
      Safely finds either the numerical min and max of
      a set of values, falling back to the first and
      the last value in the sorted list of values.
      """
      try:
          values = np.array(values)
          values = np.squeeze(values) if len(values.shape) > 1 else values
          if len(soft_range):
              values = np.concatenate([values, soft_range])
          if values.dtype.kind == 'M':
              return values.min(), values.max()
          return np.nanmin(values), np.nanmax(values)
      except:
          try:
              values = sorted(values)
              return (values[0], values[-1])
          except:
              return (None, None)