Showing preview only (8,641K chars total). Download the full file or copy to clipboard to get everything.
Repository: burnash/gspread
Branch: master
Commit: 1fb4ba7d37f5
Files: 176
Total size: 8.2 MB
Directory structure:
gitextract_gl9l53pb/
├── .github/
│ ├── CODE_OF_CONDUCT.md
│ ├── CONTRIBUTING.md
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ ├── dependabot.yaml
│ └── workflows/
│ ├── main.yaml
│ └── release.yaml
├── .gitignore
├── .readthedocs.yaml
├── HISTORY.rst
├── LICENSE.txt
├── README.md
├── docs/
│ ├── _templates/
│ │ └── layout.html
│ ├── advanced.rst
│ ├── api/
│ │ ├── auth.rst
│ │ ├── client.rst
│ │ ├── exceptions.rst
│ │ ├── http_client.rst
│ │ ├── index.rst
│ │ ├── models/
│ │ │ ├── cell.rst
│ │ │ ├── index.rst
│ │ │ ├── spreadsheet.rst
│ │ │ └── worksheet.rst
│ │ ├── top-level.rst
│ │ └── utils.rst
│ ├── community.rst
│ ├── conf.py
│ ├── index.rst
│ ├── oauth2.rst
│ ├── requirements.txt
│ └── user-guide.rst
├── gspread/
│ ├── __init__.py
│ ├── auth.py
│ ├── cell.py
│ ├── client.py
│ ├── exceptions.py
│ ├── http_client.py
│ ├── py.typed
│ ├── spreadsheet.py
│ ├── urls.py
│ ├── utils.py
│ └── worksheet.py
├── lint-requirements.txt
├── pyproject.toml
├── test-requirements.txt
├── tests/
│ ├── __init__.py
│ ├── cassettes/
│ │ ├── CellTest.test_a1_value.json
│ │ ├── CellTest.test_define_named_range.json
│ │ ├── CellTest.test_delete_named_range.json
│ │ ├── CellTest.test_equality.json
│ │ ├── CellTest.test_merge_cells.json
│ │ ├── CellTest.test_numeric_value.json
│ │ ├── CellTest.test_properties.json
│ │ ├── ClientTest.test_access_non_existing_spreadsheet.json
│ │ ├── ClientTest.test_access_private_spreadsheet.json
│ │ ├── ClientTest.test_add_timeout.json
│ │ ├── ClientTest.test_client_export_spreadsheet.json
│ │ ├── ClientTest.test_copy.json
│ │ ├── ClientTest.test_create.json
│ │ ├── ClientTest.test_import_csv.json
│ │ ├── ClientTest.test_list_spreadsheet_files.json
│ │ ├── ClientTest.test_no_found_exeption.json
│ │ ├── ClientTest.test_open_all_has_metadata.json
│ │ ├── ClientTest.test_open_by_key_has_metadata.json
│ │ ├── ClientTest.test_open_by_name_has_metadata.json
│ │ ├── ClientTest.test_openall.json
│ │ ├── SpreadsheetTest.test_add_del_worksheet.json
│ │ ├── SpreadsheetTest.test_bad_json_api_error.json
│ │ ├── SpreadsheetTest.test_creationTime_prop.json
│ │ ├── SpreadsheetTest.test_export_spreadsheet.json
│ │ ├── SpreadsheetTest.test_get_lastUpdateTime.json
│ │ ├── SpreadsheetTest.test_get_worksheet.json
│ │ ├── SpreadsheetTest.test_get_worksheet_by_id.json
│ │ ├── SpreadsheetTest.test_lastUpdateTime_prop.json
│ │ ├── SpreadsheetTest.test_properties.json
│ │ ├── SpreadsheetTest.test_sheet1.json
│ │ ├── SpreadsheetTest.test_timezone_and_locale.json
│ │ ├── SpreadsheetTest.test_update_title.json
│ │ ├── SpreadsheetTest.test_values_batch_get.json
│ │ ├── SpreadsheetTest.test_values_get.json
│ │ ├── SpreadsheetTest.test_worksheet.json
│ │ ├── SpreadsheetTest.test_worksheet_iteration.json
│ │ ├── SpreadsheetTest.test_worksheets.json
│ │ ├── SpreadsheetTest.test_worksheets_exclude_hidden.json
│ │ ├── WorksheetTest.test_acell.json
│ │ ├── WorksheetTest.test_add_protected_range_normal.json
│ │ ├── WorksheetTest.test_add_protected_range_warning.json
│ │ ├── WorksheetTest.test_add_validation.json
│ │ ├── WorksheetTest.test_append_row.json
│ │ ├── WorksheetTest.test_append_row_with_empty_value.json
│ │ ├── WorksheetTest.test_append_row_with_empty_value_and_table_range.json
│ │ ├── WorksheetTest.test_attributes.json
│ │ ├── WorksheetTest.test_auto_resize_columns.json
│ │ ├── WorksheetTest.test_basic_filters.json
│ │ ├── WorksheetTest.test_batch_clear.json
│ │ ├── WorksheetTest.test_batch_get.json
│ │ ├── WorksheetTest.test_batch_merged_cells.json
│ │ ├── WorksheetTest.test_batch_update.json
│ │ ├── WorksheetTest.test_cell.json
│ │ ├── WorksheetTest.test_cell_return_first.json
│ │ ├── WorksheetTest.test_clear.json
│ │ ├── WorksheetTest.test_clear_tab_color.json
│ │ ├── WorksheetTest.test_copy_cut_range.json
│ │ ├── WorksheetTest.test_delete_cols.json
│ │ ├── WorksheetTest.test_delete_protected_range.json
│ │ ├── WorksheetTest.test_delete_row.json
│ │ ├── WorksheetTest.test_find.json
│ │ ├── WorksheetTest.test_findall.json
│ │ ├── WorksheetTest.test_format.json
│ │ ├── WorksheetTest.test_freeze.json
│ │ ├── WorksheetTest.test_get_all_records.json
│ │ ├── WorksheetTest.test_get_all_records_different_header.json
│ │ ├── WorksheetTest.test_get_all_records_duplicate_keys.json
│ │ ├── WorksheetTest.test_get_all_records_numericise_unformatted.json
│ │ ├── WorksheetTest.test_get_all_records_pad_more_than_one_key.json
│ │ ├── WorksheetTest.test_get_all_records_pad_one_key.json
│ │ ├── WorksheetTest.test_get_all_records_pad_values.json
│ │ ├── WorksheetTest.test_get_all_records_value_render_options.json
│ │ ├── WorksheetTest.test_get_all_records_with_all_values_blank.json
│ │ ├── WorksheetTest.test_get_all_records_with_blank_final_headers.json
│ │ ├── WorksheetTest.test_get_all_records_with_keys_blank.json
│ │ ├── WorksheetTest.test_get_all_records_with_some_values_blank.json
│ │ ├── WorksheetTest.test_get_all_values.json
│ │ ├── WorksheetTest.test_get_all_values_date_time_render_options.json
│ │ ├── WorksheetTest.test_get_all_values_title_is_a1_notation.json
│ │ ├── WorksheetTest.test_get_and_get_values_have_same_signature.json
│ │ ├── WorksheetTest.test_get_merge_cells_and_unmerge_cells.json
│ │ ├── WorksheetTest.test_get_notes.json
│ │ ├── WorksheetTest.test_get_notes_2nd_sheet.json
│ │ ├── WorksheetTest.test_get_returns_ValueRange_with_metadata.json
│ │ ├── WorksheetTest.test_get_values_and_combine_merged_cells.json
│ │ ├── WorksheetTest.test_get_values_and_maintain_size.json
│ │ ├── WorksheetTest.test_get_values_can_emulate_get_with_kwargs.json
│ │ ├── WorksheetTest.test_get_values_merge_cells_from_centre_of_sheet.json
│ │ ├── WorksheetTest.test_get_values_merge_cells_outside_of_range.json
│ │ ├── WorksheetTest.test_get_values_merge_cells_with_named_range.json
│ │ ├── WorksheetTest.test_get_values_returns_padded_get_as_listoflists.json
│ │ ├── WorksheetTest.test_get_values_with_args_or_kwargs.json
│ │ ├── WorksheetTest.test_group_columns.json
│ │ ├── WorksheetTest.test_group_rows.json
│ │ ├── WorksheetTest.test_hide_columns_rows.json
│ │ ├── WorksheetTest.test_hide_gridlines.json
│ │ ├── WorksheetTest.test_hide_show_worksheet.json
│ │ ├── WorksheetTest.test_insert_cols.json
│ │ ├── WorksheetTest.test_insert_row.json
│ │ ├── WorksheetTest.test_range.json
│ │ ├── WorksheetTest.test_range_get_all_values.json
│ │ ├── WorksheetTest.test_range_reversed.json
│ │ ├── WorksheetTest.test_range_unbounded.json
│ │ ├── WorksheetTest.test_reorder_worksheets.json
│ │ ├── WorksheetTest.test_resize.json
│ │ ├── WorksheetTest.test_set_tab_color.json
│ │ ├── WorksheetTest.test_show_gridlines.json
│ │ ├── WorksheetTest.test_sort.json
│ │ ├── WorksheetTest.test_update_acell.json
│ │ ├── WorksheetTest.test_update_and_get.json
│ │ ├── WorksheetTest.test_update_cell.json
│ │ ├── WorksheetTest.test_update_cell_multiline.json
│ │ ├── WorksheetTest.test_update_cell_objects.json
│ │ ├── WorksheetTest.test_update_cell_unicode.json
│ │ ├── WorksheetTest.test_update_cells.json
│ │ ├── WorksheetTest.test_update_cells_noncontiguous.json
│ │ ├── WorksheetTest.test_update_cells_unicode.json
│ │ ├── WorksheetTest.test_update_tab_color.json
│ │ ├── WorksheetTest.test_update_title.json
│ │ ├── WorksheetTest.test_update_works_with_swapped_values_and_range.json
│ │ ├── WorksheetTest.test_worksheet_notes.json
│ │ └── WorksheetTest.test_worksheet_update_index.json
│ ├── cell_test.py
│ ├── client_test.py
│ ├── conftest.py
│ ├── spreadsheet_test.py
│ ├── utils_test.py
│ └── worksheet_test.py
└── tox.ini
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by opening an issue or by contacting the project team at gspread-conduct@googlegroups.com.
All complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributing Guide
- Check the [GitHub Issues](https://github.com/burnash/gspread/issues) for open issues that need attention.
- Follow the [How to submit a contribution](https://opensource.guide/how-to-contribute/#how-to-submit-a-contribution) Guide.
- Make sure unit tests pass. Please read how to run unit tests [below](#run-tests-offline).
- If you are fixing a bug:
- If you are resolving an existing issue, reference the issue ID in a commit message `(e.g., fixed #XXXX)`.
- If the issue has not been reported, please add a detailed description of the bug in the Pull Request (PR).
- Please add a regression test case to check the bug is fixed.
- If you are adding a new feature:
- Please open a suggestion issue first.
- Provide a convincing reason to add this feature and have it greenlighted before working on it.
- Add tests to cover the functionality.
- Please follow [Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/).
## Tests
To run tests, add your credentials to `tests/creds.json` and run
```bash
GS_CREDS_FILENAME="tests/creds.json" GS_RECORD_MODE="all" tox -e py -- -k "<specific test to run>"
```
For more information on tests, see below.
## CI checks
If the [test](#run-tests-offline) or [lint](#lint) commands fail, the CI will fail, and you won't be able to merge your changes into gspread.
Use [format](#format) to format your code before submitting a PR. Not doing so may cause [lint](#lint) to fail.
## Install dependencies
```bash
pip install tox
```
## Run tests (offline)
If the calls to the Sheets API have not changed, you can run the tests offline. Otherwise, you will have to [run them online](#run-tests-online) to record the new API calls.
This will use the currently recorded HTTP requests + responses. It does not make any HTTP calls, and does not require an active internet connection.
```bash
tox -e py
```
### Run a specific test
```bash
tox -e py -- -k TEST_NAME
```
The CI uses tox. For faster local development, you can set up an environment and use `pytest`, where `-k TEST_NAME` is used to filter to tests matching `TEST_NAME`. For more info run `pytest --help`.
```bash
python -m venv env
source /env/bin/activate
pip install test-requirements.txt
pytest -k TEST_NAME
```
## Format
```bash
tox -e format
```
## Lint
```bash
tox -e lint
```
## Render Documentation
The documentation uses [reStructuredText](http://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html#rst-index) markup and is rendered by [Sphinx](http://www.sphinx-doc.org/).
```bash
tox -e doc
```
The rendered documentation is placed into `docs/build/html`. `index.html` is an entry point.
## Run tests (online)
gspread uses [vcrpy](https://github.com/kevin1024/vcrpy) to record and replay HTTP interactions with Sheets API.
### `GS_CREDS_FILENAME` environment variable
You must provide service account credentials using the `GS_CREDS_FILENAME` environment variable in order to make HTTP requests to the Sheets API.
[Obtain service account credentials from Google Developers Console](https://docs.gspread.org/en/latest/oauth2.html#for-bots-using-service-account).
### `GS_RECORD_MODE` environment variable
You can control vcrpy's [Record Mode](https://vcrpy.readthedocs.io/en/latest/usage.html#record-modes) using `GS_RECORD_MODE` environment variable. It can be:
- `all` - record all HTTP requests, overwriting existing ones
- `new_episodes` - record new HTTP requests and replay existing ones
- `none` - replay existing HTTP requests only
In the following cases, you must record new HTTP requests:
- a new test is added
- an existing test is updated and does a new HTTP request
- gspread is updated and does a new HTTP request
### Run test, capturing *all* HTTP requests
In some cases if the test suite can't record new episodes, or it can't replay them offline, you can run a complete update of the cassettes.
```bash
GS_CREDS_FILENAME=<./YOUR_CREDS.json> GS_RECORD_MODE=all tox -e py
```
### Run test, capturing *only new* HTTP requests
To record new HTTP requests:
1. Remove the file holding the recorded HTTP requests of the test(s).
e.g.,
1. for the file `tests/cell_test.py`:
2. for the test `test_a1_value`
3. remove the file `tests/cassettes/CellTest.test_a1_value.json`
1. Run the tests with `GS_RECORD_MODE=new_episodes`.
```bash
GS_CREDS_FILENAME=<./YOUR_CREDS.json> GS_RECORD_MODE=new_episodes tox -e py
```
This will mostly result in a lot of updated files in `tests/cassettes/`. Don't forget to add them in your PR.
Please add them in a dedicated commit, in order to make the review process easier.
Afterwards, remember to [run the tests in offline mode](#run-tests-offline) to make sure you have recorded everything correctly.
## Release
Old release notes are [here](https://gist.github.com/burnash/335f977a74b8bfdc7968).
New release system:
- Update version number in [`gspread/__init__.py`](../gspread/__init__.py).
- Get changelog from drafting a new [GitHub release](https://github.com/burnash/gspread/releases/new) (do not publish, instead cancel.)
- Add changelog to [`HISTORY.rst`](../HISTORY.rst).
- Commit the changes as `Release vX.Y.Z` (do not push yet.)
- Run `tox -e lint,py,build,doc` to check build/etc.
- Push the commit. Wait for the CI to pass.
- Add a tag `vX.Y.Z` to the commit locally. This will trigger a new release on PyPi, and make a release on GitHub.
- View the release on [GitHub](https://github.com/burnash/gspread/releases) and [PyPi](https://pypi.org/project/gspread/)!
- Sync or add the latest version to the [Gspread ReadTheDocs](https://app.readthedocs.org/projects/gspread/)
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Important**: Please do not post usage questions here.
To get a quick response, please ask a question on Stack Overflow using `gspread` tag.
See existing questions: https://stackoverflow.com/questions/tagged/gspread
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1.
2.
3.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Code example***
If applicable, provide a code example to help explain your problem.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Environment info:**
- Operating System [e.g. Linux, Windows, macOS]:
- Python version
- gspread version
**Stack trace or other output that would be helpful**
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: true
contact_links:
- name: Questions and Help
url: http://stackoverflow.com/questions/tagged/gspread
about: This issue tracker is not for support questions. To get a quick response from the community, please ask a question on Stack Overflow using `gspread` tag.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/dependabot.yaml
================================================
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
================================================
FILE: .github/workflows/main.yaml
================================================
name: lint_python
on:
push:
branches:
- "**" # run all branches
tags-ignore:
- "*" # ignore all tags, release.yaml will trigger the CI
pull_request: # run on all pull requests
concurrency:
cancel-in-progress: true
group: group-${{ github.ref_name }}
jobs:
lint_python:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python: ["3.8", "3.9", "3.10", "3.11", "3.x"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python }}
- run: pip install -U pip
- run: pip install -U bandit pyupgrade pip-audit tox setuptools
- run: bandit --recursive --skip B105,B110,B311,B605,B607 --exclude ./.tox .
- run: tox -e lint
- run: tox -e py
- run: shopt -s globstar && pyupgrade --py3-only **/*.py # --py36-plus
- run: pip-audit --ignore-vuln PYSEC-2023-228 --ignore-vuln PYSEC-2022-43012 --ignore-vuln GHSA-5rjg-fvgr-3xxf --ignore-vuln GHSA-48p4-8xcf-vxj5 --ignore-vuln GHSA-pq67-6m6q-mj2v # pip:PYSEC-2023-228 setuptools:PYSEC-2022-43012 setuptools:GHSA-5rjg-fvgr-3xxf urllib:GHSA-48p4-8xcf-vxj5 urllib:GHSA-pq67-6m6q-mj2v
- run: tox -e build
- run: tox -e doc
================================================
FILE: .github/workflows/release.yaml
================================================
name: Releases
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup python
uses: actions/setup-python@v5
with:
python-version: "3.9"
- name: Setup tox
run: pip install tox
- name: Run linter
run: tox -e lint
- name: Run tests
run: tox -e py
- name: Build package
run: tox -e build
- name: Create release
uses: ncipollo/release-action@v1
with:
artifacts: dist/gspread-*
token: ${{ secrets.GH_TOKEN }}
generateReleaseNotes: True
artifactErrorsFailBuild: True
- name: Publish to TestPyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository-url: https://test.pypi.org/legacy/
verbose: true
- name: Publish to PyPi
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
================================================
FILE: .gitignore
================================================
# vscode
.vscode/
# python
**/__pycache__/
# secrets
tests/creds.json
# virtualenv
env/
# tox
.tox/
# build
gspread.egg-info/
dist/
docs/build/
================================================
FILE: .readthedocs.yaml
================================================
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
fail_on_warning: true
configuration: docs/conf.py
# We recommend specifying your dependencies to enable reproducible builds:
# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: ./
================================================
FILE: HISTORY.rst
================================================
Release History
===============
6.2.1 (2025-05-14)
------------------
* Fix public API auth snippet by @Jayy001 in https://github.com/burnash/gspread/pull/1545
* Clarify the first step: authentication by @hraftery in https://github.com/burnash/gspread/pull/1546
* Fix typo in community.rst by @s2t2 in https://github.com/burnash/gspread/pull/1547
* rearrange flow to remove reference to `creds` by @alifeee in https://github.com/burnash/gspread/pull/1525
* switch safety for pip-audit by @alifeee in https://github.com/burnash/gspread/pull/1551
* Include the duplicate column names in error message by @NickCrews in https://github.com/burnash/gspread/pull/1548
6.2.0 (2025-02-27)
------------------
* Add property expiry in gspread client by @lavigne958 in https://github.com/burnash/gspread/pull/1453
* Bump typing-extensions from 4.11.0 to 4.12.0 by @dependabot in https://github.com/burnash/gspread/pull/1471
* Fix code block formatting typo in README by @agrvz in https://github.com/burnash/gspread/pull/1474
* ignore jinja CVE by @lavigne958 in https://github.com/burnash/gspread/pull/1481
* Type part of test suite utils by @lavigne958 in https://github.com/burnash/gspread/pull/1483
* Remove passing exception as args to super in APIError by @mike-flowers-airbnb in https://github.com/burnash/gspread/pull/1477
* Bump mypy from 1.10.0 to 1.10.1 by @dependabot in https://github.com/burnash/gspread/pull/1488
* Update advanced.rst by @yatender-rjliving in https://github.com/burnash/gspread/pull/1492
* Bump bandit from 1.7.8 to 1.7.9 by @dependabot in https://github.com/burnash/gspread/pull/1485
* Bump flake8 from 7.0.0 to 7.1.0 by @dependabot in https://github.com/burnash/gspread/pull/1486
* Bump typing-extensions from 4.12.0 to 4.12.2 by @dependabot in https://github.com/burnash/gspread/pull/1480
* Bump mypy from 1.10.1 to 1.11.1 by @dependabot in https://github.com/burnash/gspread/pull/1497
* Bump black from 24.4.2 to 24.8.0 by @dependabot in https://github.com/burnash/gspread/pull/1499
* Bump flake8 from 7.1.0 to 7.1.1 by @dependabot in https://github.com/burnash/gspread/pull/1501
* Fix docstring about BackOffHTTPClient by @pataiji in https://github.com/burnash/gspread/pull/1502
* Fix comment to reflect correct google-auth package version requirement by @ikmals in https://github.com/burnash/gspread/pull/1503
* Doc/community addons orm package by @lavigne958 in https://github.com/burnash/gspread/pull/1506
* fix: fix type annotation for default_blank by @hiro-o918 in https://github.com/burnash/gspread/pull/1505
* Bump mypy from 1.11.1 to 1.11.2 by @dependabot in https://github.com/burnash/gspread/pull/1508
* better handler API error parsing. by @lavigne958 in https://github.com/burnash/gspread/pull/1510
* Add test on receiving an invalid JSON in the APIError exception handler. by @lavigne958 in https://github.com/burnash/gspread/pull/1512
* [feature] Add 'expand_table' feature by @lavigne958 in https://github.com/burnash/gspread/pull/1475
* Bump bandit from 1.7.9 to 1.7.10 by @dependabot in https://github.com/burnash/gspread/pull/1514
* Created a `batch_merge` function [Issue #1473] by @muddi900 in https://github.com/burnash/gspread/pull/1498
* Added a range option to `Worksheet.get_notes` [Issue #1482] by @muddi900 in https://github.com/burnash/gspread/pull/1487
* Documentation update for gspread.worksheet.Worksheet.get_all_records by @levon003 in https://github.com/burnash/gspread/pull/1529
* add example for `batch_merge` by @alifeee in https://github.com/burnash/gspread/pull/1542
* explicitly list exported package symbols by @alinsavix in https://github.com/burnash/gspread/pull/1531
6.1.4 (2024-10-21)
------------------
* remove dependency on requests-2.27.0
6.1.3 (2024-10-03)
------------------
* ignore jinja CVE by @lavigne958 in https://github.com/burnash/gspread/pull/1481
* Remove passing exception as args to super in APIError by @mike-flowers-airbnb in https://github.com/burnash/gspread/pull/1477
* better handler API error parsing. by @lavigne958 in https://github.com/burnash/gspread/pull/1510
* Add test on receiving an invalid JSON in the APIError exception handler. by @lavigne958 in https://github.com/burnash/gspread/pull/1512
6.1.2 (2024-05-17)
------------------
* add note about runnings tests to contrib guide by @alifeee in https://github.com/burnash/gspread/pull/1465
* Some updates on `get_notes` by @nbwzx in https://github.com/burnash/gspread/pull/1461
6.1.1 (2024-05-16)
------------------
* Add some missing typing in code by @lavigne958 in https://github.com/burnash/gspread/pull/1448
* More fixes for `Worksheet.update` argument ordering & single cell updating (i.e. now `Worksheet.update_acell`) by @alexmalins in https://github.com/burnash/gspread/pull/1449
* Added 'add_data_validation` to `Workhsheet` [Issue #1420] by @muddi900 in https://github.com/burnash/gspread/pull/1444
* Bump typing-extensions from 4.10.0 to 4.11.0 by @dependabot in https://github.com/burnash/gspread/pull/1450
* Bump black from 23.3.0 to 24.4.0 by @dependabot in https://github.com/burnash/gspread/pull/1452
* Fix incorrect version number in HISTORY.rst from 6.0.1 to 6.1.0 by @yhay81 in https://github.com/burnash/gspread/pull/1455
* add `get_notes` by @nbwzx in https://github.com/burnash/gspread/pull/1451
* Bump mypy from 1.9.0 to 1.10.0 by @dependabot in https://github.com/burnash/gspread/pull/1459
* Bump black from 24.4.0 to 24.4.2 by @dependabot in https://github.com/burnash/gspread/pull/1460
* bugfix: handle domain name in spreadsheet copy permissions by @lavigne958 in https://github.com/burnash/gspread/pull/1458
* Fix/api key auth version by @alifeee in https://github.com/burnash/gspread/pull/1463
* Ignore pip vulnerabilities in CI. by @lavigne958 in https://github.com/burnash/gspread/pull/1464
* Remove StrEnum dependency and added custom class[issue #1462] by @muddi900 in https://github.com/burnash/gspread/pull/1469
6.1.0 (2024-03-28)
------------------
* Add py.typed marker by @lavigne958 in https://github.com/burnash/gspread/pull/1422
* Improve back-off client by @lavigne958 in https://github.com/burnash/gspread/pull/1415
* Add new auth method API key by @lavigne958 in https://github.com/burnash/gspread/pull/1428
* Bugfix/add set timeout by @lavigne958 in https://github.com/burnash/gspread/pull/1417
* Fix wrapper `cast_to_a1_notation` by @lavigne958 in https://github.com/burnash/gspread/pull/1427
* Bump bandit from 1.7.5 to 1.7.8 by @dependabot in https://github.com/burnash/gspread/pull/1433
* Bump mypy from 1.6.1 to 1.9.0 by @dependabot in https://github.com/burnash/gspread/pull/1432
* Bump typing-extensions from 4.8.0 to 4.10.0 by @dependabot in https://github.com/burnash/gspread/pull/1424
* Bump flake8 from 5.0.4 to 7.0.0 by @dependabot in https://github.com/burnash/gspread/pull/1375
* fix error message readability by @imrehg in https://github.com/burnash/gspread/pull/1435
* Add missing method `import_csv()` by @lavigne958 in https://github.com/burnash/gspread/pull/1426
* update readme examples by @alifeee in https://github.com/burnash/gspread/pull/1431
* Add user friendly message when we can't override a test cassette by @lavigne958 in https://github.com/burnash/gspread/pull/1438
* Allow "warning" type protected ranges by @alifeee in https://github.com/burnash/gspread/pull/1439
* Improve README and documentation with value render options by @lavigne958 in https://github.com/burnash/gspread/pull/1446
6.0.2 (2024-02-14)
------------------
* Fixup gspread client init arguments by @lavigne958 in https://github.com/burnash/gspread/pull/1412
6.0.1 (2024-02-06)
------------------
* Allow client to use external Session object by @lavigne958 in https://github.com/burnash/gspread/pull/1384
* Remove-py-3.7-support by @alifeee in https://github.com/burnash/gspread/pull/1396
* bugfix/client export by @lavigne958 in https://github.com/burnash/gspread/pull/1392
* Fix oauth flow typo by @alifeee in https://github.com/burnash/gspread/pull/1397
* check oauth creds type using `isinstance` by @alifeee in https://github.com/burnash/gspread/pull/1398
* Fix type hints at find method in worksheet.py by @deftfitf in https://github.com/burnash/gspread/pull/1407
* Fixup get empty cell value is `None` by @lavigne958 in https://github.com/burnash/gspread/pull/1404
* Fix missing attribute `spreadsheet` in `Worksheet`. by @lavigne958 in https://github.com/burnash/gspread/pull/1402
* update migration guide by @alifeee in https://github.com/burnash/gspread/pull/1409
6.0.0 (2024-01-28)
------------------
New Contributor
* Remove deprecated method delete_row by @cgkoutzigiannis in https://github.com/burnash/gspread/pull/1062
* Initial typing in client.py by @OskarBrzeski in https://github.com/burnash/gspread/pull/1159
* Split client http client by @lavigne958 in https://github.com/burnash/gspread/pull/1190
* Spelling fix & update docs with date_time_render_option behaviour by @alifeee in https://github.com/burnash/gspread/pull/1187
* #966 Add sketch typing for utils.py by @butvinm in https://github.com/burnash/gspread/pull/1196
* Remove accepted_kwargs decorator by @lavigne958 in https://github.com/burnash/gspread/pull/1229
* Remove/python-3.7 by @alifeee in https://github.com/burnash/gspread/pull/1234
* Bump isort from 5.11.4 to 5.12.0 by @dependabot in https://github.com/burnash/gspread/pull/1165
* bump flake8 to 6.0.0 by @alifeee in https://github.com/burnash/gspread/pull/1236
* merge master into 6.0.0 by @lavigne958 in https://github.com/burnash/gspread/pull/1241
* Remplace named tuples with enums by @lavigne958 in https://github.com/burnash/gspread/pull/1250
* Feature/add type hints worksheets by @lavigne958 in https://github.com/burnash/gspread/pull/1254
* Implement hex color conversion by @idonec in https://github.com/burnash/gspread/pull/1270
* remove lastUpdateTime by @alifeee in https://github.com/burnash/gspread/pull/1295
* Merge `master` into `feature/release_6_0_0` by @alifeee in https://github.com/burnash/gspread/pull/1320
* Add type checking to lint by @alifeee in https://github.com/burnash/gspread/pull/1337
* Warning/update swapped args by @alifeee in https://github.com/burnash/gspread/pull/1336
* Improve `Worksheet.sort()` signature by @lavigne958 in https://github.com/burnash/gspread/pull/1342
* Make `get_values` and alias of `get` by @alifeee in https://github.com/burnash/gspread/pull/1296
* fix type issue (remove `.first()` function) by @alifeee in https://github.com/burnash/gspread/pull/1344
* Remove/get records use index by @alifeee in https://github.com/burnash/gspread/pull/1345
* increase warning stacklevel from 1 to 2 by @alifeee in https://github.com/burnash/gspread/pull/1361
* Feature/merge master by @lavigne958 in https://github.com/burnash/gspread/pull/1371
* feature/merge master by @lavigne958 in https://github.com/burnash/gspread/pull/1372
* Simplify get records by @alifeee in https://github.com/burnash/gspread/pull/1374
* Add util function `to_records` to build records by @lavigne958 in https://github.com/burnash/gspread/pull/1377
* feature/add utils get records by @lavigne958 in https://github.com/burnash/gspread/pull/1378
* Add migration guide for get_all_records by @lavigne958 in https://github.com/burnash/gspread/pull/1379
* feature/merge master into release 6 0 0 by @lavigne958 in https://github.com/burnash/gspread/pull/1381
* Feature/release 6 0 0 by @lavigne958 in https://github.com/burnash/gspread/pull/1382
5.12.4 (2023-12-31)
-------------------
* Bump actions/setup-python from 4 to 5 by @dependabot in https://github.com/burnash/gspread/pull/1370
* Fixed default value of merge_type parameter in merge_cells function docstring. by @neolooong in https://github.com/burnash/gspread/pull/1373
5.12.3 (2023-12-15)
-------------------
* 1363 get all records retrieves a large number of empty rows after the end of the data by @alifeee in https://github.com/burnash/gspread/pull/1364
5.12.2 (2023-12-04)
-------------------
* Many fixes for `get_records` by @alifeee in https://github.com/burnash/gspread/pull/1357
* change `worksheet.update` migration guide by @alifeee in https://github.com/burnash/gspread/pull/1362
5.12.1 (2023-11-29)
-------------------
* feature/readme migration v6 by @lavigne958 in https://github.com/burnash/gspread/pull/1297
* add deprecation warnings for lastUpdateTime... by @alifeee in https://github.com/burnash/gspread/pull/1333
* remove `use_index` and references to it in `get_records` by @alifeee in https://github.com/burnash/gspread/pull/1343
* make deprecation warning dependent on if kwarg is used for client_factory by @alifeee in https://github.com/burnash/gspread/pull/1349
* fix 1352 expected headers broken by @alifeee in https://github.com/burnash/gspread/pull/1353
* fix `combine_merged_cells` when using from a range that doesn't start at `A1` by @alifeee in https://github.com/burnash/gspread/pull/1335
5.12.0 (2023-10-22)
-------------------
* feature -- adding `worksheet.get_records` to get specific row ranges by @AndrewBasem1 in https://github.com/burnash/gspread/pull/1301
* Fix list_spreadsheet_files return value by @mephinet in https://github.com/burnash/gspread/pull/1308
* Fix warning message for `worksheet.update` method by @ksj20 in https://github.com/burnash/gspread/pull/1312
* change lambda function to dict (fix pyupgrade issue) by @alifeee in https://github.com/burnash/gspread/pull/1319
* allows users to silence deprecation warnings by @lavigne958 in https://github.com/burnash/gspread/pull/1324
* Add `maintain_size` to keep asked for size in `get`, `get_values` by @alifeee in https://github.com/burnash/gspread/pull/1305
5.11.3 (2023-09-29)
-------------------
* Fix list_spreadsheet_files return value by @mephinet in https://github.com/burnash/gspread/pull/1308
5.11.2 (2023-09-18)
-------------------
* Fix merge_combined_cells in get_values (AND 5.11.2 RELEASE) by @alifeee in https://github.com/burnash/gspread/pull/1299
5.11.1 (2023-09-06)
-------------------
* Bump actions/checkout from 3 to 4 by @dependabot in https://github.com/burnash/gspread/pull/1288
* remove Drive API access on Spreadsheet init (FIX - VERSION 5.11.1) by @alifeee in https://github.com/burnash/gspread/pull/1291
5.11.0 (2023-09-04)
-------------------
* add docs/build to .gitignore by @alifeee in https://github.com/burnash/gspread/pull/1246
* add release process to CONTRIBUTING.md by @alifeee in https://github.com/burnash/gspread/pull/1247
* Update/clean readme badges by @lavigne958 in https://github.com/burnash/gspread/pull/1251
* add test_fill_gaps and docstring for fill_gaps by @alifeee in https://github.com/burnash/gspread/pull/1256
* Remove API calls from `creationTime`/`lastUpdateTime` by @alifeee in https://github.com/burnash/gspread/pull/1255
* Fix Worksheet ID Type Inconsistencies by @FlantasticDan in https://github.com/burnash/gspread/pull/1269
* Add `column_count` prop as well as `col_count` by @alifeee in https://github.com/burnash/gspread/pull/1274
* Add required kwargs with no default value by @lavigne958 in https://github.com/burnash/gspread/pull/1271
* Add deprecation warnings for colors by @alifeee in https://github.com/burnash/gspread/pull/1278
* Add better Exceptions on opening spreadsheets by @alifeee in https://github.com/burnash/gspread/pull/1277
5.10.0 (2023-06-29)
-------------------
* Fix rows_auto_resize in worksheet.py by removing redundant self by @MagicMc23 in https://github.com/burnash/gspread/pull/1194
* Add deprecation warning for future release 6.0.x by @lavigne958 in https://github.com/burnash/gspread/pull/1195
* FEATURE: show/hide gridlines (#1197) by @alifeee in https://github.com/burnash/gspread/pull/1202
* CLEANUP: cleanup tox.ini, and ignore ./env by @alifeee in https://github.com/burnash/gspread/pull/1200
* Refactor/update-contributing-guide by @alifeee in https://github.com/burnash/gspread/pull/1206
* Spelling fix (with legacy option) by @alifeee in https://github.com/burnash/gspread/pull/1210
* 457-fetch-without-hidden-worksheets by @alifeee in https://github.com/burnash/gspread/pull/1207
* Add_deprecated_warning_sort_method by @lavigne958 in https://github.com/burnash/gspread/pull/1198
* Update (and test for) internal properties on change by @alifeee in https://github.com/burnash/gspread/pull/1211
* Feature: Add "Remove tab colour" method by @alifeee in https://github.com/burnash/gspread/pull/1199
* Refresh-test-cassettes by @alifeee in https://github.com/burnash/gspread/pull/1217
* update self._properties after batch_update by @alifeee in https://github.com/burnash/gspread/pull/1221
* 700-fill-merged-cells by @alifeee in https://github.com/burnash/gspread/pull/1215
* Fix/update-internal-properties by @alifeee in https://github.com/burnash/gspread/pull/1225
* Add breaking change warning in Worksheet.update() by @lavigne958 in https://github.com/burnash/gspread/pull/1226
* Bump codespell from 2.2.4 to 2.2.5 by @dependabot in https://github.com/burnash/gspread/pull/1232
* Add/refresh last update time by @alifeee in https://github.com/burnash/gspread/pull/1233
* Update-build-tools by @alifeee in https://github.com/burnash/gspread/pull/1231
* add read the doc configuration file by @lavigne958 in https://github.com/burnash/gspread/pull/1235
* update licence year by @alifeee in https://github.com/burnash/gspread/pull/1237
* remove deprecated methods from tests by @alifeee in https://github.com/burnash/gspread/pull/1238
5.9.0 (2023-05-11)
------------------
* Bugfix/fix get last update time by @lavigne958 in https://github.com/burnash/gspread/pull/1186
* Add batch notes insert/update/clear by @lavigne958 in https://github.com/burnash/gspread/pull/1189
5.8.0 (2023-04-05)
------------------
* Bump black from 22.10.0 to 22.12.0 by @dependabot in https://github.com/burnash/gspread/pull/1154
* Bump isort from 5.10.1 to 5.11.3 by @dependabot in https://github.com/burnash/gspread/pull/1155
* Bump isort from 5.11.3 to 5.11.4 by @dependabot in https://github.com/burnash/gspread/pull/1157
* #1104: added a delete by worksheet id method by @muddi900 in https://github.com/burnash/gspread/pull/1148
* improve CI workflow - upgrade setuptools to fix CVE by @lavigne958 in https://github.com/burnash/gspread/pull/1179
* Bump codespell from 2.2.2 to 2.2.4 by @dependabot in https://github.com/burnash/gspread/pull/1178
* Bump bandit from 1.7.4 to 1.7.5 by @dependabot in https://github.com/burnash/gspread/pull/1177
* Bump black from 22.12.0 to 23.1.0 by @dependabot in https://github.com/burnash/gspread/pull/1168
* Update user-guide.rst to include a warning by @alsaenko in https://github.com/burnash/gspread/pull/1181
* Fixed typo in docs/user-guide.rst by @raboba2re in https://github.com/burnash/gspread/pull/1182
* Bump black from 23.1.0 to 23.3.0 by @dependabot in https://github.com/burnash/gspread/pull/1183
* Handle cases when rgbColor is not set by @lavigne958 in https://github.com/burnash/gspread/pull/1184
5.7.2 (2022-12-03)
------------------
* Fix: `hidden` property might not be set from the API by @lavigne958 in https://github.com/burnash/gspread/pull/1151
5.7.1 (2022-11-17)
------------------
* Fix dependencies required version by @lavigne958 in https://github.com/burnash/gspread/pull/1147
5.7.0 (2022-11-13)
------------------
* chore: Update outdated LICENSE year by @bluzir in https://github.com/burnash/gspread/pull/1124
* add dependabot to maintain dependencies by @lavigne958 in https://github.com/burnash/gspread/pull/1126
* improve trigger on CI by @lavigne958 in https://github.com/burnash/gspread/pull/1134
* Bump bandit from 1.7.0 to 1.7.4 by @dependabot in https://github.com/burnash/gspread/pull/1133
* cancel previous run on same ref by @lavigne958 in https://github.com/burnash/gspread/pull/1135
* Bump actions/setup-python from 2 to 4 by @dependabot in https://github.com/burnash/gspread/pull/1127
* Bump actions/checkout from 2 to 3 by @dependabot in https://github.com/burnash/gspread/pull/1128
* Bump black from 22.3.0 to 22.10.0 by @dependabot in https://github.com/burnash/gspread/pull/1132
* Bump isort from 5.9.3 to 5.10.1 by @dependabot in https://github.com/burnash/gspread/pull/1131
* Bump codespell from 2.1.0 to 2.2.2 by @dependabot in https://github.com/burnash/gspread/pull/1130
* add named tuple for `DateTimeRenderOption` by @lavigne958 in https://github.com/burnash/gspread/pull/1136
* Feature/copy cut paste by @lavigne958 in https://github.com/burnash/gspread/pull/1138
* isSheetHidden method added to worksheet.py by @SazidAF in https://github.com/burnash/gspread/pull/1140
5.6.2 (2022-10-23)
------------------
* update parent folder for `client.copy` method by @lavigne958 in https://github.com/burnash/gspread/pull/1123
5.6.0 (2022-09-10)
------------------
* Fix `clear_note` method when using numeric boundaries by @lavigne958 in https://github.com/burnash/gspread/pull/1106
* Fix a typo in the permissions:create API payload by @jiananma in https://github.com/burnash/gspread/pull/1107
* Fix spreadsheet URL by @lavigne958 in https://github.com/burnash/gspread/pull/1110
* Return created permission on `Spreadsheet.share()` by @lavigne958 in https://github.com/burnash/gspread/pull/1111
* (fixed #1113) Supply correct Google API v3 permission for domains by @NickCrews in https://github.com/burnash/gspread/pull/1115
* Bugfix/numericese all by @lavigne958 in https://github.com/burnash/gspread/pull/1119
New Contributors
****************
* @jiananma made their first contribution in https://github.com/burnash/gspread/pull/1107
* @NickCrews made their first contribution in https://github.com/burnash/gspread/pull/1115
5.5.0 (2022-08-31)
------------------
* Use pathlib by @lavigne958 in https://github.com/burnash/gspread/pull/1057
* Migrate to drive API V3 by @lavigne958 in https://github.com/burnash/gspread/pull/1060
* Implement __eq__ method for `Cell` by @chisvi in https://github.com/burnash/gspread/pull/1063
* Add missing documentation on `set_timeout` by @lavigne958 in https://github.com/burnash/gspread/pull/1070
* Add method to transfer / accept ownership of a spreadsheet by @lavigne958 in https://github.com/burnash/gspread/pull/1068
* Add `client_factory` param to `auth` methods by @jlumbroso in https://github.com/burnash/gspread/pull/1075
* Fix `list_protected_ranges` by @lavigne958 in https://github.com/burnash/gspread/pull/1076
* Add function to convert column letter to column index by @lavigne958 in https://github.com/burnash/gspread/pull/1077
* Fix docstring name of named_range() param by @dgilman in https://github.com/burnash/gspread/pull/1081
* Fix grammar in docstring for client.export by @dgilman in https://github.com/burnash/gspread/pull/1080
* Many typo fixes to worksheet docstrings by @dgilman in https://github.com/burnash/gspread/pull/1083
* Fix function `numericise_all` by @lavigne958 in https://github.com/burnash/gspread/pull/1082
* Fix documentation about `oauth_from_dict` by @lavigne958 in https://github.com/burnash/gspread/pull/1088
* inherit_from_before option for insert_row/insert_rows by @yongrenjie in https://github.com/burnash/gspread/pull/1092
* add method to change the color of a tab by @lavigne958 in https://github.com/burnash/gspread/pull/1095
* docs: Fix a few typos by @timgates42 in https://github.com/burnash/gspread/pull/1094
* Fix typo in `Worksheet.batch_format` method by @lavigne958 in https://github.com/burnash/gspread/pull/1101
New Contributors
****************
* @chisvi made their first contribution in https://github.com/burnash/gspread/pull/1063
* @jlumbroso made their first contribution in https://github.com/burnash/gspread/pull/1075
* @yongrenjie made their first contribution in https://github.com/burnash/gspread/pull/1092
5.4.0 (2022-06-01)
------------------
* fix typo by @joswlv in https://github.com/burnash/gspread/pull/1031
* Fix error message in `get_all_records` by @lavigne958 in https://github.com/burnash/gspread/pull/1028
* Added feature request #1022. Auto resizing is now available for rows … by @mketer1 in https://github.com/burnash/gspread/pull/1033
* add new method to hide/show a worksheet by @lavigne958 in https://github.com/burnash/gspread/pull/1030
* feat: Download PDF from Spreadsheet #1035 by @100paperkite in https://github.com/burnash/gspread/pull/1036
* Add test on `auto_resize_columns` by @lavigne958 in https://github.com/burnash/gspread/pull/1039
* Add method to unmerge cells by @lavigne958 in https://github.com/burnash/gspread/pull/1040
* Add method to delete a protected range by @lavigne958 in https://github.com/burnash/gspread/pull/1042
* Feature/clean organize documentation by @lavigne958 in https://github.com/burnash/gspread/pull/1043
* Add warning about deprecated oauth flow by @lavigne958 in https://github.com/burnash/gspread/pull/1047
* Add new `batch_format` method. by @lavigne958 in https://github.com/burnash/gspread/pull/1049
* Encode string to utf-8 when importing CSV content by @lavigne958 in https://github.com/burnash/gspread/pull/1054
New Contributors
****************
* @joswlv made their first contribution in https://github.com/burnash/gspread/pull/1031
* @mketer1 made their first contribution in https://github.com/burnash/gspread/pull/1033
* @100paperkite made their first contribution in https://github.com/burnash/gspread/pull/1036
5.3.2 (2022-04-12)
------------------
* Bugfix/black python3.10 by @lavigne958 in https://github.com/burnash/gspread/pull/1020
* Automate releases by @lavigne958 in https://github.com/burnash/gspread/pull/1025
* Bugfix/get all record duplicated columns by @lavigne958 in https://github.com/burnash/gspread/pull/1021
5.3.0 (2022-03-28)
------------------
* Feature/rework test cassettes recording by @lavigne958 in https://github.com/burnash/gspread/pull/1004
* add method list protected ranges by @lavigne958 in https://github.com/burnash/gspread/pull/1008
* Add new methods to add/list/delete dimensionGroups by @lavigne958 in https://github.com/burnash/gspread/pull/1010
* Add method to hide rows/columns by @lavigne958 in https://github.com/burnash/gspread/pull/1012
* Add ability to rename Spreadsheets (via a new Spreadsheet.update_title) by @jansim in https://github.com/burnash/gspread/pull/1013
## New Contributors
* @jansim made their first contribution in https://github.com/burnash/gspread/pull/1013
5.2.0 (2022-02-27)
------------------
* Copy comments when during spreadsheet copy by @lavigne958 in https://github.com/burnash/gspread/pull/979
* Update user-guide.rst by @maky-hnou in https://github.com/burnash/gspread/pull/980
* merge setup test cassettes by @lavigne958 in https://github.com/burnash/gspread/pull/982
* Feature/add header validation get all records by @lavigne958 in https://github.com/burnash/gspread/pull/984
* Add timeout to client by @lavigne958 in https://github.com/burnash/gspread/pull/987
* Feature/update timezone and locale by @lavigne958 in https://github.com/burnash/gspread/pull/989
* Feature/make case comparison in find by @lavigne958 in https://github.com/burnash/gspread/pull/990
* Updated API rate limits by @hvinayan in https://github.com/burnash/gspread/pull/993
* Feature/prevent insert row to sheet with colon by @lavigne958 in https://github.com/burnash/gspread/pull/992
## New Contributors
* @maky-hnou made their first contribution in https://github.com/burnash/gspread/pull/980
* @hvinayan made their first contribution in https://github.com/burnash/gspread/pull/993
5.1.1 (2021-12-22)
------------------
* Fix documentation about oauth (#975 by @lavigne958)
5.1.0 (2021-12-22)
------------------
* Codespell skip docs build folder (#962 by @lavigne958)
* Update contributing guidelines (#964 by @lavigne958)
* Add oauth from dict (#967 by @lavigne958)
* Update README.md to include badges (#970 by @lavigne958)
* Add new method to get all values as a list of Cells (#968 by @lavigne958)
* automatic conversion of a cell letter to uppercase (#972 by @Burovytskyi)
5.0.0 (2021-11-26)
------------------
* Fix a typo in HISTORY.rst (#904 by @TurnrDev)
* Fix typo and fix return value written in docstrings (#903 by @rariyama)
* Add deprecation warning for delete_row method in documentation (#909 by @javad94)
* split files `models.py` and `test.py` (#912 by @lavigne958)
* parent 39d1ecb59ca3149a8f46094c720efab883a0dc11 author Christian Clauss <cclauss@me.com> 1621149013 +0200 commit
ter Christian Clauss <cclauss@me.com> 1630103641 +0200 (#869 by @cclaus)
* Enable code linter in CI (#915 by @lavigne958)
* isort your imports (again), so you don't have to (#914 by @cclaus)
* lint_python.yml: Try 'tox -e py' to test current Python (#916 by @cclaus)
* Add more flake8 tests (#917 by @cclaus)
* Update test suite (#918 by @cclaus)
* Avoid IndexError when row_values() returns an empty row (#920 by @cclaus)
* Bugfix - remove wrong argument in `batch_update` docstring (#912 by @lavigne958)
* Improvement - Add `Worksheet.index` property (#922 by @lavigne958)
* Add ability to create directory if it does not exist before saving the credentials to disk. (#925 by @benhoman)
* Update test framework and VCR and cassettes (#926 by @lavigne958)
* Delete .travis.yml (#928 by @cclaus)
* Update tox.ini with all linting commands under lint env (by @lavigne958)
* Build package and docs in CI (#930 by @lavigne958)
* Update oauth2.rst (#933 by @amlestin)
* Update the link to the Google Developers Console (#934 by @Croebh)
* allow tests to run on windows, add and improve tests in WorksheetTests, add test on unbounded range,
use canonical range as specified in the API, add test cassettes, prevent InvalidGridRange,
improve code formatting (#937 by @Fendse)
* fix fully qualified class names in API documentation (#944 by @geoffbeier)
* fix editor_users_emails - get only from list not all users added to spreadsheet (#939 by @Lukasz)
* add shadow method to get a named range from a speadsheet instance (#941 by @lavigne958)
* auto_resize_columns (#948 by @FelipeSantos75)
* add functions for defining, deleting and listing named ranges (#945 by @p-doyle)
* Implement `open` sheet within Drive folder (#951 by @datavaluepeople)
* Fix get range for unbounded ranges (#954 by @lavigne958)
* remove potential I/O when reading spreadsheet title (956 by @lavigne958)
* Add include_values_in_response to append_row & append_rows (#957 by @martimarkov)
* replace raw string "ROWS" & "COLUMNS" to Dimension named tuple,
replace raw string "FORMATTED_VALUE", "UNFORMATTED_VALUE", "FORMULA" to ValueRenderOption named tuple,
replace raw string "RAW", "USER_ENTERED" to ValueInputOption named tuple (#958 by @ccppoo)
4.0.1 (2021-08-07)
------------------
* Do not overwrite original value when trying to convert to a number (#902 by @lavigne958)
4.0.0 (2021-08-01)
------------------
* Changed `Worksheet.find()` method returns `None` if nothing is found (#899 by @GastonBC)
* Add `Worksheet.batch_clear()` to clear multiple ranges. (#897 by @lavigne958)
* Fix `copy_permission` argument comparison in `Client.copy()` method (#898 by @lavigne958)
* Allow creation of spreadhsheets in a shared drive (#895 by @lavigne958)
* Allow `gspread.oauth()` to accept a custom credential file (#891 by @slmtpz)
* Update `tox.ini`, remove python2 from env list (#887 by @cclaus)
* Add `SpreadSheet.get_worksheet_by_id()` method (#857 by @a-crovetto)
* Fix `store_credentials()` when `authorized_user_filename` is passed (#884 by @neuenmuller)
* Remove python2 (#879 by @lavigne958)
* Use `Makefile` to run tests (#883 by @lavigne958)
* Update documentation `Authentication:For End Users` using OAuth Client ID (#835 by @ManuNaEira)
* Allow fetching named ranges from `Worksheet.range()` (#809 by @agatti)
* Update README to only mention python3.3+ (#877 by @lavigne958)
* Fetch `creation` and `lastUpdate` time from `SpreadSheet` on open (#872 by @lavigne958)
* Fix bug with `Worksheet.insert_row()` with `value_input_option` argument (#873 by @elijabesu)
* Fix typos in doc and comments (#868 by @cclauss)
* Auto cast numeric values from sheet cells to python int or float (#866 by @lavigne958)
* Add `Worksheet.get_values()` method (#775 by @burnash)
* Allow `gspread.oauth()` to accept a custom filename (#847 by @bastienboutonnet)
* Document dictionary credentials auth (#860 by @dmytrostriletskyi)
* Add `Worksheet.get_note()` (#855 by @water-ghosts )
* Add steps for creating new keys (#856 by @hanzala-sohrab)
* Add `folder_id` argument to `Client.copy()` (#851 by @punnerud)
* Fix typos in docstrings (#848 by @dgilman)
3.7.0 (2021-02-18)
------------------
* Add `Worksheet.insert_note()`, `Worksheet.update_note()`, `Worksheet.clear_note()` (#818 by @lavigne958)
* Update documentation: oauth2.rst (#836 by @Prometheus3375)
* Documentation fixes (#838 by @jayeshmanani)
* Documentation fixes (#845 by @creednaylor)
* Add `Worksheet.insert_cols()` (#802 by @AlexeyDmitriev)
* Documentation fixes (#814 by @hkuffel)
* Update README.md (#811 by @tasawar-hussain)
* Add `value_render_option` parameter to `Worksheet.get_all_records()` (#776 by @damgad)
* Remove `requests` from `install_requires` (#801)
* Simplify implementation of `Worksheet.insert_rows()` (#799 by @AlexeyDmitriev)
* Add `auth.service_account_from_dict()` (#785 b7 @mahenzon)
* Fix `ValueRange.from_json()` (#791 by @erakli)
* Update documentation: oauth2.rst (#794 by @elnjensen)
* Update documentation: oauth2.rst (#789 by @Takur0)
* Allow `auth` to be `None`. Fix #773 (#774 by @lepture)
3.6.0 (2020-04-30)
------------------
* Add `Worksheet.insert_rows()` (#734 by @tr-fi)
* Add `Worksheet.copy_to()` (#758 by @JoachimKoenigslieb)
* Add ability to create a cell instance using A1 notation (#765 by @tivaliy)
* Add `auth.service_account()` (#768)
* Add Authlib usage (#552 by @lepture)
3.5.0 (2020-04-23)
------------------
* Simplified OAuth2 flow (#762)
* Fix `Worksheet.delete_rows()` index error (#760 by @rafa-guillermo)
* Deprecate `Worksheet.delete_row()` (#766)
* Scope `Worksheet.find()` to a specific row or a column (#739 by @alfonsocv12)
* Add `Worksheet.add_protected_range()` #447 (#720 by @KesterChan01)
* Add ability to fetch cell address in A1 notation (#763 by @tivaliy)
* Add `Worksheet.delete_columns()` (#761 by @rafa-guillermo)
* Ignore numericising specific columns in `get_all_records` (#701 by @benjamindhimes)
* Add option ``folder_id`` when creating a spreadsheet (#754 by @Abdellam1994)
* Add `insertDataOption` to `Worksheet.append_row()` and `Worksheet.append_rows()` (#719 by @lobatt)
3.4.2 (2020-04-06)
------------------
* Fix Python 2 `SyntaxError` in models.py #751 (#752)
3.4.1 (2020-04-05)
------------------
* Fix `TypeError` when using gspread in google colab (#750)
3.4.0 (2020-04-05)
------------------
* Remove `oauth2client` in favor of `google-auth` #472, #529 (#637 by @BigHeadGeorge)
* Convert `oauth2client` credentials to `google-auth` (#711 by @aiguofer)
* Remove unnecessary `login()` from `gspread.authorize`
* Fix sheet name quoting issue (#554, #636, #716):
* Add quotes to worksheet title for get_all_values (#640 by @grlbrwrg, #717 by @zynaxsoft)
* Escaping title containing single quotes with double quotes (#730 by @vijay-shanker)
* Use `utils.absolute_range_name()` to handle range names (#748)
* Fix `numericise()`: add underscores test to work in python2 and <python3.6 (#622 by @epicfaace)
* Add `supportsAllDrives` to Drive API requests (#709 by @justinr1234)
* Add `Worksheet.merge_cells()` (#713 by @lavigne958)
* Improve `Worksheet.merge_cells()` and add `merge_type` parameter (#742 by @aiguofer)
* Add `Worksheet.sort()` (#639 by @kirillgashkov)
* Add ability to reorder worksheets #570 (#571 by @robin900)
* Add `Spreadsheet.reorder_worksheets()`
* Add `Worksheet.update_index()`
* Add `test_update_cell_objects` (#698 by @ogroleg)
* Add `Worksheet.append_rows()` (#556 by @martinwarby, #694 by @fabytm)
* Add `Worksheet.delete_rows()` (#615 by @deverlex)
* Add Python 3.8 to Travis CI (#738 by @artemrys)
* Speed up `Client.open()` by querying files by title in Google Drive (#684 by @aiguofer)
* Add `freeze`, `set_basic_filter` and `clear_basic_filter` methods to `Worksheet` (#574 by @aiguofer)
* Use Drive API v3 for creating and deleting spreadsheets (#573 by @aiguofer)
* Implement `value_render_option` in `get_all_values` (#648 by @mklaber)
* Set position of a newly added worksheet (#688 by @djmgit)
* Add url properties for `Spreadsheet` and `Worksheet` (#725 by @CrossNox)
* Update docs: "APIs & auth" menu deprecation, remove outdated images in oauth2.rst (#706 by @manasouza)
3.3.1 (2020-04-01)
------------------
* Support old and new collections.abc.Sequence in `utils` (#745 by @timgates42)
3.3.0 (2020-03-12)
------------------
* Added `Spreadsheet.values_batch_update()` (#731)
* Added:
* `Worksheet.get()`
* `Worksheet.batch_get()`
* `Worksheet.update()`
* `Worksheet.batch_update()`
* `Worksheet.format()`
* Added more parameters to `Worksheet.append_row()` (#719 by @lobatt, #726)
* Fix usage of client.openall when a title is passed in (#572 by @aiguofer)
3.2.0 (2020-01-30)
------------------
* Fixed `gspread.utils.cell_list_to_rect()` on non-rect cell list (#613 by @skaparis)
* Fixed sharing from Team Drives (#646 by @wooddar)
* Fixed KeyError in list comprehension in `Spreadsheet.remove_permissions()` (#643 by @wooddar)
* Fixed typos in docstrings and a docstring type param (#690 by @pedrovhb)
* Clarified supported Python versions (#651 by @hugovk)
* Fixed the Exception message in `APIError` class (#634 by @lordofinsomnia)
* Fixed IndexError in `Worksheet.get_all_records()` (#633 by @AivanF)
* Added `Spreadsheet.values_batch_get()` (#705 by @aiguofer)
3.1.0 (2018-11-27)
------------------
* Dropped Python 2.6 support
* Fixed `KeyError` in `urllib.quote` in Python 2 (#605, #558)
* Fixed `Worksheet.title` being out of sync after using `update_title` (#542 by @ryanpineo)
* Fix parameter typos in docs (#616 by @bryanallen22)
* Miscellaneous docs fixes (#604 by @dgilman)
* Fixed typo in docs (#591 by @davidefiocco)
* Added a method to copy spreadsheets (#625 by @dsask)
* Added `with_link` attribute when sharing / adding permissions (#621 by @epicfaace)
* Added ability to duplicate a worksheet (#617)
* Change default behaviour of numericise function #499 (#502 by @danthelion)
* Added `stacklevel=2` to deprecation warnings
3.0.1 (2018-06-30)
------------------
* Fixed #538 (#553 by @ADraginda)
3.0.0 (2018-04-12)
------------------
* This version drops Google Sheets API v3 support.
- API v4 was the default backend since version 2.0.0.
- All v4-related code has been moved from `gspread.v4` module to `gspread` module.
2.1.1 (2018-04-08)
------------------
* Fixed #533 (#534 by @reallistic)
2.1.0 (2018-04-07)
------------------
* URL encode the range in the value_* functions (#530 by @aiguofer)
* Open team drive sheets by name (#527 by @ryantuck)
2.0.1 (2018-04-01)
------------------
* Fixed #518
* Include v4 in setup.py
* Fetch all spreadsheets in Spreadsheet.list_spreadsheet_files (#522 by @aiguofer)
2.0.0 (2018-03-11)
------------------
* Ported the library to Google Sheets API v4.
This is a transition release. The v3-related code is untouched,
but v4 is used by default. It is encouraged to move to v4 since
the API is faster and has more features.
API v4 is a significant change from v3. Some methods are not
backward compatible, so there's no support for this compatibility
in gspread either.
These methods and properties are not supported in v4:
* `Spreadsheet.updated`
* `Worksheet.updated`
* `Worksheet.export()`
* `Cell.input_value`
0.6.2 (2016-12-20)
------------------
* Remove deprecated HTTPError
0.6.1 (2016-12-20)
------------------
* Fixed error when inserting permissions #431
0.6.0 (2016-12-15)
------------------
* Added spreadsheet sharing functionality
* Added csv import
* Fixed bug where list of sheets isn't cleared on refetch
#429, #386
0.5.1 (2016-12-12)
------------------
* Fixed a missing return value in `utils.a1_to_rowcol`
* Fixed url parsing in `Client.open_by_url`
* Added `updated` property to `Spreadsheet` objects
0.5.0 (2016-12-12)
------------------
* Added method to create blank spreadsheets #253
* Added method to clear worksheets #156
* Added method to delete a row in a worksheet #337
* Changed `Worksheet.range` method to accept integers as coordinates #142
* Added `default_blank` parameter to `Worksheet.get_all_records` #423
* Use xml.etree.cElementTree when available to reduce memory usage #348
* Fixed losing input_value data from following cells in `Worksheet.insert_row` #338
* Deprecated `Worksheet.get_int_addr` and `Worksheet.get_addr_int`
in favour of `utils.a1_to_rowcol` and `utils.rowcol_to_a1` respectively
0.4.1 (2016-07-17)
------------------
* Fix exception format to support Python 2.6
0.4.0 (2016-06-30)
------------------
* Use request session's connection pool in HTTPSession
* Removed deprecated ClientLogin
0.3.0 (2015-12-15)
------------------
* Use Python requests instead of the native HTTPConnection object
* Optimized row_values and col_values
* Optimized row_values and col_values
Removed the _fetch_cells call for each method. This eliminates the
adverse effect on runtime for large worksheets.
Fixes #285, #190, #179, and #113
* Optimized row_values and col_values
Removed the _fetch_cells call for each method. This eliminates the
adverse effect on runtime for large worksheets.
Fixes #285, #190, #179, and #113
* Altered insert_row semantics to utilize range
This avoids issuing one API request per cell to retrieve the Cell
objects after the insertion row. This provides a significant speed-up
for insertions at the beginning of large sheets.
* Added mock tests for Travis (MockSpreadsheetTest)
* Fixed XML header issue with Python 3
* Fixed Worksheet.export function and associated test
* Added spreadsheet feed helper
* Add CellNotFound to module exports
Fixes #88
* Fixed utf8 encoding error caused by duplicate XML declarations
* Fixed AttributeError when URLError caught by HTTPError catch block
Fixes #257
* Added __iter__ method to Spreadsheet class
* Fixed export test
* Switched tests to oauth
0.2.5 (2015-04-22)
------------------
* Deprecation warning for ClientLogin #210
* Redirect github pages to ReadTheDocs
* Bugfixes
0.2.4 (2015-04-17)
------------------
* Output error response #219 #170 #194.
* Added instructions on how to get oAuth credentials to docs.
0.2.3 (2015-03-11)
------------------
* Fixed issue with `Spreadsheet.del_worksheet`.
* Automatically refresh OAuth2 token when it has expired.
* Added an `insert_row` method to `Worksheet`.
* Moved docs to Read The Docs.
* Added the `numeric_value` attribute to `Cell`.
* Added title property to `Spreadsheet`.
* Support for exporting worksheets.
* Added row selection for keys in `Worksheet.get_all_records`.
0.2.2 (2014-08-26)
------------------
* Fixed version not available for read-only spreadsheets bug
0.2.1 (2014-05-10)
------------------
* Added OAuth2 support
* Fixed regression bug #130. Not every POST needs If-Match header
0.2.0 (2014-05-09)
------------------
* New Google Sheets support.
* Fixed get_all_values() on empty worksheet.
* Bugfix in get_int_addr().
* Changed the HTTP connectivity from urllib to httlib for persistent http connections.
0.1.0 (2013-07-09)
------------------
* Support for deleting worksheets from a spreadsheet.
0.0.15 (2013-02-01)
------------------
* Couple of bugfixes.
0.0.14 (2013-01-31)
------------------
* Bugfix in Python 3.
0.0.12 (2011-12-25)
------------------
* Python 3 support.
0.0.9 (2011-12-16)
------------------
* Enter the Docs.
* New skinnier login method.
0.0.7 (2011-12-14)
------------------
* Pypi install bugfix.
0.0.6 (2011-12-13)
------------------
* Batch cells update.
0.0.2 (2011-12-12)
------------------
* New spreadsheet open methods:
- Client.open_by_key
- Client.open_by_url
0.0.1 (2011-12-12)
------------------
* Got rid of the wrapper.
* Support for pluggable http session object.
pre 0.0.1 (2011-12-02)
----------------------
* Hacked a wrapper around Google's Python client library.
================================================
FILE: LICENSE.txt
================================================
Copyright (C) 2011-2023 Anton Burnashev
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Google Spreadsheets Python API v4







## Maintainer needed
We are sorry to announce that we are currently unable to maintain Gspread.
We are looking for new maintainers to keep up the good work.
Feel free to reach out to us using this issue [#1570](https://github.com/burnash/gspread/issues/1570)
## Overview
Simple interface for working with Google Sheets.
Features:
- Open a spreadsheet by **title**, **key** or **URL**.
- Read, write, and format cell ranges.
- Sharing and access control.
- Batching updates.
## Installation
```sh
pip install gspread
```
Requirements: Python 3.8+.
## Basic Usage
1. [Create credentials in Google API Console](http://gspread.readthedocs.org/en/latest/oauth2.html)
2. Start using gspread
```python
import gspread
# First you need access to the Google API. Based on the route you
# chose in Step 1, call either service_account(), oauth() or api_key().
gc = gspread.service_account()
# Open a sheet from a spreadsheet in one go
wks = gc.open("Where is the money Lebowski?").sheet1
# Update a range of cells using the top left corner address
wks.update([[1, 2], [3, 4]], "A1")
# Or update a single cell
wks.update_acell("B42", "it's down there somewhere, let me take another look.")
# Format the header
wks.format('A1:B1', {'textFormat': {'bold': True}})
```
## v5.12 to v6.0 Migration Guide
### Upgrade from Python 3.7
Python 3.7 is [end-of-life](https://devguide.python.org/versions/). gspread v6 requires a minimum of Python 3.8.
### Change `Worksheet.update` arguments
The first two arguments (`values` & `range_name`) have swapped (to `range_name` & `values`). Either swap them (works in v6 only), or use named arguments (works in v5 & v6).
As well, `values` can no longer be a list, and must be a 2D array.
```diff
- file.sheet1.update([["new", "values"]])
+ file.sheet1.update([["new", "values"]]) # unchanged
- file.sheet1.update("B2:C2", [["54", "55"]])
+ file.sheet1.update([["54", "55"]], "B2:C2")
# or
+ file.sheet1.update(range_name="B2:C2", values=[["54", "55"]])
```
### More
<details><summary>See More Migration Guide</summary>
### Change colors from dictionary to text
v6 uses hexadecimal color representation. Change all colors to hex. You can use the compatibility function `gspread.utils.convert_colors_to_hex_value()` to convert a dictionary to a hex string.
```diff
- tab_color = {"red": 1, "green": 0.5, "blue": 1}
+ tab_color = "#FF7FFF"
file.sheet1.update_tab_color(tab_color)
```
### Switch lastUpdateTime from property to method
```diff
- age = spreadsheet.lastUpdateTime
+ 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.
They can be silenced by setting the `GSPREAD_SILENCE_WARNINGS` environment variable to `1`
### Add more data to `gspread.Worksheet.__init__`
```diff
gc = gspread.service_account(filename="google_credentials.json")
spreadsheet = gc.open_by_key("{{key}}")
properties = spreadsheet.fetch_sheet_metadata()["sheets"][0]["properties"]
- worksheet = gspread.Worksheet(spreadsheet, properties)
+ worksheet = gspread.Worksheet(spreadsheet, properties, spreadsheet.id, gc.http_client)
```
</details>
## More Examples
### Opening a Spreadsheet
```python
# You can open a spreadsheet by its title as it appears in Google Docs
sh = gc.open('My poor gym results') # <-- Look ma, no keys!
# If you want to be specific, use a key (which can be extracted from
# the spreadsheet's url)
sht1 = gc.open_by_key('0BmgG6nO_6dprdS1MN3d3MkdPa142WFRrdnRRUWl1UFE')
# Or, if you feel really lazy to extract that key, paste the entire url
sht2 = gc.open_by_url('https://docs.google.com/spreadsheet/ccc?key=0Bm...FE&hl')
```
### Creating a Spreadsheet
```python
sh = gc.create('A new spreadsheet')
# But that new spreadsheet will be visible only to your script's account.
# To be able to access newly created spreadsheet you *must* share it
# with your email. Which brings us to…
```
### Sharing a Spreadsheet
```python
sh.share('otto@example.com', perm_type='user', role='writer')
```
### Selecting a Worksheet
```python
# Select worksheet by index. Worksheet indexes start from zero
worksheet = sh.get_worksheet(0)
# By title
worksheet = sh.worksheet("January")
# Most common case: Sheet1
worksheet = sh.sheet1
# Get a list of all worksheets
worksheet_list = sh.worksheets()
```
### Creating a Worksheet
```python
worksheet = sh.add_worksheet(title="A worksheet", rows="100", cols="20")
```
### Deleting a Worksheet
```python
sh.del_worksheet(worksheet)
```
### Getting a Cell Value
```python
# With label
val = worksheet.get('B1').first()
# With coords
val = worksheet.cell(1, 2).value
```
### Getting All Values From a Row or a Column
```python
# Get all values from the first row
values_list = worksheet.row_values(1)
# Get all values from the first column
values_list = worksheet.col_values(1)
```
### Getting All Values From a Worksheet as a List of Lists
```python
from gspread.utils import GridRangeType
list_of_lists = worksheet.get(return_type=GridRangeType.ListOfLists)
```
### Getting a range of values
Receive only the cells with a value in them.
```python
>>> worksheet.get("A1:B4")
[['A1', 'B1'], ['A2']]
```
Receive a rectangular array around the cells with values in them.
```python
>>> worksheet.get("A1:B4", pad_values=True)
[['A1', 'B1'], ['A2', '']]
```
Receive an array matching the request size regardless of if values are empty or not.
```python
>>> worksheet.get("A1:B4", maintain_size=True)
[['A1', 'B1'], ['A2', ''], ['', ''], ['', '']]
```
### Finding a Cell
```python
# Find a cell with exact string value
cell = worksheet.find("Dough")
print("Found something at R%sC%s" % (cell.row, cell.col))
# Find a cell matching a regular expression
amount_re = re.compile(r'(Big|Enormous) dough')
cell = worksheet.find(amount_re)
```
### Finding All Matched Cells
```python
# Find all cells with string value
cell_list = worksheet.findall("Rug store")
# Find all cells with regexp
criteria_re = re.compile(r'(Small|Room-tiering) rug')
cell_list = worksheet.findall(criteria_re)
```
### Updating Cells
```python
# Update a single cell
worksheet.update_acell('B1', 'Bingo!')
# Update a range
worksheet.update([[1, 2], [3, 4]], 'A1:B2')
# Update multiple ranges at once
worksheet.batch_update([{
'range': 'A1:B2',
'values': [['A1', 'B1'], ['A2', 'B2']],
}, {
'range': 'J42:K43',
'values': [[1, 2], [3, 4]],
}])
```
### Get unformatted cell value or formula
```python
from gspread.utils import ValueRenderOption
# Get formatted cell value as displayed in the UI
>>> worksheet.get("A1:B2")
[['$12.00']]
# Get unformatted value from the same cell range
>>> worksheet.get("A1:B2", value_render_option=ValueRenderOption.unformatted)
[[12]]
# Get formula from a cell
>>> worksheet.get("C2:D2", value_render_option=ValueRenderOption.formula)
[['=1/1024']]
```
### Add data validation to a range
```python
import gspread
from gspread.utils import ValidationConditionType
# Restrict the input to greater than 10 in a single cell
worksheet.add_validation(
'A1',
ValidationConditionType.number_greater,
[10],
strict=True,
inputMessage='Value must be greater than 10',
)
# Restrict the input to Yes/No for a specific range with dropdown
worksheet.add_validation(
'C2:C7',
ValidationConditionType.one_of_list,
['Yes',
'No',]
showCustomUi=True
)
```
## Documentation
[Documentation]\: [https://gspread.readthedocs.io/][Documentation]
[Documentation]: https://gspread.readthedocs.io/en/latest/
### Ask Questions
The best way to get an answer to a question is to ask on [Stack Overflow with a gspread tag](http://stackoverflow.com/questions/tagged/gspread?sort=votes&pageSize=50).
## Contributors
[List of contributors](https://github.com/burnash/gspread/graphs/contributors)
## How to Contribute
Please make sure to take a moment and read the [Code of Conduct](https://github.com/burnash/gspread/blob/master/.github/CODE_OF_CONDUCT.md).
### Report Issues
Please report bugs and suggest features via the [GitHub Issues](https://github.com/burnash/gspread/issues).
Before opening an issue, search the tracker for possible duplicates. If you find a duplicate, please add a comment saying that you encountered the problem as well.
### Improve Documentation
[Documentation](https://gspread.readthedocs.io/) is as important as code. If you know how to make it more consistent, readable and clear, please submit a pull request. The documentation files are in [`docs`](https://github.com/burnash/gspread/tree/master/docs) folder, use [reStructuredText](http://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html#rst-index) markup and rendered by [Sphinx](http://www.sphinx-doc.org/).
### Contribute code
Please make sure to read the [Contributing Guide](https://github.com/burnash/gspread/blob/master/.github/CONTRIBUTING.md) before making a pull request.
================================================
FILE: docs/_templates/layout.html
================================================
{% extends "!layout.html" %}
{% block htmltitle %}
{{ super() }}
<!-- Google Tag Manager -->
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-MQMWH92');</script>
<!-- End Google Tag Manager -->
{% endblock %}
{% block extrabody %}
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MQMWH92"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
{% endblock %}
{% block footer %}
{{ super() }}
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-44504650-1']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
{% endblock %}
================================================
FILE: docs/advanced.rst
================================================
Advanced Usage
==============
Custom Authentication
---------------------
Google Colaboratory
~~~~~~~~~~~~~~~~~~~
If you familiar with the Jupyter Notebook, `Google Colaboratory <https://colab.research.google.com/>`_ is probably the easiest way to get started using gspread::
from google.colab import auth
auth.authenticate_user()
import gspread
from google.auth import default
creds, _ = default()
gc = gspread.authorize(creds)
See the full example in the `External data: Local Files, Drive, Sheets, and Cloud Storage <https://colab.research.google.com/notebooks/io.ipynb#scrollTo=sOm9PFrT8mGG>`_ notebook.
Using Authlib
~~~~~~~~~~~~~
Using ``Authlib`` instead of ``google-auth``. Similar to `google.auth.transport.requests.AuthorizedSession <https://google-auth.readthedocs.io/en/latest/reference/google.auth.transport.requests.html#google.auth.transport.requests.AuthorizedSession>`_ Authlib's ``AssertionSession`` can automatically refresh tokens.::
import json
from gspread import Client
from authlib.integrations.requests_client import AssertionSession
def create_assertion_session(conf_file, scopes, subject=None):
with open(conf_file, 'r') as f:
conf = json.load(f)
token_url = conf['token_uri']
issuer = conf['client_email']
key = conf['private_key']
key_id = conf.get('private_key_id')
header = {'alg': 'RS256'}
if key_id:
header['kid'] = key_id
# Google puts scope in payload
claims = {'scope': ' '.join(scopes)}
return AssertionSession(
grant_type=AssertionSession.JWT_BEARER_GRANT_TYPE,
token_endpoint=token_url,
issuer=issuer,
audience=token_url,
claims=claims,
subject=subject,
key=key,
header=header,
)
scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive',
]
session = create_assertion_session('your-google-conf.json', scopes)
gc = Client(None, session)
wks = gc.open("Where is the money Lebowski?").sheet1
wks.update_acell('B2', "it's down there somewhere, let me take another look.")
# Fetch a cell range
cell_list = wks.range('A1:B7')
================================================
FILE: docs/api/auth.rst
================================================
Auth
====
.. automodule:: gspread.auth
:members:
================================================
FILE: docs/api/client.rst
================================================
Client
======
.. autoclass:: gspread.Client
:members:
================================================
FILE: docs/api/exceptions.rst
================================================
Exceptions
==========
.. autoexception:: gspread.exceptions.APIError
.. autoexception:: gspread.exceptions.GSpreadException
.. autoexception:: gspread.exceptions.IncorrectCellLabel
.. autoexception:: gspread.exceptions.InvalidInputValue
.. autoexception:: gspread.exceptions.NoValidUrlKeyFound
.. autoexception:: gspread.exceptions.SpreadsheetNotFound
.. autoexception:: gspread.exceptions.UnSupportedExportFormat
.. autoexception:: gspread.exceptions.WorksheetNotFound
================================================
FILE: docs/api/http_client.rst
================================================
HTTP Client
===========
.. note::
This class is not intended to be used directly.
It is used by all gspread models to interact with the Google API
.. autoclass:: gspread.HTTPClient
:members:
.. autoclass:: gspread.BackOffHTTPClient
:members:
================================================
FILE: docs/api/index.rst
================================================
API Reference
=============
.. toctree::
:maxdepth: 2
top-level
auth
client
http_client
models/index
utils
exceptions
================================================
FILE: docs/api/models/cell.rst
================================================
Cell
====
.. autoclass:: gspread.cell.Cell
:members:
================================================
FILE: docs/api/models/index.rst
================================================
Models
======
The models represent common spreadsheet entities: :class:`a spreadsheet <gspread.spreadsheet.Spreadsheet>`,
:class:`a worksheet <gspread.worksheet.Worksheet>` and :class:`a cell <gspread.cell.Cell>`.
.. note::
The classes described below should not be instantiated by the end-user. Their
instances result from calling other objects' methods.
.. toctree::
:maxdepth: 2
spreadsheet
worksheet
cell
================================================
FILE: docs/api/models/spreadsheet.rst
================================================
Spreadsheet
===========
.. autoclass:: gspread.spreadsheet.Spreadsheet
:members:
================================================
FILE: docs/api/models/worksheet.rst
================================================
Worksheet
=========
ValueRange
----------
.. autoclass:: gspread.worksheet.ValueRange
:members:
Worksheet
---------
.. autoclass:: gspread.worksheet.Worksheet
:members:
================================================
FILE: docs/api/top-level.rst
================================================
Top level
=========
.. module:: gspread
.. autofunction:: oauth
.. autofunction:: service_account
.. autofunction:: authorize
================================================
FILE: docs/api/utils.rst
================================================
Utils
=====
.. automodule:: gspread.utils
:members:
:undoc-members:
================================================
FILE: docs/community.rst
================================================
Community Extensions
====================
.. _gspread-formating-label:
gspread-formating
~~~~~~~~~~~~~~~~~
`gspread-formatting <https://github.com/robin900/gspread-formatting>`_ offers extensive functionality to help you when you go beyond basic format
provided by ``gspread``.
.. _gspread-pandas-label:
Using gspread with pandas
~~~~~~~~~~~~~~~~~~~~~~~~~
You can find the below libraries to use gspread with pandas:
* `gspread-pandas <https://github.com/aiguofer/gspread-pandas>`_
* `gspread-dataframe <https://github.com/robin900/gspread-dataframe>`_
.. _gspread-orm-label:
Object Relational Mappers (ORMs)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The `gspread-models <https://github.com/s2t2/gspread-models-py>`_ package provides a straightforward and intuitive model-based
query interface, making it easy to interact with Google Sheets as if it were more like a database.
================================================
FILE: docs/conf.py
================================================
#
# gspread documentation build configuration file, created by
# sphinx-quickstart on Thu Dec 15 14:44:32 2011.
#
# This file is execfile()d with the current directory set to its containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import sys
from datetime import date
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(".."))
from gspread import __version__ # noqa: E402
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"sphinx.ext.doctest",
"sphinx.ext.todo",
"sphinx.ext.coverage",
"sphinx.ext.ifconfig",
"sphinx.ext.intersphinx",
"sphinx_toolbox.more_autodoc.autonamedtuple",
"sphinx.ext.autodoc",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# The suffix of source filenames.
source_suffix = ".rst"
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "gspread"
copyright = "%s, Anton Burnashev" % date.today().year
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = __version__
# The full version, including alpha/beta/rc tags.
release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ["_build"]
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# -- Options for HTML output ---------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = "sphinx_rtd_theme"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
# html_static_path = ["_static"]
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = "gspreaddoc"
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
("index", "gspread.tex", "gspread Documentation", "Anton Burnashev", "manual"),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [("index", "gspread", "gspread Documentation", ["Anton Burnashev"], 1)]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
"index",
"gspread",
"gspread Documentation",
"Anton Burnashev",
"gspread",
"Google Spreadsheets Python API.",
"Miscellaneous",
),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# -- Options for Epub output ---------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = "gspread"
epub_author = "Anton Burnashev"
epub_publisher = "Anton Burnashev"
epub_copyright = "%s, Anton Burnashev" % date.today().year
# The language of the text. It defaults to the language option
# or en if the language is not set.
# epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
# epub_scheme = ''
# The unique identifier of the text. This can be a ISBN number
# or the project homepage.
# epub_identifier = ''
# A unique identification for the text.
# epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
# epub_cover = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
# epub_pre_files = []
# HTML files that should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
# epub_post_files = []
# A list of files that should not be packed into the epub file.
# epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
# epub_tocdepth = 3
# Allow duplicate toc entries.
# epub_tocdup = True
# Intersphinx configuration
intersphinx_mapping = {
"python": ("https://docs.python.org/3.6", (None, "python-inv.txt")),
}
================================================
FILE: docs/index.rst
================================================
gspread
=======
`gspread`_ is a Python API for Google Sheets.
Features:
- Google Sheets API v4.
- Open a spreadsheet by title, key or url.
- Read, write, and format cell ranges.
- Sharing and access control.
- Batching updates.
Installation
------------
.. code:: sh
pip install gspread
Requirements: Python 3+.
Quick Example
-------------
.. code:: python
import gspread
gc = gspread.service_account()
# Open a sheet from a spreadsheet in one go
wks = gc.open("Where is the money Lebowski?").sheet1
# Update a range of cells using the top left corner address
wks.update([[1, 2], [3, 4]], 'A1')
# Or update a single cell
wks.update_acell('B42', "it's down there somewhere, let me take another look.")
# Format the header
wks.format('A1:B1', {'textFormat': {'bold': True}})
Getting Started
---------------
.. toctree::
:maxdepth: 2
oauth2
Usage
-----
.. toctree::
:maxdepth: 2
user-guide
Advanced
--------
.. toctree::
:maxdepth: 2
advanced
Community extensions
--------------------
.. toctree::
:maxdepth: 2
community
API Documentation
---------------------------
.. toctree::
:maxdepth: 2
api/index
How to Contribute
-----------------
Please make sure to take a moment and read the `Code of Conduct`_.
Ask Questions
~~~~~~~~~~~~~
The best way to get an answer to a question is to ask on `Stack Overflow
with a gspread tag`_.
Report Issues
~~~~~~~~~~~~~
Please report bugs and suggest features via the `GitHub Issues`_.
Before opening an issue, search the tracker for possible duplicates. If
you find a duplicate, please add a comment saying that you encountered
the problem as well.
Contribute code
~~~~~~~~~~~~~~~
Please make sure to read the `Contributing Guide`_ before making a pull
request.
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
.. _gspread: https://github.com/burnash/gspread
.. _Obtain OAuth2 credentials from Google Developers Console: oauth2.html
.. _Code of Conduct: https://github.com/burnash/gspread/blob/master/.github/CODE_OF_CONDUCT.md
.. _Stack Overflow with a gspread tag: http://stackoverflow.com/questions/tagged/gspread?sort=votes&pageSize=50
.. _GitHub Issues: https://github.com/burnash/gspread/issues
.. _Contributing Guide: https://github.com/burnash/gspread/blob/master/.github/CONTRIBUTING.md
================================================
FILE: docs/oauth2.rst
================================================
Authentication
==============
To access spreadsheets your application needs to authenticate itself with the Google Sheets API. Choose from the following options.
#. Create an :ref:`API key <api-key>` if you’d like to only open public spreadsheets.
#. (or) Create a :ref:`OAuth Client ID <oauth-client-id>` if you’d like to access spreadsheets on behalf of end users (including yourself).
- When your application runs, it will prompt the user to authorize it.
#. (or) Create a :ref:`Service Account <service-account>` to access spreadsheets as a standalone bot.
- Service accounts get their own email address, so can be authorized by sharing the Sheet with the account in the same way it is shared with a person.
- Service accounts don't need any explicit permissions to access Sheets that are shared to "anyone with the URL".
An API Key is the easiest option, but to access private Sheets you need authorization. To provide that interactively, use an OAuth Client ID. To pre-configure authorization, use a Service Account.
.. _enable-api-access:
Enable API Access for a Project
-------------------------------
1. Head to `Google Developers Console <https://console.developers.google.com/>`_ and create a new project (or select the one you already have).
2. In the box labeled "Search for APIs and Services", search for "Google Drive API" and enable it.
3. In the box labeled "Search for APIs and Services", search for "Google Sheets API" and enable it.
.. _service-account:
For Bots: Using Service Account
-------------------------------
A service account is a special type of Google account intended to represent a non-human user that needs to authenticate and be authorized to access data in Google APIs [sic].
Since it's a separate account, by default it does not have access to any spreadsheet until you share it with this account. Just like any other Google account.
Here's how to get one:
1. :ref:`enable-api-access` if you haven't done it yet.
2. Go to "APIs & Services > Credentials" and choose "Create credentials > Service account key".
3. Fill out the form
4. Click "Create" and "Done".
5. Press "Manage service accounts" above Service Accounts.
6. Press on **⋮** near recently created service account and select "Manage keys" and then click on "ADD KEY > Create new key".
7. Select JSON key type and press "Create".
You will automatically download a JSON file with credentials. It may look like this:
::
{
"type": "service_account",
"project_id": "api-project-XXX",
"private_key_id": "2cd … ba4",
"private_key": "-----BEGIN PRIVATE KEY-----\nNrDyLw … jINQh/9\n-----END PRIVATE KEY-----\n",
"client_email": "473000000000-yoursisdifferent@developer.gserviceaccount.com",
"client_id": "473 … hd.apps.googleusercontent.com",
...
}
Remember the path to the downloaded credentials file. Also, in the next step you'll need the value of *client_email* from this file.
6. Very important! Go to your spreadsheet and share it with a *client_email* from the step above. Just like you do with any other Google account. If you don't do this, you'll get a ``gspread.exceptions.SpreadsheetNotFound`` exception when trying to access this spreadsheet from your application or a script.
7. Move the downloaded file to ``~/.config/gspread/service_account.json``. Windows users should put this file to ``%APPDATA%\gspread\service_account.json``.
8. Create a new Python file with this code:
::
import gspread
gc = gspread.service_account()
sh = gc.open("Example spreadsheet")
print(sh.sheet1.get('A1'))
Ta-da!
.. NOTE::
If you want to store the credentials file somewhere else, specify the path to `service_account.json` in :meth:`~gspread.service_account`:
::
gc = gspread.service_account(filename='path/to/the/downloaded/file.json')
Make sure you store the credentials file in a safe place.
For the curious, under the hood :meth:`~gspread.service_account` loads your credentials and authorizes gspread. Similarly to the code
that has been used for authentication prior to the gspread version 3.6:
::
from google.oauth2.service_account import Credentials
scopes = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
credentials = Credentials.from_service_account_file(
'path/to/the/downloaded/file.json',
scopes=scopes
)
gc = gspread.authorize(credentials)
There is also the option to pass credentials as a dictionary:
::
import gspread
credentials = {
"type": "service_account",
"project_id": "api-project-XXX",
"private_key_id": "2cd … ba4",
"private_key": "-----BEGIN PRIVATE KEY-----\nNrDyLw … jINQh/9\n-----END PRIVATE KEY-----\n",
"client_email": "473000000000-yoursisdifferent@developer.gserviceaccount.com",
"client_id": "473 … hd.apps.googleusercontent.com",
...
}
gc = gspread.service_account_from_dict(credentials)
sh = gc.open("Example spreadsheet")
print(sh.sheet1.get('A1'))
.. NOTE::
Older versions of gspread have used `oauth2client <https://github.com/google/oauth2client>`_. Google has
`deprecated <https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html>`_
it in favor of `google-auth`. If you're still using `oauth2client` credentials, the library will convert
these to `google-auth` for you, but you can change your code to use the new credentials to make sure nothing
breaks in the future.
.. _oauth-client-id:
For Bots Running inside GCP (Cloud Run, Cloud Functions, Cloud Build): Using Application-Default Credentials
------------------------------------------------------------------------------------------------------------
When your code runs *inside* Google Cloud, every container, function, or build already
has an **identity**—the service account the runtime is configured to use.
Google injects a short-lived OAuth 2.0 access token for that service account, so you
don’t need to ship or mount a JSON key file. All you have to do is:
1. :ref:`enable-api-access` if you haven’t done it yet (Sheets API **and** Drive API).
2. Attach a service account to the Cloud Run service / Cloud Function / Cloud Build step.
Share the target spreadsheet with the service account’s **email address**
(e.g. `my-run-sa@project.iam.gserviceaccount.com`) just as you would with a colleague.
3. Use `google.auth.default()` to pick up the in-runtime credentials and hand them to gspread:
::
import google.auth
import gspread
SCOPES = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive",
]
creds, _ = google.auth.default(scopes=SCOPES)
gc = gspread.authorize(creds)
sh = gc.open_by_key("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms")
print(sh.sheet1.get("A1"))
* No `GOOGLE_APPLICATION_CREDENTIALS` environment variable.
* No JSON key copied into the container.
* Tokens are rotated automatically by the platform.
4. **Local testing:** run
::
gcloud auth application-default login \
--scopes=https://www.googleapis.com/auth/drive,\
https://www.googleapis.com/auth/spreadsheets
to emulate the same Application-Default Credentials flow on your laptop.
.. note::
If you forget to pass the Sheets **and** Drive scopes when calling
``google.auth.default(scopes=...)`` you will get a *403: insufficient
permissions* error even though the code is running on GCP. Always include
both scopes.
.. warning::
ADC proves *who* your code is, but Sheets access is still controlled by the
spreadsheet’s share list. Make sure the service account is listed there,
otherwise you’ll see ``gspread.exceptions.SpreadsheetNotFound``.
For End Users: Using OAuth Client ID
------------------------------------
This is the case where your application or a script is accessing spreadsheets on behalf of an end user. When you use this scenario, your application or a script will ask the end user (or yourself if you're running it) to grant access to the user's data.
1. :ref:`enable-api-access` if you haven't done it yet.
#. Go to "APIs & Services > OAuth Consent Screen." Click the button for "Configure Consent Screen".
a. In the "1 OAuth consent screen" tab, give your app a name and fill the "User support email" and "Developer contact information". Click "SAVE AND CONTINUE".
#. There is no need to fill in anything in the tab "2 Scopes", just click "SAVE AND CONTINUE".
#. In the tab "3 Test users", add the Google account email of the end user, typically your own Google email. Click "SAVE AND CONTINUE".
#. Double check the "4 Summary" presented and click "BACK TO DASHBOARD".
3. Go to "APIs & Services > Credentials"
#. Click "+ Create credentials" at the top, then select "OAuth client ID".
#. Select "Desktop app", name the credentials and click "Create". Click "Ok" in the "OAuth client created" popup.
#. Download the credentials by clicking the Download JSON button in "OAuth 2.0 Client IDs" section.
#. Move the downloaded file to ``~/.config/gspread/credentials.json``. Windows users should put this file to ``%APPDATA%\gspread\credentials.json``.
Create a new Python file with this code:
::
import gspread
gc = gspread.oauth()
sh = gc.open("Example spreadsheet")
print(sh.sheet1.get('A1'))
When you run this code, it launches a browser asking you for authentication. Follow the instruction on the web page. Once finished, gspread stores authorized credentials in the config directory next to `credentials.json`.
You only need to do authorization in the browser once, following runs will reuse stored credentials.
.. NOTE::
If you want to store the credentials file somewhere else, specify the path to `credentials.json` and `authorized_user.json` in :meth:`~gspread.oauth`:
::
gc = gspread.oauth(
credentials_filename='path/to/the/credentials.json',
authorized_user_filename='path/to/the/authorized_user.json'
)
Make sure you store the credentials file in a safe place.
There is also the option to pass your credentials directly as a python dict. This way you don't have to store them as files or you can store them in your favorite password
manager.
::
import gspread
credentials = {
"installed": {
"client_id": "12345678901234567890abcdefghijklmn.apps.googleusercontent.com",
"project_id": "my-project1234",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
...
}
}
gc, authorized_user = gspread.oauth_from_dict(credentials)
sh = gc.open("Example spreadsheet")
print(sh.sheet1.get('A1'))
Once authenticated you must store the returned json string containing your authenticated user information. Provide that details as a python dict
as second argument in your next `oauth` request to be directly authenticated and skip the flow.
.. NOTE::
The second time if your authorized user has not expired, you can omit the credentials.
Be aware, if the authorized user has expired your credentials are required to authenticate again.
::
import gspread
credentials = {
"installed": {
"client_id": "12345678901234567890abcdefghijklmn.apps.googleusercontent.com",
"project_id": "my-project1234",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
...
}
}
authorized_user = {
"refresh_token": "8//ThisALONGTOkEn....",
"token_uri": "https://oauth2.googleapis.com/token",
"client_id": "12345678901234567890abcdefghijklmn.apps.googleusercontent.com",
"client_secret": "MySecRet....",
"scopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive"
],
"expiry": "1070-01-01T00:00:00.000001Z"
}
gc, authorized_user = gspread.oauth_from_dict(credentials, authorized_user)
sh = gc.open("Example spreadsheet")
print(sh.sheet1.get('A1'))
.. warning::
Security credentials file and authorized credentials contain sensitive data. **Do not share these files with others** and treat them like private keys.
If you are concerned about giving the application access to your spreadsheets and Drive, use Service Accounts.
.. NOTE::
The user interface of Google Developers Console may be different when you're reading this. If you find that this document is out of sync with the actual UI, please update it. Improvements to the documentation are always welcome.
Click **Edit on GitHub** in the top right corner of the page, make it better and submit a PR.
.. _api-key:
For public spreadsheets only
----------------------------
An API key is a token that allows an application to open public spreadsheet files.
Here's how to get one:
1. :ref:`enable-api-access` if you haven't done it yet.
2. Go to "APIs & Services > Credentials" and choose "Create credentials > API key"
3. A pop-up should display your newly created key.
4. Copy the key.
5. That's it your key is created.
.. note::
You can access your key any time later, come back to the "APIs & Services > Credentials" page,
you'll be able to see your key again.
6. Create a new Python file with this code:
::
import gspread
gc = gspread.api_key("<your newly create key>")
sh = gc.open_by_key("1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms")
print(sh.sheet1.get('A1'))
Ta-da !
.. note::
You can only open public keys, this means you can only open spreadsheet files
using the methods: ``gc.open_by_key`` and ``gc.open_by_url``.
The method ``gc.open()`` searches your private files to find the one with a matching
name so it will never work.
================================================
FILE: docs/requirements.txt
================================================
sphinx==6.2.1
sphinx_rtd_theme
sphinx-toolbox
================================================
FILE: docs/user-guide.rst
================================================
Examples of gspread Usage
=========================
If you haven't yet authorized your app, read :doc:`oauth2` first.
Opening a Spreadsheet
~~~~~~~~~~~~~~~~~~~~~
You can open a spreadsheet by its title as it appears in Google Docs:
.. code:: python
sh = gc.open('My poor gym results')
.. NOTE::
If you have multiple Google Sheets with the same title, only the latest sheet will be
opened by this method without throwing an error. It's recommended to open the sheet
using its unique ID instead (see below)
If you want to be specific, use a key (which can be extracted from
the spreadsheet's url):
.. code:: python
sht1 = gc.open_by_key('0BmgG6nO_6dprdS1MN3d3MkdPa142WFRrdnRRUWl1UFE')
Or, if you feel really lazy to extract that key, paste the entire spreadsheet's url
.. code:: python
sht2 = gc.open_by_url('https://docs.google.com/spreadsheet/ccc?key=0Bm...FE&hl')
Creating a Spreadsheet
~~~~~~~~~~~~~~~~~~~~~~
Use :meth:`~gspread.Client.create` to create a new blank spreadsheet:
.. code:: python
sh = gc.create('A new spreadsheet')
.. NOTE::
If you're using a :ref:`service account <service-account>`, this new spreadsheet will be
visible only to this account. To be able to access newly created spreadsheet
from Google Sheets with your own Google account you *must* share it with your
email. See how to share a spreadsheet in the section below.
Sharing a Spreadsheet
~~~~~~~~~~~~~~~~~~~~~
If your email is *otto@example.com* you can share the newly created spreadsheet
with yourself:
.. code:: python
sh.share('otto@example.com', perm_type='user', role='writer')
See :meth:`~gspread.models.Spreadsheet.share` documentation for a full list of accepted parameters.
Selecting a Worksheet
~~~~~~~~~~~~~~~~~~~~~
Select worksheet by index. Worksheet indexes start from zero:
.. code:: python
worksheet = sh.get_worksheet(0)
Or by title:
.. code:: python
worksheet = sh.worksheet("January")
Or the most common case: *Sheet1*:
.. code:: python
worksheet = sh.sheet1
To get a list of all worksheets:
.. code:: python
worksheet_list = sh.worksheets()
Creating a Worksheet
~~~~~~~~~~~~~~~~~~~~
.. code:: python
worksheet = sh.add_worksheet(title="A worksheet", rows=100, cols=20)
Deleting a Worksheet
~~~~~~~~~~~~~~~~~~~~
.. code:: python
sh.del_worksheet(worksheet)
Updating a Worksheet's name and color
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
worksheet.update_title("December Transactions")
worksheet.update_tab_color({"red": 1, "green": 0.5, "blue": 0.5})
Getting a Cell Value
~~~~~~~~~~~~~~~~~~~~
Using `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_:
.. code:: python
val = worksheet.acell('B1').value
Or row and column coordinates:
.. code:: python
val = worksheet.cell(1, 2).value
If you want to get a cell formula:
.. code:: python
cell = worksheet.acell('B1', value_render_option='FORMULA').value
# or
cell = worksheet.cell(1, 2, value_render_option='FORMULA').value
Getting Unformatted Cell Value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Get the Unformatted value from a cell.
Example: cells formatted as currency will display with the selected
currency but they actual value is regular number.
Get the formatted (as displayed) value:
.. code:: python
worksheet.get("A1:B2")
Results in: ``[['$12.00']]``
Get the unformatted value:
.. code:: python
from gspread.utils import ValueRenderOption
worksheet.get("A1:B2", value_render_option=ValueRenderOption.unformatted)
Results in: ``[[12]]``
Getting Cell formula
~~~~~~~~~~~~~~~~~~~~
Get the formula from a cell instead of the resulting value:
.. code:: python
from gspread.utils import ValueRenderOption
worksheet.get("G6", value_render_option=ValueRenderOption.formula)
Resulsts in: ``[['=1/1024']]``
Getting All Values From a Row or a Column
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Get all values from the first row:
.. code:: python
values_list = worksheet.row_values(1)
Get all values from the first column:
.. code:: python
values_list = worksheet.col_values(1)
.. NOTE::
So far we've been fetching a limited amount of data from a sheet. This works great until
you need to get values from hundreds of cells or iterating over many rows or columns.
Under the hood, gspread uses `Google Sheets API v4 <https://developers.google.com/sheets/api>`_.
Most of the time when you call a gspread method to fetch or update a sheet gspread produces
one HTTP API call.
HTTP calls have performance costs. So if you find your app fetching values one by one in
a loop or iterating over rows or columns you can improve the performance of the app by fetching
data in one go.
What's more, Sheets API v4 introduced `Usage Limits <https://developers.google.com/sheets/api/limits>`_
(as of this writing, 300 requests per 60 seconds per project, and 60 requests per 60 seconds per user). When your
application hits that limit, you get an :exc:`~gspread.exceptions.APIError` `429 RESOURCE_EXHAUSTED`.
Here are the methods that may help you to reduce API calls:
* :meth:`~gspread.models.Worksheet.get_all_values` fetches values from all of the cells of the sheet.
* :meth:`~gspread.models.Worksheet.get` fetches all values from a range of cells.
* :meth:`~gspread.models.Worksheet.batch_get` can fetch values from multiple ranges of cells with one API call.
* :meth:`~gspread.models.Worksheet.update` lets you update a range of cells with a list of lists.
* :meth:`~gspread.models.Worksheet.batch_update` lets you update multiple ranges of cells with one API call.
Getting All Values From a Worksheet as a List of Lists
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
list_of_lists = worksheet.get_all_values()
Getting All Values From a Worksheet as a List of Dictionaries
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. code:: python
list_of_dicts = worksheet.get_all_records()
Finding a Cell
~~~~~~~~~~~~~~
Find a cell matching a string:
.. code:: python
cell = worksheet.find("Dough")
print("Found something at R%sC%s" % (cell.row, cell.col))
Find a cell matching a regular expression
.. code:: python
amount_re = re.compile(r'(Big|Enormous) dough')
cell = worksheet.find(amount_re)
`find` returns `None` if value is not Found
Finding All Matched Cells
~~~~~~~~~~~~~~~~~~~~~~~~~
Find all cells matching a string:
.. code:: python
cell_list = worksheet.findall("Rug store")
Find all cells matching a regexp:
.. code:: python
criteria_re = re.compile(r'(Small|Room-tiering) rug')
cell_list = worksheet.findall(criteria_re)
Clear A Worksheet
~~~~~~~~~~~~~~~~~
Clear one or multiple cells ranges at once:
.. code:: python
worksheet.batch_clear(["A1:B1", "C2:E2", "my_named_range"])
Clear the entire worksheet:
.. code:: python
worksheet.clear()
Cell Object
~~~~~~~~~~~
Each cell has a value and coordinates properties:
.. code:: python
value = cell.value
row_number = cell.row
column_number = cell.col
Updating Cells
~~~~~~~~~~~~~~
Using `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_:
.. code:: python
worksheet.update_acell('B1', 'Bingo!')
Or row and column coordinates:
.. code:: python
worksheet.update_cell(1, 2, 'Bingo!')
Update a range
.. code:: python
worksheet.update([[1, 2], [3, 4]], 'A1:B2')
Adding Data Validation
~~~~~~~~~~~~~~~~~~~~~~
You can add a strict validation to a cell.
.. code:: python
ws.add_validation(
'A1',
ValidationConditionType.number_greater,
[10],
strict=True,
inputMessage='Value must be greater than 10',
)
Or add validation with a drop down.
.. code:: python
worksheet.add_validation(
'C2:C7',
ValidationConditionType.one_of_list,
['Yes',
'No',]
showCustomUi=True
)
Check out the api docs for `DataValidationRule`_ and `CondtionType`_ for more details.
.. _CondtionType: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionType
.. _DataValidationRule: https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#DataValidationRule
Extract table
~~~~~~~~~~~~~
Gspread provides a function to extract a data table.
A data table is defined as a rectangular table that stops either on the **first empty** cell or
the **enge of the sheet**.
You can extract table from any address by providing the top left corner of the desired table.
Gspread provides 3 directions for searching the end of the table:
* :attr:`~gspread.utils.TableDirection.right`: extract a single row searching on the right of the starting cell
* :attr:`~gspread.utils.TableDirection.down`: extract a single column searching on the bottom of the starting cell
* :attr:`~gspread.utils.TableDirection.table`: extract a rectangular table by first searching right from starting cell,
then searching down from starting cell.
.. note::
Gspread will not look for empty cell inside the table. it only look at the top row and first column.
Example extracting a table from the below sample sheet:
.. list-table:: Find table
:header-rows: 1
* - ID
- Name
- Universe
- Super power
* - 1
- Batman
- DC
- Very rich
* - 2
- DeadPool
- Marvel
- self healing
* - 3
- Superman
- DC
- super human
* -
- \-
- \-
- \-
* - 5
- Lavigne958
-
- maintains Gspread
* - 6
- Alifee
-
- maintains Gspread
Using the below code will result in rows 2 to 4:
.. code:: python
worksheet.expand("A2")
[
["Batman", "DC", "Very rich"],
["DeadPool", "Marvel", "self healing"],
["Superman", "DC", "super human"],
]
Formatting
~~~~~~~~~~
Here's an example of basic formatting.
Set **A1:B1** text format to bold:
.. code:: python
worksheet.format('A1:B1', {'textFormat': {'bold': True}})
Color the background of **A2:B2** cell range in black, change horizontal alignment, text color and font size:
.. code:: python
worksheet.format("A2:B2", {
"backgroundColor": {
"red": 0.0,
"green": 0.0,
"blue": 0.0
},
"horizontalAlignment": "CENTER",
"textFormat": {
"foregroundColor": {
"red": 1.0,
"green": 1.0,
"blue": 1.0
},
"fontSize": 12,
"bold": True
}
})
The second argument to :meth:`~gspread.models.Worksheet.format` is a dictionary containing the fields to update. A full specification of format options is available at `CellFormat <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#cellformat>`_ in Sheet API Reference.
.. Tip::
for more complex formatting see :ref:`gspread-formating-label`.
Using gspread with pandas
~~~~~~~~~~~~~~~~~~~~~~~~~
`pandas <https://pandas.pydata.org/>`_ is a popular library for data analysis. The simplest way to get data from a sheet to a pandas DataFrame is with :meth:`~gspread.models.Worksheet.get_all_records`:
.. code:: python
import pandas as pd
dataframe = pd.DataFrame(worksheet.get_all_records())
Here's a basic example for writing a dataframe to a sheet. With :meth:`~gspread.models.Worksheet.update` we put the header of a dataframe into the first row of a sheet followed by the values of a dataframe:
.. code:: python
import pandas as pd
worksheet.update([dataframe.columns.values.tolist()] + dataframe.values.tolist())
For advanced pandas use cases check out community section :ref:`gspread-pandas-label`
Using gspread with NumPy
~~~~~~~~~~~~~~~~~~~~~~~~
`NumPy <https://numpy.org/>`_ is a library for scientific computing in Python. It provides tools for working with high performance multi-dimensional arrays.
Read contents of a sheet into a NumPy array:
.. code:: python
import numpy as np
array = np.array(worksheet.get_all_values())
The code above assumes that your data starts from the first row of the sheet. If you have a header row in the first row, you need replace ``worksheet.get_all_values()`` with ``worksheet.get_all_values()[1:]``.
Write a NumPy array to a sheet:
.. code:: python
import numpy as np
array = np.array([[1, 2, 3], [4, 5, 6]])
# Write the array to worksheet starting from the A2 cell
worksheet.update(array.tolist(), 'A2')
================================================
FILE: gspread/__init__.py
================================================
"""Google Spreadsheets Python API"""
__version__ = "6.2.1"
__author__ = "Anton Burnashev"
from .auth import (
api_key,
authorize,
oauth,
oauth_from_dict,
service_account,
service_account_from_dict,
)
from .cell import Cell
from .client import Client
from .exceptions import (
GSpreadException,
IncorrectCellLabel,
NoValidUrlKeyFound,
SpreadsheetNotFound,
WorksheetNotFound,
)
from .http_client import BackOffHTTPClient, HTTPClient
from .spreadsheet import Spreadsheet
from .worksheet import ValueRange, Worksheet
from . import urls as urls
from . import utils as utils
__all__ = [
# from .auth
"api_key",
"authorize",
"oauth",
"oauth_from_dict",
"service_account",
"service_account_from_dict",
# from .cell
"Cell",
# from .client
"Client",
# from .http_client
"BackOffHTTPClient",
"HTTPClient",
# from .spreadsheet
"Spreadsheet",
# from .worksheet
"Worksheet",
"ValueRange",
# from .exceptions
"GSpreadException",
"IncorrectCellLabel",
"NoValidUrlKeyFound",
"SpreadsheetNotFound",
"WorksheetNotFound",
# full module imports
"urls",
"utils",
]
================================================
FILE: gspread/auth.py
================================================
"""
gspread.auth
~~~~~~~~~~~~
Simple authentication with OAuth.
"""
import json
import os
from pathlib import Path
from typing import Any, Dict, Iterable, Mapping, Optional, Protocol, Tuple, Union
from google.auth.credentials import Credentials
try:
from google.auth.api_key import Credentials as APIKeyCredentials
GOOGLE_AUTH_API_KEY_AVAILABLE = True
except ImportError:
GOOGLE_AUTH_API_KEY_AVAILABLE = False
from google.oauth2.credentials import Credentials as OAuthCredentials
from google.oauth2.service_account import Credentials as SACredentials
from google_auth_oauthlib.flow import InstalledAppFlow
from requests import Session
from .client import Client
from .http_client import HTTPClient, HTTPClientType
DEFAULT_SCOPES = [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive",
]
READONLY_SCOPES = [
"https://www.googleapis.com/auth/spreadsheets.readonly",
"https://www.googleapis.com/auth/drive.readonly",
]
def get_config_dir(
config_dir_name: str = "gspread", os_is_windows: bool = os.name == "nt"
) -> Path:
r"""Construct a config dir path.
By default:
* `%APPDATA%\gspread` on Windows
* `~/.config/gspread` everywhere else
"""
if os_is_windows:
return Path(os.environ["APPDATA"], config_dir_name)
else:
return Path(Path.home(), ".config", config_dir_name)
DEFAULT_CONFIG_DIR = get_config_dir()
DEFAULT_CREDENTIALS_FILENAME = DEFAULT_CONFIG_DIR / "credentials.json"
DEFAULT_AUTHORIZED_USER_FILENAME = DEFAULT_CONFIG_DIR / "authorized_user.json"
DEFAULT_SERVICE_ACCOUNT_FILENAME = DEFAULT_CONFIG_DIR / "service_account.json"
def authorize(
credentials: Credentials,
http_client: HTTPClientType = HTTPClient,
session: Optional[Session] = None,
) -> Client:
"""Login to Google API using OAuth2 credentials.
This is a shortcut/helper function which
instantiates a client using `http_client`.
By default :class:`gspread.HTTPClient` is used (but could also use
:class:`gspread.BackOffHTTPClient` to avoid rate limiting).
It can take an additional `requests.Session` object in order to provide
you own session object.
.. note::
When providing your own `requests.Session` object,
use the value `None` as `credentials`.
:returns: An instance of the class produced by `http_client`.
:rtype: :class:`gspread.client.Client`
"""
return Client(auth=credentials, session=session, http_client=http_client)
class FlowCallable(Protocol):
"""Protocol for OAuth flow callables."""
def __call__(
self, client_config: Mapping[str, Any], scopes: Iterable[str], port: int = 0
) -> OAuthCredentials: ...
def local_server_flow(
client_config: Mapping[str, Any], scopes: Iterable[str], port: int = 0
) -> OAuthCredentials:
"""Run an OAuth flow using a local server strategy.
Creates an OAuth flow and runs `google_auth_oauthlib.flow.InstalledAppFlow.run_local_server <https://google-auth-oauthlib.readthedocs.io/en/latest/reference/google_auth_oauthlib.flow.html#google_auth_oauthlib.flow.InstalledAppFlow.run_local_server>`_.
This will start a local web server and open the authorization URL in
the user's browser.
Pass this function to ``flow`` parameter of :meth:`~gspread.oauth` to run
a local server flow.
"""
flow = InstalledAppFlow.from_client_config(client_config, scopes)
return flow.run_local_server(port=port)
def load_credentials(
filename: Path = DEFAULT_AUTHORIZED_USER_FILENAME,
) -> Optional[Credentials]:
if filename.exists():
return OAuthCredentials.from_authorized_user_file(filename)
return None
def store_credentials(
creds: OAuthCredentials,
filename: Path = DEFAULT_AUTHORIZED_USER_FILENAME,
strip: str = "token",
) -> None:
filename.parent.mkdir(parents=True, exist_ok=True)
with filename.open("w") as f:
f.write(creds.to_json(strip))
def oauth(
scopes: Iterable[str] = DEFAULT_SCOPES,
flow: FlowCallable = local_server_flow,
credentials_filename: Union[str, Path] = DEFAULT_CREDENTIALS_FILENAME,
authorized_user_filename: Union[str, Path] = DEFAULT_AUTHORIZED_USER_FILENAME,
http_client: HTTPClientType = HTTPClient,
) -> Client:
r"""Authenticate with OAuth Client ID.
By default this function will use the local server strategy and open
the authorization URL in the user's browser::
gc = gspread.oauth()
Another option is to run a console strategy. This way, the user is
instructed to open the authorization URL in their browser. Once the
authorization is complete, the user must then copy & paste the
authorization code into the application::
gc = gspread.oauth(flow=gspread.auth.console_flow)
``scopes`` parameter defaults to read/write scope available in
``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets
and Drive API::
DEFAULT_SCOPES =[
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
You can also use ``gspread.auth.READONLY_SCOPES`` for read only access.
Obviously any method of ``gspread`` that updates a spreadsheet
**will not work** in this case::
gc = gspread.oauth(scopes=gspread.auth.READONLY_SCOPES)
sh = gc.open("A spreadsheet")
sh.sheet1.update_acell('A1', '42') # <-- this will not work
If you're storing your user credentials in a place other than the
default, you may provide a path to that file like so::
gc = gspread.oauth(
credentials_filename='/alternative/path/credentials.json',
authorized_user_filename='/alternative/path/authorized_user.json',
)
:param list scopes: The scopes used to obtain authorization.
:param function flow: OAuth flow to use for authentication.
Defaults to :meth:`~gspread.auth.local_server_flow`
:param str credentials_filename: Filepath (including name) pointing to a
credentials `.json` file.
Defaults to DEFAULT_CREDENTIALS_FILENAME:
* `%APPDATA%\gspread\credentials.json` on Windows
* `~/.config/gspread/credentials.json` everywhere else
:param str authorized_user_filename: Filepath (including name) pointing to
an authorized user `.json` file.
Defaults to DEFAULT_AUTHORIZED_USER_FILENAME:
* `%APPDATA%\gspread\authorized_user.json` on Windows
* `~/.config/gspread/authorized_user.json` everywhere else
:type http_client: :class:`gspread.http_client.HTTPClient`
:param http_client: A factory function that returns a client class.
Defaults to :class:`gspread.http_client.HTTPClient` (but could also use
:class:`gspread.http_client.BackOffHTTPClient` to avoid rate limiting)
:rtype: :class:`gspread.client.Client`
"""
authorized_user_filename = Path(authorized_user_filename)
creds = load_credentials(filename=authorized_user_filename)
if not isinstance(creds, Credentials):
with open(credentials_filename) as json_file:
client_config = json.load(json_file)
creds = flow(client_config=client_config, scopes=scopes)
store_credentials(creds, filename=authorized_user_filename)
return Client(auth=creds, http_client=http_client)
def oauth_from_dict(
credentials: Optional[Mapping[str, Any]] = None,
authorized_user_info: Optional[Mapping[str, Any]] = None,
scopes: Iterable[str] = DEFAULT_SCOPES,
flow: FlowCallable = local_server_flow,
http_client: HTTPClientType = HTTPClient,
) -> Tuple[Client, Dict[str, Any]]:
r"""Authenticate with OAuth Client ID.
By default this function will use the local server strategy and open
the authorization URL in the user's browser::
gc = gspread.oauth_from_dict()
Another option is to run a console strategy. This way, the user is
instructed to open the authorization URL in their browser. Once the
authorization is complete, the user must then copy & paste the
authorization code into the application::
gc = gspread.oauth_from_dict(flow=gspread.auth.console_flow)
``scopes`` parameter defaults to read/write scope available in
``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets
and Drive API::
DEFAULT_SCOPES =[
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
You can also use ``gspread.auth.READONLY_SCOPES`` for read only access.
Obviously any method of ``gspread`` that updates a spreadsheet
**will not work** in this case::
gc = gspread.oauth_from_dict(scopes=gspread.auth.READONLY_SCOPES)
sh = gc.open("A spreadsheet")
sh.sheet1.update_acell('A1', '42') # <-- this will not work
This function requires you to pass the credentials directly as
a python dict. After the first authentication the function returns
the authenticated user info, this can be passed again to authenticate
the user without the need to run the flow again.
..
code block below must be explicitly announced using code-block
.. code-block:: python
gc = gspread.oauth_from_dict(
credentials=my_creds,
authorized_user_info=my_auth_user
)
:param dict credentials: The credentials from google cloud platform
:param dict authorized_user_info: The authenticated user
if already authenticated.
:param list scopes: The scopes used to obtain authorization.
:param function flow: OAuth flow to use for authentication.
Defaults to :meth:`~gspread.auth.local_server_flow`
:type http_client: :class:`gspread.http_client.HTTPClient`
:param http_client: A factory function that returns a client class.
Defaults to :class:`gspread.http_client.HTTPClient` (but could also use
:class:`gspread.http_client.BackOffHTTPClient` to avoid rate limiting)
:rtype: (:class:`gspread.client.Client`, str)
"""
if authorized_user_info is not None:
creds = OAuthCredentials.from_authorized_user_info(authorized_user_info, scopes)
elif credentials is not None:
creds = flow(client_config=credentials, scopes=scopes)
else:
raise ValueError("no credentials object supplied")
client = Client(auth=creds, http_client=http_client)
# must return the creds to the user
# must strip the token an use the dedicated method from Credentials
# to return a dict "safe to store".
return (client, creds.to_json("token"))
def service_account(
filename: Union[Path, str] = DEFAULT_SERVICE_ACCOUNT_FILENAME,
scopes: Iterable[str] = DEFAULT_SCOPES,
http_client: HTTPClientType = HTTPClient,
) -> Client:
"""Authenticate using a service account.
``scopes`` parameter defaults to read/write scope available in
``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets
and Drive API::
DEFAULT_SCOPES =[
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
You can also use ``gspread.auth.READONLY_SCOPES`` for read only access.
Obviously any method of ``gspread`` that updates a spreadsheet
**will not work** in this case.
:param str filename: The path to the service account json file.
:param list scopes: The scopes used to obtain authorization.
:type http_client: :class:`gspread.http_client.HTTPClient`
:param http_client: A factory function that returns a client class.
Defaults to :class:`gspread.HTTPClient` (but could also use
:class:`gspread.BackOffHTTPClient` to avoid rate limiting)
:rtype: :class:`gspread.client.Client`
"""
creds = SACredentials.from_service_account_file(filename, scopes=scopes)
return Client(auth=creds, http_client=http_client)
def service_account_from_dict(
info: Mapping[str, Any],
scopes: Iterable[str] = DEFAULT_SCOPES,
http_client: HTTPClientType = HTTPClient,
) -> Client:
"""Authenticate using a service account (json).
``scopes`` parameter defaults to read/write scope available in
``gspread.auth.DEFAULT_SCOPES``. It's read/write for Sheets
and Drive API::
DEFAULT_SCOPES =[
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
You can also use ``gspread.auth.READONLY_SCOPES`` for read only access.
Obviously any method of ``gspread`` that updates a spreadsheet
**will not work** in this case.
:param info (Mapping[str, str]): The service account info in Google format
:param list scopes: The scopes used to obtain authorization.
:type http_client: :class:`gspread.http_client.HTTPClient`
:param http_client: A factory function that returns a client class.
Defaults to :class:`gspread.http_client.HTTPClient` (but could also use
:class:`gspread.http_client.BackOffHTTPClient` to avoid rate limiting)
:rtype: :class:`gspread.client.Client`
"""
creds = SACredentials.from_service_account_info(
info=info,
scopes=scopes,
)
return Client(auth=creds, http_client=http_client)
def api_key(token: str, http_client: HTTPClientType = HTTPClient) -> Client:
"""Authenticate using an API key.
Allows you to open public spreadsheet files.
.. warning::
This method only allows you to open public spreadsheet files.
It does not work for private spreadsheet files.
:param token str: The actual API key to use
:type http_client: :class:`gspread.http_client.HTTPClient`
:param http_client: A factory function that returns a client class.
Defaults to :class:`gspread.http_client.HTTPClient` (but could also use
:class:`gspread.http_client.BackOffHTTPClient` to avoid rate limiting)
:rtype: :class:`gspread.client.Client`
"""
if GOOGLE_AUTH_API_KEY_AVAILABLE is False:
raise NotImplementedError(
"api_key is only available with package google.auth>=2.15.0. "
'Install it with "pip install google-auth>=2.15.0".'
)
creds = APIKeyCredentials(token)
return Client(auth=creds, http_client=http_client)
================================================
FILE: gspread/cell.py
================================================
"""
gspread.cell
~~~~~~~~~~~~
This module contains common cells' models.
"""
from typing import Optional, Union
from .utils import a1_to_rowcol, numericise, rowcol_to_a1
class Cell:
"""An instance of this class represents a single cell
in a :class:`~gspread.worksheet.Worksheet`.
"""
def __init__(self, row: int, col: int, value: Optional[str] = "") -> None:
self._row: int = row
self._col: int = col
#: Value of the cell.
self.value: Optional[str] = value
@classmethod
def from_address(cls, label: str, value: str = "") -> "Cell":
"""Instantiate a new :class:`~gspread.cell.Cell`
from an A1 notation address and a value
:param string label: the A1 label of the returned cell
:param string value: the value for the returned cell
:rtype: Cell
"""
row, col = a1_to_rowcol(label)
return cls(row, col, value)
def __repr__(self) -> str:
return "<{} R{}C{} {}>".format(
self.__class__.__name__,
self.row,
self.col,
repr(self.value),
)
def __eq__(self, other: object) -> bool:
if not isinstance(other, Cell):
return False
same_row = self.row == other.row
same_col = self.col == other.col
same_value = self.value == other.value
return same_row and same_col and same_value
@property
def row(self) -> int:
"""Row number of the cell.
:type: int
"""
return self._row
@property
def col(self) -> int:
"""Column number of the cell.
:type: int
"""
return self._col
@property
def numeric_value(self) -> Optional[Union[int, float]]:
"""Numeric value of this cell.
Will try to numericise this cell value,
upon success will return its numeric value
with the appropriate type.
:type: int or float
"""
numeric_value = numericise(self.value, default_blank=None)
# if could not convert, return None
if isinstance(numeric_value, int) or isinstance(numeric_value, float):
return numeric_value
else:
return None
@property
def address(self) -> str:
"""Cell address in A1 notation.
:type: str
"""
return rowcol_to_a1(self.row, self.col)
================================================
FILE: gspread/client.py
================================================
"""
gspread.client
~~~~~~~~~~~~~~
This module contains Client class responsible for managing spreadsheet files
"""
from datetime import datetime
from http import HTTPStatus
from typing import Any, Dict, List, Optional, Tuple, Union
from google.auth.credentials import Credentials
from requests import Response, Session
from .exceptions import APIError, SpreadsheetNotFound
from .http_client import HTTPClient, HTTPClientType, ParamsType
from .spreadsheet import Spreadsheet
from .urls import DRIVE_FILES_API_V3_COMMENTS_URL, DRIVE_FILES_API_V3_URL
from .utils import ExportFormat, MimeType, extract_id_from_url, finditem
class Client:
"""An instance of this class Manages Spreadsheet files
It is used to:
- open/create/list/delete spreadsheets
- create/delete/list spreadsheet permission
- etc
It is the gspread entry point.
It will handle creating necessary :class:`~gspread.models.Spreadsheet` instances.
"""
def __init__(
self,
auth: Credentials,
session: Optional[Session] = None,
http_client: HTTPClientType = HTTPClient,
) -> None:
self.http_client = http_client(auth, session)
@property
def expiry(self) -> Optional[datetime]:
"""Returns the expiry date of the curenlty loaded credentials
:returns: (optional) datetime the expiry date time object.
.. note::
It only applies to gspread client created using oauth
"""
return self.http_client.auth.expiry
def set_timeout(
self, timeout: Optional[Union[float, Tuple[float, float]]] = None
) -> None:
"""How long to wait for the server to send
data before giving up, as a float, or a ``(connect timeout,
read timeout)`` tuple.
Use value ``None`` to restore default timeout
Value for ``timeout`` is in seconds (s).
"""
self.http_client.set_timeout(timeout)
def get_file_drive_metadata(self, id: str) -> Any:
"""Get the metadata from the Drive API for a specific file
This method is mainly here to retrieve the create/update time
of a file (these metadata are only accessible from the Drive API).
"""
return self.http_client.get_file_drive_metadata(id)
def list_spreadsheet_files(
self, title: Optional[str] = None, folder_id: Optional[str] = None
) -> List[Dict[str, Any]]:
"""List all the spreadsheet files
Will list all spreadsheet files owned by/shared with this user account.
:param str title: Filter only spreadsheet files with this title
:param str folder_id: Only look for spreadsheet files in this folder
The parameter ``folder_id`` can be obtained from the URL when looking at
a folder in a web browser as follow:
``https://drive.google.com/drive/u/0/folders/<folder_id>``
:returns: a list of dicts containing the keys id, name, createdTime and modifiedTime.
"""
files, _ = self._list_spreadsheet_files(title=title, folder_id=folder_id)
return files
def _list_spreadsheet_files(
self, title: Optional[str] = None, folder_id: Optional[str] = None
) -> Tuple[List[Dict[str, Any]], Response]:
files = []
page_token = ""
url = DRIVE_FILES_API_V3_URL
query = f'mimeType="{MimeType.google_sheets}"'
if title:
query += f' and name = "{title}"'
if folder_id:
query += f' and parents in "{folder_id}"'
params: ParamsType = {
"q": query,
"pageSize": 1000,
"supportsAllDrives": True,
"includeItemsFromAllDrives": True,
"fields": "kind,nextPageToken,files(id,name,createdTime,modifiedTime)",
}
while True:
if page_token:
params["pageToken"] = page_token
response = self.http_client.request("get", url, params=params)
response_json = response.json()
files.extend(response_json["files"])
page_token = response_json.get("nextPageToken", None)
if page_token is None:
break
return files, response
def open(self, title: str, folder_id: Optional[str] = None) -> Spreadsheet:
"""Opens a spreadsheet.
:param str title: A title of a spreadsheet.
:param str folder_id: (optional) If specified can be used to filter
spreadsheets by parent folder ID.
:returns: a :class:`~gspread.models.Spreadsheet` instance.
If there's more than one spreadsheet with same title the first one
will be opened.
:raises gspread.SpreadsheetNotFound: if no spreadsheet with
specified `title` is found.
>>> gc.open('My fancy spreadsheet')
"""
spreadsheet_files, response = self._list_spreadsheet_files(title, folder_id)
try:
properties = finditem(
lambda x: x["name"] == title,
spreadsheet_files,
)
except StopIteration as ex:
raise SpreadsheetNotFound(response) from ex
# Drive uses different terminology
properties["title"] = properties["name"]
return Spreadsheet(self.http_client, properties)
def open_by_key(self, key: str) -> Spreadsheet:
"""Opens a spreadsheet specified by `key` (a.k.a Spreadsheet ID).
:param str key: A key of a spreadsheet as it appears in a URL in a browser.
:returns: a :class:`~gspread.models.Spreadsheet` instance.
>>> gc.open_by_key('0BmgG6nO_6dprdS1MN3d3MkdPa142WFRrdnRRUWl1UFE')
"""
try:
spreadsheet = Spreadsheet(self.http_client, {"id": key})
except APIError as ex:
if ex.response.status_code == HTTPStatus.NOT_FOUND:
raise SpreadsheetNotFound(ex.response) from ex
if ex.response.status_code == HTTPStatus.FORBIDDEN:
raise PermissionError from ex
raise ex
return spreadsheet
def open_by_url(self, url: str) -> Spreadsheet:
"""Opens a spreadsheet specified by `url`.
:param str url: URL of a spreadsheet as it appears in a browser.
:returns: a :class:`~gspread.models.Spreadsheet` instance.
:raises gspread.SpreadsheetNotFound: if no spreadsheet with
specified `url` is found.
>>> gc.open_by_url('https://docs.google.com/spreadsheet/ccc?key=0Bm...FE&hl')
"""
return self.open_by_key(extract_id_from_url(url))
def openall(self, title: Optional[str] = None) -> List[Spreadsheet]:
"""Opens all available spreadsheets.
:param str title: (optional) If specified can be used to filter
spreadsheets by title.
:returns: a list of :class:`~gspread.models.Spreadsheet` instances.
"""
spreadsheet_files = self.list_spreadsheet_files(title)
if title:
spreadsheet_files = [
spread for spread in spreadsheet_files if title == spread["name"]
]
return [
Spreadsheet(self.http_client, dict(title=x["name"], **x))
for x in spreadsheet_files
]
def create(self, title: str, folder_id: Optional[str] = None) -> Spreadsheet:
"""Creates a new spreadsheet.
:param str title: A title of a new spreadsheet.
:param str folder_id: Id of the folder where we want to save
the spreadsheet.
:returns: a :class:`~gspread.models.Spreadsheet` instance.
"""
payload: Dict[str, Any] = {
"name": title,
"mimeType": MimeType.google_sheets,
}
params: ParamsType = {
"supportsAllDrives": True,
}
if folder_id is not None:
payload["parents"] = [folder_id]
r = self.http_client.request(
"post", DRIVE_FILES_API_V3_URL, json=payload, params=params
)
spreadsheet_id = r.json()["id"]
return self.open_by_key(spreadsheet_id)
def export(self, file_id: str, format: str = ExportFormat.PDF) -> bytes:
"""Export the spreadsheet in the given format.
:param str file_id: The key of the spreadsheet to export
:param str format: The format of the resulting file.
Possible values are:
* ``ExportFormat.PDF``
* ``ExportFormat.EXCEL``
* ``ExportFormat.CSV``
* ``ExportFormat.OPEN_OFFICE_SHEET``
* ``ExportFormat.TSV``
* ``ExportFormat.ZIPPED_HTML``
See `ExportFormat`_ in the Drive API.
:type format: :class:`~gspread.utils.ExportFormat`
:returns bytes: The content of the exported file.
.. _ExportFormat: https://developers.google.com/drive/api/guides/ref-export-formats
"""
return self.http_client.export(file_id=file_id, format=format)
def copy(
self,
file_id: str,
title: Optional[str] = None,
copy_permissions: bool = False,
folder_id: Optional[str] = None,
copy_comments: bool = True,
) -> Spreadsheet:
"""Copies a spreadsheet.
:param str file_id: A key of a spreadsheet to copy.
:param str title: (optional) A title for the new spreadsheet.
:param bool copy_permissions: (optional) If True, copy permissions from
the original spreadsheet to the new spreadsheet.
:param str folder_id: Id of the folder where we want to save
the spreadsheet.
:param bool copy_comments: (optional) If True, copy the comments from
the original spreadsheet to the new spreadsheet.
:returns: a :class:`~gspread.models.Spreadsheet` instance.
.. versionadded:: 3.1.0
.. note::
If you're using custom credentials without the Drive scope, you need to add
``https://www.googleapis.com/auth/drive`` to your OAuth scope in order to use
this method.
Example::
scope = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
Otherwise, you will get an ``Insufficient Permission`` error
when you try to copy a spreadsheet.
"""
url = "{}/{}/copy".format(DRIVE_FILES_API_V3_URL, file_id)
payload: Dict[str, Any] = {
"name": title,
"mimeType": MimeType.google_sheets,
}
if folder_id is not None:
payload["parents"] = [folder_id]
params: ParamsType = {"supportsAllDrives": True}
r = self.http_client.request("post", url, json=payload, params=params)
spreadsheet_id = r.json()["id"]
new_spreadsheet = self.open_by_key(spreadsheet_id)
if copy_permissions is True:
original = self.open_by_key(file_id)
permissions = original.list_permissions()
for p in permissions:
if p.get("deleted"):
continue
# In case of domain type the domain extract the domain
# In case of user/group extract the emailAddress
# Otherwise use None for type 'Anyone'
email_or_domain = ""
if str(p["type"]) == "domain":
email_or_domain = str(p["domain"])
elif str(p["type"]) in ("user", "group"):
email_or_domain = str(p["emailAddress"])
new_spreadsheet.share(
email_address=email_or_domain,
perm_type=str(p["type"]),
role=str(p["role"]),
notify=False,
)
if copy_comments is True:
source_url = DRIVE_FILES_API_V3_COMMENTS_URL % (file_id)
page_token = ""
comments = []
params = {
"fields": "comments/content,comments/anchor,nextPageToken",
"includeDeleted": False,
"pageSize": 100, # API limit to maximum 100
}
while page_token is not None:
params["pageToken"] = page_token
res = self.http_client.request("get", source_url, params=params).json()
comments.extend(res["comments"])
page_token = res.get("nextPageToken", None)
destination_url = DRIVE_FILES_API_V3_COMMENTS_URL % (new_spreadsheet.id)
# requesting some fields in the response is mandatory from the API.
# choose 'id' randomly out of all the fields, but no need to use it for now.
params = {"fields": "id"}
for comment in comments:
self.http_client.request(
"post", destination_url, json=comment, params=params
)
return new_spreadsheet
def del_spreadsheet(self, file_id: str) -> None:
"""Deletes a spreadsheet.
:param str file_id: a spreadsheet ID (a.k.a file ID).
"""
url = "{}/{}".format(DRIVE_FILES_API_V3_URL, file_id)
params: ParamsType = {"supportsAllDrives": True}
self.http_client.request("delete", url, params=params)
def import_csv(self, file_id: str, data: Union[str, bytes]) -> Any:
"""Imports data into the first page of the spreadsheet.
:param str file_id:
:param str data: A CSV string of data.
Example:
.. code::
# Read CSV file contents
content = open('file_to_import.csv', 'r').read()
gc.import_csv(spreadsheet.id, content)
.. note::
This method removes all other worksheets and then entirely
replaces the contents of the first worksheet.
"""
return self.http_client.import_csv(file_id, data)
def list_permissions(self, file_id: str) -> List[Dict[str, Union[str, bool]]]:
"""Retrieve a list of permissions for a file.
:param str file_id: a spreadsheet ID (aka file ID).
"""
return self.http_client.list_permissions(file_id)
def insert_permission(
self,
file_id: str,
value: Optional[str] = None,
perm_type: Optional[str] = None,
role: Optional[str] = None,
notify: bool = True,
email_message: Optional[str] = None,
with_link: bool = False,
) -> Response:
"""Creates a new permission for a file.
:param str file_id: a spreadsheet ID (aka file ID).
:param value: user or group e-mail address, domain name
or None for 'anyone' type.
:type value: str, None
:param str perm_type: (optional) The account type.
Allowed values are: ``user``, ``group``, ``domain``, ``anyone``
:param str role: (optional) The primary role for this user.
Allowed values are: ``owner``, ``writer``, ``reader``
:param bool notify: (optional) Whether to send an email to the target
user/domain.
:param str email_message: (optional) An email message to be sent
if ``notify=True``.
:param bool with_link: (optional) Whether the link is required for this
permission to be active.
:returns dict: the newly created permission
Examples::
# Give write permissions to otto@example.com
gc.insert_permission(
'0BmgG6nO_6dprnRRUWl1UFE',
'otto@example.org',
perm_type='user',
role='writer'
)
# Make the spreadsheet publicly readable
gc.insert_permission(
'0BmgG6nO_6dprnRRUWl1UFE',
None,
perm_type='anyone',
role='reader'
)
"""
return self.http_client.insert_permission(
file_id, value, perm_type, role, notify, email_message, with_link
)
def remove_permission(self, file_id: str, permission_id: str) -> None:
"""Deletes a permission from a file.
:param str file_id: a spreadsheet ID (aka file ID.)
:param str permission_id: an ID for the permission.
"""
self.http_client.remove_permission(file_id, permission_id)
================================================
FILE: gspread/exceptions.py
================================================
"""
gspread.exceptions
~~~~~~~~~~~~~~~~~~
Exceptions used in gspread.
"""
from typing import Any, Mapping
from requests import Response
class UnSupportedExportFormat(Exception):
"""Raised when export format is not supported."""
class GSpreadException(Exception):
"""A base class for gspread's exceptions."""
class WorksheetNotFound(GSpreadException):
"""Trying to open non-existent or inaccessible worksheet."""
class NoValidUrlKeyFound(GSpreadException):
"""No valid key found in URL."""
class IncorrectCellLabel(GSpreadException):
"""The cell label is incorrect."""
class InvalidInputValue(GSpreadException):
"""The provided values is incorrect."""
class APIError(GSpreadException):
"""Errors coming from the API itself,
such as when we attempt to retrieve things that don't exist."""
def __init__(self, response: Response):
try:
error = response.json()["error"]
except Exception as e:
# in case we failed to parse the error from the API
# build an empty error object to notify the caller
# and keep the exception raise flow running
error = {
"code": -1,
"message": response.text,
"status": "invalid JSON: '{}'".format(e),
}
super().__init__(error)
self.response: Response = response
self.error: Mapping[str, Any] = error
self.code: int = self.error["code"]
def __str__(self) -> str:
return "{}: [{}]: {}".format(
self.__class__.__name__, self.code, self.error["message"]
)
def __repr__(self) -> str:
return self.__str__()
def __reduce__(self) -> tuple:
return self.__class__, (self.response,)
class SpreadsheetNotFound(GSpreadException):
"""Trying to open non-existent or inaccessible spreadsheet."""
================================================
FILE: gspread/http_client.py
================================================
"""
gspread.http_client
~~~~~~~~~~~~~~
This module contains HTTPClient class responsible for communicating with
Google API.
"""
import time
from http import HTTPStatus
from typing import (
IO,
Any,
Dict,
List,
Mapping,
MutableMapping,
Optional,
Tuple,
Type,
Union,
)
from google.auth.credentials import Credentials
from google.auth.exceptions import RefreshError
from google.auth.transport.requests import AuthorizedSession
from requests import Response, Session
from .exceptions import APIError, UnSupportedExportFormat
from .urls import (
DRIVE_FILES_API_V3_URL,
DRIVE_FILES_UPLOAD_API_V2_URL,
SPREADSHEET_BATCH_UPDATE_URL,
SPREADSHEET_SHEETS_COPY_TO_URL,
SPREADSHEET_URL,
SPREADSHEET_VALUES_APPEND_URL,
SPREADSHEET_VALUES_BATCH_CLEAR_URL,
SPREADSHEET_VALUES_BATCH_UPDATE_URL,
SPREADSHEET_VALUES_BATCH_URL,
SPREADSHEET_VALUES_CLEAR_URL,
SPREADSHEET_VALUES_URL,
)
from .utils import ExportFormat, convert_credentials, quote
ParamsType = MutableMapping[str, Optional[Union[str, int, bool, float, List[str]]]]
FileType = Optional[
Union[
MutableMapping[str, IO[Any]],
MutableMapping[str, Tuple[str, IO[Any]]],
MutableMapping[str, Tuple[str, IO[Any], str]],
MutableMapping[str, Tuple[str, IO[Any], str, MutableMapping[str, str]]],
]
]
class HTTPClient:
"""An instance of this class communicates with Google API.
:param Credentials auth: An instance of google.auth.Credentials used to authenticate requests
created by either:
* gspread.auth.oauth()
* gspread.auth.oauth_from_dict()
* gspread.auth.service_account()
* gspread.auth.service_account_from_dict()
:param Session session: (Optional) An OAuth2 credential object. Credential objects
created by `google-auth <https://github.com/googleapis/google-auth-library-python>`_.
You can pass you own Session object, simply pass ``auth=None`` and ``session=my_custom_session``.
This class is not intended to be created manually.
It will be created by the gspread.Client class.
"""
def __init__(self, auth: Credentials, session: Optional[Session] = None) -> None:
if session is not None:
self.session = session
else:
self.auth: Credentials = convert_credentials(auth)
self.session = AuthorizedSession(self.auth)
self.timeout: Optional[Union[float, Tuple[float, float]]] = None
def login(self) -> None:
from google.auth.transport.requests import Request
self.auth.refresh(Request(self.session))
self.session.headers.update({"Authorization": "Bearer %s" % self.auth.token})
def set_timeout(self, timeout: Optional[Union[float, Tuple[float, float]]]) -> None:
"""How long to wait for the server to send
data before giving up, as a float, or a ``(connect timeout,
read timeout)`` tuple.
Use value ``None`` to restore default timeout
Value for ``timeout`` is in seconds (s).
"""
self.timeout = timeout
def request(
self,
method: str,
endpoint: str,
params: Optional[ParamsType] = None,
data: Optional[bytes] = None,
json: Optional[Mapping[str, Any]] = None,
files: FileType = None,
headers: Optional[MutableMapping[str, str]] = None,
) -> Response:
response = self.session.request(
method=method,
url=endpoint,
json=json,
params=params,
data=data,
files=files,
headers=headers,
timeout=self.timeout,
)
if response.ok:
return response
else:
raise APIError(response)
def batch_update(self, id: str, body: Optional[Mapping[str, Any]]) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>:batchUpdate <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate>`_.
:param dict body: `Batch Update Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate#request-body>`_.
:returns: `Batch Update Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
r = self.request("post", SPREADSHEET_BATCH_UPDATE_URL % id, json=body)
return r.json()
def values_update(
self,
id: str,
range: str,
params: Optional[ParamsType] = None,
body: Optional[Mapping[str, Any]] = None,
) -> Any:
"""Lower-level method that directly calls `PUT spreadsheets/<ID>/values/<range> <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to update.
:param dict params: (optional) `Values Update Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update#query-parameters>`_.
:param dict body: (optional) `Values Update Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update#request-body>`_.
:returns: `Values Update Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update#response-body>`_.
:rtype: dict
Example::
sh.values_update(
'Sheet1!A2',
params={
'valueInputOption': 'USER_ENTERED'
},
body={
'values': [[1, 2, 3]]
}
)
.. versionadded:: 3.0
"""
url = SPREADSHEET_VALUES_URL % (id, quote(range))
r = self.request("put", url, params=params, json=body)
return r.json()
def values_append(
self, id: str, range: str, params: ParamsType, body: Optional[Mapping[str, Any]]
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:append <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_
of a range to search for a logical table of data. Values will be appended after the last row of the table.
:param dict params: `Values Append Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#query-parameters>`_.
:param dict body: `Values Append Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#request-body>`_.
:returns: `Values Append Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
url = SPREADSHEET_VALUES_APPEND_URL % (id, quote(range))
r = self.request("post", url, params=params, json=body)
return r.json()
def values_clear(self, id: str, range: str) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:clear <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to clear.
:returns: `Values Clear Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
url = SPREADSHEET_VALUES_CLEAR_URL % (id, quote(range))
r = self.request("post", url)
return r.json()
def values_batch_clear(
self,
id: str,
params: Optional[ParamsType] = None,
body: Optional[Mapping[str, Any]] = None,
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:batchClear`
:param dict params: (optional) `Values Batch Clear Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchClear#path-parameters>`_.
:param dict body: (optional) `Values Batch Clear request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchClear#request-body>`_.
:rtype: dict
"""
url = SPREADSHEET_VALUES_BATCH_CLEAR_URL % id
r = self.request("post", url, params=params, json=body)
return r.json()
def values_get(
self, id: str, range: str, params: Optional[ParamsType] = None
) -> Any:
"""Lower-level method that directly calls `GET spreadsheets/<ID>/values/<range> <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to retrieve.
:param dict params: (optional) `Values Get Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get#query-parameters>`_.
:returns: `Values Get Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
url = SPREADSHEET_VALUES_URL % (id, quote(range))
r = self.request("get", url, params=params)
return r.json()
def values_batch_get(
self, id: str, ranges: List[str], params: Optional[ParamsType] = None
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:batchGet <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet>`_.
:param list ranges: List of ranges in the `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to retrieve.
:param dict params: (optional) `Values Batch Get Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet#query-parameters>`_.
:returns: `Values Batch Get Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet#response-body>`_.
:rtype: dict
"""
if params is None:
params = {}
params["ranges"] = ranges
url = SPREADSHEET_VALUES_BATCH_URL % id
r = self.request("get", url, params=params)
return r.json()
def values_batch_update(
self, id: str, body: Optional[Mapping[str, Any]] = None
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:batchUpdate <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate>`_.
:param dict body: (optional) `Values Batch Update Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate#request-body>`_.
:returns: `Values Batch Update Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate#response-body>`_.
:rtype: dict
"""
url = SPREADSHEET_VALUES_BATCH_UPDATE_URL % id
r = self.request("post", url, json=body)
return r.json()
def spreadsheets_get(self, id: str, params: Optional[ParamsType] = None) -> Any:
"""A method stub that directly calls `spreadsheets.get <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get>`_."""
url = SPREADSHEET_URL % id
r = self.request("get", url, params=params)
return r.json()
def spreadsheets_sheets_copy_to(
self, id: str, sheet_id: int, destination_spreadsheet_id: str
) -> Any:
"""Lower-level method that directly calls `spreadsheets.sheets.copyTo <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.sheets/copyTo>`_."""
url = SPREADSHEET_SHEETS_COPY_TO_URL % (id, sheet_id)
body = {"destinationSpreadsheetId": destination_spreadsheet_id}
r = self.request("post", url, json=body)
return r.json()
def fetch_sheet_metadata(
self, id: str, params: Optional[ParamsType] = None
) -> Mapping[str, Any]:
"""Similar to :method spreadsheets_get:`gspread.http_client.spreadsheets_get`,
get the spreadsheet from the API but by default **does not get the cells data**.
It only retrieves the metadata from the spreadsheet.
:param str id: the spreadsheet ID key
:param dict params: (optional) the HTTP params for the GET request.
By default sets the parameter ``includeGridData`` to ``false``.
:returns: The raw spreadsheet
:rtype: dict
"""
if params is None:
params = {"includeGridData": "false"}
url = SPREADSHEET_URL % id
r = self.request("get", url, params=params)
return r.json()
def get_file_drive_metadata(self, id: str) -> Any:
"""Get the metadata from the Drive API for a specific file
This method is mainly here to retrieve the create/update time
of a file (these metadata are only accessible from the Drive API).
"""
url = DRIVE_FILES_API_V3_URL + "/{}".format(id)
params: ParamsType = {
"supportsAllDrives": True,
"includeItemsFromAllDrives": True,
"fields": "id,name,createdTime,modifiedTime",
}
res = self.request("get", url, params=params)
return res.json()
def export(self, file_id: str, format: str = ExportFormat.PDF) -> bytes:
"""Export the spreadsheet in the given format.
:param str file_id: The key of the spreadsheet to export
:param str format: The format of the resulting file.
Possible values are:
* ``ExportFormat.PDF``
* ``ExportFormat.EXCEL``
* ``ExportFormat.CSV``
* ``ExportFormat.OPEN_OFFICE_SHEET``
* ``ExportFormat.TSV``
* ``ExportFormat.ZIPPED_HTML``
See `ExportFormat`_ in the Drive API.
:type format: :class:`~gspread.utils.ExportFormat`
:returns bytes: The content of the exported file.
.. _ExportFormat: https://developers.google.com/drive/api/guides/ref-export-formats
"""
if format not in ExportFormat:
raise UnSupportedExportFormat
url = "{}/{}/export".format(DRIVE_FILES_API_V3_URL, file_id)
params: ParamsType = {"mimeType": format}
r = self.request("get", url, params=params)
return r.content
def insert_permission(
self,
file_id: str,
email_address: Optional[str],
perm_type: Optional[str],
role: Optional[str],
notify: bool = True,
email_message: Optional[str] = None,
with_link: bool = False,
) -> Response:
"""Creates a new permission for a file.
:param str file_id: a spreadsheet ID (aka file ID).
:param email_address: user or group e-mail address, domain name
or None for 'anyone' type.
:type email_address: str, None
:param str perm_type: (optional) The account type.
Allowed values are: ``user``, ``group``, ``domain``, ``anyone``
:param str role: (optional) The primary role for this user.
Allowed values are: ``owner``, ``writer``, ``reader``
:param bool notify: Whether to send an email to the target
user/domain. Default ``True``.
:param str email_message: (optional) An email message to be sent
if ``notify=True``.
:param bool with_link: Whether the link is required for this
permission to be active. Default ``False``.
:returns dict: the newly created permission
Examples::
# Give write permissions to otto@example.com
gc.insert_permission(
'0BmgG6nO_6dprnRRUWl1UFE',
'otto@example.org',
perm_type='user',
role='writer'
)
# Make the spreadsheet publicly readable
gc.insert_permission(
'0BmgG6nO_6dprnRRUWl1UFE',
None,
perm_type='anyone',
role='reader'
)
"""
url = "{}/{}/permissions".format(DRIVE_FILES_API_V3_URL, file_id)
payload = {
"type": perm_type,
"role": role,
"withLink": with_link,
}
params: ParamsType = {
"supportsAllDrives": "true",
}
if perm_type == "domain":
payload["domain"] = email_address
elif perm_type in {"user", "group"}:
payload["emailAddress"] = email_address
params["sendNotificationEmail"] = notify
params["emailMessage"] = email_message
elif perm_type == "anyone":
pass
else:
raise ValueError("Invalid permission type: {}".format(perm_type))
return self.request("post", url, json=payload, params=params)
def list_permissions(self, file_id: str) -> List[Dict[str, Union[str, bool]]]:
"""Retrieve a list of permissions for a file.
:param str file_id: a spreadsheet ID (aka file ID).
"""
url = "{}/{}/permissions".format(DRIVE_FILES_API_V3_URL, file_id)
params: ParamsType = {
"supportsAllDrives": True,
"fields": "nextPageToken,permissions",
}
token = ""
permissions = []
while token is not None:
if token:
params["pageToken"] = token
r = self.request("get", url, params=params).json()
permissions.extend(r["permissions"])
token = r.get("nextPageToken", None)
return permissions
def remove_permission(self, file_id: str, permission_id: str) -> None:
"""Deletes a permission from a file.
:param str file_id: a spreadsheet ID (aka file ID.)
:param str permission_id: an ID for the permission.
"""
url = "{}/{}/permissions/{}".format(
DRIVE_FILES_API_V3_URL, file_id, permission_id
)
params: ParamsType = {"supportsAllDrives": True}
self.request("delete", url, params=params)
def import_csv(self, file_id: str, data: Union[str, bytes]) -> Any:
"""Imports data into the first page of the spreadsheet.
:param str data: A CSV string of data.
Example:
.. code::
# Read CSV file contents
content = open('file_to_import.csv', 'r').read()
gc.import_csv(spreadsheet.id, content)
.. note::
This method removes all other worksheets and then entirely
replaces the contents of the first worksheet.
"""
# Make sure we send utf-8
if isinstance(data, str):
data = data.encode("utf-8")
headers = {"Content-Type": "text/csv"}
url = "{}/{}".format(DRIVE_FILES_UPLOAD_API_V2_URL, file_id)
res = self.request(
"put",
url,
data=data,
params={
"uploadType": "media",
"convert": True,
"supportsAllDrives": True,
},
headers=headers,
)
return res.json()
class BackOffHTTPClient(HTTPClient):
"""BackOffHTTPClient is a http client with exponential
backoff retries.
In case a request fails due to some API rate limits,
it will wait for some time, then retry the request.
This can help by trying the request after some time and
prevent the application from failing (by raising an APIError exception).
.. Warning::
This HTTPClient is not production ready yet.
Use it at your own risk !
.. note::
To use with the `auth` module, make sure to pass this backoff
http client using the ``http_client`` parameter of the
method used.
.. note::
Currently known issues are:
* will retry exponentially even when the error should
raise instantly. Due to the Drive API that raises
403 (Forbidden) errors for forbidden access and
for api rate limit exceeded."""
_HTTP_ERROR_CODES: List[HTTPStatus] = [
HTTPStatus.REQUEST_TIMEOUT, # in case of a timeout
HTTPStatus.TOO_MANY_REQUESTS, # sheet API usage rate limit exceeded
]
_NR_BACKOFF: int = 0
_MAX_BACKOFF: int = 128 # arbitrary maximum backoff
def request(self, *args: Any, **kwargs: Any) -> Response:
# Check if we should retry the request
def _should_retry(
code: int,
error: Mapping[str, Any],
wait: int,
) -> bool:
# Drive API return a dict object 'errors', the sheet API does not
if "errors" in error:
# Drive API returns a code 403 when reaching quotas/usage limits
if (
code == HTTPStatus.FORBIDDEN
and error["errors"][0]["domain"] == "usageLimits"
):
return True
# We retry if:
# - the return code is one of:
# - 429: too many requests
# - 408: request timeout
# - >= 500: some server error
# - AND we did not reach the max retry limit
return (
code in self._HTTP_ERROR_CODES
or code >= HTTPStatus.INTERNAL_SERVER_ERROR
) and wait <= self._MAX_BACKOFF
try:
return super().request(*args, **kwargs)
except APIError as err:
code = err.code
error = err.error
self._NR_BACKOFF += 1
wait = min(2**self._NR_BACKOFF, self._MAX_BACKOFF)
# check if error should retry
if _should_retry(code, error, wait) is True:
time.sleep(wait)
# make the request again
response = self.request(*args, **kwargs)
# reset counters for next time
self._NR_BACKOFF = 0
return response
# failed too many times, raise APIEerror
raise err
except RefreshError as err:
self._NR_BACKOFF += 1
wait = min(2**self._NR_BACKOFF, self._MAX_BACKOFF)
if wait <= self._MAX_BACKOFF:
time.sleep(wait)
# make the request again
response = self.request(*args, **kwargs)
# reset counters for next time
self._NR_BACKOFF = 0
return response
# failed too many times, raise APIEerror
raise err
HTTPClientType = Type[HTTPClient]
================================================
FILE: gspread/py.typed
================================================
================================================
FILE: gspread/spreadsheet.py
================================================
"""
gspread.spreadsheet
~~~~~~~~~~~~~~
This module contains common spreadsheets' models.
"""
import warnings
from typing import Any, Dict, Generator, Iterable, List, Mapping, Optional, Union
from requests import Response
from .cell import Cell
from .exceptions import WorksheetNotFound
from .http_client import HTTPClient, ParamsType
from .urls import DRIVE_FILES_API_V3_URL, SPREADSHEET_DRIVE_URL
from .utils import ExportFormat, finditem
from .worksheet import Worksheet
class Spreadsheet:
"""The class that represents a spreadsheet."""
def __init__(self, http_client: HTTPClient, properties: Dict[str, Union[str, Any]]):
self.client = http_client
self._properties = properties
metadata = self.fetch_sheet_metadata()
self._properties.update(metadata["properties"])
@property
def id(self) -> str:
"""Spreadsheet ID."""
return self._properties["id"]
@property
def title(self) -> str:
"""Spreadsheet title."""
return self._properties["title"]
@property
def url(self) -> str:
"""Spreadsheet URL."""
return SPREADSHEET_DRIVE_URL % self.id
@property
def creationTime(self) -> str:
"""Spreadsheet Creation time."""
if "createdTime" not in self._properties:
self.update_drive_metadata()
return self._properties["createdTime"]
@property
def lastUpdateTime(self) -> str:
"""Spreadsheet last updated time.
Only updated on initialisation.
For actual last updated time, use get_lastUpdateTime()."""
warnings.warn(
"worksheet.lastUpdateTime is deprecated, please use worksheet.get_lastUpdateTime()",
category=DeprecationWarning,
)
if "modifiedTime" not in self._properties:
self.update_drive_metadata()
return self._properties["modifiedTime"]
@property
def timezone(self) -> str:
"""Spreadsheet timeZone"""
return self._properties["timeZone"]
@property
def locale(self) -> str:
"""Spreadsheet locale"""
return self._properties["locale"]
@property
def sheet1(self) -> Worksheet:
"""Shortcut property for getting the first worksheet."""
return self.get_worksheet(0)
def __iter__(self) -> Generator[Worksheet, None, None]:
yield from self.worksheets()
def __repr__(self) -> str:
return "<{} {} id:{}>".format(
self.__class__.__name__,
repr(self.title),
self.id,
)
def batch_update(self, body: Mapping[str, Any]) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>:batchUpdate <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate>`_.
:param dict body: `Batch Update Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate#request-body>`_.
:returns: `Batch Update Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/batchUpdate#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
return self.client.batch_update(self.id, body)
def values_append(
self, range: str, params: ParamsType, body: Mapping[str, Any]
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:append <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_
of a range to search for a logical table of data. Values will be appended after the last row of the table.
:param dict params: `Values Append Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#query-parameters>`_.
:param dict body: `Values Append Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#request-body>`_.
:returns: `Values Append Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/append#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
return self.client.values_append(self.id, range, params, body)
def values_clear(self, range: str) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:clear <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to clear.
:returns: `Values Clear Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/clear#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
return self.client.values_clear(self.id, range)
def values_batch_clear(
self,
params: Optional[ParamsType] = None,
body: Optional[Mapping[str, Any]] = None,
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:batchClear`
:param dict params: (optional) `Values Batch Clear Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchClear#path-parameters>`_.
:param dict body: (optional) `Values Batch Clear request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchClear#request-body>`_.
:rtype: dict
"""
return self.client.values_batch_clear(self.id, params, body)
def values_get(self, range: str, params: Optional[ParamsType] = None) -> Any:
"""Lower-level method that directly calls `GET spreadsheets/<ID>/values/<range> <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to retrieve.
:param dict params: (optional) `Values Get Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get#query-parameters>`_.
:returns: `Values Get Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/get#response-body>`_.
:rtype: dict
.. versionadded:: 3.0
"""
return self.client.values_get(self.id, range, params=params)
def values_batch_get(
self, ranges: List[str], params: Optional[ParamsType] = None
) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:batchGet <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet>`_.
:param list ranges: List of ranges in the `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to retrieve.
:param dict params: (optional) `Values Batch Get Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet#query-parameters>`_.
:returns: `Values Batch Get Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchGet#response-body>`_.
:rtype: dict
"""
return self.client.values_batch_get(self.id, ranges, params=params)
def values_update(
self,
range: str,
params: Optional[ParamsType] = None,
body: Optional[Mapping[str, Any]] = None,
) -> Any:
"""Lower-level method that directly calls `PUT spreadsheets/<ID>/values/<range> <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update>`_.
:param str range: The `A1 notation <https://developers.google.com/sheets/api/guides/concepts#a1_notation>`_ of the values to update.
:param dict params: (optional) `Values Update Query parameters <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update#query-parameters>`_.
:param dict body: (optional) `Values Update Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update#request-body>`_.
:returns: `Values Update Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update#response-body>`_.
:rtype: dict
Example::
sh.values_update(
'Sheet1!A2',
params={
'valueInputOption': 'USER_ENTERED'
},
body={
'values': [[1, 2, 3]]
}
)
.. versionadded:: 3.0
"""
return self.client.values_update(self.id, range, params=params, body=body)
def values_batch_update(self, body: Optional[Mapping[str, Any]] = None) -> Any:
"""Lower-level method that directly calls `spreadsheets/<ID>/values:batchUpdate <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate>`_.
:param dict body: (optional) `Values Batch Update Request body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate#request-body>`_.
:returns: `Values Batch Update Response body <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/batchUpdate#response-body>`_.
:rtype: dict
"""
return self.client.values_batch_update(self.id, body=body)
def _spreadsheets_get(self, params: Optional[ParamsType] = None) -> Any:
"""A method stub that directly calls `spreadsheets.get <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/get>`_."""
return self.client.spreadsheets_get(self.id, params=params)
def _spreadsheets_sheets_copy_to(
self, sheet_id: int, destination_spreadsheet_id: str
) -> Any:
"""Lower-level method that directly calls `spreadsheets.sheets.copyTo <https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.sheets/copyTo>`_."""
return self.client.spreadsheets_sheets_copy_to(
self.id, sheet_id, destination_spreadsheet_id
)
def fetch_sheet_metadata(
self, params: Optional[ParamsType] = None
) -> Mapping[str, Any]:
"""Similar to :method spreadsheets_get:`gspread.http_client.spreadsheets_get`,
get the spreadsheet from the API but by default **does not get the cells data**.
It only retrieves the metadata from the spreadsheet.
:param dict params: (optional) the HTTP params for the GET request.
By default sets the parameter ``includeGridData`` to ``false``.
:returns: The raw spreadsheet
:rtype: dict
"""
return self.client.fetch_sheet_metadata(self.id, params=params)
def get_worksheet(self, index: int) -> Worksheet:
"""Returns a worksheet with specified `index`.
:param index: An index of a worksheet. Indexes start from zero.
:type index: int
:returns: an instance of :class:`gspread.worksheet.Worksheet`.
:raises:
:class:`~gspread.exceptions.WorksheetNotFound`: if can't find the worksheet
Example. To get third worksheet of a spreadsheet:
>>> sht = client.open('My fancy spreadsheet')
>>> worksheet = sht.get_worksheet(2)
"""
sheet_data = self.fetch_sheet_metadata()
try:
properties = sheet_data["sheets"][index]["properties"]
return Worksheet(self, properties, self.id, self.client)
except (KeyError, IndexError):
raise WorksheetNotFound("index {} not found".format(index))
def get_worksheet_by_id(self, id: Union[str, int]) -> Worksheet:
"""Returns a worksheet with specified `worksheet id`.
:param id: The id of a worksheet. it can be seen in the url as the value of the parameter 'gid'.
:type id: str | int
:returns: an instance of :class:`gspread.worksheet.Worksheet`.
:raises:
:class:`~gspread.exceptions.WorksheetNotFound`: if can't find the worksheet
Example. To get the worksheet 123456 of a spreadsheet:
>>> sht = client.open('My fancy spreadsheet')
>>> worksheet = sht.get_worksheet_by_id(123456)
"""
sheet_data = self.fetch_sheet_metadata()
try:
worksheet_id_int = int(id)
except ValueError as ex:
raise ValueError("id should be int") from ex
try:
item = finditem(
lambda x: x["properties"]["sheetId"] == worksheet_id_int,
sheet_data["sheets"],
)
return Worksheet(self, item["properties"], self.id, self.client)
except (StopIteration, KeyError):
raise WorksheetNotFound("id {} not found".format(worksheet_id_int))
def worksheets(self, exclude_hidden: bool = False) -> List[Worksheet]:
"""Returns a list of all :class:`worksheets <gspread.worksheet.Worksheet>`
in a spreadsheet.
:param exclude_hidden: (optional) If set to ``True`` will only return
visible worksheets. Default is ``False``.
:type exclude_hidden: bool
:returns: a list of :class:`worksheets <gspread.worksheet.Worksheet>`.
:rtype: list
"""
sheet_data = self.fetch_sheet_metadata()
worksheets = [
Worksheet(self, s["properties"], self.id, self.client)
for s in sheet_data["sheets"]
]
if exclude_hidden:
worksheets = [w for w in worksheets if not w.isSheetHidden]
return worksheets
def worksheet(self, title: str) -> Worksheet:
"""Returns a worksheet with specified `title`.
:param title: A title of a worksheet. If there're multiple
worksheets with the same title, first one will
be returned.
:type title: str
:returns: an instance of :class:`gspread.worksheet.Worksheet`.
:raises:
WorksheetNotFound: if can't find the worksheet
Example. Getting worksheet named 'Annual bonuses'
>>> sht = client.open('Sample one')
>>> worksheet = sht.worksheet('Annual bonuses')
"""
sheet_data = self.fetch_sheet_metadata()
try:
item = finditem(
lambda x: x["properties"]["title"] == title,
sheet_data["sheets"],
)
return Worksheet(self, item["properties"], self.id, self.client)
except (StopIteration, KeyError):
raise WorksheetNotFound(title)
def add_worksheet(
self, title: str, rows: int, cols: int, index: Optional[int] = None
) -> Worksheet:
"""Adds a new worksheet to a spreadsheet.
:param title: A title of a new worksheet.
:type title: str
:param rows: Number of rows.
:type rows: int
:param cols: Number of columns.
:type cols: int
:param index: Position of the sheet.
:type index: int
:returns: a newly created :class:`worksheets <gspread.worksheet.Worksheet>`.
"""
body: Dict[
str, List[Dict[str, Dict[str, Dict[str, Union[str, int, Dict[str, int]]]]]]
] = {
"requests": [
{
"addSheet": {
"properties": {
"title": title,
"sheetType": "GRID",
"gridProperties": {
"rowCount": rows,
"columnCount": cols,
},
}
}
}
]
}
if index is not None:
body["requests"][0]["addSheet"]["properties"]["index"] = index
data = self.client.batch_update(self.id, body)
properties = data["replies"][0]["addSheet"]["properties"]
return Worksheet(self, properties, self.id, self.client)
def duplicate_sheet(
self,
source_sheet_id: int,
insert_sheet_index: Optional[int] = None,
new_sheet_id: Optional[int] = None,
new_sheet_name: Optional[str] = None,
) -> Worksheet:
"""Duplicates the contents of a sheet.
:param int source_sheet_id: The sheet ID to duplicate.
:param int insert_sheet_index: (optional) The zero-based index
where the new sheet should be inserted.
The index of all sheets after this are
incremented.
:param int new_sheet_id: (optional) The ID of the new sheet.
If not set, an ID is chosen. If set, the ID
must not conflict with any existing sheet ID.
If set, it must be non-negative.
:param str new_sheet_name: (optional) The name of the new sheet.
If empty, a new name is chosen for you.
:returns: a newly created :class:`gspread.worksheet.Worksheet`
.. versionadded:: 3.1
"""
return Worksheet._duplicate(
self.client,
self.id,
source_sheet_id,
self,
insert_sheet_index=insert_sheet_index,
new_sheet_id=new_sheet_id,
new_sheet_name=new_sheet_name,
)
def del_worksheet(self, worksheet: Worksheet) -> Any:
"""Deletes a worksheet from a spreadsheet.
:param worksheet: The worksheet to be deleted.
:type worksheet: :class:`~gspread.worksheet.Worksheet`
"""
body = {"requests": [{"deleteSheet": {"sheetId": worksheet.id}}]}
return self.client.batch_update(self.id, body)
def del_worksheet_by_id(self, worksheet_id: Union[str, int]) -> Any:
"""
Deletes a Worksheet by id
"""
try:
worksheet_id_int = int(worksheet_id)
except ValueError as ex:
raise ValueError("id should be int") from ex
body = {"requests": [{"deleteSheet": {"sheetId": worksheet_id_int}}]}
return self.client.batch_update(self.id, body)
def reorder_worksheets(
self, worksheets_in_desired_order: Iterable[Worksheet]
) -> Any:
"""Updates the ``index`` property of each Worksheet to reflect
its index in the provided sequence of Worksheets.
:param worksheets_in_desired_order: Iterable of Worksheet objects in desired order.
Note: If you omit some of the Spreadsheet's existing Worksheet objects from
the provided sequence, those Worksheets will be appended to the end of the sequence
in the order that they appear in the list returned by :meth:`gspread.spreadsheet.Spreadsheet.worksheets`.
.. versionadded:: 3.4
"""
idx_map = {}
for idx, w in enumerate(worksheets_in_desired_order):
idx_map[w.id] = idx
for w in self.worksheets():
if w.id in idx_map:
continue
idx += 1
idx_map[w.id] = idx
body = {
"requests": [
{
"updateSheetProperties": {
"properties": {"sheetId": key, "index": val},
"fields": "index",
}
}
for key, val in idx_map.items()
]
}
return self.client.batch_update(self.id, body)
def share(
self,
email_address: str,
perm_type: str,
role: str,
notify: bool = True,
email_message: Optional[str] = None,
with_link: bool = False,
) -> Response:
"""Share the spreadsheet with other accounts.
:param email_address: user or group e-mail address, domain name
or None for 'anyone' type.
:type email_address: str, None
:param perm_type: The account type.
Allowed values are: ``user``, ``group``, ``domain``,
``anyone``.
:type perm_type: str
:param role: The primary role for this user.
Allowed values are: ``owner``, ``writer``, ``reader``.
:type role: str
:param notify: (optional) Whether to send an email to
gitextract_gl9l53pb/ ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ ├── config.yml │ │ └── feature_request.md │ ├── dependabot.yaml │ └── workflows/ │ ├── main.yaml │ └── release.yaml ├── .gitignore ├── .readthedocs.yaml ├── HISTORY.rst ├── LICENSE.txt ├── README.md ├── docs/ │ ├── _templates/ │ │ └── layout.html │ ├── advanced.rst │ ├── api/ │ │ ├── auth.rst │ │ ├── client.rst │ │ ├── exceptions.rst │ │ ├── http_client.rst │ │ ├── index.rst │ │ ├── models/ │ │ │ ├── cell.rst │ │ │ ├── index.rst │ │ │ ├── spreadsheet.rst │ │ │ └── worksheet.rst │ │ ├── top-level.rst │ │ └── utils.rst │ ├── community.rst │ ├── conf.py │ ├── index.rst │ ├── oauth2.rst │ ├── requirements.txt │ └── user-guide.rst ├── gspread/ │ ├── __init__.py │ ├── auth.py │ ├── cell.py │ ├── client.py │ ├── exceptions.py │ ├── http_client.py │ ├── py.typed │ ├── spreadsheet.py │ ├── urls.py │ ├── utils.py │ └── worksheet.py ├── lint-requirements.txt ├── pyproject.toml ├── test-requirements.txt ├── tests/ │ ├── __init__.py │ ├── cassettes/ │ │ ├── CellTest.test_a1_value.json │ │ ├── CellTest.test_define_named_range.json │ │ ├── CellTest.test_delete_named_range.json │ │ ├── CellTest.test_equality.json │ │ ├── CellTest.test_merge_cells.json │ │ ├── CellTest.test_numeric_value.json │ │ ├── CellTest.test_properties.json │ │ ├── ClientTest.test_access_non_existing_spreadsheet.json │ │ ├── ClientTest.test_access_private_spreadsheet.json │ │ ├── ClientTest.test_add_timeout.json │ │ ├── ClientTest.test_client_export_spreadsheet.json │ │ ├── ClientTest.test_copy.json │ │ ├── ClientTest.test_create.json │ │ ├── ClientTest.test_import_csv.json │ │ ├── ClientTest.test_list_spreadsheet_files.json │ │ ├── ClientTest.test_no_found_exeption.json │ │ ├── ClientTest.test_open_all_has_metadata.json │ │ ├── ClientTest.test_open_by_key_has_metadata.json │ │ ├── ClientTest.test_open_by_name_has_metadata.json │ │ ├── ClientTest.test_openall.json │ │ ├── SpreadsheetTest.test_add_del_worksheet.json │ │ ├── SpreadsheetTest.test_bad_json_api_error.json │ │ ├── SpreadsheetTest.test_creationTime_prop.json │ │ ├── SpreadsheetTest.test_export_spreadsheet.json │ │ ├── SpreadsheetTest.test_get_lastUpdateTime.json │ │ ├── SpreadsheetTest.test_get_worksheet.json │ │ ├── SpreadsheetTest.test_get_worksheet_by_id.json │ │ ├── SpreadsheetTest.test_lastUpdateTime_prop.json │ │ ├── SpreadsheetTest.test_properties.json │ │ ├── SpreadsheetTest.test_sheet1.json │ │ ├── SpreadsheetTest.test_timezone_and_locale.json │ │ ├── SpreadsheetTest.test_update_title.json │ │ ├── SpreadsheetTest.test_values_batch_get.json │ │ ├── SpreadsheetTest.test_values_get.json │ │ ├── SpreadsheetTest.test_worksheet.json │ │ ├── SpreadsheetTest.test_worksheet_iteration.json │ │ ├── SpreadsheetTest.test_worksheets.json │ │ ├── SpreadsheetTest.test_worksheets_exclude_hidden.json │ │ ├── WorksheetTest.test_acell.json │ │ ├── WorksheetTest.test_add_protected_range_normal.json │ │ ├── WorksheetTest.test_add_protected_range_warning.json │ │ ├── WorksheetTest.test_add_validation.json │ │ ├── WorksheetTest.test_append_row.json │ │ ├── WorksheetTest.test_append_row_with_empty_value.json │ │ ├── WorksheetTest.test_append_row_with_empty_value_and_table_range.json │ │ ├── WorksheetTest.test_attributes.json │ │ ├── WorksheetTest.test_auto_resize_columns.json │ │ ├── WorksheetTest.test_basic_filters.json │ │ ├── WorksheetTest.test_batch_clear.json │ │ ├── WorksheetTest.test_batch_get.json │ │ ├── WorksheetTest.test_batch_merged_cells.json │ │ ├── WorksheetTest.test_batch_update.json │ │ ├── WorksheetTest.test_cell.json │ │ ├── WorksheetTest.test_cell_return_first.json │ │ ├── WorksheetTest.test_clear.json │ │ ├── WorksheetTest.test_clear_tab_color.json │ │ ├── WorksheetTest.test_copy_cut_range.json │ │ ├── WorksheetTest.test_delete_cols.json │ │ ├── WorksheetTest.test_delete_protected_range.json │ │ ├── WorksheetTest.test_delete_row.json │ │ ├── WorksheetTest.test_find.json │ │ ├── WorksheetTest.test_findall.json │ │ ├── WorksheetTest.test_format.json │ │ ├── WorksheetTest.test_freeze.json │ │ ├── WorksheetTest.test_get_all_records.json │ │ ├── WorksheetTest.test_get_all_records_different_header.json │ │ ├── WorksheetTest.test_get_all_records_duplicate_keys.json │ │ ├── WorksheetTest.test_get_all_records_numericise_unformatted.json │ │ ├── WorksheetTest.test_get_all_records_pad_more_than_one_key.json │ │ ├── WorksheetTest.test_get_all_records_pad_one_key.json │ │ ├── WorksheetTest.test_get_all_records_pad_values.json │ │ ├── WorksheetTest.test_get_all_records_value_render_options.json │ │ ├── WorksheetTest.test_get_all_records_with_all_values_blank.json │ │ ├── WorksheetTest.test_get_all_records_with_blank_final_headers.json │ │ ├── WorksheetTest.test_get_all_records_with_keys_blank.json │ │ ├── WorksheetTest.test_get_all_records_with_some_values_blank.json │ │ ├── WorksheetTest.test_get_all_values.json │ │ ├── WorksheetTest.test_get_all_values_date_time_render_options.json │ │ ├── WorksheetTest.test_get_all_values_title_is_a1_notation.json │ │ ├── WorksheetTest.test_get_and_get_values_have_same_signature.json │ │ ├── WorksheetTest.test_get_merge_cells_and_unmerge_cells.json │ │ ├── WorksheetTest.test_get_notes.json │ │ ├── WorksheetTest.test_get_notes_2nd_sheet.json │ │ ├── WorksheetTest.test_get_returns_ValueRange_with_metadata.json │ │ ├── WorksheetTest.test_get_values_and_combine_merged_cells.json │ │ ├── WorksheetTest.test_get_values_and_maintain_size.json │ │ ├── WorksheetTest.test_get_values_can_emulate_get_with_kwargs.json │ │ ├── WorksheetTest.test_get_values_merge_cells_from_centre_of_sheet.json │ │ ├── WorksheetTest.test_get_values_merge_cells_outside_of_range.json │ │ ├── WorksheetTest.test_get_values_merge_cells_with_named_range.json │ │ ├── WorksheetTest.test_get_values_returns_padded_get_as_listoflists.json │ │ ├── WorksheetTest.test_get_values_with_args_or_kwargs.json │ │ ├── WorksheetTest.test_group_columns.json │ │ ├── WorksheetTest.test_group_rows.json │ │ ├── WorksheetTest.test_hide_columns_rows.json │ │ ├── WorksheetTest.test_hide_gridlines.json │ │ ├── WorksheetTest.test_hide_show_worksheet.json │ │ ├── WorksheetTest.test_insert_cols.json │ │ ├── WorksheetTest.test_insert_row.json │ │ ├── WorksheetTest.test_range.json │ │ ├── WorksheetTest.test_range_get_all_values.json │ │ ├── WorksheetTest.test_range_reversed.json │ │ ├── WorksheetTest.test_range_unbounded.json │ │ ├── WorksheetTest.test_reorder_worksheets.json │ │ ├── WorksheetTest.test_resize.json │ │ ├── WorksheetTest.test_set_tab_color.json │ │ ├── WorksheetTest.test_show_gridlines.json │ │ ├── WorksheetTest.test_sort.json │ │ ├── WorksheetTest.test_update_acell.json │ │ ├── WorksheetTest.test_update_and_get.json │ │ ├── WorksheetTest.test_update_cell.json │ │ ├── WorksheetTest.test_update_cell_multiline.json │ │ ├── WorksheetTest.test_update_cell_objects.json │ │ ├── WorksheetTest.test_update_cell_unicode.json │ │ ├── WorksheetTest.test_update_cells.json │ │ ├── WorksheetTest.test_update_cells_noncontiguous.json │ │ ├── WorksheetTest.test_update_cells_unicode.json │ │ ├── WorksheetTest.test_update_tab_color.json │ │ ├── WorksheetTest.test_update_title.json │ │ ├── WorksheetTest.test_update_works_with_swapped_values_and_range.json │ │ ├── WorksheetTest.test_worksheet_notes.json │ │ └── WorksheetTest.test_worksheet_update_index.json │ ├── cell_test.py │ ├── client_test.py │ ├── conftest.py │ ├── spreadsheet_test.py │ ├── utils_test.py │ └── worksheet_test.py └── tox.ini
SYMBOL INDEX (454 symbols across 14 files)
FILE: gspread/auth.py
function get_config_dir (line 41) | def get_config_dir(
function authorize (line 64) | def authorize(
class FlowCallable (line 90) | class FlowCallable(Protocol):
method __call__ (line 93) | def __call__(
function local_server_flow (line 98) | def local_server_flow(
function load_credentials (line 114) | def load_credentials(
function store_credentials (line 123) | def store_credentials(
function oauth (line 133) | def oauth(
function oauth_from_dict (line 216) | def oauth_from_dict(
function service_account (line 300) | def service_account(
function service_account_from_dict (line 333) | def service_account_from_dict(
function api_key (line 369) | def api_key(token: str, http_client: HTTPClientType = HTTPClient) -> Cli...
FILE: gspread/cell.py
class Cell (line 14) | class Cell:
method __init__ (line 19) | def __init__(self, row: int, col: int, value: Optional[str] = "") -> N...
method from_address (line 27) | def from_address(cls, label: str, value: str = "") -> "Cell":
method __repr__ (line 38) | def __repr__(self) -> str:
method __eq__ (line 46) | def __eq__(self, other: object) -> bool:
method row (line 56) | def row(self) -> int:
method col (line 64) | def col(self) -> int:
method numeric_value (line 72) | def numeric_value(self) -> Optional[Union[int, float]]:
method address (line 90) | def address(self) -> str:
FILE: gspread/client.py
class Client (line 23) | class Client:
method __init__ (line 35) | def __init__(
method expiry (line 44) | def expiry(self) -> Optional[datetime]:
method set_timeout (line 55) | def set_timeout(
method get_file_drive_metadata (line 68) | def get_file_drive_metadata(self, id: str) -> Any:
method list_spreadsheet_files (line 75) | def list_spreadsheet_files(
method _list_spreadsheet_files (line 93) | def _list_spreadsheet_files(
method open (line 129) | def open(self, title: str, folder_id: Optional[str] = None) -> Spreads...
method open_by_key (line 159) | def open_by_key(self, key: str) -> Spreadsheet:
method open_by_url (line 177) | def open_by_url(self, url: str) -> Spreadsheet:
method openall (line 191) | def openall(self, title: Optional[str] = None) -> List[Spreadsheet]:
method create (line 211) | def create(self, title: str, folder_id: Optional[str] = None) -> Sprea...
method export (line 240) | def export(self, file_id: str, format: str = ExportFormat.PDF) -> bytes:
method copy (line 266) | def copy(
method del_spreadsheet (line 378) | def del_spreadsheet(self, file_id: str) -> None:
method import_csv (line 388) | def import_csv(self, file_id: str, data: Union[str, bytes]) -> Any:
method list_permissions (line 411) | def list_permissions(self, file_id: str) -> List[Dict[str, Union[str, ...
method insert_permission (line 418) | def insert_permission(
method remove_permission (line 472) | def remove_permission(self, file_id: str, permission_id: str) -> None:
FILE: gspread/exceptions.py
class UnSupportedExportFormat (line 14) | class UnSupportedExportFormat(Exception):
class GSpreadException (line 18) | class GSpreadException(Exception):
class WorksheetNotFound (line 22) | class WorksheetNotFound(GSpreadException):
class NoValidUrlKeyFound (line 26) | class NoValidUrlKeyFound(GSpreadException):
class IncorrectCellLabel (line 30) | class IncorrectCellLabel(GSpreadException):
class InvalidInputValue (line 34) | class InvalidInputValue(GSpreadException):
class APIError (line 38) | class APIError(GSpreadException):
method __init__ (line 42) | def __init__(self, response: Response):
method __str__ (line 61) | def __str__(self) -> str:
method __repr__ (line 66) | def __repr__(self) -> str:
method __reduce__ (line 69) | def __reduce__(self) -> tuple:
class SpreadsheetNotFound (line 73) | class SpreadsheetNotFound(GSpreadException):
FILE: gspread/http_client.py
class HTTPClient (line 58) | class HTTPClient:
method __init__ (line 78) | def __init__(self, auth: Credentials, session: Optional[Session] = Non...
method login (line 87) | def login(self) -> None:
method set_timeout (line 94) | def set_timeout(self, timeout: Optional[Union[float, Tuple[float, floa...
method request (line 105) | def request(
method batch_update (line 131) | def batch_update(self, id: str, body: Optional[Mapping[str, Any]]) -> ...
method values_update (line 144) | def values_update(
method values_append (line 177) | def values_append(
method values_clear (line 195) | def values_clear(self, id: str, range: str) -> Any:
method values_batch_clear (line 208) | def values_batch_clear(
method values_get (line 224) | def values_get(
method values_batch_get (line 240) | def values_batch_get(
method values_batch_update (line 259) | def values_batch_update(
method spreadsheets_get (line 272) | def spreadsheets_get(self, id: str, params: Optional[ParamsType] = Non...
method spreadsheets_sheets_copy_to (line 278) | def spreadsheets_sheets_copy_to(
method fetch_sheet_metadata (line 288) | def fetch_sheet_metadata(
method get_file_drive_metadata (line 310) | def get_file_drive_metadata(self, id: str) -> Any:
method export (line 328) | def export(self, file_id: str, format: str = ExportFormat.PDF) -> bytes:
method insert_permission (line 362) | def insert_permission(
method list_permissions (line 435) | def list_permissions(self, file_id: str) -> List[Dict[str, Union[str, ...
method remove_permission (line 462) | def remove_permission(self, file_id: str, permission_id: str) -> None:
method import_csv (line 475) | def import_csv(self, file_id: str, data: Union[str, bytes]) -> Any:
class BackOffHTTPClient (line 517) | class BackOffHTTPClient(HTTPClient):
method request (line 551) | def request(self, *args: Any, **kwargs: Any) -> Response:
FILE: gspread/spreadsheet.py
class Spreadsheet (line 22) | class Spreadsheet:
method __init__ (line 25) | def __init__(self, http_client: HTTPClient, properties: Dict[str, Unio...
method id (line 33) | def id(self) -> str:
method title (line 38) | def title(self) -> str:
method url (line 43) | def url(self) -> str:
method creationTime (line 48) | def creationTime(self) -> str:
method lastUpdateTime (line 55) | def lastUpdateTime(self) -> str:
method timezone (line 68) | def timezone(self) -> str:
method locale (line 73) | def locale(self) -> str:
method sheet1 (line 78) | def sheet1(self) -> Worksheet:
method __iter__ (line 82) | def __iter__(self) -> Generator[Worksheet, None, None]:
method __repr__ (line 85) | def __repr__(self) -> str:
method batch_update (line 92) | def batch_update(self, body: Mapping[str, Any]) -> Any:
method values_append (line 103) | def values_append(
method values_clear (line 119) | def values_clear(self, range: str) -> Any:
method values_batch_clear (line 130) | def values_batch_clear(
method values_get (line 143) | def values_get(self, range: str, params: Optional[ParamsType] = None) ...
method values_batch_get (line 155) | def values_batch_get(
method values_update (line 167) | def values_update(
method values_batch_update (line 197) | def values_batch_update(self, body: Optional[Mapping[str, Any]] = None...
method _spreadsheets_get (line 206) | def _spreadsheets_get(self, params: Optional[ParamsType] = None) -> Any:
method _spreadsheets_sheets_copy_to (line 210) | def _spreadsheets_sheets_copy_to(
method fetch_sheet_metadata (line 218) | def fetch_sheet_metadata(
method get_worksheet (line 232) | def get_worksheet(self, index: int) -> Worksheet:
method get_worksheet_by_id (line 256) | def get_worksheet_by_id(self, id: Union[str, int]) -> Worksheet:
method worksheets (line 287) | def worksheets(self, exclude_hidden: bool = False) -> List[Worksheet]:
method worksheet (line 307) | def worksheet(self, title: str) -> Worksheet:
method add_worksheet (line 335) | def add_worksheet(
method duplicate_sheet (line 379) | def duplicate_sheet(
method del_worksheet (line 415) | def del_worksheet(self, worksheet: Worksheet) -> Any:
method del_worksheet_by_id (line 425) | def del_worksheet_by_id(self, worksheet_id: Union[str, int]) -> Any:
method reorder_worksheets (line 438) | def reorder_worksheets(
method share (line 475) | def share(
method export (line 521) | def export(self, format: ExportFormat = ExportFormat.PDF) -> bytes:
method list_permissions (line 546) | def list_permissions(self) -> List[Dict[str, Union[str, bool]]]:
method remove_permissions (line 550) | def remove_permissions(self, value: str, role: str = "any") -> List[str]:
method transfer_ownership (line 582) | def transfer_ownership(self, permission_id: str) -> Response:
method accept_ownership (line 610) | def accept_ownership(self, permission_id: str) -> Response:
method named_range (line 636) | def named_range(self, named_range: str) -> List[Cell]:
method list_named_ranges (line 648) | def list_named_ranges(self) -> List[Any]:
method update_title (line 654) | def update_title(self, title: str) -> Any:
method update_timezone (line 674) | def update_timezone(self, timezone: str) -> Any:
method update_locale (line 695) | def update_locale(self, locale: str) -> Any:
method list_protected_ranges (line 721) | def list_protected_ranges(self, sheetid: int) -> List[Any]:
method get_lastUpdateTime (line 737) | def get_lastUpdateTime(self) -> str:
method update_drive_metadata (line 742) | def update_drive_metadata(self) -> None:
FILE: gspread/utils.py
class StrEnum (line 50) | class StrEnum(str, enum.Enum):
method __new__ (line 51) | def __new__(cls, value, *args, **kwargs):
method __str__ (line 58) | def __str__(self):
method _generate_next_value_ (line 61) | def _generate_next_value_(name, *_):
class Dimension (line 65) | class Dimension(StrEnum):
class MergeType (line 70) | class MergeType(StrEnum):
class ValueRenderOption (line 76) | class ValueRenderOption(StrEnum):
class ValueInputOption (line 82) | class ValueInputOption(StrEnum):
class InsertDataOption (line 87) | class InsertDataOption(StrEnum):
class DateTimeOption (line 92) | class DateTimeOption(StrEnum):
class MimeType (line 97) | class MimeType(StrEnum):
class ExportFormat (line 107) | class ExportFormat(StrEnum):
class PasteType (line 116) | class PasteType(StrEnum):
class PasteOrientation (line 126) | class PasteOrientation(StrEnum):
class GridRangeType (line 131) | class GridRangeType(StrEnum):
class ValidationConditionType (line 136) | class ValidationConditionType(StrEnum):
class TableDirection (line 171) | class TableDirection(StrEnum):
function convert_credentials (line 177) | def convert_credentials(credentials: Credentials) -> Credentials:
function _convert_oauth (line 196) | def _convert_oauth(credentials: Any) -> Credentials:
function _convert_service_account (line 208) | def _convert_service_account(credentials: Any) -> Credentials:
function finditem (line 222) | def finditem(func: Callable[[T], bool], seq: Iterable[T]) -> T:
function numericise (line 227) | def numericise(
function numericise_all (line 302) | def numericise_all(
function rowcol_to_a1 (line 342) | def rowcol_to_a1(row: int, col: int) -> str:
function a1_to_rowcol (line 379) | def a1_to_rowcol(label: str) -> Tuple[int, int]:
function _a1_to_rowcol_unbounded (line 411) | def _a1_to_rowcol_unbounded(label: str) -> Tuple[IntOrInf, IntOrInf]:
function a1_range_to_grid_range (line 472) | def a1_range_to_grid_range(name: str, sheet_id: Optional[int] = None) ->...
function column_letter_to_index (line 538) | def column_letter_to_index(column: str) -> int:
function cast_to_a1_notation (line 580) | def cast_to_a1_notation(method: Callable[..., T]) -> Callable[..., T]:
function extract_id_from_url (line 613) | def extract_id_from_url(url: str) -> str:
function wid_to_gid (line 625) | def wid_to_gid(wid: str) -> str:
function rightpad (line 632) | def rightpad(row: List[Any], max_len: int, padding_value: Any = "") -> L...
function fill_gaps (line 637) | def fill_gaps(
function cell_list_to_rect (line 682) | def cell_list_to_rect(cell_list: List["Cell"]) -> List[List[Optional[str...
function quote (line 709) | def quote(value: str, safe: str = "", encoding: str = "utf-8") -> str:
function absolute_range_name (line 713) | def absolute_range_name(sheet_name: str, range_name: Optional[str] = Non...
function is_scalar (line 742) | def is_scalar(x: Any) -> bool:
function combined_merge_values (line 768) | def combined_merge_values(
function convert_hex_to_colors_dict (line 838) | def convert_hex_to_colors_dict(hex_color: str) -> Mapping[str, float]:
function convert_colors_to_hex_value (line 884) | def convert_colors_to_hex_value(
function is_full_a1_notation (line 921) | def is_full_a1_notation(range_name: str) -> bool:
function get_a1_from_absolute_range (line 943) | def get_a1_from_absolute_range(range_name: str) -> str:
function to_records (line 959) | def to_records(
function _expand_right (line 988) | def _expand_right(values: List[List[str]], start: int, end: int, row: in...
function _expand_bottom (line 1002) | def _expand_bottom(values: List[List[str]], start: int, end: int, col: i...
function find_table (line 1022) | def find_table(
FILE: gspread/worksheet.py
class ValueRange (line 85) | class ValueRange(list):
method from_json (line 124) | def from_json(cls: Type[ValueRangeType], json: Mapping[str, Any]) -> V...
method range (line 135) | def range(self) -> str:
method major_dimension (line 140) | def major_dimension(self) -> str:
method first (line 150) | def first(self, default: Optional[str] = None) -> Optional[str]:
class Worksheet (line 161) | class Worksheet:
method __init__ (line 166) | def __init__(
method __repr__ (line 204) | def __repr__(self) -> str:
method id (line 212) | def id(self) -> int:
method spreadsheet (line 217) | def spreadsheet(self) -> "Spreadsheet":
method title (line 222) | def title(self) -> str:
method url (line 227) | def url(self) -> str:
method index (line 232) | def index(self) -> int:
method isSheetHidden (line 237) | def isSheetHidden(self) -> bool:
method row_count (line 243) | def row_count(self) -> int:
method col_count (line 248) | def col_count(self) -> int:
method column_count (line 259) | def column_count(self) -> int:
method frozen_row_count (line 264) | def frozen_row_count(self) -> int:
method frozen_col_count (line 269) | def frozen_col_count(self) -> int:
method is_gridlines_hidden (line 274) | def is_gridlines_hidden(self) -> bool:
method tab_color (line 281) | def tab_color(self) -> Optional[str]:
method get_tab_color (line 285) | def get_tab_color(self) -> Optional[str]:
method _get_sheet_property (line 292) | def _get_sheet_property(self, property: str, default_value: Optional[T...
method acell (line 301) | def acell(
method cell (line 327) | def cell(
method range (line 374) | def range(self, name: str = "") -> List[Cell]:
method get_values (line 446) | def get_values(
method get_all_values (line 474) | def get_all_values(
method get_all_records (line 497) | def get_all_records(
method get_all_cells (line 615) | def get_all_cells(self) -> List[Cell]:
method row_values (line 620) | def row_values(
method col_values (line 696) | def col_values(
method update_acell (line 733) | def update_acell(self, label: str, value: Union[int, float, str]) -> J...
method update_cell (line 745) | def update_cell(
method update_cells (line 769) | def update_cells(
method get (line 826) | def get(
method batch_get (line 1023) | def batch_get(
method update (line 1108) | def update(
method batch_update (line 1255) | def batch_update(
method batch_format (line 1372) | def batch_format(self, formats: List[CellFormat]) -> JSONResponse:
method format (line 1439) | def format(
method resize (line 1488) | def resize(
method sort (line 1530) | def sort(
method update_title (line 1600) | def update_title(self, title: str) -> JSONResponse:
method update_tab_color (line 1620) | def update_tab_color(self, color: str) -> JSONResponse:
method clear_tab_color (line 1650) | def clear_tab_color(self) -> JSONResponse:
method update_index (line 1673) | def update_index(self, index: int) -> JSONResponse:
method _auto_resize (line 1700) | def _auto_resize(
method columns_auto_resize (line 1732) | def columns_auto_resize(
method rows_auto_resize (line 1748) | def rows_auto_resize(
method add_rows (line 1763) | def add_rows(self, rows: int) -> None:
method add_cols (line 1772) | def add_cols(self, cols: int) -> None:
method append_row (line 1781) | def append_row(
method append_rows (line 1820) | def append_rows(
method insert_row (line 1867) | def insert_row(
method insert_rows (line 1906) | def insert_rows(
method insert_cols (line 1978) | def insert_cols(
method add_protected_range (line 2044) | def add_protected_range(
method delete_protected_range (line 2117) | def delete_protected_range(self, id: str) -> JSONResponse:
method delete_dimension (line 2136) | def delete_dimension(
method delete_rows (line 2176) | def delete_rows(
method delete_columns (line 2197) | def delete_columns(
method clear (line 2209) | def clear(self) -> JSONResponse:
method batch_clear (line 2215) | def batch_clear(self, ranges: Sequence[str]) -> JSONResponse:
method _finder (line 2241) | def _finder(
method _list_cells (line 2282) | def _list_cells(
method find (line 2311) | def find(
method findall (line 2336) | def findall(
method freeze (line 2364) | def freeze(
method set_basic_filter (line 2407) | def set_basic_filter(self, name: Optional[str] = None) -> Any:
method clear_basic_filter (line 2434) | def clear_basic_filter(self) -> JSONResponse:
method _duplicate (line 2452) | def _duplicate(
method duplicate (line 2502) | def duplicate(
method copy_to (line 2533) | def copy_to(
method merge_cells (line 2550) | def merge_cells(self, name: str, merge_type: str = MergeType.merge_all...
method unmerge_cells (line 2582) | def unmerge_cells(self, name: str) -> JSONResponse:
method batch_merge (line 2615) | def batch_merge(
method get_notes (line 2655) | def get_notes(
method get_note (line 2719) | def get_note(self, cell: str) -> str:
method update_notes (line 2740) | def update_notes(self, notes: Mapping[str, str]) -> None:
method update_note (line 2792) | def update_note(self, cell: str, content: str) -> None:
method insert_note (line 2804) | def insert_note(self, cell: str, content: str) -> None:
method insert_notes (line 2823) | def insert_notes(self, notes: Mapping[str, str]) -> None:
method clear_notes (line 2844) | def clear_notes(self, ranges: Iterable[str]) -> None:
method clear_note (line 2855) | def clear_note(self, cell: str) -> None:
method define_named_range (line 2875) | def define_named_range(self, name: str, range_name: str) -> JSONResponse:
method delete_named_range (line 2907) | def delete_named_range(self, named_range_id: str) -> JSONResponse:
method _add_dimension_group (line 2926) | def _add_dimension_group(
method add_dimension_group_columns (line 2955) | def add_dimension_group_columns(self, start: int, end: int) -> JSONRes...
method add_dimension_group_rows (line 2971) | def add_dimension_group_rows(self, start: int, end: int) -> JSONResponse:
method _delete_dimension_group (line 2985) | def _delete_dimension_group(
method delete_dimension_group_columns (line 3006) | def delete_dimension_group_columns(self, start: int, end: int) -> JSON...
method delete_dimension_group_rows (line 3022) | def delete_dimension_group_rows(self, start: int, end: int) -> JSONRes...
method list_dimension_group_columns (line 3035) | def list_dimension_group_columns(self) -> List[JSONResponse]:
method list_dimension_group_rows (line 3044) | def list_dimension_group_rows(self) -> List[JSONResponse]:
method _hide_dimension (line 3053) | def _hide_dimension(
method hide_columns (line 3088) | def hide_columns(self, start: int, end: int) -> JSONResponse:
method hide_rows (line 3099) | def hide_rows(self, start: int, end: int) -> JSONResponse:
method _unhide_dimension (line 3110) | def _unhide_dimension(
method unhide_columns (line 3145) | def unhide_columns(self, start: int, end: int) -> JSONResponse:
method unhide_rows (line 3156) | def unhide_rows(self, start: int, end: int) -> JSONResponse:
method _set_hidden_flag (line 3167) | def _set_hidden_flag(self, hidden: bool) -> JSONResponse:
method hide (line 3188) | def hide(self) -> JSONResponse:
method show (line 3192) | def show(self) -> JSONResponse:
method _set_gridlines_hidden_flag (line 3196) | def _set_gridlines_hidden_flag(self, hidden: bool) -> JSONResponse:
method hide_gridlines (line 3219) | def hide_gridlines(self) -> JSONResponse:
method show_gridlines (line 3223) | def show_gridlines(self) -> JSONResponse:
method copy_range (line 3227) | def copy_range(
method cut_range (line 3272) | def cut_range(
method add_validation (line 3318) | def add_validation(
method expand (line 3402) | def expand(
FILE: tests/cell_test.py
class CellTest (line 14) | class CellTest(GspreadTest):
method init (line 21) | def init(
method test_properties (line 34) | def test_properties(self):
method test_equality (line 44) | def test_equality(self):
method test_numeric_value (line 59) | def test_numeric_value(self):
method test_a1_value (line 80) | def test_a1_value(self):
method test_merge_cells (line 100) | def test_merge_cells(self):
method test_define_named_range (line 119) | def test_define_named_range(self):
method test_delete_named_range (line 178) | def test_delete_named_range(self):
FILE: tests/client_test.py
class ClientTest (line 14) | class ClientTest(GspreadTest):
method init (line 21) | def init(
method test_no_found_exeption (line 33) | def test_no_found_exeption(self):
method test_list_spreadsheet_files (line 38) | def test_list_spreadsheet_files(self):
method test_openall (line 49) | def test_openall(self):
method test_create (line 60) | def test_create(self):
method test_copy (line 66) | def test_copy(self):
method test_import_csv (line 76) | def test_import_csv(self):
method test_access_non_existing_spreadsheet (line 94) | def test_access_non_existing_spreadsheet(self):
method test_open_all_has_metadata (line 101) | def test_open_all_has_metadata(self):
method test_open_by_key_has_metadata (line 112) | def test_open_by_key_has_metadata(self):
method test_open_by_name_has_metadata (line 121) | def test_open_by_name_has_metadata(self):
method test_access_private_spreadsheet (line 130) | def test_access_private_spreadsheet(self):
method test_client_export_spreadsheet (line 137) | def test_client_export_spreadsheet(self):
method test_add_timeout (line 161) | def test_add_timeout(self):
FILE: tests/conftest.py
function read_credentials (line 31) | def read_credentials(filename: str) -> Credentials:
function prefixed_counter (line 35) | def prefixed_counter(prefix: str, start: int = 1) -> Generator[str, None...
function get_method_name (line 41) | def get_method_name(self_id: str) -> str:
function ignore_retry_requests (line 45) | def ignore_retry_requests(
function vcr_config (line 56) | def vcr_config():
class DummyCredentials (line 73) | class DummyCredentials(UserCredentials):
class GspreadTest (line 77) | class GspreadTest(unittest.TestCase):
method get_temporary_spreadsheet_title (line 79) | def get_temporary_spreadsheet_title(cls, suffix: str = "") -> str:
method get_cassette_name (line 83) | def get_cassette_name(cls) -> str:
method _sequence_generator (line 86) | def _sequence_generator(self) -> Generator[str, None, None]:
class VCRHTTPClient (line 90) | class VCRHTTPClient(BackOffHTTPClient):
method request (line 91) | def request(self, *args: Any, **kwargs: Any) -> Response:
class InvalidJsonApiErrorClient (line 111) | class InvalidJsonApiErrorClient(VCRHTTPClient):
method request (line 119) | def request(self, *args: Any, **kwargs: Any) -> Response:
function client (line 132) | def client() -> Client:
function invalid_json_client (line 145) | def invalid_json_client() -> Tuple[Client, bytes]:
FILE: tests/spreadsheet_test.py
class SpreadsheetTest (line 11) | class SpreadsheetTest(GspreadTest):
method init (line 15) | def init(self, client, request):
method test_bad_json_api_error (line 24) | def test_bad_json_api_error(self):
method test_properties (line 34) | def test_properties(self):
method test_sheet1 (line 39) | def test_sheet1(self):
method test_get_worksheet (line 44) | def test_get_worksheet(self):
method test_get_worksheet_by_id (line 49) | def test_get_worksheet_by_id(self):
method test_worksheet (line 56) | def test_worksheet(self):
method test_worksheets (line 62) | def test_worksheets(self):
method test_worksheets_exclude_hidden (line 73) | def test_worksheets_exclude_hidden(self):
method test_worksheet_iteration (line 85) | def test_worksheet_iteration(self):
method test_values_get (line 92) | def test_values_get(self):
method test_add_del_worksheet (line 113) | def test_add_del_worksheet(self):
method test_values_batch_get (line 142) | def test_values_batch_get(self):
method test_timezone_and_locale (line 168) | def test_timezone_and_locale(self):
method test_update_title (line 191) | def test_update_title(self):
method test_get_lastUpdateTime (line 208) | def test_get_lastUpdateTime(self):
method test_creationTime_prop (line 220) | def test_creationTime_prop(self):
method test_export_spreadsheet (line 226) | def test_export_spreadsheet(self):
FILE: tests/utils_test.py
class UtilsTest (line 7) | class UtilsTest(unittest.TestCase):
method test_extract_id_from_url (line 8) | def test_extract_id_from_url(self):
method test_no_extract_id_from_url (line 36) | def test_no_extract_id_from_url(self):
method test_a1_to_rowcol (line 41) | def test_a1_to_rowcol(self):
method test_rowcol_to_a1 (line 44) | def test_rowcol_to_a1(self):
method test_addr_converters (line 48) | def test_addr_converters(self):
method test_get_gid (line 55) | def test_get_gid(self):
method test_numericise (line 63) | def test_numericise(self):
method test_a1_to_grid_range_simple (line 99) | def test_a1_to_grid_range_simple(self):
method test_a1_to_grid_range_unbounded (line 129) | def test_a1_to_grid_range_unbounded(self):
method test_a1_to_grid_range_improper_range (line 152) | def test_a1_to_grid_range_improper_range(self):
method test_a1_to_grid_range_other_directions (line 181) | def test_a1_to_grid_range_other_directions(self):
method test_column_letter_to_index (line 191) | def test_column_letter_to_index(self):
method test_combine_merge_values (line 222) | def test_combine_merge_values(self):
method test_convert_colors_to_hex_value (line 263) | def test_convert_colors_to_hex_value(self):
method test_combine_merge_values_outside_range (line 279) | def test_combine_merge_values_outside_range(self):
method test_combine_merge_values_from_centre_of_sheet (line 317) | def test_combine_merge_values_from_centre_of_sheet(self):
method test_convert_hex_to_color (line 366) | def test_convert_hex_to_color(self):
method test_fill_gaps (line 388) | def test_fill_gaps(self):
method test_fill_gaps_with_value (line 403) | def test_fill_gaps_with_value(self):
method test_fill_gaps_with_non_square_array (line 427) | def test_fill_gaps_with_non_square_array(self):
method test_is_full_a1_notation (line 444) | def test_is_full_a1_notation(self):
method test_get_a1_from_absolute_range (line 460) | def test_get_a1_from_absolute_range(self):
method test_to_records_empty_args (line 469) | def test_to_records_empty_args(self):
method test_to_records (line 480) | def test_to_records(self):
method test_find_table_simple (line 507) | def test_find_table_simple(self):
method test_find_table_inner_gap (line 574) | def test_find_table_inner_gap(self):
method test_find_table_first_row_gap (line 597) | def test_find_table_first_row_gap(self):
method test_find_table_first_column_gap (line 620) | def test_find_table_first_column_gap(self):
method test_find_table_last_column_gap (line 641) | def test_find_table_last_column_gap(self):
method test_find_table_empty_top_left_corner (line 664) | def test_find_table_empty_top_left_corner(self):
FILE: tests/worksheet_test.py
class WorksheetTest (line 21) | class WorksheetTest(GspreadTest):
method init (line 28) | def init(
method reset_sheet (line 41) | def reset_sheet(self):
method test_acell (line 45) | def test_acell(self):
method test_attributes (line 50) | def test_attributes(self):
method test_cell (line 60) | def test_cell(self):
method test_range (line 65) | def test_range(self):
method test_range_unbounded (line 79) | def test_range_unbounded(self):
method test_range_reversed (line 87) | def test_range_reversed(self):
method test_range_get_all_values (line 95) | def test_range_get_all_values(self):
method test_get_returns_ValueRange_with_metadata (line 115) | def test_get_returns_ValueRange_with_metadata(self):
method test_get_values_returns_padded_get_as_listoflists (line 139) | def test_get_values_returns_padded_get_as_listoflists(self):
method test_get_values_can_emulate_get_with_kwargs (line 160) | def test_get_values_can_emulate_get_with_kwargs(self):
method test_get_values_and_combine_merged_cells (line 183) | def test_get_values_and_combine_merged_cells(self):
method test_batch_merged_cells (line 216) | def test_batch_merged_cells(self):
method test_get_values_with_args_or_kwargs (line 253) | def test_get_values_with_args_or_kwargs(self):
method test_get_values_merge_cells_outside_of_range (line 279) | def test_get_values_merge_cells_outside_of_range(self):
method test_get_values_merge_cells_from_centre_of_sheet (line 305) | def test_get_values_merge_cells_from_centre_of_sheet(self):
method test_get_values_merge_cells_with_named_range (line 330) | def test_get_values_merge_cells_with_named_range(self):
method test_get_merge_cells_and_unmerge_cells (line 356) | def test_get_merge_cells_and_unmerge_cells(self):
method test_get_values_and_maintain_size (line 389) | def test_get_values_and_maintain_size(self):
method test_update_acell (line 414) | def test_update_acell(self):
method test_update_cell (line 422) | def test_update_cell(self):
method test_update_cell_multiline (line 442) | def test_update_cell_multiline(self):
method test_update_cell_unicode (line 451) | def test_update_cell_unicode(self):
method test_update_cells (line 458) | def test_update_cells(self):
method test_update_cells_unicode (line 481) | def test_update_cells_unicode(self):
method test_update_title (line 490) | def test_update_title(self):
method test_update_tab_color (line 508) | def test_update_tab_color(self):
method test_clear_tab_color (line 547) | def test_clear_tab_color(self):
method test_update_cells_noncontiguous (line 578) | def test_update_cells_noncontiguous(self):
method test_update_cell_objects (line 610) | def test_update_cell_objects(self):
method test_resize (line 626) | def test_resize(self):
method test_sort (line 660) | def test_sort(self):
method test_freeze (line 714) | def test_freeze(self):
method test_basic_filters (line 745) | def test_basic_filters(self):
method test_find (line 785) | def test_find(self):
method test_findall (line 818) | def test_findall(self):
method test_get_all_values (line 854) | def test_get_all_values(self):
method test_get_all_values_title_is_a1_notation (line 878) | def test_get_all_values_title_is_a1_notation(self):
method test_get_all_values_date_time_render_options (line 904) | def test_get_all_values_date_time_render_options(self):
method test_get_all_records (line 967) | def test_get_all_records(self):
method test_get_all_records_different_header (line 1003) | def test_get_all_records_different_header(self):
method test_get_all_records_value_render_options (line 1041) | def test_get_all_records_value_render_options(self):
method test_get_all_records_duplicate_keys (line 1081) | def test_get_all_records_duplicate_keys(self):
method test_get_all_records_with_blank_final_headers (line 1120) | def test_get_all_records_with_blank_final_headers(self):
method test_get_all_records_with_keys_blank (line 1149) | def test_get_all_records_with_keys_blank(self):
method test_get_all_records_with_all_values_blank (line 1179) | def test_get_all_records_with_all_values_blank(self):
method test_get_all_records_with_some_values_blank (line 1197) | def test_get_all_records_with_some_values_blank(self):
method test_get_all_records_numericise_unformatted (line 1219) | def test_get_all_records_numericise_unformatted(self):
method test_get_all_records_pad_one_key (line 1243) | def test_get_all_records_pad_one_key(self):
method test_get_all_records_pad_values (line 1258) | def test_get_all_records_pad_values(self):
method test_get_all_records_pad_more_than_one_key (line 1273) | def test_get_all_records_pad_more_than_one_key(self):
method test_append_row (line 1285) | def test_append_row(self):
method test_append_row_with_empty_value (line 1298) | def test_append_row_with_empty_value(self):
method test_append_row_with_empty_value_and_table_range (line 1311) | def test_append_row_with_empty_value_and_table_range(self):
method test_insert_row (line 1324) | def test_insert_row(self):
method test_insert_cols (line 1365) | def test_insert_cols(self):
method test_delete_row (line 1393) | def test_delete_row(self):
method test_delete_cols (line 1412) | def test_delete_cols(self):
method test_clear (line 1439) | def test_clear(self):
method test_update_and_get (line 1458) | def test_update_and_get(self):
method test_batch_get (line 1481) | def test_batch_get(self):
method test_cell_return_first (line 1499) | def test_cell_return_first(self):
method test_batch_update (line 1506) | def test_batch_update(self):
method test_add_protected_range_normal (line 1533) | def test_add_protected_range_normal(self):
method test_add_protected_range_warning (line 1545) | def test_add_protected_range_warning(self):
method test_delete_protected_range (line 1558) | def test_delete_protected_range(self):
method test_format (line 1569) | def test_format(self):
method test_reorder_worksheets (line 1603) | def test_reorder_worksheets(self):
method test_worksheet_update_index (line 1612) | def test_worksheet_update_index(self):
method test_worksheet_notes (line 1628) | def test_worksheet_notes(self):
method test_get_notes (line 1670) | def test_get_notes(self):
method test_get_notes_2nd_sheet (line 1701) | def test_get_notes_2nd_sheet(self):
method test_batch_clear (line 1731) | def test_batch_clear(self):
method test_group_columns (line 1755) | def test_group_columns(self):
method test_group_rows (line 1772) | def test_group_rows(self):
method test_hide_columns_rows (line 1789) | def test_hide_columns_rows(self):
method test_hide_show_worksheet (line 1801) | def test_hide_show_worksheet(self):
method test_hide_gridlines (line 1842) | def test_hide_gridlines(self):
method test_show_gridlines (line 1872) | def test_show_gridlines(self):
method test_auto_resize_columns (line 1903) | def test_auto_resize_columns(self):
method test_copy_cut_range (line 1928) | def test_copy_cut_range(self):
method test_get_and_get_values_have_same_signature (line 1958) | def test_get_and_get_values_have_same_signature(self):
method test_add_validation (line 1991) | def test_add_validation(self):
Condensed preview — 176 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (9,526K chars).
[
{
"path": ".github/CODE_OF_CONDUCT.md",
"chars": 3284,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": ".github/CONTRIBUTING.md",
"chars": 5655,
"preview": "# Contributing Guide\n\n- Check the [GitHub Issues](https://github.com/burnash/gspread/issues) for open issues that need a"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 948,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Important**: P"
},
{
"path": ".github/ISSUE_TEMPLATE/config.yml",
"chars": 301,
"preview": "blank_issues_enabled: true\ncontact_links:\n - name: Questions and Help\n url: http://stackoverflow.com/questions/t"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 595,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n**Is your fea"
},
{
"path": ".github/dependabot.yaml",
"chars": 194,
"preview": "version: 2\nupdates:\n - package-ecosystem: github-actions\n directory: /\n schedule:\n interval: weekly\n\n - pac"
},
{
"path": ".github/workflows/main.yaml",
"chars": 1256,
"preview": "name: lint_python\non:\n push:\n branches:\n - \"**\" # run all branches\n tags-ignore:\n - \"*\" # ignore all ta"
},
{
"path": ".github/workflows/release.yaml",
"chars": 1124,
"preview": "name: Releases\n\non:\n push:\n tags:\n - 'v*'\n\njobs:\n\n release:\n runs-on: ubuntu-latest\n permissions:\n co"
},
{
"path": ".gitignore",
"chars": 149,
"preview": "# vscode\n.vscode/\n\n# python\n**/__pycache__/\n\n# secrets\ntests/creds.json\n\n# virtualenv\nenv/\n\n# tox\n.tox/\n\n# build\ngspread"
},
{
"path": ".readthedocs.yaml",
"chars": 630,
"preview": "# .readthedocs.yaml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html f"
},
{
"path": "HISTORY.rst",
"chars": 44276,
"preview": "Release History\n===============\n\n6.2.1 (2025-05-14)\n------------------\n\n* Fix public API auth snippet by @Jayy001 in htt"
},
{
"path": "LICENSE.txt",
"chars": 1064,
"preview": "Copyright (C) 2011-2023 Anton Burnashev\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of"
},
{
"path": "README.md",
"chars": 10194,
"preview": "# Google Spreadsheets Python API v4\n\n }}\n<!-- Google Tag Manager -->\n<script>(function(w,d,s,l,i"
},
{
"path": "docs/advanced.rst",
"chars": 2315,
"preview": "Advanced Usage\n==============\n\nCustom Authentication\n---------------------\n\nGoogle Colaboratory\n~~~~~~~~~~~~~~~~~~~\n\nIf "
},
{
"path": "docs/api/auth.rst",
"chars": 53,
"preview": "Auth\n====\n\n.. automodule:: gspread.auth\n :members:\n"
},
{
"path": "docs/api/client.rst",
"chars": 58,
"preview": "Client\n======\n\n.. autoclass:: gspread.Client\n :members:\n"
},
{
"path": "docs/api/exceptions.rst",
"chars": 472,
"preview": "Exceptions\n==========\n\n\n.. autoexception:: gspread.exceptions.APIError\n.. autoexception:: gspread.exceptions.GSpreadExce"
},
{
"path": "docs/api/http_client.rst",
"chars": 258,
"preview": "HTTP Client\n===========\n\n.. note::\n\n This class is not intended to be used directly.\n It is used by all gspread mode"
},
{
"path": "docs/api/index.rst",
"chars": 144,
"preview": "API Reference\n=============\n\n.. toctree::\n :maxdepth: 2\n\n top-level\n auth\n client\n http_client\n models/index"
},
{
"path": "docs/api/models/cell.rst",
"chars": 57,
"preview": "Cell\n====\n\n.. autoclass:: gspread.cell.Cell\n :members:\n"
},
{
"path": "docs/api/models/index.rst",
"chars": 432,
"preview": "Models\n======\n\nThe models represent common spreadsheet entities: :class:`a spreadsheet <gspread.spreadsheet.Spreadsheet>"
},
{
"path": "docs/api/models/spreadsheet.rst",
"chars": 85,
"preview": "Spreadsheet\n===========\n\n.. autoclass:: gspread.spreadsheet.Spreadsheet\n :members:\n"
},
{
"path": "docs/api/models/worksheet.rst",
"chars": 179,
"preview": "Worksheet\n=========\n\nValueRange\n----------\n\n.. autoclass:: gspread.worksheet.ValueRange\n :members:\n\nWorksheet\n--------"
},
{
"path": "docs/api/top-level.rst",
"chars": 128,
"preview": "Top level\n=========\n\n.. module:: gspread\n\n.. autofunction:: oauth\n.. autofunction:: service_account\n.. autofunction:: au"
},
{
"path": "docs/api/utils.rst",
"chars": 75,
"preview": "Utils\n=====\n\n.. automodule:: gspread.utils\n :members:\n :undoc-members:\n"
},
{
"path": "docs/community.rst",
"chars": 882,
"preview": "Community Extensions\n====================\n\n.. _gspread-formating-label:\n\ngspread-formating\n~~~~~~~~~~~~~~~~~\n\n`gspread-f"
},
{
"path": "docs/conf.py",
"chars": 9539,
"preview": "#\n# gspread documentation build configuration file, created by\n# sphinx-quickstart on Thu Dec 15 14:44:32 2011.\n#\n# This"
},
{
"path": "docs/index.rst",
"chars": 2395,
"preview": "gspread\n=======\n\n`gspread`_ is a Python API for Google Sheets.\n\nFeatures:\n\n- Google Sheets API v4.\n- Open a spreadshee"
},
{
"path": "docs/oauth2.rst",
"chars": 14080,
"preview": "Authentication\n==============\n\nTo access spreadsheets your application needs to authenticate itself with the Google Shee"
},
{
"path": "docs/requirements.txt",
"chars": 46,
"preview": "sphinx==6.2.1\nsphinx_rtd_theme\nsphinx-toolbox\n"
},
{
"path": "docs/user-guide.rst",
"chars": 12574,
"preview": "Examples of gspread Usage\n=========================\n\nIf you haven't yet authorized your app, read :doc:`oauth2` first.\n\n"
},
{
"path": "gspread/__init__.py",
"chars": 1211,
"preview": "\"\"\"Google Spreadsheets Python API\"\"\"\n\n__version__ = \"6.2.1\"\n__author__ = \"Anton Burnashev\"\n\n\nfrom .auth import (\n api"
},
{
"path": "gspread/auth.py",
"chars": 14354,
"preview": "\"\"\"\ngspread.auth\n~~~~~~~~~~~~\n\nSimple authentication with OAuth.\n\n\"\"\"\n\nimport json\nimport os\nfrom pathlib import Path\nfr"
},
{
"path": "gspread/cell.py",
"chars": 2407,
"preview": "\"\"\"\ngspread.cell\n~~~~~~~~~~~~\n\nThis module contains common cells' models.\n\n\"\"\"\n\nfrom typing import Optional, Union\n\nfrom"
},
{
"path": "gspread/client.py",
"chars": 16489,
"preview": "\"\"\"\ngspread.client\n~~~~~~~~~~~~~~\n\nThis module contains Client class responsible for managing spreadsheet files\n\n\"\"\"\n\nfr"
},
{
"path": "gspread/exceptions.py",
"chars": 1894,
"preview": "\"\"\"\ngspread.exceptions\n~~~~~~~~~~~~~~~~~~\n\nExceptions used in gspread.\n\n\"\"\"\n\nfrom typing import Any, Mapping\n\nfrom reque"
},
{
"path": "gspread/http_client.py",
"chars": 23199,
"preview": "\"\"\"\ngspread.http_client\n~~~~~~~~~~~~~~\n\nThis module contains HTTPClient class responsible for communicating with\nGoogle "
},
{
"path": "gspread/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "gspread/spreadsheet.py",
"chars": 29055,
"preview": "\"\"\"\ngspread.spreadsheet\n~~~~~~~~~~~~~~\n\nThis module contains common spreadsheets' models.\n\n\"\"\"\n\nimport warnings\nfrom typ"
},
{
"path": "gspread/urls.py",
"chars": 1287,
"preview": "\"\"\"\ngspread.urls\n~~~~~~~~~~~~\n\nGoogle API urls.\n\n\"\"\"\n\nSPREADSHEETS_API_V4_BASE_URL: str = \"https://sheets.googleapis.com"
},
{
"path": "gspread/utils.py",
"chars": 32608,
"preview": "\"\"\"\ngspread.utils\n~~~~~~~~~~~~~\n\nThis module contains utility functions.\n\n\"\"\"\n\nimport enum\nimport re\nfrom collections im"
},
{
"path": "gspread/worksheet.py",
"chars": 124137,
"preview": "\"\"\"\ngspread.worksheet\n~~~~~~~~~~~~~~~~~\n\nThis module contains common worksheets' models.\n\n\"\"\"\n\nimport re\nimport warnings"
},
{
"path": "lint-requirements.txt",
"chars": 136,
"preview": "bandit==1.7.10\nblack==24.8.0\ncodespell==2.2.5\nflake8==7.1.1\nisort==5.12.0\nmypy==1.11.2\nmypy-extensions==1.0.0\ntyping_ext"
},
{
"path": "pyproject.toml",
"chars": 1512,
"preview": "[build-system]\nrequires = [\"flit_core >=3.2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n\n[project]\nname = \"gspread\"\nauthor"
},
{
"path": "test-requirements.txt",
"chars": 89,
"preview": "google-auth==1.12.0\ngoogle-auth-oauthlib==0.4.1\nvcrpy\npytest\npytest-vcr\nurllib3==1.26.15\n"
},
{
"path": "tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tests/cassettes/CellTest.test_a1_value.json",
"chars": 83523,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/CellTest.test_define_named_range.json",
"chars": 34544,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/CellTest.test_delete_named_range.json",
"chars": 85507,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/CellTest.test_equality.json",
"chars": 117039,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/CellTest.test_merge_cells.json",
"chars": 151068,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/CellTest.test_numeric_value.json",
"chars": 109018,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/CellTest.test_properties.json",
"chars": 76237,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_access_non_existing_spreadsheet.json",
"chars": 35440,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_access_private_spreadsheet.json",
"chars": 13874,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_add_timeout.json",
"chars": 17592,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_client_export_spreadsheet.json",
"chars": 23131,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_copy.json",
"chars": 75662,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_create.json",
"chars": 46110,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_import_csv.json",
"chars": 74601,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_list_spreadsheet_files.json",
"chars": 15248,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_no_found_exeption.json",
"chars": 30965,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_open_all_has_metadata.json",
"chars": 532635,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_open_by_key_has_metadata.json",
"chars": 40658,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_open_by_name_has_metadata.json",
"chars": 46515,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/ClientTest.test_openall.json",
"chars": 553590,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_add_del_worksheet.json",
"chars": 100693,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_bad_json_api_error.json",
"chars": 11532,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_creationTime_prop.json",
"chars": 57413,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_export_spreadsheet.json",
"chars": 23123,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_get_lastUpdateTime.json",
"chars": 19886,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_get_worksheet.json",
"chars": 38668,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_get_worksheet_by_id.json",
"chars": 51044,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_lastUpdateTime_prop.json",
"chars": 20922,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_properties.json",
"chars": 26337,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_sheet1.json",
"chars": 38603,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_timezone_and_locale.json",
"chars": 50235,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_update_title.json",
"chars": 44441,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_values_batch_get.json",
"chars": 51340,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_values_get.json",
"chars": 49757,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_worksheet.json",
"chars": 38737,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_worksheet_iteration.json",
"chars": 50939,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_worksheets.json",
"chars": 57925,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/SpreadsheetTest.test_worksheets_exclude_hidden.json",
"chars": 71667,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_acell.json",
"chars": 48835,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_add_protected_range_normal.json",
"chars": 30236,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_add_protected_range_warning.json",
"chars": 30281,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_add_validation.json",
"chars": 26417,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_append_row.json",
"chars": 55853,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_append_row_with_empty_value.json",
"chars": 61600,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_append_row_with_empty_value_and_table_range.json",
"chars": 62003,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_attributes.json",
"chars": 20197,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_auto_resize_columns.json",
"chars": 74404,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_basic_filters.json",
"chars": 122388,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_batch_clear.json",
"chars": 103468,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_batch_get.json",
"chars": 55564,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_batch_merged_cells.json",
"chars": 50546,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_batch_update.json",
"chars": 56077,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_cell.json",
"chars": 48931,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_cell_return_first.json",
"chars": 22709,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_clear.json",
"chars": 65119,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_clear_tab_color.json",
"chars": 65511,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_copy_cut_range.json",
"chars": 77098,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_delete_cols.json",
"chars": 83865,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_delete_protected_range.json",
"chars": 39081,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_delete_row.json",
"chars": 104443,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_find.json",
"chars": 93783,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_findall.json",
"chars": 79130,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_format.json",
"chars": 57521,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_freeze.json",
"chars": 98069,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records.json",
"chars": 36778,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_different_header.json",
"chars": 36970,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_duplicate_keys.json",
"chars": 36868,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_numericise_unformatted.json",
"chars": 31184,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_pad_more_than_one_key.json",
"chars": 28638,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_pad_one_key.json",
"chars": 28621,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_pad_values.json",
"chars": 28583,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_value_render_options.json",
"chars": 36426,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_with_all_values_blank.json",
"chars": 28672,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_with_blank_final_headers.json",
"chars": 31458,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_with_keys_blank.json",
"chars": 33897,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_records_with_some_values_blank.json",
"chars": 28787,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_values.json",
"chars": 80756,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_values_date_time_render_options.json",
"chars": 82644,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_all_values_title_is_a1_notation.json",
"chars": 86571,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_and_get_values_have_same_signature.json",
"chars": 20309,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_merge_cells_and_unmerge_cells.json",
"chars": 58879,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_notes.json",
"chars": 38518,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_notes_2nd_sheet.json",
"chars": 69406,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_returns_ValueRange_with_metadata.json",
"chars": 62941,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_and_combine_merged_cells.json",
"chars": 55984,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_and_maintain_size.json",
"chars": 28235,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_can_emulate_get_with_kwargs.json",
"chars": 31006,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_merge_cells_from_centre_of_sheet.json",
"chars": 40899,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_merge_cells_outside_of_range.json",
"chars": 219031,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_merge_cells_with_named_range.json",
"chars": 44371,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_returns_padded_get_as_listoflists.json",
"chars": 30715,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_get_values_with_args_or_kwargs.json",
"chars": 33820,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_group_columns.json",
"chars": 81207,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_group_rows.json",
"chars": 80916,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_hide_columns_rows.json",
"chars": 67298,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_hide_gridlines.json",
"chars": 74336,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_hide_show_worksheet.json",
"chars": 106050,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_insert_cols.json",
"chars": 78687,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_insert_row.json",
"chars": 96193,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_range.json",
"chars": 53906,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_range_get_all_values.json",
"chars": 66282,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_range_reversed.json",
"chars": 53779,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_range_unbounded.json",
"chars": 54119,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_reorder_worksheets.json",
"chars": 86310,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_resize.json",
"chars": 97779,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_set_tab_color.json",
"chars": 30400,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_show_gridlines.json",
"chars": 79856,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_sort.json",
"chars": 118693,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_acell.json",
"chars": 80249,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_and_get.json",
"chars": 55351,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cell.json",
"chars": 98038,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cell_multiline.json",
"chars": 55156,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cell_objects.json",
"chars": 60242,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cell_unicode.json",
"chars": 55241,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cells.json",
"chars": 61093,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cells_noncontiguous.json",
"chars": 77715,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_cells_unicode.json",
"chars": 60207,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_tab_color.json",
"chars": 59745,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_title.json",
"chars": 74285,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_update_works_with_swapped_values_and_range.json",
"chars": 25844,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_worksheet_notes.json",
"chars": 75319,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cassettes/WorksheetTest.test_worksheet_update_index.json",
"chars": 89178,
"preview": "{\n \"version\": 1,\n \"interactions\": [\n {\n \"request\": {\n \"method\": \"POST\",\n "
},
{
"path": "tests/cell_test.py",
"chars": 7200,
"preview": "from typing import Generator\n\nimport pytest\nfrom pytest import FixtureRequest\n\nimport gspread\nfrom gspread.client import"
},
{
"path": "tests/client_test.py",
"chars": 6180,
"preview": "import time\nfrom typing import Generator\n\nimport pytest\nfrom pytest import FixtureRequest\n\nimport gspread\nfrom gspread.c"
},
{
"path": "tests/conftest.py",
"chars": 4968,
"preview": "import io\nimport itertools\nimport os\nimport unittest\nfrom typing import Any, Dict, Generator, Optional, Tuple\n\nimport py"
},
{
"path": "tests/spreadsheet_test.py",
"chars": 8481,
"preview": "import re\nimport time\n\nimport pytest\n\nimport gspread\n\nfrom .conftest import GspreadTest, invalid_json_client\n\n\nclass Spr"
},
{
"path": "tests/utils_test.py",
"chars": 22958,
"preview": "import unittest\n\nimport gspread\nimport gspread.utils as utils\n\n\nclass UtilsTest(unittest.TestCase):\n def test_extract"
},
{
"path": "tests/worksheet_test.py",
"chars": 66917,
"preview": "import itertools\nimport pickle # nosec\nimport random\nimport re\nfrom inspect import signature\nfrom typing import Generat"
},
{
"path": "tox.ini",
"chars": 1438,
"preview": "[tox]\nenvlist = py38,py39,py310,py311,py312\nskip_missing_interpreters = true\n\n# Used to run tests, **do not** set GS_CRE"
}
]
About this extraction
This page contains the full source code of the burnash/gspread GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 176 files (8.2 MB), approximately 2.2M tokens, and a symbol index with 454 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.