From 59fd12f97a041d5a384f6c0ab076a456658cd8c8 Mon Sep 17 00:00:00 2001 From: Himanshu Masani <47810143+masani1989@users.noreply.github.com> Date: Fri, 9 Aug 2024 10:21:51 +0530 Subject: [PATCH 1/9] Fix for Gsheets connection based on streamlit changes --- streamlit_gsheets/gsheets_connection.py | 10 +++++----- tests/test_public_sheet.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/streamlit_gsheets/gsheets_connection.py b/streamlit_gsheets/gsheets_connection.py index d7938e4..8023b22 100644 --- a/streamlit_gsheets/gsheets_connection.py +++ b/streamlit_gsheets/gsheets_connection.py @@ -34,7 +34,7 @@ from sql_metadata import Parser from streamlit.connections import ExperimentalBaseConnection from streamlit.runtime.caching import cache_data -from streamlit.type_util import convert_anything_to_df, is_dataframe_compatible +from streamlit.dataframe_util import convert_anything_to_pandas_df, is_dataframe_like from validators.url import url as validate_url from validators.utils import ValidationError @@ -280,8 +280,8 @@ def create( title=spreadsheet, folder_id=folder_id ) - if is_dataframe_compatible(data): - return_data = convert_anything_to_df(data) + if is_dataframe_like(data): + return_data = convert_anything_to_pandas_df(data) elif type(data) is ndarray: return_data = DataFrame.from_records(data) else: @@ -327,8 +327,8 @@ def update( spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet ) - if is_dataframe_compatible(data): - data = convert_anything_to_df(data) + if is_dataframe_like(data): + data = convert_anything_to_pandas_df(data) elif type(data) is ndarray: data = DataFrame.from_records(data) else: diff --git a/tests/test_public_sheet.py b/tests/test_public_sheet.py index 09803bc..a3ae01d 100644 --- a/tests/test_public_sheet.py +++ b/tests/test_public_sheet.py @@ -21,7 +21,7 @@ def expected_df() -> pd.DataFrame: def test_read_public_sheet(expected_df: pd.DataFrame): url = "https://docs.google.com/spreadsheets/d/1JDy9md2VZPz4JbYtRPJLs81_3jUK47nx6GYQjgU8qNY/edit" - conn = st.experimental_connection("connection_name", type=GSheetsConnection) + conn = st.connection("connection_name", type=GSheetsConnection) df = conn.read(spreadsheet=url, usecols=[0, 1]) @@ -31,7 +31,7 @@ def test_read_public_sheet(expected_df: pd.DataFrame): def test_query_public_sheet(): url = "https://docs.google.com/spreadsheets/d/1JDy9md2VZPz4JbYtRPJLs81_3jUK47nx6GYQjgU8qNY/edit" - conn = st.experimental_connection("connection_name", type=GSheetsConnection) + conn = st.connection("connection_name", type=GSheetsConnection) df = conn.query("select date from my_table where births = 265775", spreadsheet=url) @@ -45,7 +45,7 @@ def test_query_worksheet_public_sheet(): 1585633377 # Example 2, note that this is the gid, not the worksheet name ) - conn = st.experimental_connection("connection_name", type=GSheetsConnection) + conn = st.connection("connection_name", type=GSheetsConnection) df = conn.query( "select date from my_table where births = 1000000", @@ -65,7 +65,7 @@ def test_query_worksheet_public_sheet(): @patch("builtins.open", mock_open(read_data=secrets_contents)) def test_secrets_contents(expected_df): - conn = st.experimental_connection("test_connection_name", type=GSheetsConnection) + conn = st.connection("test_connection_name", type=GSheetsConnection) df = conn.read() @@ -73,7 +73,7 @@ def test_secrets_contents(expected_df): def test_no_secrets_contents(): - conn = st.experimental_connection("other_connection_name", type=GSheetsConnection) + conn = st.connection("other_connection_name", type=GSheetsConnection) with pytest.raises(ValueError): conn.read() From a365a2af0faa38d451a0fb0241d1279cb102d64b Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Wed, 14 Aug 2024 16:45:53 -0400 Subject: [PATCH 2/9] Fix ruff --- .github/workflows/lint_and_test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint_and_test.yaml b/.github/workflows/lint_and_test.yaml index 3cb9879..5232d3f 100644 --- a/.github/workflows/lint_and_test.yaml +++ b/.github/workflows/lint_and_test.yaml @@ -27,9 +27,9 @@ jobs: - name: Lint with ruff run: | # stop the build if there are Python syntax errors or undefined names - ruff --select=E9,F63,F7,F82 --target-version=py39 . + ruff check --select=E9,F63,F7,F82 --target-version=py39 . # default set of ruff rules with GitHub Annotations - ruff --target-version=py39 . + ruff check --target-version=py39 . - name: Check types with mypy run: | mypy --ignore-missing-imports . From b968e247c446f1963ff3d6568e0fa01a5551f60f Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Wed, 14 Aug 2024 16:49:00 -0400 Subject: [PATCH 3/9] Drop experimental --- README.md | 6 +++--- examples/Public_Sheet_Example.py | 4 ++-- examples/pages/Service_Account_Example.py | 20 ++++++++++---------- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 40b0575..1a974b7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Streamlit GSheetsConnection -Connect to public or private Google Sheets from your Streamlit app. Powered by `st.experimental_connection()` and [gspread](https://github.com/burnash/gspread). +Connect to public or private Google Sheets from your Streamlit app. Powered by `st.connection()` and [gspread](https://github.com/burnash/gspread). GSheets Connection works in two modes: @@ -26,7 +26,7 @@ from streamlit_gsheets import GSheetsConnection url = "https://docs.google.com/spreadsheets/d/1JDy9md2VZPz4JbYtRPJLs81_3jUK47nx6GYQjgU8qNY/edit?usp=sharing" -conn = st.experimental_connection("gsheets", type=GSheetsConnection) +conn = st.connection("gsheets", type=GSheetsConnection) data = conn.read(spreadsheet=url, usecols=[0, 1]) st.dataframe(data) @@ -99,7 +99,7 @@ from streamlit_gsheets import GSheetsConnection st.title("Read Google Sheet as DataFrame") -conn = st.experimental_connection("gsheets", type=GSheetsConnection) +conn = st.connection("gsheets", type=GSheetsConnection) df = conn.read(worksheet="Example 1") st.dataframe(df) diff --git a/examples/Public_Sheet_Example.py b/examples/Public_Sheet_Example.py index 0c69e94..af64934 100644 --- a/examples/Public_Sheet_Example.py +++ b/examples/Public_Sheet_Example.py @@ -11,7 +11,7 @@ from streamlit_gsheets import GSheetsConnection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) df = conn.read(spreadsheet=url, usecols=[0, 1]) st.dataframe(df) @@ -34,7 +34,7 @@ from streamlit_gsheets import GSheetsConnection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) df = conn.query('select births from "Example 2" limit 10', spreadsheet=url) st.dataframe(df) diff --git a/examples/pages/Service_Account_Example.py b/examples/pages/Service_Account_Example.py index 3a3d2ab..bdba613 100644 --- a/examples/pages/Service_Account_Example.py +++ b/examples/pages/Service_Account_Example.py @@ -9,7 +9,7 @@ from streamlit_gsheets import GSheetsConnection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) st.write(conn) st.help(conn) @@ -105,7 +105,7 @@ from streamlit_gsheets import GSheetsConnection # Create GSheets connection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) # Demo Births DataFrame df = psql.load_births() @@ -118,7 +118,7 @@ data=df, ) st.cache_data.clear() - st.experimental_rerun() + st.rerun() # Display our Spreadsheet as st.dataframe st.dataframe(df.head(10)) @@ -136,7 +136,7 @@ from streamlit_gsheets import GSheetsConnection # Create GSheets connection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) # Read Google WorkSheet as DataFrame df = conn.read( @@ -157,7 +157,7 @@ from streamlit_gsheets import GSheetsConnection # Create GSheets connection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) # Demo Meat DataFrame df = psql.load_meat() @@ -170,7 +170,7 @@ data=df, ) st.cache_data.clear() - st.experimental_rerun() + st.rerun() # Display our Spreadsheet as st.dataframe st.dataframe(df.head(10)) @@ -188,7 +188,7 @@ from streamlit_gsheets import GSheetsConnection # Create GSheets connection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) # make sure worksheet name is in double quota "", in our case it's "Example 1" # DuckDB SQL dialect is supported @@ -206,7 +206,7 @@ from streamlit_gsheets import GSheetsConnection # Create GSheets connection - conn = st.experimental_connection("gsheets", type=GSheetsConnection) + conn = st.connection("gsheets", type=GSheetsConnection) # click button to update worksheet # This is behind a button to avoid exceeding Google API Quota @@ -214,7 +214,7 @@ conn.clear(worksheet="Example 1") st.info("Worksheet Example 1 Cleared!") st.cache_data.clear() - st.experimental_rerun() + st.rerun() # click button to delete worksheet using the underlying gspread API # This is behind a button to avoid exceeding Google API Quota @@ -223,4 +223,4 @@ worksheet = spreadsheet.worksheet("Example 1") spreadsheet.del_worksheet(worksheet) st.cache_data.clear() - st.experimental_rerun() + st.rerun() From 7e7239ebe392bd4234686586880c373b21b78423 Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Thu, 15 Aug 2024 09:15:01 -0400 Subject: [PATCH 4/9] Drop incorrect validation error --- streamlit_gsheets/gsheets_connection.py | 80 +++++++------------------ 1 file changed, 20 insertions(+), 60 deletions(-) diff --git a/streamlit_gsheets/gsheets_connection.py b/streamlit_gsheets/gsheets_connection.py index 8023b22..5273aec 100644 --- a/streamlit_gsheets/gsheets_connection.py +++ b/streamlit_gsheets/gsheets_connection.py @@ -36,7 +36,6 @@ from streamlit.runtime.caching import cache_data from streamlit.dataframe_util import convert_anything_to_pandas_df, is_dataframe_like from validators.url import url as validate_url -from validators.utils import ValidationError class GSheetsClient(ABC): @@ -144,9 +143,7 @@ def _open_spreadsheet( if validate_url(spreadsheet): return self._client.open_by_url(url=spreadsheet) else: - raise ValidationError( - "spreadsheet is not URL", arg_dict={"spreadsheet": spreadsheet} - ) + raise ValidationError("spreadsheet is not URL", arg_dict={"spreadsheet": spreadsheet}) except ValidationError: return self._client.open(title=spreadsheet, folder_id=folder_id) @@ -166,9 +163,7 @@ def _select_worksheet( folder_id = self._worksheet if isinstance(spreadsheet, str): - spreadsheet = self._open_spreadsheet( - spreadsheet=spreadsheet, folder_id=folder_id - ) + spreadsheet = self._open_spreadsheet(spreadsheet=spreadsheet, folder_id=folder_id) if isinstance(worksheet, str): return spreadsheet.worksheet(worksheet) @@ -193,13 +188,9 @@ def read( folder_id = self._worksheet @cache_data(ttl=ttl, max_entries=max_entries) - def _get_as_dataframe( - spreadsheet, folder_id, worksheet, evaluate_formulas, **options - ): + def _get_as_dataframe(spreadsheet, folder_id, worksheet, evaluate_formulas, **options): return get_as_dataframe( - worksheet=self._select_worksheet( - spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet - ), + worksheet=self._select_worksheet(spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet), evaluate_formulas=evaluate_formulas, **options, ) @@ -235,9 +226,7 @@ def _query(sql, spreadsheet, folder_id, evaluate_formulas, **options): for worksheet in Parser(sql).tables: df = DataFrame() create_table_sql = f'CREATE TABLE "{worksheet}" AS SELECT * FROM df' - if not self._select_worksheet( - spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet - ): + if not self._select_worksheet(spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet): in_memory_db.sql(create_table_sql) _ = df continue @@ -272,13 +261,9 @@ def create( folder_id = self._worksheet try: - new_spreadsheet = self._open_spreadsheet( - spreadsheet=spreadsheet, folder_id=folder_id - ) + new_spreadsheet = self._open_spreadsheet(spreadsheet=spreadsheet, folder_id=folder_id) except SpreadsheetNotFound: - new_spreadsheet = self._client.create( - title=spreadsheet, folder_id=folder_id - ) + new_spreadsheet = self._client.create(title=spreadsheet, folder_id=folder_id) if is_dataframe_like(data): return_data = convert_anything_to_pandas_df(data) @@ -294,14 +279,10 @@ def create( n_rows, n_cols = return_data.shape - new_worksheet = new_spreadsheet.add_worksheet( - title=worksheet, rows=n_rows, cols=n_cols - ) + new_worksheet = new_spreadsheet.add_worksheet(title=worksheet, rows=n_rows, cols=n_cols) set_with_dataframe(new_worksheet, return_data) - set_format_with_dataframe( - new_worksheet, return_data, include_column_header=True - ) + set_format_with_dataframe(new_worksheet, return_data, include_column_header=True) return return_data @@ -319,13 +300,9 @@ def update( folder_id = self._worksheet if isinstance(spreadsheet, str): - spreadsheet = self._open_spreadsheet( - spreadsheet=spreadsheet, folder_id=folder_id - ) + spreadsheet = self._open_spreadsheet(spreadsheet=spreadsheet, folder_id=folder_id) - worksheet = self._select_worksheet( - spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet - ) + worksheet = self._select_worksheet(spreadsheet=spreadsheet, folder_id=folder_id, worksheet=worksheet) if is_dataframe_like(data): data = convert_anything_to_pandas_df(data) @@ -351,9 +328,7 @@ def clear( worksheet: Optional[Union[str, int, Worksheet]] = None, folder_id: Optional[str] = None, ) -> dict: - return self._select_worksheet( - spreadsheet=spreadsheet, worksheet=worksheet, folder_id=folder_id - ).clear() + return self._select_worksheet(spreadsheet=spreadsheet, worksheet=worksheet, folder_id=folder_id).clear() class UnsupportedOperationException(Exception): @@ -367,10 +342,7 @@ def _get_download_as_csv_url( spreadsheet: str, worksheet: str | int | None = None, ) -> str: - validation_failure = ValidationError( - "spreadsheet validation failure", - arg_dict={"spreadsheet": spreadsheet}, - ) + validation_failure = ValueError(f"spreadsheet validation failure for {spreadsheet}") try: if validate_url(spreadsheet): # type: ignore r = re.compile(r"\/d\/.+?(?=\/)") @@ -396,10 +368,8 @@ def _get_download_as_csv_url( return url else: raise validation_failure - except (ValidationError, TypeError): - url = ( - f"https://docs.google.com/spreadsheet/ccc?key={spreadsheet}&output=csv" - ) + except (ValueError, TypeError): + url = f"https://docs.google.com/spreadsheet/ccc?key={spreadsheet}&output=csv" if worksheet: return f"{url}&gid={worksheet}" return url @@ -420,9 +390,7 @@ def read( if not worksheet and self._worksheet: worksheet = self._worksheet - url = self._get_download_as_csv_url( - spreadsheet=spreadsheet, worksheet=worksheet - ) + url = self._get_download_as_csv_url(spreadsheet=spreadsheet, worksheet=worksheet) @cache_data(ttl=ttl, max_entries=max_entries) def _get_as_dataframe(url: str, **options) -> DataFrame: @@ -449,9 +417,7 @@ def query( if not spreadsheet: raise ValueError("Spreadsheet must be specified") - url = self._get_download_as_csv_url( - spreadsheet=spreadsheet, worksheet=worksheet - ) + url = self._get_download_as_csv_url(spreadsheet=spreadsheet, worksheet=worksheet) @cache_data(ttl=ttl, max_entries=max_entries) def _query(sql: str, url: str, **options): @@ -673,9 +639,7 @@ def create( ----------- df: pandas.DataFrame. """ - return self.client.create( - spreadsheet=spreadsheet, worksheet=worksheet, data=data, folder_id=folder_id - ) + return self.client.create(spreadsheet=spreadsheet, worksheet=worksheet, data=data, folder_id=folder_id) def update( self, @@ -707,9 +671,7 @@ def update( ----------- df: pandas.DataFrame. """ - return self.client.update( - spreadsheet=spreadsheet, worksheet=worksheet, data=data, folder_id=folder_id - ) + return self.client.update(spreadsheet=spreadsheet, worksheet=worksheet, data=data, folder_id=folder_id) def clear( self, @@ -738,9 +700,7 @@ def clear( response: dict Google API GSpread clear response. """ - return self.client.clear( - spreadsheet=spreadsheet, worksheet=worksheet, folder_id=folder_id - ) + return self.client.clear(spreadsheet=spreadsheet, worksheet=worksheet, folder_id=folder_id) def set_default( self, From 3d92e11c37355deb3a6f0259362eb1a6a10ee252 Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Thu, 15 Aug 2024 09:18:17 -0400 Subject: [PATCH 5/9] Add pre-commit --- .github/workflows/lint_and_test.yaml | 9 --------- .pre-commit-config.yaml | 20 ++++++++++++++++++++ pyproject.toml | 24 +++++++++++++++++++++++- 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/lint_and_test.yaml b/.github/workflows/lint_and_test.yaml index 5232d3f..b24866d 100644 --- a/.github/workflows/lint_and_test.yaml +++ b/.github/workflows/lint_and_test.yaml @@ -24,15 +24,6 @@ jobs: run: | python -m pip install --upgrade pip pip install -r test-requirements.txt - - name: Lint with ruff - run: | - # stop the build if there are Python syntax errors or undefined names - ruff check --select=E9,F63,F7,F82 --target-version=py39 . - # default set of ruff rules with GitHub Annotations - ruff check --target-version=py39 . - - name: Check types with mypy - run: | - mypy --ignore-missing-imports . - name: Test with pytest run: | pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7fd7e46 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,20 @@ +--- +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.5.6" + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + args: [--config=pyproject.toml] + + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.11.1 + hooks: + - id: mypy + language_version: python3.8 + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: end-of-file-fixer diff --git a/pyproject.toml b/pyproject.toml index a9bf9fe..00f376b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,24 @@ [tool.ruff] -line-length=125 \ No newline at end of file +exclude = [".git", ".vscode", ".pytest_cache", ".mypy_cache", ".env"] +ignore = ["B008", "ISC001", "E501", "W191"] +line-length = 125 +select = [ + "B", + "E", + "F", + "W", + "I", + "N", + "C4", + "EXE", + "ISC", + "ICN", + "PIE", + "PT", + "RET", + "SIM", + "ERA", + "PLC", + "RUF", + "ARG", +] From 61984d29364b481978a4d4c64d252a37d4e98922 Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Thu, 15 Aug 2024 09:34:38 -0400 Subject: [PATCH 6/9] Cleanup, use pre-commit --- examples/Public_Sheet_Example.py | 2 +- examples/pages/Service_Account_Example.py | 10 ++-- mypy.ini | 2 +- pyproject.toml | 4 +- setup.py | 4 +- streamlit_gsheets/gsheets_connection.py | 61 +++++++---------------- test-requirements.txt | 2 +- tests/test_public_sheet.py | 8 ++- 8 files changed, 36 insertions(+), 57 deletions(-) diff --git a/examples/Public_Sheet_Example.py b/examples/Public_Sheet_Example.py index af64934..7dafcad 100644 --- a/examples/Public_Sheet_Example.py +++ b/examples/Public_Sheet_Example.py @@ -19,7 +19,7 @@ st.write("#### 2. Query public Google Worksheet using SQL") st.info( "Mutation SQL queries are in-memory only and do not results in the Worksheet update.", - icon="ℹ️", + icon="ℹ️", # noqa: RUF001 ) st.warning( """You can query only one Worksheet in provided public Spreadsheet, diff --git a/examples/pages/Service_Account_Example.py b/examples/pages/Service_Account_Example.py index bdba613..01587db 100644 --- a/examples/pages/Service_Account_Example.py +++ b/examples/pages/Service_Account_Example.py @@ -36,7 +36,7 @@ and enable it. 3. [Using Service Account](https://docs.gspread.org/en/v5.7.1/oauth2.html#for-bots-using-service-account) - * Enable API Access for a Project if you haven’t done it yet. + * Enable API Access for a Project if you haven't done it yet. * Go to “APIs & Services > Credentials” and choose “Create credentials > Service account key”. * Fill out the form @@ -58,12 +58,12 @@ ... }} ``` -Remember the path to the downloaded credentials file. Also, in the next step you’ll need +Remember the path to the downloaded credentials file. Also, in the next step you'll need the value of client_email from this file. * **:red[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 +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. @@ -127,7 +127,7 @@ st.write("#### 4. Read Google WorkSheet as DataFrame") st.info( "If the sheet has been deleted, press 'Create new worksheet' button above.", - icon="ℹ️", + icon="ℹ️", # noqa: RUF001 ) with st.echo(): @@ -178,7 +178,7 @@ st.write("#### 6. Query Google WorkSheet with SQL and get results as DataFrame") st.info( "Mutation SQL queries are in-memory only and do not results in the Worksheet update.", - icon="ℹ️", + icon="ℹ️", # noqa: RUF001 ) diff --git a/mypy.ini b/mypy.ini index 1215375..976ba02 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,2 +1,2 @@ [mypy] -ignore_missing_imports = True \ No newline at end of file +ignore_missing_imports = True diff --git a/pyproject.toml b/pyproject.toml index 00f376b..f62a22e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,9 @@ [tool.ruff] exclude = [".git", ".vscode", ".pytest_cache", ".mypy_cache", ".env"] -ignore = ["B008", "ISC001", "E501", "W191"] line-length = 125 + +[tool.ruff.lint] +ignore = ["B008", "ISC001", "E501", "W191"] select = [ "B", "E", diff --git a/setup.py b/setup.py index f7d3f72..eeb3e28 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,5 @@ +from pathlib import Path + import setuptools VERSION = "0.0.4" # PEP-440 @@ -42,6 +44,6 @@ # Requirements install_requires=INSTALL_REQUIRES, packages=["streamlit_gsheets"], - long_description=open("README.md").read(), + long_description=Path("README.md").read_text(), long_description_content_type="text/markdown", ) diff --git a/streamlit_gsheets/gsheets_connection.py b/streamlit_gsheets/gsheets_connection.py index 5273aec..dd1297e 100644 --- a/streamlit_gsheets/gsheets_connection.py +++ b/streamlit_gsheets/gsheets_connection.py @@ -33,8 +33,8 @@ from pandas import DataFrame, read_csv from sql_metadata import Parser from streamlit.connections import ExperimentalBaseConnection -from streamlit.runtime.caching import cache_data from streamlit.dataframe_util import convert_anything_to_pandas_df, is_dataframe_like +from streamlit.runtime.caching import cache_data from validators.url import url as validate_url @@ -46,7 +46,7 @@ class GSheetsClient(ABC): def __init__(self, secrets_dict: dict): self._spreadsheet = secrets_dict.pop("spreadsheet", None) self._worksheet = secrets_dict.pop("worksheet", None) - if secrets_dict.get("type", None) == "service_account": + if secrets_dict.get("type") == "service_account": self._optional_client = service_account_from_dict(secrets_dict) def set_default( @@ -142,9 +142,8 @@ def _open_spreadsheet( try: if validate_url(spreadsheet): return self._client.open_by_url(url=spreadsheet) - else: - raise ValidationError("spreadsheet is not URL", arg_dict={"spreadsheet": spreadsheet}) - except ValidationError: + raise ValueError(f"spreadsheet is not URL: {spreadsheet}") + except ValueError: return self._client.open(title=spreadsheet, folder_id=folder_id) def _select_worksheet( @@ -157,14 +156,17 @@ def _select_worksheet( if type(worksheet) is Worksheet: return worksheet - if not spreadsheet and self._spreadsheet: + if spreadsheet is None and self._spreadsheet: spreadsheet = self._spreadsheet - if not folder_id and self._worksheet: + if folder_id is None and self._worksheet: folder_id = self._worksheet if isinstance(spreadsheet, str): spreadsheet = self._open_spreadsheet(spreadsheet=spreadsheet, folder_id=folder_id) + if spreadsheet is None: + raise ValueError("Spreadsheet must not be None") + if isinstance(worksheet, str): return spreadsheet.worksheet(worksheet) if not worksheet: @@ -208,7 +210,6 @@ def query( sql: str, *, # keyword-only arguments: spreadsheet: Optional[str] = None, - worksheet: Optional[Union[int, str]] = None, ttl: Optional[Union[int, timedelta, None]] = 3600, max_entries: Optional[Union[int, None]] = None, evaluate_formulas: bool = True, @@ -331,7 +332,7 @@ def clear( return self._select_worksheet(spreadsheet=spreadsheet, worksheet=worksheet, folder_id=folder_id).clear() -class UnsupportedOperationException(Exception): +class UnsupportedOperationError(Exception): pass @@ -366,8 +367,7 @@ def _get_download_as_csv_url( if final_gid: return f"{url}&gid={final_gid}" return url - else: - raise validation_failure + raise validation_failure except (ValueError, TypeError): url = f"https://docs.google.com/spreadsheet/ccc?key={spreadsheet}&output=csv" if worksheet: @@ -434,46 +434,24 @@ def _query(sql: str, url: str, **options): return _query(sql, url, **options) - def create( - self, - *, - spreadsheet: Optional[str] = None, - worksheet: Optional[str] = None, - data: Optional[Union[DataFrame, ndarray, List[list], List[dict]]] = None, - folder_id: Optional[str] = None, - ) -> DataFrame: - raise UnsupportedOperationException( - "Use Service Account authentication to enable CRUD methods on your Spreadsheets." - ) + def create(self, *args, **kwargs) -> DataFrame: # noqa: ARG002 + raise UnsupportedOperationError("Use Service Account authentication to enable CRUD methods on your Spreadsheets.") - def update( - self, - *, - spreadsheet: Optional[Union[str, Spreadsheet]] = None, - worksheet: Optional[Union[str, int, Worksheet]] = None, - data: Optional[Union[DataFrame, ndarray, List[list], List[dict]]] = None, - folder_id: Optional[str] = None, - ) -> DataFrame: - raise UnsupportedOperationException( + def update(self, *args, **kwargs) -> DataFrame: # noqa: ARG002 + raise UnsupportedOperationError( "Public Spreadsheet cannot be written to, " "use Service Account authentication to enable CRUD methods on your Spreadsheets." ) - def clear( - self, - *, - spreadsheet: Optional[Union[str, Spreadsheet]] = None, - worksheet: Optional[Union[str, int, Worksheet]] = None, - folder_id: Optional[str] = None, - ): - raise UnsupportedOperationException( + def clear(self, *args, **kwargs): # noqa: ARG002 + raise UnsupportedOperationError( "Public Spreadsheet cannot be cleared, " "use Service Account authentication to enable CRUD methods on your Spreadsheets." ) class GSheetsConnection(ExperimentalBaseConnection[GSheetsClient], GSheetsClient): - def _connect(self, **kwargs) -> GSheetsClient: + def _connect(self) -> GSheetsClient: """Reads st.connection .streamlit/secrets.toml and returns GSheets client based on them.""" secrets_dict = self._secrets.to_dict() @@ -736,11 +714,10 @@ def _repr_html_(self) -> str: else: name = "" cfg = "" - md = f""" + return f""" --- **st.connection {name}built from `{module_name}.{class_name}`** {cfg} - Learn more using `st.help()` --- """ - return md diff --git a/test-requirements.txt b/test-requirements.txt index 0f70d84..1277c8d 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,4 @@ pytest mypy ruff --e . \ No newline at end of file +-e . diff --git a/tests/test_public_sheet.py b/tests/test_public_sheet.py index a3ae01d..4bcf732 100644 --- a/tests/test_public_sheet.py +++ b/tests/test_public_sheet.py @@ -8,7 +8,7 @@ from streamlit_gsheets import GSheetsConnection -@pytest.fixture +@pytest.fixture() def expected_df() -> pd.DataFrame: return pd.DataFrame( { @@ -41,9 +41,7 @@ def test_query_public_sheet(): def test_query_worksheet_public_sheet(): url = "https://docs.google.com/spreadsheets/d/1JDy9md2VZPz4JbYtRPJLs81_3jUK47nx6GYQjgU8qNY/edit" - worksheet = ( - 1585633377 # Example 2, note that this is the gid, not the worksheet name - ) + worksheet = 1585633377 # Example 2, note that this is the gid, not the worksheet name conn = st.connection("connection_name", type=GSheetsConnection) @@ -75,5 +73,5 @@ def test_secrets_contents(expected_df): def test_no_secrets_contents(): conn = st.connection("other_connection_name", type=GSheetsConnection) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="Spreadsheet must be specified"): conn.read() From cec9f5af2f9fd3a895eb2f32b91c28d1cb0cef5d Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Thu, 15 Aug 2024 09:39:35 -0400 Subject: [PATCH 7/9] Remove pandas pin, use streamlit's instead --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index eeb3e28..07aa289 100644 --- a/setup.py +++ b/setup.py @@ -7,12 +7,11 @@ NAME = "st-gsheets-connection" INSTALL_REQUIRES = [ - "streamlit>=1.22.0", + "streamlit>=1.32.0", "gspread>=5.8.0, <6", "gspread-pandas>=3.2.2", "gspread-dataframe>=3.3.0", "gspread-formatting>=1.1.2", - "pandas>=1.3.0, <2", "duckdb>=0.8.1", "sql-metadata>=2.7.0", "validators>=0.22.0", From 8df387d41694f83130d052ba8317299fdfc94e3d Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Thu, 15 Aug 2024 09:43:01 -0400 Subject: [PATCH 8/9] Bump version, but versions, rename action --- .github/workflows/{lint_and_test.yaml => test.yaml} | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename .github/workflows/{lint_and_test.yaml => test.yaml} (88%) diff --git a/.github/workflows/lint_and_test.yaml b/.github/workflows/test.yaml similarity index 88% rename from .github/workflows/lint_and_test.yaml rename to .github/workflows/test.yaml index b24866d..7dbbc4e 100644 --- a/.github/workflows/lint_and_test.yaml +++ b/.github/workflows/test.yaml @@ -14,9 +14,9 @@ jobs: python-version: ["3.9", "3.10", "3.11"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: "pip" diff --git a/setup.py b/setup.py index 07aa289..acfb830 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ import setuptools -VERSION = "0.0.4" # PEP-440 +VERSION = "0.1.0" # PEP-440 NAME = "st-gsheets-connection" From 1e09c423b95f8ae685520acc2bf7b7d81dfab2af Mon Sep 17 00:00:00 2001 From: Zachary Blackwood Date: Thu, 15 Aug 2024 10:19:22 -0400 Subject: [PATCH 9/9] Tweak service account --- streamlit_gsheets/gsheets_connection.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/streamlit_gsheets/gsheets_connection.py b/streamlit_gsheets/gsheets_connection.py index dd1297e..b7306fe 100644 --- a/streamlit_gsheets/gsheets_connection.py +++ b/streamlit_gsheets/gsheets_connection.py @@ -209,6 +209,7 @@ def query( self, sql: str, *, # keyword-only arguments: + worksheet: Optional[Union[int, str]] = None, spreadsheet: Optional[str] = None, ttl: Optional[Union[int, timedelta, None]] = 3600, max_entries: Optional[Union[int, None]] = None, @@ -216,9 +217,11 @@ def query( folder_id: Optional[str] = None, **options, ) -> DataFrame: - if not spreadsheet and self._spreadsheet: + if worksheet is None and self._worksheet: + worksheet = self._worksheet + if spreadsheet is None and self._spreadsheet: spreadsheet = self._spreadsheet - if not folder_id and self._worksheet: + if folder_id is None and self._worksheet: folder_id = self._worksheet @cache_data(ttl=ttl, max_entries=max_entries) @@ -435,7 +438,10 @@ def _query(sql: str, url: str, **options): return _query(sql, url, **options) def create(self, *args, **kwargs) -> DataFrame: # noqa: ARG002 - raise UnsupportedOperationError("Use Service Account authentication to enable CRUD methods on your Spreadsheets.") + raise UnsupportedOperationError( + "Public Spreadsheet cannot be created, " + "use Service Account authentication to enable CRUD methods on your Spreadsheets." + ) def update(self, *args, **kwargs) -> DataFrame: # noqa: ARG002 raise UnsupportedOperationError(