Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/add utils get records #1378

Merged
merged 4 commits into from
Jan 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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])