python-google-sheets 1.1.0__tar.gz → 1.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {python_google_sheets-1.1.0/python_google_sheets.egg-info → python_google_sheets-1.2.0}/PKG-INFO +46 -19
  2. python_google_sheets-1.1.0/PKG-INFO → python_google_sheets-1.2.0/README.md +39 -35
  3. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/api_request.py +12 -10
  4. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/google_sheets.py +52 -40
  5. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/__init__.py +3 -0
  6. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/general_models.py +12 -0
  7. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/pyproject.toml +11 -2
  8. python_google_sheets-1.1.0/README.md → python_google_sheets-1.2.0/python_google_sheets.egg-info/PKG-INFO +62 -19
  9. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/LICENSE +0 -0
  10. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/__init__.py +0 -0
  11. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/conditional_format_rule.py +0 -0
  12. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/dimension.py +0 -0
  13. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/merge_cells.py +0 -0
  14. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/spreadsheet.py +0 -0
  15. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/update_cells.py +0 -0
  16. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/spreadsheet_requests/update_sheet_properties.py +0 -0
  17. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/styles.py +0 -0
  18. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/google_sheets/utils.py +0 -0
  19. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/python_google_sheets.egg-info/SOURCES.txt +0 -0
  20. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/python_google_sheets.egg-info/dependency_links.txt +0 -0
  21. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/python_google_sheets.egg-info/requires.txt +0 -0
  22. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/python_google_sheets.egg-info/top_level.txt +0 -0
  23. {python_google_sheets-1.1.0 → python_google_sheets-1.2.0}/setup.cfg +0 -0
@@ -1,10 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-google-sheets
3
- Version: 1.1.0
3
+ Version: 1.2.0
4
4
  Summary: A lightweight and efficient Python wrapper for the Google Sheets API v4
5
5
  Author-email: Timofey Egorov <timegorr@gmail.com>
6
6
  License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Timofey28/python-google-sheets
8
+ Project-URL: Issues, https://github.com/Timofey28/python-google-sheets/issues
9
+ Keywords: spreadsheets,google-spreadsheets,google-sheets,api,wrapper,python,pydantic
7
10
  Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
8
15
  Classifier: Operating System :: OS Independent
9
16
  Requires-Python: >=3.11
10
17
  Description-Content-Type: text/markdown
@@ -81,7 +88,7 @@ spreadsheet_id, url = GoogleSheets.create_spreadsheet(
81
88
  )
82
89
  ```
83
90
 
84
- ### `update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], service: 'Resource') -> None`
91
+ ### `update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], *, service: 'Resource') -> None`
85
92
  Execute a `batchUpdate` with one or more request objects.
86
93
 
87
94
  ```python
@@ -93,7 +100,7 @@ requests = [
93
100
  GoogleSheets.update_spreadsheet(spreadsheet_id, requests, service)
94
101
  ```
95
102
 
96
- ### `copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, service: 'Resource') -> SheetProperties`
103
+ ### `copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, *, service: 'Resource') -> SheetProperties`
97
104
  Copy a sheet from one spreadsheet into another.
98
105
 
99
106
  ```python
@@ -113,19 +120,39 @@ spreadsheet = GoogleSheets.get_spreadsheet(spreadsheet_id, service)
113
120
  print([sheet.properties.title for sheet in spreadsheet.sheets])
114
121
  ```
115
122
 
116
- ### `get_spreadsheet_range_values(spreadsheet_id: str, sheets: list[str | int], ranges: list[list[str]], service: 'Resource') -> list[list[SimpleType] | list[list[SimpleType]]]`
117
- Read values from multiple ranges across one or more sheets.
123
+ ### `get_spreadsheet_range_values(spreadsheet_id: str, sheets: list[str | int] | str | int, ranges: list[list[str]] | list[str] | str, *, by_columns: bool = False, service: 'Resource') -> list[list[RangeData]] | None:`
124
+ Reads values from multiple ranges across multiple sheets. Returns a matrix of values (RangeData - list[list[SimpleType]]) for each range of each sheet. Returns None if an error occurs.
125
+
126
+ Code from this example returns one range from sheet with id `1601337967` and three ranges from sheet `Summary` (assuming that these sheets exist in the spreadsheet). You can mix and match sheet names and ids as needed.
118
127
 
119
128
  ```python
120
129
  values = GoogleSheets.get_spreadsheet_range_values(
121
- spreadsheet_id=spreadsheet_id,
122
- sheets=[0, 'Summary'],
123
- ranges=[['A2:C8'], ['B2:B12']],
124
- service=service,
130
+ spreadsheet_id=SPREADSHEET_ID,
131
+ sheets=[1601337967, 'Summary'],
132
+ ranges=[['A2:C100'], ['A1:E10', 'F1:F10', 'H1']],
133
+ service=service
125
134
  )
126
- print(values)
127
135
  ```
128
136
 
137
+ Next two examples return the same data (assuming that sheet `Sheet1` exists and has id `0`). With `by_columns=True` the values are grouped by columns instead of rows.
138
+ ```python
139
+ values = GoogleSheets.get_spreadsheet_range_values(
140
+ SPREADSHEET_ID,
141
+ sheets='Sheet1',
142
+ ranges=['A1:E5'],
143
+ by_columns=True,
144
+ service=service
145
+ )
146
+ ```
147
+ ```python
148
+ values = GoogleSheets.get_spreadsheet_range_values(
149
+ SPREADSHEET_ID,
150
+ sheets=0,
151
+ ranges='A1:E5',
152
+ by_columns=True,
153
+ service=service
154
+ )
155
+ ```
129
156
  ---
130
157
 
131
158
  ## `ApiRequest` methods for `batchUpdate`
@@ -154,7 +181,7 @@ req = ApiRequest.update_cells(
154
181
  ---
155
182
  ### Conditional formatting
156
183
 
157
- ### `add_boolean_format_rule(*, sheet_id: int, ranges: list[str], condition_type: ConditionType, condition_values: list[ConditionValue] = None, cell_format: CellFormat) -> dict`
184
+ ### `add_boolean_format_rule(sheet_id: int, ranges: list[str], condition_type: ConditionType, *, condition_values: list[ConditionValue] = None, cell_format: CellFormat) -> dict`
158
185
  Add a rule (for example, highlight values greater than `100`).
159
186
 
160
187
  ```python
@@ -172,7 +199,7 @@ req = ApiRequest.add_boolean_format_rule(
172
199
  )
173
200
  ```
174
201
 
175
- ### `GradientRule.add(*, sheet_id: int, ranges: list[str], interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue], interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle]) -> dict`
202
+ ### `GradientRule.add(sheet_id: int, *, ranges: list[str], interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue], interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle]) -> dict`
176
203
  Add a custom color scale with 2 or 3 interpolation points.
177
204
 
178
205
  ```python
@@ -194,7 +221,7 @@ req = ApiRequest.GradientRule.add(
194
221
  )
195
222
  ```
196
223
 
197
- ### `GradientRule.add_preset(*, sheet_id: int, ranges: list[str], preset: Preset) -> dict`
224
+ ### `GradientRule.add_preset(sheet_id: int, ranges: list[str], preset: Preset) -> dict`
198
225
  Add a gradient rule from a built-in preset.
199
226
 
200
227
  ```python
@@ -205,14 +232,14 @@ req = ApiRequest.GradientRule.add_preset(
205
232
  )
206
233
  ```
207
234
 
208
- ### `delete_conditional_format_rule(*, sheet_id: int, index: int) -> dict`
235
+ ### `delete_conditional_format_rule(sheet_id: int, *, index: int) -> dict`
209
236
  Delete a conditional format rule by index.
210
237
 
211
238
  ```python
212
239
  req = ApiRequest.delete_conditional_format_rule(sheet_id=0, index=0)
213
240
  ```
214
241
 
215
- ### `update_conditional_format_rule(*, sheet_id: int, index: int, rule: ConditionalFormatRule) -> dict`
242
+ ### `update_conditional_format_rule(sheet_id: int, *, index: int, rule: ConditionalFormatRule) -> dict`
216
243
  Replace an existing conditional format rule by index.
217
244
 
218
245
  ```python
@@ -300,7 +327,7 @@ Delete a sheet.
300
327
  req = ApiRequest.delete_sheet(sheet_id=3)
301
328
  ```
302
329
 
303
- ### `add_sheet(*, sheet_id: int = None, title: str = None, index: int = None, hidden: bool = None, row_count: int = None, column_count: int = None, frozen_row_count: int = None, frozen_column_count: int = None, hide_grid_lines: bool = None) -> dict`
330
+ ### `add_sheet(sheet_id: int = None, *, title: str = None, index: int = None, hidden: bool = None, row_count: int = None, column_count: int = None, frozen_row_count: int = None, frozen_column_count: int = None, hide_grid_lines: bool = None) -> dict`
304
331
  Create a new sheet with optional properties.
305
332
 
306
333
  ```python
@@ -322,7 +349,7 @@ Merge cells in a range.
322
349
  req = ApiRequest.merge_cells(sheet_id=0, range_='A1:C1')
323
350
  ```
324
351
 
325
- ### `unmerge_cells(sheet_id: int, range_: str = None, start_row: int = None, end_row: int = None, start_column: int | str = None, end_column: int | str = None) -> dict`
352
+ ### `unmerge_cells(sheet_id: int, *, range_: str = None, start_row: int = None, end_row: int = None, start_column: int | str = None, end_column: int | str = None) -> dict`
326
353
  Unmerge cells in a range (or by explicit indexes).
327
354
 
328
355
  ```python
@@ -339,14 +366,14 @@ req = ApiRequest.freeze(sheet_id=0, rows=1, columns=1)
339
366
  ---
340
367
  ### Rows and columns
341
368
 
342
- ### `insert_rows(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict`
369
+ ### `insert_rows(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict`
343
370
  Insert one or more rows (Indexes are zero-based and inclusive [start_index, end_index]).
344
371
 
345
372
  ```python
346
373
  req = ApiRequest.insert_rows(sheet_id=0, start_index=5, end_index=9)
347
374
  ```
348
375
 
349
- ### `insert_columns(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict`
376
+ ### `insert_columns(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict`
350
377
  Insert columns (Indexes are zero-based and inclusive [start_index, end_index]).
351
378
 
352
379
  ```python
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: python-google-sheets
3
- Version: 1.1.0
4
- Summary: A lightweight and efficient Python wrapper for the Google Sheets API v4
5
- Author-email: Timofey Egorov <timegorr@gmail.com>
6
- License-Expression: MIT
7
- Classifier: Programming Language :: Python :: 3
8
- Classifier: Operating System :: OS Independent
9
- Requires-Python: >=3.11
10
- Description-Content-Type: text/markdown
11
- License-File: LICENSE
12
- Requires-Dist: google-auth
13
- Requires-Dist: google-api-python-client
14
- Requires-Dist: pydantic>=2.0.0
15
- Dynamic: license-file
16
-
17
1
  # python-google-sheets
18
2
 
19
3
  A lightweight and efficient Python wrapper for the Google Sheets API v4, leveraging `service account` credentials for seamless authentication.
@@ -81,7 +65,7 @@ spreadsheet_id, url = GoogleSheets.create_spreadsheet(
81
65
  )
82
66
  ```
83
67
 
84
- ### `update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], service: 'Resource') -> None`
68
+ ### `update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], *, service: 'Resource') -> None`
85
69
  Execute a `batchUpdate` with one or more request objects.
86
70
 
87
71
  ```python
@@ -93,7 +77,7 @@ requests = [
93
77
  GoogleSheets.update_spreadsheet(spreadsheet_id, requests, service)
94
78
  ```
95
79
 
96
- ### `copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, service: 'Resource') -> SheetProperties`
80
+ ### `copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, *, service: 'Resource') -> SheetProperties`
97
81
  Copy a sheet from one spreadsheet into another.
98
82
 
99
83
  ```python
@@ -113,19 +97,39 @@ spreadsheet = GoogleSheets.get_spreadsheet(spreadsheet_id, service)
113
97
  print([sheet.properties.title for sheet in spreadsheet.sheets])
114
98
  ```
115
99
 
116
- ### `get_spreadsheet_range_values(spreadsheet_id: str, sheets: list[str | int], ranges: list[list[str]], service: 'Resource') -> list[list[SimpleType] | list[list[SimpleType]]]`
117
- Read values from multiple ranges across one or more sheets.
100
+ ### `get_spreadsheet_range_values(spreadsheet_id: str, sheets: list[str | int] | str | int, ranges: list[list[str]] | list[str] | str, *, by_columns: bool = False, service: 'Resource') -> list[list[RangeData]] | None:`
101
+ Reads values from multiple ranges across multiple sheets. Returns a matrix of values (RangeData - list[list[SimpleType]]) for each range of each sheet. Returns None if an error occurs.
102
+
103
+ Code from this example returns one range from sheet with id `1601337967` and three ranges from sheet `Summary` (assuming that these sheets exist in the spreadsheet). You can mix and match sheet names and ids as needed.
118
104
 
119
105
  ```python
120
106
  values = GoogleSheets.get_spreadsheet_range_values(
121
- spreadsheet_id=spreadsheet_id,
122
- sheets=[0, 'Summary'],
123
- ranges=[['A2:C8'], ['B2:B12']],
124
- service=service,
107
+ spreadsheet_id=SPREADSHEET_ID,
108
+ sheets=[1601337967, 'Summary'],
109
+ ranges=[['A2:C100'], ['A1:E10', 'F1:F10', 'H1']],
110
+ service=service
125
111
  )
126
- print(values)
127
112
  ```
128
113
 
114
+ Next two examples return the same data (assuming that sheet `Sheet1` exists and has id `0`). With `by_columns=True` the values are grouped by columns instead of rows.
115
+ ```python
116
+ values = GoogleSheets.get_spreadsheet_range_values(
117
+ SPREADSHEET_ID,
118
+ sheets='Sheet1',
119
+ ranges=['A1:E5'],
120
+ by_columns=True,
121
+ service=service
122
+ )
123
+ ```
124
+ ```python
125
+ values = GoogleSheets.get_spreadsheet_range_values(
126
+ SPREADSHEET_ID,
127
+ sheets=0,
128
+ ranges='A1:E5',
129
+ by_columns=True,
130
+ service=service
131
+ )
132
+ ```
129
133
  ---
130
134
 
131
135
  ## `ApiRequest` methods for `batchUpdate`
@@ -154,7 +158,7 @@ req = ApiRequest.update_cells(
154
158
  ---
155
159
  ### Conditional formatting
156
160
 
157
- ### `add_boolean_format_rule(*, sheet_id: int, ranges: list[str], condition_type: ConditionType, condition_values: list[ConditionValue] = None, cell_format: CellFormat) -> dict`
161
+ ### `add_boolean_format_rule(sheet_id: int, ranges: list[str], condition_type: ConditionType, *, condition_values: list[ConditionValue] = None, cell_format: CellFormat) -> dict`
158
162
  Add a rule (for example, highlight values greater than `100`).
159
163
 
160
164
  ```python
@@ -172,7 +176,7 @@ req = ApiRequest.add_boolean_format_rule(
172
176
  )
173
177
  ```
174
178
 
175
- ### `GradientRule.add(*, sheet_id: int, ranges: list[str], interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue], interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle]) -> dict`
179
+ ### `GradientRule.add(sheet_id: int, *, ranges: list[str], interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue], interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle]) -> dict`
176
180
  Add a custom color scale with 2 or 3 interpolation points.
177
181
 
178
182
  ```python
@@ -194,7 +198,7 @@ req = ApiRequest.GradientRule.add(
194
198
  )
195
199
  ```
196
200
 
197
- ### `GradientRule.add_preset(*, sheet_id: int, ranges: list[str], preset: Preset) -> dict`
201
+ ### `GradientRule.add_preset(sheet_id: int, ranges: list[str], preset: Preset) -> dict`
198
202
  Add a gradient rule from a built-in preset.
199
203
 
200
204
  ```python
@@ -205,14 +209,14 @@ req = ApiRequest.GradientRule.add_preset(
205
209
  )
206
210
  ```
207
211
 
208
- ### `delete_conditional_format_rule(*, sheet_id: int, index: int) -> dict`
212
+ ### `delete_conditional_format_rule(sheet_id: int, *, index: int) -> dict`
209
213
  Delete a conditional format rule by index.
210
214
 
211
215
  ```python
212
216
  req = ApiRequest.delete_conditional_format_rule(sheet_id=0, index=0)
213
217
  ```
214
218
 
215
- ### `update_conditional_format_rule(*, sheet_id: int, index: int, rule: ConditionalFormatRule) -> dict`
219
+ ### `update_conditional_format_rule(sheet_id: int, *, index: int, rule: ConditionalFormatRule) -> dict`
216
220
  Replace an existing conditional format rule by index.
217
221
 
218
222
  ```python
@@ -300,7 +304,7 @@ Delete a sheet.
300
304
  req = ApiRequest.delete_sheet(sheet_id=3)
301
305
  ```
302
306
 
303
- ### `add_sheet(*, sheet_id: int = None, title: str = None, index: int = None, hidden: bool = None, row_count: int = None, column_count: int = None, frozen_row_count: int = None, frozen_column_count: int = None, hide_grid_lines: bool = None) -> dict`
307
+ ### `add_sheet(sheet_id: int = None, *, title: str = None, index: int = None, hidden: bool = None, row_count: int = None, column_count: int = None, frozen_row_count: int = None, frozen_column_count: int = None, hide_grid_lines: bool = None) -> dict`
304
308
  Create a new sheet with optional properties.
305
309
 
306
310
  ```python
@@ -322,7 +326,7 @@ Merge cells in a range.
322
326
  req = ApiRequest.merge_cells(sheet_id=0, range_='A1:C1')
323
327
  ```
324
328
 
325
- ### `unmerge_cells(sheet_id: int, range_: str = None, start_row: int = None, end_row: int = None, start_column: int | str = None, end_column: int | str = None) -> dict`
329
+ ### `unmerge_cells(sheet_id: int, *, range_: str = None, start_row: int = None, end_row: int = None, start_column: int | str = None, end_column: int | str = None) -> dict`
326
330
  Unmerge cells in a range (or by explicit indexes).
327
331
 
328
332
  ```python
@@ -339,14 +343,14 @@ req = ApiRequest.freeze(sheet_id=0, rows=1, columns=1)
339
343
  ---
340
344
  ### Rows and columns
341
345
 
342
- ### `insert_rows(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict`
346
+ ### `insert_rows(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict`
343
347
  Insert one or more rows (Indexes are zero-based and inclusive [start_index, end_index]).
344
348
 
345
349
  ```python
346
350
  req = ApiRequest.insert_rows(sheet_id=0, start_index=5, end_index=9)
347
351
  ```
348
352
 
349
- ### `insert_columns(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict`
353
+ ### `insert_columns(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict`
350
354
  Insert columns (Indexes are zero-based and inclusive [start_index, end_index]).
351
355
 
352
356
  ```python
@@ -505,4 +509,4 @@ GoogleSheets.update_spreadsheet(spreadsheet_id, requests, service)
505
509
  If you have any questions, encounter issues, or want to suggest improvements:
506
510
 
507
511
  - **Bug Reports & Feature Requests:** Please use [GitHub Issues](https://github.com/Timofey28/python-google-sheets/issues) to report bugs or submit ideas.
508
- - **Questions:** For general inquiries or help with integration, feel free to reach out via email at [timegorr@gmail.com](mailto:timegorr@gmail.com).
512
+ - **Questions:** For general inquiries or help with integration, feel free to reach out via email at [timegorr@gmail.com](mailto:timegorr@gmail.com).
@@ -112,10 +112,10 @@ class ApiRequest:
112
112
 
113
113
  @staticmethod
114
114
  def add_boolean_format_rule(
115
- *,
116
115
  sheet_id: int,
117
116
  ranges: list[str],
118
117
  condition_type: ConditionType,
118
+ *,
119
119
  condition_values: list[ConditionValue] = None,
120
120
  cell_format: CellFormat,
121
121
  ) -> dict:
@@ -143,8 +143,8 @@ class ApiRequest:
143
143
 
144
144
  @staticmethod
145
145
  def add(
146
- *,
147
146
  sheet_id: int,
147
+ *,
148
148
  ranges: list[str],
149
149
  interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue],
150
150
  interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle],
@@ -210,10 +210,11 @@ class ApiRequest:
210
210
  RED_YELLOW_GREEN_PERCENT = 'RED_YELLOW_GREEN_PERCENT'
211
211
 
212
212
  @staticmethod
213
- def add_preset(*, sheet_id: int, ranges: list[str], preset: Preset) -> dict:
213
+ def add_preset(sheet_id: int, ranges: list[str], preset: Preset) -> dict:
214
214
  grid_ranges = [GridRange(sheet_id=sheet_id, **ApiRequest._split_excel_range(range_, return_as_dict=True)) for range_ in ranges]
215
215
  AGP = ApiRequest.GradientRule.Preset
216
216
 
217
+ # Two interpolation points presets
217
218
  if preset in (AGP.WHITE_GREEN, AGP.WHITE_YELLOW, AGP.WHITE_RED, AGP.GREEN_WHITE, AGP.YELLOW_WHITE, AGP.RED_WHITE):
218
219
  if preset == AGP.WHITE_YELLOW:
219
220
  minpoint_color_style, maxpoint_color_style = Color_.Basic.WHITE, Color_.ConditionalFormatting.YELLOW
@@ -242,7 +243,7 @@ class ApiRequest:
242
243
  )
243
244
  )).dict()
244
245
 
245
- else: # three interpolation points
246
+ else: # Three interpolation points presets
246
247
  if preset in (AGP.RED_WHITE_GREEN_PERCENTILE, AGP.RED_WHITE_GREEN_PERCENT):
247
248
  minpoint_cs, midpoint_cs, maxpoint_cs = Color_.ConditionalFormatting.RED, Color_.Basic.WHITE, Color_.ConditionalFormatting.GREEN
248
249
  midpoint_type = InterpolationPointType.PERCENTILE if preset == AGP.RED_WHITE_GREEN_PERCENTILE else InterpolationPointType.PERCENT
@@ -276,11 +277,11 @@ class ApiRequest:
276
277
  )).dict()
277
278
 
278
279
  @staticmethod
279
- def delete_conditional_format_rule(*, sheet_id: int, index: int) -> dict:
280
+ def delete_conditional_format_rule(sheet_id: int, *, index: int) -> dict:
280
281
  return DeleteConditionalFormatRule(sheet_id=sheet_id, index=index).dict()
281
282
 
282
283
  @staticmethod
283
- def update_conditional_format_rule(*, sheet_id: int, index: int, rule: ConditionalFormatRule) -> dict:
284
+ def update_conditional_format_rule(sheet_id: int, *, index: int, rule: ConditionalFormatRule) -> dict:
284
285
  return UpdateConditionalFormatRule(sheet_id=sheet_id, index=index, rule=rule).dict()
285
286
 
286
287
  @staticmethod
@@ -341,6 +342,7 @@ class ApiRequest:
341
342
  @staticmethod
342
343
  def unmerge_cells(
343
344
  sheet_id: int,
345
+ *,
344
346
  range_: str = None,
345
347
  start_row: int = None,
346
348
  end_row: int = None,
@@ -377,7 +379,7 @@ class ApiRequest:
377
379
  ).dict()
378
380
 
379
381
  @staticmethod
380
- def insert_rows(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict:
382
+ def insert_rows(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict:
381
383
  """
382
384
  Indexes are zero-based and inclusive [start_index, end_index]. If end_index is not specified, then a single
383
385
  row will be inserted at start_index.
@@ -394,7 +396,7 @@ class ApiRequest:
394
396
  ).dict()
395
397
 
396
398
  @staticmethod
397
- def insert_columns(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict:
399
+ def insert_columns(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict:
398
400
  """
399
401
  Indexes are zero-based and inclusive [start_index, end_index]. If end_index is not specified, then a single
400
402
  column will be inserted at start_index.
@@ -549,8 +551,8 @@ class ApiRequest:
549
551
 
550
552
  @staticmethod
551
553
  def add_sheet(
552
- *,
553
554
  sheet_id: int = None,
555
+ *,
554
556
  title: str = None,
555
557
  index: int = None,
556
558
  hidden: bool = None,
@@ -575,7 +577,7 @@ class ApiRequest:
575
577
  )).dict()
576
578
 
577
579
  @staticmethod
578
- def _split_excel_range(range_: str, return_as_dict: bool = False) -> tuple[int, int, int, int] | dict[str, int]:
580
+ def _split_excel_range(range_: str, *, return_as_dict: bool = False) -> tuple[int, int, int, int] | dict[str, int]:
579
581
  if ':' in range_:
580
582
  match = re.match(r'([A-Z]+)(\d+):([A-Z]+)(\d+)$', range_)
581
583
  if not match:
@@ -4,7 +4,7 @@ from google.oauth2.service_account import Credentials
4
4
  from googleapiclient.discovery import build
5
5
  from googleapiclient.errors import HttpError
6
6
 
7
- from .spreadsheet_requests import Spreadsheet, SheetProperties, SimpleType
7
+ from .spreadsheet_requests import Spreadsheet, SheetProperties, RangeData, Dimension, ValueRenderOption, DateTimeRenderOption
8
8
 
9
9
  if TYPE_CHECKING:
10
10
  from googleapiclient.discovery import Resource # noqa
@@ -85,7 +85,7 @@ class GoogleSheets:
85
85
  return spreadsheet_id, f'https://docs.google.com/spreadsheets/d/{spreadsheet_id}'
86
86
 
87
87
  @staticmethod
88
- def update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], service: 'Resource') -> None:
88
+ def update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], *, service: 'Resource') -> None:
89
89
  """
90
90
  Updates Google Sheet with the specified API requests.
91
91
 
@@ -100,7 +100,7 @@ class GoogleSheets:
100
100
  raise e
101
101
 
102
102
  @staticmethod
103
- def copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, service: 'Resource') -> SheetProperties:
103
+ def copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, *, service: 'Resource') -> SheetProperties:
104
104
  request = service.spreadsheets().sheets().copyTo(
105
105
  spreadsheetId=source_spreadsheet_id,
106
106
  sheetId=source_sheet_id,
@@ -118,63 +118,75 @@ class GoogleSheets:
118
118
  @staticmethod
119
119
  def get_spreadsheet_range_values(
120
120
  spreadsheet_id: str,
121
- sheets: list[str | int],
122
- ranges: list[list[str]],
121
+ sheets: list[str | int] | str | int,
122
+ ranges: list[list[str]] | list[str] | str,
123
+ *,
124
+ by_columns: bool = False,
123
125
  service: 'Resource'
124
- ) -> list[list[SimpleType] | list[list[SimpleType]]]:
126
+ ) -> list[list[RangeData]] | None:
125
127
  """
126
128
  Reads values from the specified ranges of the table.
127
129
  IMPORTANT: If the last cells in the range are empty, they will be omitted. If all cells are empty, an empty
128
130
  list will be returned for that range. However, leading empty cells are preserved and will appear in
129
- the result
131
+ the result.
130
132
 
131
133
  Args:
132
- spreadsheet_id (str): ID of the table
133
- sheets (list[str | int]): Name or ID of the sheets to read from
134
- ranges (list[list[str]]): List of ranges from each sheet to read in A1 notation
135
- service (googleapiclient.discovery.Resource): Google Sheets service object
134
+ spreadsheet_id (str): ID of the table.
135
+ sheets (list[str | int] | str | int): Name or ID of the sheet(s).
136
+ ranges (list[list[str]] | list[str] | str): Single range or list of ranges from each sheet in A1 notation.
137
+ by_columns (bool, optional): If True, returns data organized by columns instead of rows. Defaults to False.
138
+ service (googleapiclient.discovery.Resource): Google Sheets service object.
136
139
 
137
140
  Returns:
138
- list[list[SimpleType] | list[list[SimpleType]]]: List of values from the specified ranges. Each range
139
- corresponds to an element in the list. If the range is a single row or a single column, a list of values
140
- is returned Otherwise, a list of lists of values is returned.
141
+ list[list[RangeData]] | None: For each range of each sheet, returns a matrix of values (RangeData - list[list[SimpleType]]). Returns None if an error occurs.
141
142
  """
142
- assert len(sheets) == len(ranges), 'sheets and ranges must have the same length'
143
+ assert \
144
+ ( # sheets: list[str | int], ranges: list[list[str]]
145
+ isinstance(sheets, list) and not isinstance(sheets[0], list) and
146
+ isinstance(ranges, list) and isinstance(ranges[0], list) and not isinstance(ranges[0][0], list) and
147
+ len(sheets) == len(ranges)
148
+ ) or ( # sheets: str | int, ranges: list[str] | str
149
+ (isinstance(sheets, str) or isinstance(sheets, int)) and
150
+ (isinstance(ranges, list) and not isinstance(ranges[0], list) or isinstance(ranges, str))
151
+ ), 'sheets and ranges must be either list[str | int] and list[list[str]] respectively and same size, or str | int and list[str] | str respectively'
152
+
153
+ # Normalize sheets and ranges to list[str | int] and list[list[str]] respectively
154
+ if isinstance(ranges, str):
155
+ ranges = [ranges]
156
+ if isinstance(sheets, str) or isinstance(sheets, int):
157
+ sheets = [sheets]
158
+ ranges = [ranges]
143
159
 
144
160
  ranges_processed = []
161
+ ss = None
145
162
  if any(isinstance(sheet, int) for sheet in sheets):
146
163
  ss = GoogleSheets.get_spreadsheet(spreadsheet_id, service)
147
- for i in range(len(sheets)):
148
- if isinstance(sheets[i], int):
149
- try:
150
- sheets[i] = next(sht.properties.title for sht in ss.sheets if sht.properties.sheet_id == sheets[i])
151
- except StopIteration:
152
- return [[]]
153
- ranges_processed.extend([f'{sheets[i]}!{range_}' for range_ in ranges[i]])
164
+ for sheet_id_or_name, sheet_ranges in zip(sheets, ranges):
165
+ if isinstance(sheet_id_or_name, int):
166
+ try:
167
+ sheet_name = next(sht.properties.title for sht in ss.sheets if sht.properties.sheet_id == sheet_id_or_name)
168
+ except StopIteration:
169
+ return None
170
+ else:
171
+ sheet_name = sheet_id_or_name
172
+ ranges_processed.extend([f'{sheet_name}!{range_}' for range_ in sheet_ranges])
154
173
 
155
174
  try:
156
175
  response = service.spreadsheets().values().batchGet(
157
176
  spreadsheetId=spreadsheet_id,
158
177
  ranges=ranges_processed,
159
- valueRenderOption='UNFORMATTED_VALUE',
160
- dateTimeRenderOption='FORMATTED_STRING'
178
+ majorDimension=Dimension.COLUMNS if by_columns else Dimension.ROWS,
179
+ valueRenderOption=ValueRenderOption.UNFORMATTED_VALUE,
180
+ dateTimeRenderOption=DateTimeRenderOption.FORMATTED_STRING
161
181
  ).execute(num_retries=5)
162
- except HttpError as e:
163
- raise e
182
+ except HttpError:
183
+ return None
164
184
  else:
165
185
  result = []
166
- for value_range in response['valueRanges']:
167
- values = value_range.get('values', [])
168
- if not values:
169
- result.append([])
170
- continue
171
-
172
- if len(values) == 1: # If range is a single row
173
- result.append(values[0])
174
- else:
175
- if max([len(row) for row in values]) <= 1: # If range is a single column
176
- result.append([(row[0] if row else '') for row in values])
177
- else:
178
- result.append(values)
179
-
186
+ value_ranges = iter(response.get('valueRanges', []))
187
+ for sheet_no in range(len(ranges)):
188
+ sheet_ranges = []
189
+ for range_no in range(len(ranges[sheet_no])):
190
+ sheet_ranges.append(next(value_ranges).get('values', []))
191
+ result.append(sheet_ranges)
180
192
  return result
@@ -59,9 +59,12 @@ from .spreadsheet import (
59
59
  )
60
60
 
61
61
  from .general_models import (
62
+ ValueRenderOption,
63
+ DateTimeRenderOption,
62
64
  Color,
63
65
  ColorStyle,
64
66
  GridRange,
65
67
  FieldMask,
66
68
  SimpleType,
69
+ RangeData,
67
70
  )
@@ -4,6 +4,18 @@ from pydantic import BaseModel, Field, model_validator
4
4
 
5
5
 
6
6
  SimpleType = str | int | float | bool
7
+ RangeData = list[list[SimpleType]]
8
+
9
+
10
+ class ValueRenderOption(StrEnum):
11
+ FORMATTED_VALUE = 'FORMATTED_VALUE' # Calculated & formatted according to the cell's formatting. DEFAULT value.
12
+ UNFORMATTED_VALUE = 'UNFORMATTED_VALUE' # Calculated, not formatted (e.g. 123.45 instead of $123.45).
13
+ FORMULA = 'FORMULA' # The formula as entered in the cell, e.g. '=SUM(A1:B1)'. If the cell contains a formula, this field will start with an '=' sign.
14
+
15
+
16
+ class DateTimeRenderOption(StrEnum):
17
+ SERIAL_NUMBER = 'SERIAL_NUMBER' # A number, where 1 corresponds to December 30, 1899. DEFAULT value.
18
+ FORMATTED_STRING = 'FORMATTED_STRING' # A string, formatted according to the cell's formatting.
7
19
 
8
20
 
9
21
  class ThemeColorType(StrEnum):
@@ -6,11 +6,16 @@ build-backend = "setuptools.build_meta"
6
6
  name = "python-google-sheets"
7
7
  description = "A lightweight and efficient Python wrapper for the Google Sheets API v4"
8
8
  authors = [{ name = "Timofey Egorov", email = "timegorr@gmail.com" }]
9
- version = "1.1.0"
9
+ version = "1.2.0"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
12
+ keywords = ["spreadsheets", "google-spreadsheets", "google-sheets", "api", "wrapper", "python", "pydantic"]
12
13
  classifiers = [
13
14
  "Programming Language :: Python :: 3",
15
+ "Programming Language :: Python :: 3.11",
16
+ "Programming Language :: Python :: 3.12",
17
+ "Programming Language :: Python :: 3.13",
18
+ "Programming Language :: Python :: 3.14",
14
19
  "Operating System :: OS Independent",
15
20
  ]
16
21
  dependencies = [
@@ -19,4 +24,8 @@ dependencies = [
19
24
  "pydantic>=2.0.0",
20
25
  ]
21
26
  license = "MIT"
22
- license-files = ["LICEN[CS]E*"]
27
+ license-files = ["LICENSE*"]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/Timofey28/python-google-sheets"
31
+ Issues = "https://github.com/Timofey28/python-google-sheets/issues"
@@ -1,3 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: python-google-sheets
3
+ Version: 1.2.0
4
+ Summary: A lightweight and efficient Python wrapper for the Google Sheets API v4
5
+ Author-email: Timofey Egorov <timegorr@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Timofey28/python-google-sheets
8
+ Project-URL: Issues, https://github.com/Timofey28/python-google-sheets/issues
9
+ Keywords: spreadsheets,google-spreadsheets,google-sheets,api,wrapper,python,pydantic
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Operating System :: OS Independent
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: google-auth
20
+ Requires-Dist: google-api-python-client
21
+ Requires-Dist: pydantic>=2.0.0
22
+ Dynamic: license-file
23
+
1
24
  # python-google-sheets
2
25
 
3
26
  A lightweight and efficient Python wrapper for the Google Sheets API v4, leveraging `service account` credentials for seamless authentication.
@@ -65,7 +88,7 @@ spreadsheet_id, url = GoogleSheets.create_spreadsheet(
65
88
  )
66
89
  ```
67
90
 
68
- ### `update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], service: 'Resource') -> None`
91
+ ### `update_spreadsheet(spreadsheet_id: str, api_requests: list[dict], *, service: 'Resource') -> None`
69
92
  Execute a `batchUpdate` with one or more request objects.
70
93
 
71
94
  ```python
@@ -77,7 +100,7 @@ requests = [
77
100
  GoogleSheets.update_spreadsheet(spreadsheet_id, requests, service)
78
101
  ```
79
102
 
80
- ### `copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, service: 'Resource') -> SheetProperties`
103
+ ### `copy_sheet(source_spreadsheet_id: str, source_sheet_id: str, destination_spreadsheet_id: str, *, service: 'Resource') -> SheetProperties`
81
104
  Copy a sheet from one spreadsheet into another.
82
105
 
83
106
  ```python
@@ -97,19 +120,39 @@ spreadsheet = GoogleSheets.get_spreadsheet(spreadsheet_id, service)
97
120
  print([sheet.properties.title for sheet in spreadsheet.sheets])
98
121
  ```
99
122
 
100
- ### `get_spreadsheet_range_values(spreadsheet_id: str, sheets: list[str | int], ranges: list[list[str]], service: 'Resource') -> list[list[SimpleType] | list[list[SimpleType]]]`
101
- Read values from multiple ranges across one or more sheets.
123
+ ### `get_spreadsheet_range_values(spreadsheet_id: str, sheets: list[str | int] | str | int, ranges: list[list[str]] | list[str] | str, *, by_columns: bool = False, service: 'Resource') -> list[list[RangeData]] | None:`
124
+ Reads values from multiple ranges across multiple sheets. Returns a matrix of values (RangeData - list[list[SimpleType]]) for each range of each sheet. Returns None if an error occurs.
125
+
126
+ Code from this example returns one range from sheet with id `1601337967` and three ranges from sheet `Summary` (assuming that these sheets exist in the spreadsheet). You can mix and match sheet names and ids as needed.
102
127
 
103
128
  ```python
104
129
  values = GoogleSheets.get_spreadsheet_range_values(
105
- spreadsheet_id=spreadsheet_id,
106
- sheets=[0, 'Summary'],
107
- ranges=[['A2:C8'], ['B2:B12']],
108
- service=service,
130
+ spreadsheet_id=SPREADSHEET_ID,
131
+ sheets=[1601337967, 'Summary'],
132
+ ranges=[['A2:C100'], ['A1:E10', 'F1:F10', 'H1']],
133
+ service=service
109
134
  )
110
- print(values)
111
135
  ```
112
136
 
137
+ Next two examples return the same data (assuming that sheet `Sheet1` exists and has id `0`). With `by_columns=True` the values are grouped by columns instead of rows.
138
+ ```python
139
+ values = GoogleSheets.get_spreadsheet_range_values(
140
+ SPREADSHEET_ID,
141
+ sheets='Sheet1',
142
+ ranges=['A1:E5'],
143
+ by_columns=True,
144
+ service=service
145
+ )
146
+ ```
147
+ ```python
148
+ values = GoogleSheets.get_spreadsheet_range_values(
149
+ SPREADSHEET_ID,
150
+ sheets=0,
151
+ ranges='A1:E5',
152
+ by_columns=True,
153
+ service=service
154
+ )
155
+ ```
113
156
  ---
114
157
 
115
158
  ## `ApiRequest` methods for `batchUpdate`
@@ -138,7 +181,7 @@ req = ApiRequest.update_cells(
138
181
  ---
139
182
  ### Conditional formatting
140
183
 
141
- ### `add_boolean_format_rule(*, sheet_id: int, ranges: list[str], condition_type: ConditionType, condition_values: list[ConditionValue] = None, cell_format: CellFormat) -> dict`
184
+ ### `add_boolean_format_rule(sheet_id: int, ranges: list[str], condition_type: ConditionType, *, condition_values: list[ConditionValue] = None, cell_format: CellFormat) -> dict`
142
185
  Add a rule (for example, highlight values greater than `100`).
143
186
 
144
187
  ```python
@@ -156,7 +199,7 @@ req = ApiRequest.add_boolean_format_rule(
156
199
  )
157
200
  ```
158
201
 
159
- ### `GradientRule.add(*, sheet_id: int, ranges: list[str], interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue], interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle]) -> dict`
202
+ ### `GradientRule.add(sheet_id: int, *, ranges: list[str], interpolation_points: tuple[IPTypeAndValue, IPTypeAndValue] | tuple[IPTypeAndValue, IPTypeAndValue, IPTypeAndValue], interpolation_point_colors: tuple[ColorStyle, ColorStyle] | tuple[ColorStyle, ColorStyle, ColorStyle]) -> dict`
160
203
  Add a custom color scale with 2 or 3 interpolation points.
161
204
 
162
205
  ```python
@@ -178,7 +221,7 @@ req = ApiRequest.GradientRule.add(
178
221
  )
179
222
  ```
180
223
 
181
- ### `GradientRule.add_preset(*, sheet_id: int, ranges: list[str], preset: Preset) -> dict`
224
+ ### `GradientRule.add_preset(sheet_id: int, ranges: list[str], preset: Preset) -> dict`
182
225
  Add a gradient rule from a built-in preset.
183
226
 
184
227
  ```python
@@ -189,14 +232,14 @@ req = ApiRequest.GradientRule.add_preset(
189
232
  )
190
233
  ```
191
234
 
192
- ### `delete_conditional_format_rule(*, sheet_id: int, index: int) -> dict`
235
+ ### `delete_conditional_format_rule(sheet_id: int, *, index: int) -> dict`
193
236
  Delete a conditional format rule by index.
194
237
 
195
238
  ```python
196
239
  req = ApiRequest.delete_conditional_format_rule(sheet_id=0, index=0)
197
240
  ```
198
241
 
199
- ### `update_conditional_format_rule(*, sheet_id: int, index: int, rule: ConditionalFormatRule) -> dict`
242
+ ### `update_conditional_format_rule(sheet_id: int, *, index: int, rule: ConditionalFormatRule) -> dict`
200
243
  Replace an existing conditional format rule by index.
201
244
 
202
245
  ```python
@@ -284,7 +327,7 @@ Delete a sheet.
284
327
  req = ApiRequest.delete_sheet(sheet_id=3)
285
328
  ```
286
329
 
287
- ### `add_sheet(*, sheet_id: int = None, title: str = None, index: int = None, hidden: bool = None, row_count: int = None, column_count: int = None, frozen_row_count: int = None, frozen_column_count: int = None, hide_grid_lines: bool = None) -> dict`
330
+ ### `add_sheet(sheet_id: int = None, *, title: str = None, index: int = None, hidden: bool = None, row_count: int = None, column_count: int = None, frozen_row_count: int = None, frozen_column_count: int = None, hide_grid_lines: bool = None) -> dict`
288
331
  Create a new sheet with optional properties.
289
332
 
290
333
  ```python
@@ -306,7 +349,7 @@ Merge cells in a range.
306
349
  req = ApiRequest.merge_cells(sheet_id=0, range_='A1:C1')
307
350
  ```
308
351
 
309
- ### `unmerge_cells(sheet_id: int, range_: str = None, start_row: int = None, end_row: int = None, start_column: int | str = None, end_column: int | str = None) -> dict`
352
+ ### `unmerge_cells(sheet_id: int, *, range_: str = None, start_row: int = None, end_row: int = None, start_column: int | str = None, end_column: int | str = None) -> dict`
310
353
  Unmerge cells in a range (or by explicit indexes).
311
354
 
312
355
  ```python
@@ -323,14 +366,14 @@ req = ApiRequest.freeze(sheet_id=0, rows=1, columns=1)
323
366
  ---
324
367
  ### Rows and columns
325
368
 
326
- ### `insert_rows(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict`
369
+ ### `insert_rows(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict`
327
370
  Insert one or more rows (Indexes are zero-based and inclusive [start_index, end_index]).
328
371
 
329
372
  ```python
330
373
  req = ApiRequest.insert_rows(sheet_id=0, start_index=5, end_index=9)
331
374
  ```
332
375
 
333
- ### `insert_columns(sheet_id: int, start_index: int, end_index: int = None, inherit_from_before: bool = True) -> dict`
376
+ ### `insert_columns(sheet_id: int, start_index: int, end_index: int = None, *, inherit_from_before: bool = True) -> dict`
334
377
  Insert columns (Indexes are zero-based and inclusive [start_index, end_index]).
335
378
 
336
379
  ```python
@@ -489,4 +532,4 @@ GoogleSheets.update_spreadsheet(spreadsheet_id, requests, service)
489
532
  If you have any questions, encounter issues, or want to suggest improvements:
490
533
 
491
534
  - **Bug Reports & Feature Requests:** Please use [GitHub Issues](https://github.com/Timofey28/python-google-sheets/issues) to report bugs or submit ideas.
492
- - **Questions:** For general inquiries or help with integration, feel free to reach out via email at [timegorr@gmail.com](mailto:timegorr@gmail.com).
535
+ - **Questions:** For general inquiries or help with integration, feel free to reach out via email at [timegorr@gmail.com](mailto:timegorr@gmail.com).