gsheeter


Namegsheeter JSON
Version 0.2.6 PyPI version JSON
download
home_pageNone
SummaryGoogle spreadsheet Python API Abstractor integrated with pandas
upload_time2025-07-09 01:00:30
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseNone
keywords spreadsheets google-spreadsheets google-sheets
VCS
bugtrack_url
requirements asttokens build cachetools certifi cffi charset-normalizer colorama cryptography dateparser docutils et_xmlfile executing google-api-core google-api-python-client google-auth google-auth-httplib2 google-auth-oauthlib googleapis-common-protos httplib2 id idna jaraco.classes jaraco.context jaraco.functools jeepney keyring markdown-it-py mdurl more-itertools nh3 numpy oauthlib openpyxl packaging pandas proto-plus protobuf pyasn1 pyasn1_modules pycparser Pygments pyparsing pyproject_hooks python-dateutil pytz readme_renderer regex requests requests-oauthlib requests-toolbelt rfc3986 rich rsa SecretStorage six twine typing_extensions tzdata tzlocal uritemplate urllib3 XlsxWriter
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Google sheets API For Python3 v1

![main workflow](https://img.shields.io/github/actions/workflow/status/haydenkuk/gsheeter/main.yaml?logo=github)
![GitHub licence](https://img.shields.io/pypi/l/gsheeter?logo=github)
![GitHub downloads](https://img.shields.io/github/downloads-pre/haydenkuk/gsheeter/latest/total?logo=github)
![documentation](https://img.shields.io/readthedocs/gsheeter?logo=readthedocs)
![PyPi download](https://img.shields.io/pypi/dm/gsheeter?logo=pypi)
![PyPi version](https://img.shields.io/pypi/v/gsheeter?logo=pypi)
![python version](https://img.shields.io/pypi/pyversions/gsheeter?style=pypi)

Pandas-integrated, Auto-positioning library for Google Sheet API

Features:
- Auto-positioning for value updates on sheets
- Designed to minimize API calls
- Auto-parse values on sheet to get tables with positions and pd.DataFrame interpretations
- Multi-level indexes and columns supported

## 1. Installation
```sh
pip install gsheeter
```
Requires Python 3.9+.

## 2. Basic Usage

1. Authentication
(Only service account usage is allowed so far, I will develop functions to use other auth methods in the future)
```python
# using a service account will create a global api client for the project to use throughout
import gsheeter
filename = 'service-account.json' # add a path of your service account json file
gsheeter.service_account(filename)
```
2. Load spreadsheet
```python
from gsheeter import Drive

# 1. using fileId
fileId = 'spreadsheetFileId' # use the file id of the spreadsheet you want to access

spreadsheet = Drive.get_spreadsheet(fileId)

# 2. using filename
filename = 'spreadsheetFileName' # use the name of the spreadsheet you want to access
parentId = 'folderId' # use id of the parent folder of the spreadsheet above, I recommend using folderId when using filename, not fileId

spreadsheet = Drive.get_spreadsheet(
  target=filename,
  folderId=parentId)

```
3. Add(Create) spreadsheet
```python
from gsheeter import Drive

filename = 'yourspeadsheet' # use the name you want
sheetname = 'default' # default value is None, use only if you want to create the first sheet with a specific name
parentId = 'anyFolderId' # if empty, spreadsheet will be replaced in your root
spreadsheet = Drive.create_spreadsheet(
  filename=filename,
  sheetname=sheetname,
  parentId=parentId)

```

4. Load sheet
```python
from gsheeter import Drive


spreadsheet = Drive.get_spreadsheet(
  target='test',
  folderId='parentFolderId')

# 1. using sheetId
sheetId = 0
sheet = spreadsheet.get_sheet(sheetId)

# 2. using sheetname
sheetname = 'Sheet1'
sheet = spreadsheet.get_sheet(sheetname)
```

You can also use kwargs as follows when using Spreadsheet.get_sheet:
- delete_exist: bool = False: if the sheet being searched already exists and delete_exist is set to True, delete the existing sheet and create another one, otherwise, return the existing sheet
```python
# example
sheet = spreadsheet.get_sheet(sheetname, delete_exist=True)
```
- add: bool = True: if the sheet being searched does not exist, add one
```python
# example, throws exception if the sheet does not exist or is not added
sheet = spreadsheet.get_sheet(sheetname, add=False)
```

5. Add(Create) sheet
```python
from gsheeter import Drive

spreadsheet = Drive.get_spreadsheet(
  target='test',
  folderId='parentFolderId'
)

# default function behavior, Create a sheet with sheetname "Sheet1", 1000 rowCount and 26 columnCount and index of 0
sheet = spreadsheet.add_sheet()

# with a sheetname
sheetname = 'new_sheet'
sheet = spreadsheet.add_sheet(sheetname)

# create a smaller sheet with 100 rows and 10 columns
sheet = spreadsheet.add_sheet(
  sheetname=sheetname,
  rowCount=10,
  columnCount=10,
)
# after creating a new sheet named "new_sheet", with 10 rows and 10 columns
```
![Screenshot](doc_imgs/add_sheet_1.png)
![Screenshot](doc_imgs/add_sheet_2.png)

6. Read sheet values: entire sheet
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheetname = 'Sheet1'
sheet = spreadsheet.get_sheet(sheetname)
values = sheet.matrix # returns 2D np.ndarray filled with Values.
# If the sheet is empty, An empty np.ndarray with size of (sheet.rowCount, sheet.columnCount)
```
![Screenshot](doc_imgs/matrix_1.png)
![Screenshot](doc_imgs/matrix_2.png)

7. Read sheet values: tables
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheetname = 'Sheet1'
sheet = spreadsheet.get_sheet(sheetname)
tables = sheet.tables

for t in tables:
  print(t)
```

![Screenshot](doc_imgs/table_ex_1.png)
![Screenshot](doc_imgs/table_ex_2.png)

8. Read sheet values: a table
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheetname = 'Sheet1'
sheet = spreadsheet.get_sheet(sheetname)
table = sheet.table # by default, the first table(table #1) is assigned to sheet.table
```

9. Update values: using sheet
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheetname = 'Sheet1'
sheet = spreadsheet.get_sheet(sheetname)
```
```python
import numpy as np

# all coordinates follow array indexing convention, starting from 0
# gsheeter adds 1 to each coordinate to match cell address on sheet
# 1. set values of 2D np.ndarray with x, y coordinate on sheet
arr = np.zeros(shape=(4, 3))
y_offset = 0 # row 1
x_offset = 0 # column 1(A)
sheet.set_values(
  data=arr,
  y_offset=y_offset,
  x_offset=x_offset)
```
![Screenshot](doc_imgs/update_values_1.png)

```python
# 2. set values of 1D np.ndarray with x and y coordinate on sheet
arr = np.array([0,1,2,3,4])
y_offset = 1 # row 2
x_offset = 2 # column 3(C)
sheet.set_values(
  data=arr,
  y_offset=y_offset,
  x_offset=x_offset)
```
![Screenshot](doc_imgs/update_values_2.png)

```python
# 3. set values of pd.DataFrame with x and y coordinate on sheet
df = pd.DataFrame(...)
y_offset = 0
x_offset = 0
sheet.set_values(
  data=df,
  y_offset=y_offset,
  x_offset=x_offset)
```

```python
# 4. set values of pd.Series
row = pd.Series(...)
# default values of y_offset and x_offset are 0
# if the input to .set_values() is of type pd.Series, the output value is transposed.
sheet.set_values(row)
```
![Screenshot](doc_imgs/update_values_3.png)

```python
# 5. append values at the next row after the last non-empty row
# x_offset determines the index at which the search for non-empty row starts
arr = np.zeros(shape=(4, 3))
sheet.set_values(
  data=arr,
  x_offset=1, # searches for the next empty row after the last non-empty row starting from x coordinate of 1 to x coordinate of 1 + width of input data
  append=True)
```
![Screenshot](doc_imgs/update_values_4.png)
![Screenshot](doc_imgs/update_values_5.png)

```python
arr = np.zeros(shape=(4, 3))
sheet.set_values(
  data=arr,
  x_offset=1,
  y_offset=1, # paste the input data 1 index array along y-axis from the last-fill row
  append=True
)
```
![Screenshot](doc_imgs/update_values_4.png)
![Screenshot](doc_imgs/update_values_6.png)

10. Update values: replace values in a row of a table
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheet = spreadsheet.get_sheet('Sheet1')
table = sheet.table
row = table.df.iloc[2] # select third row from this table
row['x'] = 'test'
row['y'] = 'test'
table.update_row(row) # then update the values of the row
```
11. Update values: delete a row of a table
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheet = spreadsheet.get_sheet('Sheet1')
table = sheet.table

# delete second row
row = table.df.iloc[1]
table.delete_row(row)
```
12. Update values: delete a table
```python
from gsheeter import Drive

fileId = 'fileId'
spreadsheet = Drive.get_spreadsheet(fileId)
sheet = spreadsheet.get_sheet('Sheet1')
table = sheet.tables[2] # select third table in the sheet
sheet.delete_table(table)
sheet.delete_table(1) # you can also use table index
```


## Advanced Usage and Object structures

1. Drive
2. Spreadsheet
3. Sheet
4. Table

## Future updates
1. Chart
2. Other authentication methods
3. Cell format


            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "gsheeter",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": "Yunjong Guk <haydenkuk@gmail.com>",
    "keywords": "spreadsheets, google-spreadsheets, google-sheets",
    "author": null,
    "author_email": "Yunjong Guk <haydenkuk@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/35/af/69edddb3bb830910101c6436b47b93a4a19b3fbf851eb0f0dccce199c44d/gsheeter-0.2.6.tar.gz",
    "platform": null,
    "description": "# Google sheets API For Python3 v1\n\n![main workflow](https://img.shields.io/github/actions/workflow/status/haydenkuk/gsheeter/main.yaml?logo=github)\n![GitHub licence](https://img.shields.io/pypi/l/gsheeter?logo=github)\n![GitHub downloads](https://img.shields.io/github/downloads-pre/haydenkuk/gsheeter/latest/total?logo=github)\n![documentation](https://img.shields.io/readthedocs/gsheeter?logo=readthedocs)\n![PyPi download](https://img.shields.io/pypi/dm/gsheeter?logo=pypi)\n![PyPi version](https://img.shields.io/pypi/v/gsheeter?logo=pypi)\n![python version](https://img.shields.io/pypi/pyversions/gsheeter?style=pypi)\n\nPandas-integrated, Auto-positioning library for Google Sheet API\n\nFeatures:\n- Auto-positioning for value updates on sheets\n- Designed to minimize API calls\n- Auto-parse values on sheet to get tables with positions and pd.DataFrame interpretations\n- Multi-level indexes and columns supported\n\n## 1. Installation\n```sh\npip install gsheeter\n```\nRequires Python 3.9+.\n\n## 2. Basic Usage\n\n1. Authentication\n(Only service account usage is allowed so far, I will develop functions to use other auth methods in the future)\n```python\n# using a service account will create a global api client for the project to use throughout\nimport gsheeter\nfilename = 'service-account.json' # add a path of your service account json file\ngsheeter.service_account(filename)\n```\n2. Load spreadsheet\n```python\nfrom gsheeter import Drive\n\n# 1. using fileId\nfileId = 'spreadsheetFileId' # use the file id of the spreadsheet you want to access\n\nspreadsheet = Drive.get_spreadsheet(fileId)\n\n# 2. using filename\nfilename = 'spreadsheetFileName' # use the name of the spreadsheet you want to access\nparentId = 'folderId' # use id of the parent folder of the spreadsheet above, I recommend using folderId when using filename, not fileId\n\nspreadsheet = Drive.get_spreadsheet(\n  target=filename,\n  folderId=parentId)\n\n```\n3. Add(Create) spreadsheet\n```python\nfrom gsheeter import Drive\n\nfilename = 'yourspeadsheet' # use the name you want\nsheetname = 'default' # default value is None, use only if you want to create the first sheet with a specific name\nparentId = 'anyFolderId' # if empty, spreadsheet will be replaced in your root\nspreadsheet = Drive.create_spreadsheet(\n  filename=filename,\n  sheetname=sheetname,\n  parentId=parentId)\n\n```\n\n4. Load sheet\n```python\nfrom gsheeter import Drive\n\n\nspreadsheet = Drive.get_spreadsheet(\n  target='test',\n  folderId='parentFolderId')\n\n# 1. using sheetId\nsheetId = 0\nsheet = spreadsheet.get_sheet(sheetId)\n\n# 2. using sheetname\nsheetname = 'Sheet1'\nsheet = spreadsheet.get_sheet(sheetname)\n```\n\nYou can also use kwargs as follows when using Spreadsheet.get_sheet:\n- delete_exist: bool = False: if the sheet being searched already exists and delete_exist is set to True, delete the existing sheet and create another one, otherwise, return the existing sheet\n```python\n# example\nsheet = spreadsheet.get_sheet(sheetname, delete_exist=True)\n```\n- add: bool = True: if the sheet being searched does not exist, add one\n```python\n# example, throws exception if the sheet does not exist or is not added\nsheet = spreadsheet.get_sheet(sheetname, add=False)\n```\n\n5. Add(Create) sheet\n```python\nfrom gsheeter import Drive\n\nspreadsheet = Drive.get_spreadsheet(\n  target='test',\n  folderId='parentFolderId'\n)\n\n# default function behavior, Create a sheet with sheetname \"Sheet1\", 1000 rowCount and 26 columnCount and index of 0\nsheet = spreadsheet.add_sheet()\n\n# with a sheetname\nsheetname = 'new_sheet'\nsheet = spreadsheet.add_sheet(sheetname)\n\n# create a smaller sheet with 100 rows and 10 columns\nsheet = spreadsheet.add_sheet(\n  sheetname=sheetname,\n  rowCount=10,\n  columnCount=10,\n)\n# after creating a new sheet named \"new_sheet\", with 10 rows and 10 columns\n```\n![Screenshot](doc_imgs/add_sheet_1.png)\n![Screenshot](doc_imgs/add_sheet_2.png)\n\n6. Read sheet values: entire sheet\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheetname = 'Sheet1'\nsheet = spreadsheet.get_sheet(sheetname)\nvalues = sheet.matrix # returns 2D np.ndarray filled with Values.\n# If the sheet is empty, An empty np.ndarray with size of (sheet.rowCount, sheet.columnCount)\n```\n![Screenshot](doc_imgs/matrix_1.png)\n![Screenshot](doc_imgs/matrix_2.png)\n\n7. Read sheet values: tables\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheetname = 'Sheet1'\nsheet = spreadsheet.get_sheet(sheetname)\ntables = sheet.tables\n\nfor t in tables:\n  print(t)\n```\n\n![Screenshot](doc_imgs/table_ex_1.png)\n![Screenshot](doc_imgs/table_ex_2.png)\n\n8. Read sheet values: a table\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheetname = 'Sheet1'\nsheet = spreadsheet.get_sheet(sheetname)\ntable = sheet.table # by default, the first table(table #1) is assigned to sheet.table\n```\n\n9. Update values: using sheet\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheetname = 'Sheet1'\nsheet = spreadsheet.get_sheet(sheetname)\n```\n```python\nimport numpy as np\n\n# all coordinates follow array indexing convention, starting from 0\n# gsheeter adds 1 to each coordinate to match cell address on sheet\n# 1. set values of 2D np.ndarray with x, y coordinate on sheet\narr = np.zeros(shape=(4, 3))\ny_offset = 0 # row 1\nx_offset = 0 # column 1(A)\nsheet.set_values(\n  data=arr,\n  y_offset=y_offset,\n  x_offset=x_offset)\n```\n![Screenshot](doc_imgs/update_values_1.png)\n\n```python\n# 2. set values of 1D np.ndarray with x and y coordinate on sheet\narr = np.array([0,1,2,3,4])\ny_offset = 1 # row 2\nx_offset = 2 # column 3(C)\nsheet.set_values(\n  data=arr,\n  y_offset=y_offset,\n  x_offset=x_offset)\n```\n![Screenshot](doc_imgs/update_values_2.png)\n\n```python\n# 3. set values of pd.DataFrame with x and y coordinate on sheet\ndf = pd.DataFrame(...)\ny_offset = 0\nx_offset = 0\nsheet.set_values(\n  data=df,\n  y_offset=y_offset,\n  x_offset=x_offset)\n```\n\n```python\n# 4. set values of pd.Series\nrow = pd.Series(...)\n# default values of y_offset and x_offset are 0\n# if the input to .set_values() is of type pd.Series, the output value is transposed.\nsheet.set_values(row)\n```\n![Screenshot](doc_imgs/update_values_3.png)\n\n```python\n# 5. append values at the next row after the last non-empty row\n# x_offset determines the index at which the search for non-empty row starts\narr = np.zeros(shape=(4, 3))\nsheet.set_values(\n  data=arr,\n  x_offset=1, # searches for the next empty row after the last non-empty row starting from x coordinate of 1 to x coordinate of 1 + width of input data\n  append=True)\n```\n![Screenshot](doc_imgs/update_values_4.png)\n![Screenshot](doc_imgs/update_values_5.png)\n\n```python\narr = np.zeros(shape=(4, 3))\nsheet.set_values(\n  data=arr,\n  x_offset=1,\n  y_offset=1, # paste the input data 1 index array along y-axis from the last-fill row\n  append=True\n)\n```\n![Screenshot](doc_imgs/update_values_4.png)\n![Screenshot](doc_imgs/update_values_6.png)\n\n10. Update values: replace values in a row of a table\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheet = spreadsheet.get_sheet('Sheet1')\ntable = sheet.table\nrow = table.df.iloc[2] # select third row from this table\nrow['x'] = 'test'\nrow['y'] = 'test'\ntable.update_row(row) # then update the values of the row\n```\n11. Update values: delete a row of a table\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheet = spreadsheet.get_sheet('Sheet1')\ntable = sheet.table\n\n# delete second row\nrow = table.df.iloc[1]\ntable.delete_row(row)\n```\n12. Update values: delete a table\n```python\nfrom gsheeter import Drive\n\nfileId = 'fileId'\nspreadsheet = Drive.get_spreadsheet(fileId)\nsheet = spreadsheet.get_sheet('Sheet1')\ntable = sheet.tables[2] # select third table in the sheet\nsheet.delete_table(table)\nsheet.delete_table(1) # you can also use table index\n```\n\n\n## Advanced Usage and Object structures\n\n1. Drive\n2. Spreadsheet\n3. Sheet\n4. Table\n\n## Future updates\n1. Chart\n2. Other authentication methods\n3. Cell format\n\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "Google spreadsheet Python API Abstractor integrated with pandas",
    "version": "0.2.6",
    "project_urls": {
        "Documentation": "https://github.com/haydenkuk/gsheeter/blob/main/README.md",
        "Issues": "https://github.com/haydenkuk/gsheeter/issues",
        "Source": "https://github.com/haydenkuk/gsheeter"
    },
    "split_keywords": [
        "spreadsheets",
        " google-spreadsheets",
        " google-sheets"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "ce2819102c756aee6fcf3be99b76d13579f8b62815f1f755be4bd7eef2bbe9b9",
                "md5": "9b6f0f5414bd35134fbcf888cadb86a4",
                "sha256": "99e973dd6bda71adebc3de261c478bf3c948be3b4ff429db4104e17435f2e3aa"
            },
            "downloads": -1,
            "filename": "gsheeter-0.2.6-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "9b6f0f5414bd35134fbcf888cadb86a4",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 28278,
            "upload_time": "2025-07-09T01:00:28",
            "upload_time_iso_8601": "2025-07-09T01:00:28.929234Z",
            "url": "https://files.pythonhosted.org/packages/ce/28/19102c756aee6fcf3be99b76d13579f8b62815f1f755be4bd7eef2bbe9b9/gsheeter-0.2.6-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "35af69edddb3bb830910101c6436b47b93a4a19b3fbf851eb0f0dccce199c44d",
                "md5": "9efcf5d568268c6415844aa864809ce0",
                "sha256": "35706321221501835faec7c41c603a568b3000ea55535db18626d4067f00c322"
            },
            "downloads": -1,
            "filename": "gsheeter-0.2.6.tar.gz",
            "has_sig": false,
            "md5_digest": "9efcf5d568268c6415844aa864809ce0",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 23696,
            "upload_time": "2025-07-09T01:00:30",
            "upload_time_iso_8601": "2025-07-09T01:00:30.194908Z",
            "url": "https://files.pythonhosted.org/packages/35/af/69edddb3bb830910101c6436b47b93a4a19b3fbf851eb0f0dccce199c44d/gsheeter-0.2.6.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-09 01:00:30",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "haydenkuk",
    "github_project": "gsheeter",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "asttokens",
            "specs": [
                [
                    "==",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "build",
            "specs": [
                [
                    "==",
                    "1.2.2.post1"
                ]
            ]
        },
        {
            "name": "cachetools",
            "specs": [
                [
                    "==",
                    "5.5.2"
                ]
            ]
        },
        {
            "name": "certifi",
            "specs": [
                [
                    "==",
                    "2025.1.31"
                ]
            ]
        },
        {
            "name": "cffi",
            "specs": [
                [
                    "==",
                    "1.17.1"
                ]
            ]
        },
        {
            "name": "charset-normalizer",
            "specs": [
                [
                    "==",
                    "3.4.1"
                ]
            ]
        },
        {
            "name": "colorama",
            "specs": [
                [
                    "==",
                    "0.4.6"
                ]
            ]
        },
        {
            "name": "cryptography",
            "specs": [
                [
                    "==",
                    "44.0.3"
                ]
            ]
        },
        {
            "name": "dateparser",
            "specs": [
                [
                    "==",
                    "1.2.1"
                ]
            ]
        },
        {
            "name": "docutils",
            "specs": [
                [
                    "==",
                    "0.21.2"
                ]
            ]
        },
        {
            "name": "et_xmlfile",
            "specs": [
                [
                    "==",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "executing",
            "specs": [
                [
                    "==",
                    "2.2.0"
                ]
            ]
        },
        {
            "name": "google-api-core",
            "specs": [
                [
                    "==",
                    "2.24.2"
                ]
            ]
        },
        {
            "name": "google-api-python-client",
            "specs": [
                [
                    "==",
                    "2.166.0"
                ]
            ]
        },
        {
            "name": "google-auth",
            "specs": [
                [
                    "==",
                    "2.38.0"
                ]
            ]
        },
        {
            "name": "google-auth-httplib2",
            "specs": [
                [
                    "==",
                    "0.2.0"
                ]
            ]
        },
        {
            "name": "google-auth-oauthlib",
            "specs": [
                [
                    "==",
                    "1.2.1"
                ]
            ]
        },
        {
            "name": "googleapis-common-protos",
            "specs": [
                [
                    "==",
                    "1.69.2"
                ]
            ]
        },
        {
            "name": "httplib2",
            "specs": [
                [
                    "==",
                    "0.22.0"
                ]
            ]
        },
        {
            "name": "id",
            "specs": [
                [
                    "==",
                    "1.5.0"
                ]
            ]
        },
        {
            "name": "idna",
            "specs": [
                [
                    "==",
                    "3.10"
                ]
            ]
        },
        {
            "name": "jaraco.classes",
            "specs": [
                [
                    "==",
                    "3.4.0"
                ]
            ]
        },
        {
            "name": "jaraco.context",
            "specs": [
                [
                    "==",
                    "6.0.1"
                ]
            ]
        },
        {
            "name": "jaraco.functools",
            "specs": [
                [
                    "==",
                    "4.1.0"
                ]
            ]
        },
        {
            "name": "jeepney",
            "specs": [
                [
                    "==",
                    "0.9.0"
                ]
            ]
        },
        {
            "name": "keyring",
            "specs": [
                [
                    "==",
                    "25.6.0"
                ]
            ]
        },
        {
            "name": "markdown-it-py",
            "specs": [
                [
                    "==",
                    "3.0.0"
                ]
            ]
        },
        {
            "name": "mdurl",
            "specs": [
                [
                    "==",
                    "0.1.2"
                ]
            ]
        },
        {
            "name": "more-itertools",
            "specs": [
                [
                    "==",
                    "10.7.0"
                ]
            ]
        },
        {
            "name": "nh3",
            "specs": [
                [
                    "==",
                    "0.2.21"
                ]
            ]
        },
        {
            "name": "numpy",
            "specs": [
                [
                    "==",
                    "2.2.4"
                ]
            ]
        },
        {
            "name": "oauthlib",
            "specs": [
                [
                    "==",
                    "3.2.2"
                ]
            ]
        },
        {
            "name": "openpyxl",
            "specs": [
                [
                    "==",
                    "3.1.5"
                ]
            ]
        },
        {
            "name": "packaging",
            "specs": [
                [
                    "==",
                    "25.0"
                ]
            ]
        },
        {
            "name": "pandas",
            "specs": [
                [
                    "==",
                    "2.2.3"
                ]
            ]
        },
        {
            "name": "proto-plus",
            "specs": [
                [
                    "==",
                    "1.26.1"
                ]
            ]
        },
        {
            "name": "protobuf",
            "specs": [
                [
                    "==",
                    "6.30.2"
                ]
            ]
        },
        {
            "name": "pyasn1",
            "specs": [
                [
                    "==",
                    "0.6.1"
                ]
            ]
        },
        {
            "name": "pyasn1_modules",
            "specs": [
                [
                    "==",
                    "0.4.2"
                ]
            ]
        },
        {
            "name": "pycparser",
            "specs": [
                [
                    "==",
                    "2.22"
                ]
            ]
        },
        {
            "name": "Pygments",
            "specs": [
                [
                    "==",
                    "2.19.1"
                ]
            ]
        },
        {
            "name": "pyparsing",
            "specs": [
                [
                    "==",
                    "3.2.3"
                ]
            ]
        },
        {
            "name": "pyproject_hooks",
            "specs": [
                [
                    "==",
                    "1.2.0"
                ]
            ]
        },
        {
            "name": "python-dateutil",
            "specs": [
                [
                    "==",
                    "2.9.0.post0"
                ]
            ]
        },
        {
            "name": "pytz",
            "specs": [
                [
                    "==",
                    "2025.2"
                ]
            ]
        },
        {
            "name": "readme_renderer",
            "specs": [
                [
                    "==",
                    "44.0"
                ]
            ]
        },
        {
            "name": "regex",
            "specs": [
                [
                    "==",
                    "2024.11.6"
                ]
            ]
        },
        {
            "name": "requests",
            "specs": [
                [
                    "==",
                    "2.32.3"
                ]
            ]
        },
        {
            "name": "requests-oauthlib",
            "specs": [
                [
                    "==",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "requests-toolbelt",
            "specs": [
                [
                    "==",
                    "1.0.0"
                ]
            ]
        },
        {
            "name": "rfc3986",
            "specs": [
                [
                    "==",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "rich",
            "specs": [
                [
                    "==",
                    "14.0.0"
                ]
            ]
        },
        {
            "name": "rsa",
            "specs": [
                [
                    "==",
                    "4.9"
                ]
            ]
        },
        {
            "name": "SecretStorage",
            "specs": [
                [
                    "==",
                    "3.3.3"
                ]
            ]
        },
        {
            "name": "six",
            "specs": [
                [
                    "==",
                    "1.17.0"
                ]
            ]
        },
        {
            "name": "twine",
            "specs": [
                [
                    "==",
                    "6.1.0"
                ]
            ]
        },
        {
            "name": "typing_extensions",
            "specs": [
                [
                    "==",
                    "4.13.2"
                ]
            ]
        },
        {
            "name": "tzdata",
            "specs": [
                [
                    "==",
                    "2025.2"
                ]
            ]
        },
        {
            "name": "tzlocal",
            "specs": [
                [
                    "==",
                    "5.3.1"
                ]
            ]
        },
        {
            "name": "uritemplate",
            "specs": [
                [
                    "==",
                    "4.1.1"
                ]
            ]
        },
        {
            "name": "urllib3",
            "specs": [
                [
                    "==",
                    "2.4.0"
                ]
            ]
        },
        {
            "name": "XlsxWriter",
            "specs": [
                [
                    "==",
                    "3.2.3"
                ]
            ]
        }
    ],
    "lcname": "gsheeter"
}
        
Elapsed time: 1.04888s