nodes: 0, module; 1, function_definition; 2, function_name:_reconstruct; 3, parameters; 4, block; 5, identifier:keyvals; 6, identifier:dialect; 7, default_parameter; 8, default_parameter; 9, expression_statement; 10, if_statement; 11, if_statement; 12, expression_statement; 13, comment:# Re-encode when reconstructing attributes; 14, if_statement; 15, comment:# May need to split multiple values into multiple key/val pairs; 16, if_statement; 17, function_definition; 18, if_statement; 19, for_statement; 20, comment:# Typically ";" or "; "; 21, expression_statement; 22, comment:# Sometimes need to add this; 23, if_statement; 24, return_statement; 25, identifier:keep_order; 26, False; 27, identifier:sort_attribute_values; 28, False; 29, comment:"""
      Reconstructs the original attributes string according to the dialect.
  
      Parameters
      ==========
      keyvals : dict
          Attributes from a GFF/GTF feature
  
      dialect : dict
          Dialect containing info on how to reconstruct a string version of the
          attributes
  
      keep_order : bool
          If True, then perform sorting of attribute keys to ensure they are in
          the same order as those provided in the original file.  Default is
          False, which saves time especially on large data sets.
  
      sort_attribute_values : bool
          If True, then sort values to ensure they will always be in the same
          order.  Mostly only useful for testing; default is False.
      """; 30, not_operator; 31, block; 32, not_operator; 33, block; 34, assignment; 35, boolean_operator; 36, block; 37, else_clause; 38, subscript; 39, block; 40, else_clause; 41, function_name:sort_key; 42, parameters; 43, comment:# sort keys by their order in the dialect; anything not in there will; 44, comment:# be in arbitrary order at the end.; 45, block; 46, identifier:keep_order; 47, block; 48, pattern_list; 49, identifier:items; 50, comment:# Multival sep is usually a comma:; 51, block; 52, assignment; 53, subscript; 54, block; 55, identifier:parts_str; 56, identifier:dialect; 57, raise_statement; 58, identifier:keyvals; 59, return_statement; 60, identifier:parts; 61, list; 62, attribute; 63, comparison_operator:dialect['fmt'] != 'gff3'; 64, expression_statement; 65, block; 66, identifier:dialect; 67, string; 68, expression_statement; 69, for_statement; 70, block; 71, identifier:x; 72, try_statement; 73, expression_statement; 74, identifier:key; 75, identifier:val; 76, if_statement; 77, expression_statement; 78, identifier:parts_str; 79, call; 80, identifier:dialect; 81, string; 82, expression_statement; 83, call; 84, string:""; 85, identifier:constants; 86, identifier:ignore_url_escape_characters; 87, subscript; 88, string; 89, assignment; 90, expression_statement; 91, for_statement; 92, string_content:repeated keys; 93, assignment; 94, pattern_list; 95, call; 96, block; 97, expression_statement; 98, block; 99, except_clause; 100, call; 101, identifier:val; 102, block; 103, else_clause; 104, call; 105, attribute; 106, argument_list; 107, string_content:trailing semicolon; 108, augmented_assignment; 109, identifier:AttributeStringError; 110, argument_list; 111, identifier:dialect; 112, string; 113, string_content:gff3; 114, identifier:attributes; 115, identifier:keyvals; 116, assignment; 117, pattern_list; 118, call; 119, block; 120, identifier:items; 121, list; 122, identifier:key; 123, identifier:val; 124, attribute; 125, argument_list; 126, if_statement; 127, assignment; 128, return_statement; 129, identifier:ValueError; 130, block; 131, attribute; 132, argument_list; 133, if_statement; 134, expression_statement; 135, if_statement; 136, block; 137, attribute; 138, argument_list; 139, subscript; 140, identifier:join; 141, identifier:parts; 142, identifier:parts_str; 143, string; 144, string_content:fmt; 145, identifier:attributes; 146, dictionary; 147, identifier:k; 148, identifier:v; 149, attribute; 150, argument_list; 151, expression_statement; 152, for_statement; 153, identifier:attributes; 154, identifier:items; 155, comparison_operator:len(val) > 1; 156, block; 157, else_clause; 158, identifier:items; 159, call; 160, call; 161, return_statement; 162, identifier:items; 163, identifier:sort; 164, keyword_argument; 165, identifier:sort_attribute_values; 166, block; 167, assignment; 168, identifier:val_str; 169, comment:# Surround with quotes if needed; 170, block; 171, if_statement; 172, identifier:parts; 173, identifier:append; 174, identifier:part; 175, identifier:dialect; 176, string; 177, string_content:;; 178, identifier:keyvals; 179, identifier:items; 180, assignment; 181, identifier:i; 182, identifier:v; 183, block; 184, call; 185, integer:1; 186, for_statement; 187, block; 188, identifier:list; 189, argument_list; 190, attribute; 191, argument_list; 192, float:1e6; 193, identifier:key; 194, identifier:sort_key; 195, expression_statement; 196, identifier:val_str; 197, call; 198, if_statement; 199, comment:# Typically "=" for GFF3 or " " otherwise; 200, expression_statement; 201, comparison_operator:dialect['fmt'] == 'gtf'; 202, block; 203, else_clause; 204, string_content:field separator; 205, subscript; 206, list; 207, expression_statement; 208, identifier:len; 209, argument_list; 210, identifier:v; 211, identifier:val; 212, block; 213, expression_statement; 214, call; 215, subscript; 216, identifier:index; 217, subscript; 218, assignment; 219, attribute; 220, argument_list; 221, subscript; 222, block; 223, assignment; 224, subscript; 225, string; 226, expression_statement; 227, block; 228, identifier:attributes; 229, identifier:k; 230, call; 231, identifier:val; 232, expression_statement; 233, call; 234, attribute; 235, argument_list; 236, identifier:dialect; 237, string; 238, identifier:x; 239, integer:0; 240, identifier:val; 241, call; 242, subscript; 243, identifier:join; 244, identifier:val; 245, identifier:dialect; 246, string; 247, expression_statement; 248, identifier:part; 249, call; 250, identifier:dialect; 251, string; 252, string_content:gtf; 253, assignment; 254, expression_statement; 255, attribute; 256, argument_list; 257, call; 258, attribute; 259, argument_list; 260, identifier:attributes; 261, identifier:items; 262, string_content:order; 263, identifier:sorted; 264, argument_list; 265, identifier:dialect; 266, string; 267, string_content:quoted GFF2 values; 268, assignment; 269, attribute; 270, argument_list; 271, string_content:fmt; 272, identifier:part; 273, call; 274, assignment; 275, subscript; 276, identifier:append; 277, call; 278, attribute; 279, argument_list; 280, identifier:items; 281, identifier:append; 282, tuple; 283, identifier:val; 284, string_content:multival separator; 285, identifier:val_str; 286, binary_operator:'"%s"' % val_str; 287, subscript; 288, identifier:join; 289, list; 290, attribute; 291, argument_list; 292, identifier:part; 293, identifier:key; 294, identifier:attributes; 295, identifier:k; 296, attribute; 297, argument_list; 298, identifier:items; 299, identifier:append; 300, tuple; 301, identifier:key; 302, identifier:val; 303, string:'"%s"'; 304, identifier:val_str; 305, identifier:dialect; 306, string; 307, identifier:key; 308, identifier:val_str; 309, subscript; 310, identifier:join; 311, list; 312, string; 313, identifier:join; 314, list_comprehension; 315, identifier:key; 316, list; 317, string_content:keyval separator; 318, identifier:dialect; 319, string; 320, identifier:key; 321, string:'""'; 322, subscript; 323, for_in_clause; 324, identifier:v; 325, string_content:keyval separator; 326, identifier:quoter; 327, identifier:j; 328, identifier:j; 329, identifier:i
code: def _reconstruct(keyvals, dialect, keep_order=False,
                   sort_attribute_values=False):
      """
      Reconstructs the original attributes string according to the dialect.
  
      Parameters
      ==========
      keyvals : dict
          Attributes from a GFF/GTF feature
  
      dialect : dict
          Dialect containing info on how to reconstruct a string version of the
          attributes
  
      keep_order : bool
          If True, then perform sorting of attribute keys to ensure they are in
          the same order as those provided in the original file.  Default is
          False, which saves time especially on large data sets.
  
      sort_attribute_values : bool
          If True, then sort values to ensure they will always be in the same
          order.  Mostly only useful for testing; default is False.
      """
      if not dialect:
          raise AttributeStringError()
      if not keyvals:
          return ""
      parts = []
  
      # Re-encode when reconstructing attributes
      if constants.ignore_url_escape_characters or dialect['fmt'] != 'gff3':
          attributes = keyvals
      else:
          attributes = {}
          for k, v in keyvals.items():
              attributes[k] = []
              for i in v:
                  attributes[k].append(''.join([quoter[j] for j in i]))
  
      # May need to split multiple values into multiple key/val pairs
      if dialect['repeated keys']:
          items = []
          for key, val in attributes.items():
              if len(val) > 1:
                  for v in val:
                      items.append((key, [v]))
              else:
                  items.append((key, val))
      else:
          items = list(attributes.items())
  
      def sort_key(x):
          # sort keys by their order in the dialect; anything not in there will
          # be in arbitrary order at the end.
          try:
              return dialect['order'].index(x[0])
          except ValueError:
              return 1e6
  
      if keep_order:
          items.sort(key=sort_key)
  
      for key, val in items:
  
          # Multival sep is usually a comma:
          if val:
              if sort_attribute_values:
                  val = sorted(val)
  
              val_str = dialect['multival separator'].join(val)
  
              if val_str:
  
                  # Surround with quotes if needed
                  if dialect['quoted GFF2 values']:
                      val_str = '"%s"' % val_str
  
                  # Typically "=" for GFF3 or " " otherwise
                  part = dialect['keyval separator'].join([key, val_str])
          else:
              if dialect['fmt'] == 'gtf':
                  part = dialect['keyval separator'].join([key, '""'])
              else:
                  part = key
          parts.append(part)
  
      # Typically ";" or "; "
      parts_str = dialect['field separator'].join(parts)
  
      # Sometimes need to add this
      if dialect['trailing semicolon']:
          parts_str += ';'
  
      return parts_str