From 61a054cb136d6f0bbc58f0f03706d8b5030efa14 Mon Sep 17 00:00:00 2001 From: Alexandre Lavigne Date: Mon, 15 Jan 2024 23:38:31 +0100 Subject: [PATCH] Add util function `to_record` to build records Add a new util function that helps building record values. Records are list of dictionaries, each dictionary has the same set of keys, they are set from the given list of headers. Each key is associated to a value from the given matrix of values. They are as many dictionaries as they are lines in the matrix. Each line in the matrix will be associated to a dictionary, each key is associated to a value from that line in the given order. Example: Headers: A B C Values: 1 2 3 7 8 9 Result: [ { A: 1, B: 2, C: 3}, {A: 7, B: 8, C: 9}] closes #1367 Signed-off-by: Alexandre Lavigne --- gspread/utils.py | 10 ++++++++++ gspread/worksheet.py | 9 ++++----- tests/utils_test.py | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/gspread/utils.py b/gspread/utils.py index 880991c92..3e3a055dd 100644 --- a/gspread/utils.py +++ b/gspread/utils.py @@ -878,6 +878,16 @@ def get_a1_from_absolute_range(range_name: str) -> str: return range_name +def to_records( + headers: Iterable[Any] = [], values: Iterable[Iterable[Any]] = [[]] +) -> List[Dict[str, Union[str, int, float]]]: + """Builds the list of dictionaries, all of them have the headers sequence as keys set, + each key is associated to the corresponding value for the same index in the sub-list. + There are as many dictionaries as they are entry in the of given values.""" + + return [dict(zip(headers, row)) for row in values] + + # SHOULD NOT BE NEEDED UNTIL NEXT MAJOR VERSION # def deprecation_warning(version: str, msg: str) -> None: # """Emit a deprecation warning. diff --git a/gspread/worksheet.py b/gspread/worksheet.py index 8b4f551fd..e2de7e361 100644 --- a/gspread/worksheet.py +++ b/gspread/worksheet.py @@ -55,6 +55,7 @@ is_full_a1_notation, numericise_all, rowcol_to_a1, + to_records, ) CellFormat = TypedDict( @@ -498,10 +499,10 @@ def get_all_records( # Read all rows from the sheet >>> worksheet.get_all_records() - { + [ {"A1": "A6", "B2": "B7", "C3": "C8"}, {"A1": "A11", "B2": "B12", "C3": "C13"} - } + ] """ entire_sheet = self.get( value_render_option=value_render_option, @@ -551,9 +552,7 @@ def get_all_records( for row in values ] - formatted_records = [dict(zip(keys, row)) for row in values] - - return formatted_records + return to_records(keys, values) def get_all_cells(self) -> List[Cell]: """Returns a list of all `Cell` of the current sheet.""" diff --git a/tests/utils_test.py b/tests/utils_test.py index 14144fa6f..187e74eb0 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -465,3 +465,41 @@ def test_get_a1_from_absolute_range(self): self.assertEqual(utils.get_a1_from_absolute_range("A1:B2"), "A1:B2") self.assertEqual(utils.get_a1_from_absolute_range("A1:B"), "A1:B") self.assertEqual(utils.get_a1_from_absolute_range("2"), "2") + + def test_to_records_empty_args(self): + """Test to_records with empty args""" + + self.assertListEqual(utils.to_records([], []), []) + self.assertListEqual(utils.to_records([], [[]]), [{}]) + self.assertListEqual(utils.to_records(["a1", "b2"], []), []) + self.assertListEqual(utils.to_records(["a1", "b2"], [[]]), [{}]) + self.assertListEqual(utils.to_records([], [["a1"]]), [{}]) + self.assertListEqual(utils.to_records([], [["a1"], ["a2"]]), [{}, {}]) + self.assertListEqual(utils.to_records([], [[], ["a2"]]), [{}, {}]) + + def test_to_records(self): + """Test to_records with values""" + + headers = ["HA", "HB", "HC"] + values = [["A2", "B2", "C2"], ["A3", "B3"], ["", "B4", "C4"]] + + records = utils.to_records(headers, values) + + self.assertEqual(len(values), len(records)) + + for i in range(len(records)): + record = records[i] + keys = record.keys() + + # Some rows have shorter values ("A3", "B3") + # so the list of keys is smaller + # but never bigger than the list of headers + self.assertLessEqual(len(keys), len(headers)) + + # Each resulting key must be part of the given header + for key in keys: + self.assertIn(key, headers) + + # given key are unordered + # but they must match a value from the given input values + self.assertIn(record[key], values[i])