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

Added set_records and set_record to update sheet via records.[Issue #677] #1494

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 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
158 changes: 157 additions & 1 deletion gspread/worksheet.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from .cell import Cell
from .exceptions import GSpreadException
from .http_client import HTTPClient, ParamsType
from .urls import WORKSHEET_DRIVE_URL
from .urls import WORKSHEET_DRIVE_URL, SPREADSHEET_VALUES_APPEND_URL
from .utils import (
DateTimeOption,
Dimension,
Expand Down Expand Up @@ -197,6 +197,17 @@ def __init__(
# kept for backward compatibility - publicly available
# do not use if possible.
self._spreadsheet = spreadsheet
self._column_headers = []

@property
def column_headers(self) -> List[str]:
if not self._column_headers:
self._column_headers = self.row_values(1)
return self._column_headers

@column_headers.setter
def column_headers(self, value: List[str]) -> None:
self._column_headers = value
muddi900 marked this conversation as resolved.
Show resolved Hide resolved

def __repr__(self) -> str:
return "<{} {} id:{}>".format(
Expand Down Expand Up @@ -566,6 +577,7 @@ def get_all_records(
return []

keys = entire_sheet[head - 1]
self.column_headers = keys
values = entire_sheet[head:]

if expected_headers is None:
Expand Down Expand Up @@ -606,6 +618,150 @@ def get_all_records(

return to_records(keys, values)

def append_records(
self,
rows: List[Dict[str, Any]],
ignore_extra_headers: bool = False,
default_blank: Any = "",
) -> None:
"""Appends records as rows to your data range.

:param rows: A list of dictionaries, where each dictionary represents a row to be appended.
The keys of the dictionaries should correspond to the column headers of the spreadsheet.
:type rows: List[Dict[str, Any]]
:param ignore_extra_headers: If True, extra headers found in the data set will be ignored. Otherwise,
a `GSpreadException` will be raised. Defaults to False.
:type ignore_extra_headers: bool
:param default_blank: The value to use for missing columns in the data. Defaults to an empty string.
:type default_blank: Any

:raises GSpreadException: If extra headers are found in the data set and `ignore_extra_headers` is False.
"""

cols = self.column_headers
insert_rows = []
for row in rows:
if not set(row).issubset(set(cols)) and not ignore_extra_headers:
raise GSpreadException("Extra headers found in the data set")

insert_row = []
for col in cols:
insert_row.append(row.get(col, default_blank))
insert_rows.append(insert_row)

self.append_rows(
insert_rows,
value_input_option=ValueInputOption.user_entered,
muddi900 marked this conversation as resolved.
Show resolved Hide resolved
)

def append_record(
self,
row: Dict[str, Any],
ignore_extra_headers: bool = False,
default_blank: Any = "",
) -> None:
"""Appends a dict as a row to your data range.

:param row: A dict. The keys of the dict should
correspond to the column headers of the spreadsheet.
:type row: Dict[str, Any]
:param ignore_extra_headers: If True, extra headers found in the data set will be ignored. Otherwise,
a `GSpreadException` will be raised. Defaults to False.
:type ignore_extra_headers: bool
:param default_blank: The value to use for missing columns in the data. Defaults to an empty string.
:type default_blank: Any

:raises GSpreadException: If extra headers are found in the data set and `ignore_extra_headers` is False.
"""

self.append_records(
[row],
ignore_extra_headers=ignore_extra_headers,
default_blank=default_blank,
)

def insert_records(
self,
rows: List[Dict[str, Any]],
ignore_extra_headers: bool = False,
default_blank: Any = "",
insert_row: int = 2,
) -> None:
"""Insert records as rows to your data range at the stated row.

:param rows: A list of dictionaries, where
each dictionary represents a row to be inserted.
The keys of the dictionaries should correspond
to the column headers of the spreadsheet.
:type rows: List[Dict[str, Any]]
:param ignore_extra_headers: If True, extra headers found
in the data set will be ignored. Otherwise,
a `GSpreadException` will be raised. Defaults to False.
:type ignore_extra_headers: bool
:param default_blank: The value to use for missing
columns in the data. Defaults to an empty string.
:type default_blank: Any
:param insert_row: Row number(1-indexed) where the
data would be inserted. Defaults to 2.
:type insert_row: int

:raises GSpreadException: If extra headers are found
in the data set and `ignore_extra_headers` is False.
"""

cols = self.column_headers
insert_rows = []
for row in rows:
if not set(row).issubset(set(cols)) and not ignore_extra_headers:
raise GSpreadException("Extra headers found in the data set")

ins_row = []
for col in cols:
ins_row.append(row.get(col, default_blank))
insert_rows.append(ins_row)

self.insert_rows(
insert_rows,
row=insert_row,
value_input_option=ValueInputOption.user_entered,
muddi900 marked this conversation as resolved.
Show resolved Hide resolved
)

def insert_record(
self,
row: Dict[str, Any],
ignore_extra_headers: bool = False,
default_blank: Any = "",
insert_row: int = 2,
) -> None:
"""Insert a dict as rows to your data range at the stated row.

:param row: A dict, where
each dictionary represents a row to be inserted.
The keys of the dictionaries should correspond
to the column headers of the spreadsheet.
:type rows: List[Dict[str, Any]]
:param ignore_extra_headers: If True, extra headers found
in the data set will be ignored. Otherwise,
a `GSpreadException` will be raised. Defaults to False.
:type ignore_extra_headers: bool
:param default_blank: The value to use for missing
columns in the data. Defaults to an empty string.
:type default_blank: Any
:param insert_row: Row number(1-indexed) where the
data would be inserted. Defaults to 2.
:type insert_row: int

:raises GSpreadException: If extra headers are found
in the data set and `ignore_extra_headers` is False.
"""

self.insert_records(
[row],
ignore_extra_headers=ignore_extra_headers,
insert_row=insert_row,
default_blank=default_blank,
)

def get_all_cells(self) -> List[Cell]:
"""Returns a list of all `Cell` of the current sheet."""

Expand Down
88 changes: 88 additions & 0 deletions tests/worksheet_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

from .conftest import I18N_STR, GspreadTest

load_dotenv()


muddi900 marked this conversation as resolved.
Show resolved Hide resolved
class WorksheetTest(GspreadTest):
"""Test for gspread.Worksheet."""
Expand Down Expand Up @@ -1671,6 +1673,92 @@ def test_batch_clear(self):
self.assertListEqual(w.get_values("A1:B1"), [[]])
self.assertListEqual(w.get_values("C2:E2"), [[]])

@pytest.mark.vcr()
def test_append_records(self):

w = self.spreadsheet.sheet1
values = [
["header1", "header2"],
["value1", "value2"],
]
w.update(values, "A1:B2")
self.assertEqual(
w.get_all_values(value_render_option=utils.ValueRenderOption.unformatted),
values,
)
update_values = [
{"header1": "new value1", "header2": "new value2"},
{"header1": "new value3"},
]
w.append_records(update_values)
new_values = [
*values,
["new value1", "new value2"],
["new value3", ""],
]
self.assertEqual(
w.get_all_values(value_render_option=utils.ValueRenderOption.unformatted),
new_values,
)
with pytest.raises(GSpreadException):
w.append_record({"header1": "error value1", "location": "error value2"})

w.append_record(
{
"header1": "single entry1",
"status": "active",
"header2": "single entry2",
},
ignore_extra_headers=True,
)
self.assertEqual(
w.get_all_values(value_render_option=utils.ValueRenderOption.unformatted),
[
*new_values,
["single entry1", "single entry2"],
],
)

@pytest.mark.vcr()
def test_insert_rows(self):
w = self.spreadsheet.sheet1
values = [
["header1", "header2"],
["value1", "value2"],
]
w.update(values, "A1:B2")
new_values = [values[0], ["value3", "value4"], ["", "value5"], *values[1:]]
self.assertEqual(
w.get_all_values(value_render_option=utils.ValueRenderOption.unformatted),
values,
)

w.insert_records(
[
{"header1": "value3", "header2": "value4"},
{"header2": "value5"},
]
)

self.assertEqual(
w.get_all_values(value_render_option=utils.ValueRenderOption.unformatted),
new_values,
)

with pytest.raises(GSpreadException):
w.insert_record({"header1": "error value1", "location": "error value2"})

w.insert_record(
{"header4": "ignore value", "header1": "value6", "header2": "value7"},
insert_row=5,
ignore_extra_headers=True,
)

self.assertEqual(
w.get_all_values(),
[*new_values, ["value6", "value7"]],
)

@pytest.mark.vcr()
def test_group_columns(self):
w = self.sheet
Expand Down
Loading