Skip to content

Commit

Permalink
Merge pull request #1378 from burnash/feature/add_utils_get_records
Browse files Browse the repository at this point in the history
feature/add utils get records
  • Loading branch information
lavigne958 authored Jan 20, 2024
2 parents 51f9f25 + 8465fce commit 406e0a1
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 5 deletions.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
29 changes: 29 additions & 0 deletions gspread/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 13 additions & 5 deletions gspread/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
is_full_a1_notation,
numericise_all,
rowcol_to_a1,
to_records,
)

CellFormat = TypedDict(
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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."""
Expand Down
38 changes: 38 additions & 0 deletions tests/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])

0 comments on commit 406e0a1

Please sign in to comment.