diff --git a/README.md b/README.md index c84e66086..caf3b7ac9 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,20 @@ file.sheet1.update_tab_color(tab_color) + age = spreadsheet.get_lastUpdateTime() ``` +### Replace method `Worksheet.get_records` + +In v6 you can now only get *all* sheet records, using `Worksheet.get_all_records()`. The method `Worksheet.get_records()` has been removed. You can get some records using your own fetches and combine them with `gspread.utils.to_records()`. + +```diff ++ from gspread import utils + all_records = spreadsheet.get_all_records(head=1) +- some_records = spreadsheet.get_all_records(head=1, first_index=6, last_index=9) +- some_records = spreadsheet.get_records(head=1, first_index=6, last_index=9) ++ header = spreadsheet.get("1:1")[0] ++ cells = spreadsheet.get("6:9") ++ some_records = utils.to_records(header, cells) +``` + ### Silence warnings In version 5 there are many warnings to mark deprecated feature/functions/methods. diff --git a/gspread/utils.py b/gspread/utils.py index 880991c92..a22b232d2 100644 --- a/gspread/utils.py +++ b/gspread/utils.py @@ -878,6 +878,35 @@ 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 each list from + the matrix ``values``. + There are as many dictionaries as they are entry in the list of given values. + + :param list: headers the key set for all dictionaries + :param list: values a matrix of values + + Examples:: + + >>> to_records(["name", "City"], [["Spiderman", "NY"], ["Batman", "Gotham"]]) + [ + { + "Name": "Spiderman", + "City": "NY", + }, + { + "Name": "Batman", + "City": "Gotham", + }, + ] + """ + + 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..a6d6bc192 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( @@ -462,6 +463,15 @@ def get_all_records( dictionaries holding the contents of subsequent rows of cells as values. + This method uses the function :func:`gspread.utils.to_records` to build the resulting + records. It mainly wraps around the function and handle the simplest use case + using a header row (default = 1) and the the reste of the entire sheet. + + .. note:: + + for any particular use-case, please get your dataset, your headers + then use the function :func:`gspread.utils.to_records` to build the records. + Cell values are numericised (strings that can be read as ints or floats are converted), unless specified in numericise_ignore @@ -498,10 +508,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 +561,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])