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])