Repository: influxdata/influxdb-client-python

Issue:
FluxTable / FluxRecord can't handle tables with duplicate column labels
### Specifications

-


### Code sample to reproduce problem

Make a query for measurements that include a field called *result* or other labels that occur by default in the [annotated CSV response header](https://docs.influxdata.com/influxdb/v2.4/reference/syntax/annotated-csv/#example). Make the field name part of the annotated CSV response header via `pivot`. The annotated CSV returned by the API will have columns with duplicate labels.

### Expected behavior

Being able to access all data returned by the API as annotated CSV

### Actual behavior

Flux CSV parser can't handle duplicate header names. It turns CSV rows into `FluxRecord`s whose internal values are represented as python dicts, meaning values from columns with the same label overwrite each other.

See [flux_csv_parser.py from line 262](https://github.com/influxdata/influxdb-client-python/blob/1ca66901c9052432d6caa8cc303086918af7d5c1/influxdb_client/client/flux_csv_parser.py#L262-L265) for the logic that collapses a list of columns into a dict based on the column label.

### Additional info

_No response_

Comments:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 13c59ff..d84e990 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
 
 ### Features
 1. [#498](https://github.com/influxdata/influxdb-client-python/pull/498): Add possibility to update user's password by `users_api`
+1. [#502](https://github.com/influxdata/influxdb-client-python/pull/502): Add `FluxRecord.row` with response data stored in array
 
 ### Bug Fixes
 1. [#497](https://github.com/influxdata/influxdb-client-python/pull/497): Parsing InfluxDB response with new line character in CSV column [async/await]
diff --git a/influxdb_client/client/flux_csv_parser.py b/influxdb_client/client/flux_csv_parser.py
index 4756d32..db4c4c7 100644
--- a/influxdb_client/client/flux_csv_parser.py
+++ b/influxdb_client/client/flux_csv_parser.py
@@ -4,6 +4,7 @@
 import base64
 import codecs
 import csv as csv_parser
+import warnings
 from enum import Enum
 from typing import List
 
@@ -262,6 +263,7 @@ class FluxCsvParser(object):
             column_name = fluxColumn.label
             str_val = csv[fluxColumn.index + 1]
             record.values[column_name] = self._to_value(str_val, fluxColumn)
+            record.row.append(record.values[column_name])
 
         return record
 
@@ -321,6 +323,13 @@ class FluxCsvParser(object):
     @staticmethod
     def add_column_names_and_tags(table, csv):
         """Add labels to columns."""
+        if len(csv) != len(set(csv)):
+            message = f"""The response contains columns with duplicated names: '{csv}'.
+
+You should use the 'record.row' to access your data instead of 'record.values' dictionary.
+"""
+            warnings.warn(message, UserWarning)
+            print(message)
         i = 1
         for column in table.columns:
             column.label = csv[i]
diff --git a/influxdb_client/client/flux_table.py b/influxdb_client/client/flux_table.py
index 5ae5490..4232476 100644
--- a/influxdb_client/client/flux_table.py
+++ b/influxdb_client/client/flux_table.py
@@ -101,6 +101,7 @@ class FluxRecord(FluxStructure):
             values = {}
         self.table = table
         self.values = values
+        self.row = []
 
     def get_start(self):
         """Get '_start' value."""


Test patch:
diff --git a/tests/query_output.json b/tests/query_output.json
index 4cbe36d..b60aac0 100644
--- a/tests/query_output.json
+++ b/tests/query_output.json
@@ -78,7 +78,18 @@
           "_time": "2020-02-27T16:20:00+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:20:00+00:00",
+          2.0,
+          "test1"
+        ]
       },
       {
         "table": 0,
@@ -92,7 +103,18 @@
           "_time": "2020-02-27T16:21:40+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:21:40+00:00",
+          2.0,
+          "test1"
+        ]
       },
       {
         "table": 0,
@@ -106,7 +128,18 @@
           "_time": "2020-02-27T16:23:20+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:23:20+00:00",
+          2.0,
+          "test1"
+        ]
       },
       {
         "table": 0,
@@ -120,7 +153,18 @@
           "_time": "2020-02-27T16:25:00+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:25:00+00:00",
+          2.0,
+          "test1"
+        ]
       },
       {
         "table": 0,
@@ -134,7 +178,18 @@
           "_time": "2020-02-27T16:26:40+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:26:40+00:00",
+          2.0,
+          "test1"
+        ]
       },
       {
         "table": 0,
@@ -148,7 +203,18 @@
           "_time": "2020-02-27T16:28:20+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:28:20+00:00",
+          2.0,
+          "test1"
+        ]
       },
       {
         "table": 0,
@@ -162,7 +228,18 @@
           "_time": "2020-02-27T16:30:00+00:00",
           "_value": 2.0,
           "tag": "test1"
-        }
+        },
+        "row": [
+          "_result",
+          0,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:30:00+00:00",
+          2.0,
+          "test1"
+        ]
       }
     ]
   },
@@ -245,7 +322,18 @@
           "_time": "2020-02-27T16:20:00+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:20:00+00:00",
+          2.0,
+          "test2"
+        ]
       },
       {
         "table": 1,
@@ -259,7 +347,18 @@
           "_time": "2020-02-27T16:21:40+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:21:40+00:00",
+          2.0,
+          "test2"
+        ]
       },
       {
         "table": 1,
@@ -273,7 +372,18 @@
           "_time": "2020-02-27T16:23:20+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:23:20+00:00",
+          2.0,
+          "test2"
+        ]
       },
       {
         "table": 1,
@@ -287,7 +397,18 @@
           "_time": "2020-02-27T16:25:00+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:25:00+00:00",
+          2.0,
+          "test2"
+        ]
       },
       {
         "table": 1,
@@ -301,7 +422,18 @@
           "_time": "2020-02-27T16:26:40+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:26:40+00:00",
+          2.0,
+          "test2"
+        ]
       },
       {
         "table": 1,
@@ -315,7 +447,18 @@
           "_time": "2020-02-27T16:28:20+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:28:20+00:00",
+          2.0,
+          "test2"
+        ]
       },
       {
         "table": 1,
@@ -329,7 +472,18 @@
           "_time": "2020-02-27T16:30:00+00:00",
           "_value": 2.0,
           "tag": "test2"
-        }
+        },
+        "row": [
+          "_result",
+          1,
+          "value",
+          "python_client_test",
+          "2010-02-27T04:48:32.752600+00:00",
+          "2020-02-27T16:48:32.752600+00:00",
+          "2020-02-27T16:30:00+00:00",
+          2.0,
+          "test2"
+        ]
       }
     ]
   }
diff --git a/tests/test_FluxCSVParser.py b/tests/test_FluxCSVParser.py
index b7461f2..3091ff9 100644
--- a/tests/test_FluxCSVParser.py
+++ b/tests/test_FluxCSVParser.py
@@ -3,6 +3,7 @@ import math
 import unittest
 from io import BytesIO
 
+import pytest
 from urllib3 import HTTPResponse
 
 from influxdb_client.client.flux_csv_parser import FluxCsvParser, FluxSerializationMode, FluxQueryException, \
@@ -356,6 +357,26 @@ class FluxCsvParserTest(unittest.TestCase):
         self.assertEqual(['south', 'B', None, 18], parsed[2])
         self.assertEqual(['south', 'D', None, 22], parsed[3])
 
+    def test_parse_duplicate_column_names(self):
+        data = """#datatype,string,long,dateTime:RFC3339,dateTime:RFC3339,dateTime:RFC3339,string,string,double
+#group,false,false,true,true,false,true,true,false
+#default,_result,,,,,,,
+,result,table,_start,_stop,_time,_measurement,location,result
+,,0,2022-09-13T06:14:40.469404272Z,2022-09-13T06:24:40.469404272Z,2022-09-13T06:24:33.746Z,my_measurement,Prague,25.3
+,,0,2022-09-13T06:14:40.469404272Z,2022-09-13T06:24:40.469404272Z,2022-09-13T06:24:39.299Z,my_measurement,Prague,25.3
+,,0,2022-09-13T06:14:40.469404272Z,2022-09-13T06:24:40.469404272Z,2022-09-13T06:24:40.454Z,my_measurement,Prague,25.3
+"""
+        with pytest.warns(UserWarning) as warnings:
+            tables = self._parse_to_tables(data=data)
+        self.assertEqual(1, len(warnings))
+        self.assertEqual(1, tables.__len__())
+        self.assertEqual(8, tables[0].columns.__len__())
+        self.assertEqual(3, tables[0].records.__len__())
+        self.assertEqual(7, tables[0].records[0].values.__len__())
+        self.assertEqual(8, tables[0].records[0].row.__len__())
+        self.assertEqual(25.3, tables[0].records[0].row[7])
+
+
     @staticmethod
     def _parse_to_tables(data: str, serialization_mode=FluxSerializationMode.tables,
                          response_metadata_mode=FluxResponseMetadataMode.full) -> TableList: