From 85abfce5f7052178008c399e612bb96126bcb47a Mon Sep 17 00:00:00 2001 From: David Kegley Date: Thu, 5 Sep 2024 15:18:58 -0400 Subject: [PATCH 1/4] feat!: update external auth provider implementations --- src/posit/connect/external/__init__.py | 14 +++++ src/posit/connect/external/databricks.py | 31 ++++++----- src/posit/connect/external/snowflake.py | 44 +++++++++++++++ .../posit/connect/external/test_databricks.py | 2 +- .../posit/connect/external/test_snowflake.py | 54 +++++++++++++++++++ 5 files changed, 128 insertions(+), 17 deletions(-) create mode 100644 src/posit/connect/external/snowflake.py create mode 100644 tests/posit/connect/external/test_snowflake.py diff --git a/src/posit/connect/external/__init__.py b/src/posit/connect/external/__init__.py index e69de29b..e43f458a 100644 --- a/src/posit/connect/external/__init__.py +++ b/src/posit/connect/external/__init__.py @@ -0,0 +1,14 @@ +import os + +""" +NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. +""" + +def _is_local() -> bool: + """Returns true if called from a piece of content running on a Connect server. + + The connect server will always set the environment variable `RSTUDIO_PRODUCT=CONNECT`. + We can use this environment variable to determine if the content is running locally + or on a Connect server. + """ + return not os.getenv("RSTUDIO_PRODUCT") == "CONNECT" diff --git a/src/posit/connect/external/databricks.py b/src/posit/connect/external/databricks.py index 8a97ad7a..6743ee1e 100644 --- a/src/posit/connect/external/databricks.py +++ b/src/posit/connect/external/databricks.py @@ -3,7 +3,6 @@ from typing import Callable, Dict, Optional from ..client import Client -from ..oauth import OAuthIntegration """ NOTE: These APIs are provided as a convenience and are subject to breaking changes: @@ -41,13 +40,13 @@ def _is_local() -> bool: class PositCredentialsProvider: - def __init__(self, posit_oauth: OAuthIntegration, user_session_token: str): - self.posit_oauth = posit_oauth - self.user_session_token = user_session_token + def __init__(self, client: Client, user_session_token: str): + self._client = client + self._user_session_token = user_session_token def __call__(self) -> Dict[str, str]: - access_token = self.posit_oauth.get_credentials( - self.user_session_token + access_token = self._client.oauth.get_credentials( + self._user_session_token )["access_token"] return {"Authorization": f"Bearer {access_token}"} @@ -56,12 +55,12 @@ class PositCredentialsStrategy(CredentialsStrategy): def __init__( self, local_strategy: CredentialsStrategy, - user_session_token: Optional[str] = None, client: Optional[Client] = None, + user_session_token: Optional[str] = None, ): - self.user_session_token = user_session_token - self.local_strategy = local_strategy - self.client = client + self._local_strategy = local_strategy + self._client = client + self._user_session_token = user_session_token def sql_credentials_provider(self, *args, **kwargs): """The sql connector attempts to call the credentials provider w/o any args. @@ -90,25 +89,25 @@ def auth_type(self) -> str: https://github.com/databricks/databricks-sql-python/blob/v3.3.0/src/databricks/sql/client.py#L214-L219 """ if _is_local(): - return self.local_strategy.auth_type() + return self._local_strategy.auth_type() else: return "posit-oauth-integration" def __call__(self, *args, **kwargs) -> CredentialsProvider: # If the content is not running on Connect then fall back to local_strategy if _is_local(): - return self.local_strategy(*args, **kwargs) + return self._local_strategy(*args, **kwargs) # If the user-session-token wasn't provided and we're running on Connect then we raise an exception. # user_session_token is required to impersonate the viewer. - if self.user_session_token is None: + if self._user_session_token is None: raise ValueError( "The user-session-token is required for viewer authentication." ) - if self.client is None: - self.client = Client() + if self._client is None: + self._client = Client() return PositCredentialsProvider( - self.client.oauth, self.user_session_token + self._client, self._user_session_token ) diff --git a/src/posit/connect/external/snowflake.py b/src/posit/connect/external/snowflake.py new file mode 100644 index 00000000..62c7be92 --- /dev/null +++ b/src/posit/connect/external/snowflake.py @@ -0,0 +1,44 @@ +from typing import Optional + +from . import _is_local +from ..client import Client + +""" +NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. +""" + +class PositAuthenticator: + def __init__( + self, + local_authenticator: Optional[str] = None, + client: Optional[Client] = None, + user_session_token: Optional[str] = None, + ): + self._local_authenticator = local_authenticator + self._client = client + self._user_session_token = user_session_token + + def authenticator(self) -> Optional[str]: + if _is_local(): + return self._local_authenticator + return "oauth" + + def token(self) -> Optional[str]: + if _is_local(): + return None + + # If the user-session-token wasn't provided and we're running on Connect then we raise an exception. + # user_session_token is required to impersonate the viewer. + if self._user_session_token is None: + raise ValueError( + "The user-session-token is required for viewer authentication." + ) + + if self._client is None: + self._client = Client() + + access_token = self._client.oauth.get_credentials( + self._user_session_token + )["access_token"] + return access_token + diff --git a/tests/posit/connect/external/test_databricks.py b/tests/posit/connect/external/test_databricks.py index e8439b42..ea98069b 100644 --- a/tests/posit/connect/external/test_databricks.py +++ b/tests/posit/connect/external/test_databricks.py @@ -49,7 +49,7 @@ def test_posit_credentials_provider(self): client = Client(api_key="12345", url="https://connect.example/") cp = PositCredentialsProvider( - posit_oauth=client.oauth, user_session_token="cit" + client=client, user_session_token="cit" ) assert cp() == {"Authorization": f"Bearer dynamic-viewer-access-token"} diff --git a/tests/posit/connect/external/test_snowflake.py b/tests/posit/connect/external/test_snowflake.py new file mode 100644 index 00000000..fff1a694 --- /dev/null +++ b/tests/posit/connect/external/test_snowflake.py @@ -0,0 +1,54 @@ +from typing import Dict +from unittest.mock import patch + +import responses + +from posit.connect import Client +from posit.connect.external.snowflake import PositAuthenticator + + +def register_mocks(): + responses.post( + "https://connect.example/__api__/v1/oauth/integrations/credentials", + match=[ + responses.matchers.urlencoded_params_matcher( + { + "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange", + "subject_token_type": "urn:posit:connect:user-session-token", + "subject_token": "cit", + } + ) + ], + json={ + "access_token": "dynamic-viewer-access-token", + "issued_token_type": "urn:ietf:params:oauth:token-type:access_token", + "token_type": "Bearer", + }, + ) + + +class TestPositAuthenticator: + @responses.activate + @patch.dict("os.environ", {"RSTUDIO_PRODUCT": "CONNECT"}) + def test_posit_authenticator(self): + register_mocks() + + client = Client(api_key="12345", url="https://connect.example/") + auth = PositAuthenticator( + local_authenticator="SNOWFLAKE", + user_session_token="cit", + client=client, + ) + assert auth.authenticator() == "oauth" + assert auth.token() == "dynamic-viewer-access-token" + + def test_posit_authenticator_fallback(self): + # local_authenticator is used when the content is running locally + client = Client(api_key="12345", url="https://connect.example/") + auth = PositAuthenticator( + local_authenticator="SNOWFLAKE", + user_session_token="cit", + client=client, + ) + assert auth.authenticator() == "SNOWFLAKE" + assert auth.token() == None From 9ba7992bdb8e6e36c57d12ebf647077e9b69a28a Mon Sep 17 00:00:00 2001 From: David Kegley Date: Thu, 5 Sep 2024 15:20:04 -0400 Subject: [PATCH 2/4] doc: add Snowflake example and reorganize directory structure --- .../connect/{ => databricks}/dash/README.md | 0 examples/connect/{ => databricks}/dash/app.py | 0 .../{ => databricks}/dash/requirements.txt | 0 .../{ => databricks}/fastapi/README.md | 0 .../connect/{ => databricks}/fastapi/app.py | 0 .../{ => databricks}/fastapi/requirements.txt | 0 .../connect/{ => databricks}/flask/README.md | 0 .../connect/{ => databricks}/flask/app.py | 0 .../{ => databricks}/flask/requirements.txt | 0 .../shiny}/README.md | 0 .../{shiny-python => databricks/shiny}/app.py | 0 .../shiny}/requirements.txt | 0 .../{ => databricks}/streamlit/README.md | 0 .../connect/{ => databricks}/streamlit/app.py | 0 .../streamlit/requirements.txt | 0 .../connect/snowflake/streamlit/README.md | 41 ++++++++++++++++++ examples/connect/snowflake/streamlit/app.py | 43 +++++++++++++++++++ .../snowflake/streamlit/requirements.txt | 3 ++ 18 files changed, 87 insertions(+) rename examples/connect/{ => databricks}/dash/README.md (100%) rename examples/connect/{ => databricks}/dash/app.py (100%) rename examples/connect/{ => databricks}/dash/requirements.txt (100%) rename examples/connect/{ => databricks}/fastapi/README.md (100%) rename examples/connect/{ => databricks}/fastapi/app.py (100%) rename examples/connect/{ => databricks}/fastapi/requirements.txt (100%) rename examples/connect/{ => databricks}/flask/README.md (100%) rename examples/connect/{ => databricks}/flask/app.py (100%) rename examples/connect/{ => databricks}/flask/requirements.txt (100%) rename examples/connect/{shiny-python => databricks/shiny}/README.md (100%) rename examples/connect/{shiny-python => databricks/shiny}/app.py (100%) rename examples/connect/{shiny-python => databricks/shiny}/requirements.txt (100%) rename examples/connect/{ => databricks}/streamlit/README.md (100%) rename examples/connect/{ => databricks}/streamlit/app.py (100%) rename examples/connect/{ => databricks}/streamlit/requirements.txt (100%) create mode 100644 examples/connect/snowflake/streamlit/README.md create mode 100644 examples/connect/snowflake/streamlit/app.py create mode 100644 examples/connect/snowflake/streamlit/requirements.txt diff --git a/examples/connect/dash/README.md b/examples/connect/databricks/dash/README.md similarity index 100% rename from examples/connect/dash/README.md rename to examples/connect/databricks/dash/README.md diff --git a/examples/connect/dash/app.py b/examples/connect/databricks/dash/app.py similarity index 100% rename from examples/connect/dash/app.py rename to examples/connect/databricks/dash/app.py diff --git a/examples/connect/dash/requirements.txt b/examples/connect/databricks/dash/requirements.txt similarity index 100% rename from examples/connect/dash/requirements.txt rename to examples/connect/databricks/dash/requirements.txt diff --git a/examples/connect/fastapi/README.md b/examples/connect/databricks/fastapi/README.md similarity index 100% rename from examples/connect/fastapi/README.md rename to examples/connect/databricks/fastapi/README.md diff --git a/examples/connect/fastapi/app.py b/examples/connect/databricks/fastapi/app.py similarity index 100% rename from examples/connect/fastapi/app.py rename to examples/connect/databricks/fastapi/app.py diff --git a/examples/connect/fastapi/requirements.txt b/examples/connect/databricks/fastapi/requirements.txt similarity index 100% rename from examples/connect/fastapi/requirements.txt rename to examples/connect/databricks/fastapi/requirements.txt diff --git a/examples/connect/flask/README.md b/examples/connect/databricks/flask/README.md similarity index 100% rename from examples/connect/flask/README.md rename to examples/connect/databricks/flask/README.md diff --git a/examples/connect/flask/app.py b/examples/connect/databricks/flask/app.py similarity index 100% rename from examples/connect/flask/app.py rename to examples/connect/databricks/flask/app.py diff --git a/examples/connect/flask/requirements.txt b/examples/connect/databricks/flask/requirements.txt similarity index 100% rename from examples/connect/flask/requirements.txt rename to examples/connect/databricks/flask/requirements.txt diff --git a/examples/connect/shiny-python/README.md b/examples/connect/databricks/shiny/README.md similarity index 100% rename from examples/connect/shiny-python/README.md rename to examples/connect/databricks/shiny/README.md diff --git a/examples/connect/shiny-python/app.py b/examples/connect/databricks/shiny/app.py similarity index 100% rename from examples/connect/shiny-python/app.py rename to examples/connect/databricks/shiny/app.py diff --git a/examples/connect/shiny-python/requirements.txt b/examples/connect/databricks/shiny/requirements.txt similarity index 100% rename from examples/connect/shiny-python/requirements.txt rename to examples/connect/databricks/shiny/requirements.txt diff --git a/examples/connect/streamlit/README.md b/examples/connect/databricks/streamlit/README.md similarity index 100% rename from examples/connect/streamlit/README.md rename to examples/connect/databricks/streamlit/README.md diff --git a/examples/connect/streamlit/app.py b/examples/connect/databricks/streamlit/app.py similarity index 100% rename from examples/connect/streamlit/app.py rename to examples/connect/databricks/streamlit/app.py diff --git a/examples/connect/streamlit/requirements.txt b/examples/connect/databricks/streamlit/requirements.txt similarity index 100% rename from examples/connect/streamlit/requirements.txt rename to examples/connect/databricks/streamlit/requirements.txt diff --git a/examples/connect/snowflake/streamlit/README.md b/examples/connect/snowflake/streamlit/README.md new file mode 100644 index 00000000..96c0a70b --- /dev/null +++ b/examples/connect/snowflake/streamlit/README.md @@ -0,0 +1,41 @@ +# Streamlit Example + +## Start the app locally + +```bash +SNOWFLAKE_ACCOUNT = "" +SNOWFLAKE_WAREHOUSE = "" + +# USER is only required when running the example locally with external browser auth +SNOWFLAKE_USER="" streamlit run app.py +``` + +## Deploy to Posit Connect + +Validate that `rsconnect-python` is installed: + +```bash +rsconnect version +``` + +Or install it as documented in the [installation](https://docs.posit.co/rsconnect-python/#installation) section of the documentation. + +To publish, make sure `CONNECT_SERVER`, `CONNECT_API_KEY`, `SNOWFLAKE_ACCOUNT`, `SNOWFLAKE_WAREHOUSE` have valid values. Then, on a terminal session, enter the following command: + +```bash +rsconnect deploy streamlit . \ + --server "${CONNECT_SERVER}" \ + --api-key "${CONNECT_API_KEY}" \ + --environment SNOWFLAKE_ACCOUNT \ + --environment SNOWFLAKE_WAREHOUSE +``` + +Note that the Snowflake environment variables do not need to be resolved by the shell, so they do not include the `$` prefix. + +The Snowflake environment variables only need to be set once, unless a change needs to be made. If the values have not changed, you don’t need to provide them again when you publish updates to the document. + +```bash +rsconnect deploy streamlit . \ + --server "${CONNECT_SERVER}" \ + --api-key "${CONNECT_API_KEY}" +``` diff --git a/examples/connect/snowflake/streamlit/app.py b/examples/connect/snowflake/streamlit/app.py new file mode 100644 index 00000000..2f2c9423 --- /dev/null +++ b/examples/connect/snowflake/streamlit/app.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# mypy: ignore-errors +import os + +import pandas as pd +import streamlit as st +import snowflake.connector + +from posit.connect.external.snowflake import PositAuthenticator + +ACCOUNT = os.getenv("SNOWFLAKE_ACCOUNT") +WAREHOUSE = os.getenv("SNOWFLAKE_WAREHOUSE") + +# USER is only required when running the example locally with external browser auth +USER = os.getenv("SNOWFLAKE_USER") + +# https://docs.snowflake.com/en/user-guide/sample-data-using +DATABASE = os.getenv("SNOWFLAKE_DATABASE", "snowflake_sample_data") +SCHEMA = os.getenv("SNOWFLAKE_SCHEMA", "tpch_sf1") +TABLE = os.getenv("SNOWFLAKE_TABLE", "lineitem") + +session_token = st.context.headers.get("Posit-Connect-User-Session-Token") +auth = PositAuthenticator( + local_authenticator="EXTERNALBROWSER", + user_session_token=session_token) + +con = snowflake.connector.connect( + user=USER, + account=ACCOUNT, + warehouse=WAREHOUSE, + database=DATABASE, + schema=SCHEMA, + authenticator=auth.authenticator(), + token=auth.token(), +) + +snowflake_user = con.cursor().execute("SELECT CURRENT_USER()").fetchone() +st.write(f"Hello, {snowflake_user[0]}!") + +with st.spinner("Loading data from Snowflake..."): + df = pd.read_sql_query(f"SELECT * FROM {TABLE} LIMIT 10", con) + +st.dataframe(df) diff --git a/examples/connect/snowflake/streamlit/requirements.txt b/examples/connect/snowflake/streamlit/requirements.txt new file mode 100644 index 00000000..2c64371f --- /dev/null +++ b/examples/connect/snowflake/streamlit/requirements.txt @@ -0,0 +1,3 @@ +snowflake-connector-python==3.12.1 +streamlit==1.37.0 +posit-sdk>=0.4.1 From 6cf7b1f0f0274751a7bbfee5a2eb4ea73a3e131a Mon Sep 17 00:00:00 2001 From: David Kegley Date: Fri, 6 Sep 2024 12:10:48 -0400 Subject: [PATCH 3/4] chore: respond to review feedback - use @property for getter methods - move _is_local to external.py module - remove old .qmd - add examples README.md pointing to cookbook --- examples/0001-overview.qmd | 47 ------------------- examples/README.md | 11 +++++ examples/connect/snowflake/streamlit/app.py | 4 +- src/posit/connect/external/__init__.py | 11 ----- src/posit/connect/external/databricks.py | 16 ++----- src/posit/connect/external/external.py | 10 ++++ src/posit/connect/external/snowflake.py | 8 ++-- .../posit/connect/external/test_snowflake.py | 8 ++-- 8 files changed, 35 insertions(+), 80 deletions(-) delete mode 100644 examples/0001-overview.qmd create mode 100644 examples/README.md create mode 100644 src/posit/connect/external/external.py diff --git a/examples/0001-overview.qmd b/examples/0001-overview.qmd deleted file mode 100644 index a870bb17..00000000 --- a/examples/0001-overview.qmd +++ /dev/null @@ -1,47 +0,0 @@ -# Overview - -This file provides a collection of short examples to help users get started. - -The examples cover various aspects, including authentication, data retrieval, and data manipulation. - -## Example - Print information for each user where `user_role='publisher'` - -```python -from posit.connect import Client - -with Client() as client: - for user in client.users.find(user_role='publisher'): - print(user) -``` - -## Example - Print information for a single user where `prefix='b'` - -```python -from posit.connect import Client - -with Client() as client: - user = client.users.find_one(prefix='b') - print(user) -``` - -## Example - Print the title for each content item that I own. - -```python -from posit.connect import Client - -with Client() as client: - guid = client.me.guid - for item in client.content.find(owner_guid=guid) - print(item.title) -``` - -## Example - Update the title for a content item. - -```python -from posit.connect import Client - -with Client() as client: - guid = ... # the guid for a content item - item = client.content.get(guid) - item.update(title='New Title') -``` diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..a405c460 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,11 @@ +### Posit SDK Examples + +For more in-depth SDK examples, covering a variety of use cases, check out the +[Posit Connect Cookbook](https://docs.posit.co/connect/cookbook/getting-started/). + +> [!NOTE] +> The databricks and snowflake examples will be removed from this repo is a future SDK release. +> Please see the updated examples in the +> [Impersonating the Content Viewer](https://docs.posit.co/connect/cookbook/content/impersonating-the-content-viewer/) +> section of the Connect Cookbook. + diff --git a/examples/connect/snowflake/streamlit/app.py b/examples/connect/snowflake/streamlit/app.py index 2f2c9423..b1e09d95 100644 --- a/examples/connect/snowflake/streamlit/app.py +++ b/examples/connect/snowflake/streamlit/app.py @@ -30,8 +30,8 @@ warehouse=WAREHOUSE, database=DATABASE, schema=SCHEMA, - authenticator=auth.authenticator(), - token=auth.token(), + authenticator=auth.authenticator, + token=auth.token, ) snowflake_user = con.cursor().execute("SELECT CURRENT_USER()").fetchone() diff --git a/src/posit/connect/external/__init__.py b/src/posit/connect/external/__init__.py index e43f458a..2dbfce11 100644 --- a/src/posit/connect/external/__init__.py +++ b/src/posit/connect/external/__init__.py @@ -1,14 +1,3 @@ -import os - """ NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. """ - -def _is_local() -> bool: - """Returns true if called from a piece of content running on a Connect server. - - The connect server will always set the environment variable `RSTUDIO_PRODUCT=CONNECT`. - We can use this environment variable to determine if the content is running locally - or on a Connect server. - """ - return not os.getenv("RSTUDIO_PRODUCT") == "CONNECT" diff --git a/src/posit/connect/external/databricks.py b/src/posit/connect/external/databricks.py index 6743ee1e..25cec91d 100644 --- a/src/posit/connect/external/databricks.py +++ b/src/posit/connect/external/databricks.py @@ -1,7 +1,7 @@ import abc -import os from typing import Callable, Dict, Optional +from .external import is_local from ..client import Client """ @@ -29,16 +29,6 @@ def __call__(self, *args, **kwargs) -> CredentialsProvider: raise NotImplementedError -def _is_local() -> bool: - """Returns true if called from a piece of content running on a Connect server. - - The connect server will always set the environment variable `RSTUDIO_PRODUCT=CONNECT`. - We can use this environment variable to determine if the content is running locally - or on a Connect server. - """ - return not os.getenv("RSTUDIO_PRODUCT") == "CONNECT" - - class PositCredentialsProvider: def __init__(self, client: Client, user_session_token: str): self._client = client @@ -88,14 +78,14 @@ def auth_type(self) -> str: NOTE: The databricks-sql client does not use auth_type to set the user-agent. https://github.com/databricks/databricks-sql-python/blob/v3.3.0/src/databricks/sql/client.py#L214-L219 """ - if _is_local(): + if is_local(): return self._local_strategy.auth_type() else: return "posit-oauth-integration" def __call__(self, *args, **kwargs) -> CredentialsProvider: # If the content is not running on Connect then fall back to local_strategy - if _is_local(): + if is_local(): return self._local_strategy(*args, **kwargs) # If the user-session-token wasn't provided and we're running on Connect then we raise an exception. diff --git a/src/posit/connect/external/external.py b/src/posit/connect/external/external.py new file mode 100644 index 00000000..0bd2a1d2 --- /dev/null +++ b/src/posit/connect/external/external.py @@ -0,0 +1,10 @@ +import os + +def is_local() -> bool: + """Returns true if called from a piece of content running on a Connect server. + + The connect server will always set the environment variable `RSTUDIO_PRODUCT=CONNECT`. + We can use this environment variable to determine if the content is running locally + or on a Connect server. + """ + return not os.getenv("RSTUDIO_PRODUCT") == "CONNECT" diff --git a/src/posit/connect/external/snowflake.py b/src/posit/connect/external/snowflake.py index 62c7be92..655515b3 100644 --- a/src/posit/connect/external/snowflake.py +++ b/src/posit/connect/external/snowflake.py @@ -1,6 +1,6 @@ from typing import Optional -from . import _is_local +from .external import is_local from ..client import Client """ @@ -18,13 +18,15 @@ def __init__( self._client = client self._user_session_token = user_session_token + @property def authenticator(self) -> Optional[str]: - if _is_local(): + if is_local(): return self._local_authenticator return "oauth" + @property def token(self) -> Optional[str]: - if _is_local(): + if is_local(): return None # If the user-session-token wasn't provided and we're running on Connect then we raise an exception. diff --git a/tests/posit/connect/external/test_snowflake.py b/tests/posit/connect/external/test_snowflake.py index fff1a694..7c0e2bf1 100644 --- a/tests/posit/connect/external/test_snowflake.py +++ b/tests/posit/connect/external/test_snowflake.py @@ -39,8 +39,8 @@ def test_posit_authenticator(self): user_session_token="cit", client=client, ) - assert auth.authenticator() == "oauth" - assert auth.token() == "dynamic-viewer-access-token" + assert auth.authenticator == "oauth" + assert auth.token == "dynamic-viewer-access-token" def test_posit_authenticator_fallback(self): # local_authenticator is used when the content is running locally @@ -50,5 +50,5 @@ def test_posit_authenticator_fallback(self): user_session_token="cit", client=client, ) - assert auth.authenticator() == "SNOWFLAKE" - assert auth.token() == None + assert auth.authenticator == "SNOWFLAKE" + assert auth.token == None From f3b884e8ef7aaa5b2ebab2ccc84e4deddee082a7 Mon Sep 17 00:00:00 2001 From: David Kegley Date: Fri, 6 Sep 2024 12:21:34 -0400 Subject: [PATCH 4/4] chore: run `make fmt` --- examples/connect/databricks/dash/app.py | 1 - examples/connect/databricks/fastapi/app.py | 1 - examples/connect/databricks/flask/app.py | 1 - examples/connect/databricks/shiny/app.py | 3 +-- examples/connect/databricks/streamlit/app.py | 1 - examples/connect/snowflake/streamlit/app.py | 7 +++---- integration/tests/posit/connect/__init__.py | 1 - integration/tests/posit/connect/test_content.py | 1 - src/posit/connect/external/__init__.py | 4 +--- src/posit/connect/external/databricks.py | 6 ++---- src/posit/connect/external/external.py | 1 + src/posit/connect/external/snowflake.py | 4 ++-- tests/posit/connect/external/test_databricks.py | 5 +---- tests/posit/connect/external/test_snowflake.py | 2 -- tests/posit/connect/metrics/test_shiny_usage.py | 3 +-- tests/posit/connect/metrics/test_usage.py | 3 +-- tests/posit/connect/metrics/test_visits.py | 3 +-- tests/posit/connect/test_bundles.py | 3 +-- tests/posit/connect/test_client.py | 1 - tests/posit/connect/test_config.py | 1 - tests/posit/connect/test_content.py | 3 +-- tests/posit/connect/test_env.py | 3 +-- tests/posit/connect/test_errors.py | 1 - tests/posit/connect/test_hooks.py | 3 +-- tests/posit/connect/test_oauth.py | 1 - tests/posit/connect/test_permissions.py | 3 +-- tests/posit/connect/test_tasks.py | 3 +-- tests/posit/connect/test_urls.py | 1 - tests/posit/connect/test_users.py | 3 +-- 29 files changed, 21 insertions(+), 52 deletions(-) diff --git a/examples/connect/databricks/dash/app.py b/examples/connect/databricks/dash/app.py index 4ce5d4dc..f445571e 100644 --- a/examples/connect/databricks/dash/app.py +++ b/examples/connect/databricks/dash/app.py @@ -8,7 +8,6 @@ from databricks import sql from databricks.sdk.core import ApiClient, Config, databricks_cli from databricks.sdk.service.iam import CurrentUserAPI - from posit.connect.external.databricks import PositCredentialsStrategy DATABRICKS_HOST = os.getenv("DATABRICKS_HOST") diff --git a/examples/connect/databricks/fastapi/app.py b/examples/connect/databricks/fastapi/app.py index f6e05ebf..9247d5ba 100644 --- a/examples/connect/databricks/fastapi/app.py +++ b/examples/connect/databricks/fastapi/app.py @@ -7,7 +7,6 @@ from databricks.sdk.core import Config, databricks_cli from fastapi import FastAPI, Header from fastapi.responses import JSONResponse - from posit.connect.external.databricks import PositCredentialsStrategy DATABRICKS_HOST = os.getenv("DATABRICKS_HOST") diff --git a/examples/connect/databricks/flask/app.py b/examples/connect/databricks/flask/app.py index d74e97f5..9290bf4b 100644 --- a/examples/connect/databricks/flask/app.py +++ b/examples/connect/databricks/flask/app.py @@ -5,7 +5,6 @@ from databricks import sql from databricks.sdk.core import Config, databricks_cli from flask import Flask, request - from posit.connect.external.databricks import PositCredentialsStrategy DATABRICKS_HOST = os.getenv("DATABRICKS_HOST") diff --git a/examples/connect/databricks/shiny/app.py b/examples/connect/databricks/shiny/app.py index ca07a4e4..d0453787 100644 --- a/examples/connect/databricks/shiny/app.py +++ b/examples/connect/databricks/shiny/app.py @@ -6,9 +6,8 @@ from databricks import sql from databricks.sdk.core import ApiClient, Config, databricks_cli from databricks.sdk.service.iam import CurrentUserAPI -from shiny import App, Inputs, Outputs, Session, render, ui - from posit.connect.external.databricks import PositCredentialsStrategy +from shiny import App, Inputs, Outputs, Session, render, ui DATABRICKS_HOST = os.getenv("DATABRICKS_HOST") DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}" diff --git a/examples/connect/databricks/streamlit/app.py b/examples/connect/databricks/streamlit/app.py index fb4a8ae1..4af08f84 100644 --- a/examples/connect/databricks/streamlit/app.py +++ b/examples/connect/databricks/streamlit/app.py @@ -7,7 +7,6 @@ from databricks import sql from databricks.sdk.core import ApiClient, Config, databricks_cli from databricks.sdk.service.iam import CurrentUserAPI - from posit.connect.external.databricks import PositCredentialsStrategy DATABRICKS_HOST = os.getenv("DATABRICKS_HOST") diff --git a/examples/connect/snowflake/streamlit/app.py b/examples/connect/snowflake/streamlit/app.py index b1e09d95..0cd17abb 100644 --- a/examples/connect/snowflake/streamlit/app.py +++ b/examples/connect/snowflake/streamlit/app.py @@ -3,9 +3,8 @@ import os import pandas as pd -import streamlit as st import snowflake.connector - +import streamlit as st from posit.connect.external.snowflake import PositAuthenticator ACCOUNT = os.getenv("SNOWFLAKE_ACCOUNT") @@ -21,8 +20,8 @@ session_token = st.context.headers.get("Posit-Connect-User-Session-Token") auth = PositAuthenticator( - local_authenticator="EXTERNALBROWSER", - user_session_token=session_token) + local_authenticator="EXTERNALBROWSER", user_session_token=session_token +) con = snowflake.connector.connect( user=USER, diff --git a/integration/tests/posit/connect/__init__.py b/integration/tests/posit/connect/__init__.py index 7d8dd0b3..53b720f2 100644 --- a/integration/tests/posit/connect/__init__.py +++ b/integration/tests/posit/connect/__init__.py @@ -1,5 +1,4 @@ from packaging import version - from posit import connect client = connect.Client() diff --git a/integration/tests/posit/connect/test_content.py b/integration/tests/posit/connect/test_content.py index 0e520c1d..575e5d00 100644 --- a/integration/tests/posit/connect/test_content.py +++ b/integration/tests/posit/connect/test_content.py @@ -2,7 +2,6 @@ import pytest from packaging import version - from posit import connect from . import CONNECT_VERSION diff --git a/src/posit/connect/external/__init__.py b/src/posit/connect/external/__init__.py index 2dbfce11..1f263905 100644 --- a/src/posit/connect/external/__init__.py +++ b/src/posit/connect/external/__init__.py @@ -1,3 +1 @@ -""" -NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. -""" +# NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. diff --git a/src/posit/connect/external/databricks.py b/src/posit/connect/external/databricks.py index 25cec91d..9419b848 100644 --- a/src/posit/connect/external/databricks.py +++ b/src/posit/connect/external/databricks.py @@ -1,8 +1,8 @@ import abc from typing import Callable, Dict, Optional -from .external import is_local from ..client import Client +from .external import is_local """ NOTE: These APIs are provided as a convenience and are subject to breaking changes: @@ -98,6 +98,4 @@ def __call__(self, *args, **kwargs) -> CredentialsProvider: if self._client is None: self._client = Client() - return PositCredentialsProvider( - self._client, self._user_session_token - ) + return PositCredentialsProvider(self._client, self._user_session_token) diff --git a/src/posit/connect/external/external.py b/src/posit/connect/external/external.py index 0bd2a1d2..b3492ce4 100644 --- a/src/posit/connect/external/external.py +++ b/src/posit/connect/external/external.py @@ -1,5 +1,6 @@ import os + def is_local() -> bool: """Returns true if called from a piece of content running on a Connect server. diff --git a/src/posit/connect/external/snowflake.py b/src/posit/connect/external/snowflake.py index 655515b3..f4c44b2f 100644 --- a/src/posit/connect/external/snowflake.py +++ b/src/posit/connect/external/snowflake.py @@ -1,12 +1,13 @@ from typing import Optional -from .external import is_local from ..client import Client +from .external import is_local """ NOTE: The APIs in this module are provided as a convenience and are subject to breaking changes. """ + class PositAuthenticator: def __init__( self, @@ -43,4 +44,3 @@ def token(self) -> Optional[str]: self._user_session_token )["access_token"] return access_token - diff --git a/tests/posit/connect/external/test_databricks.py b/tests/posit/connect/external/test_databricks.py index ea98069b..3c3f6153 100644 --- a/tests/posit/connect/external/test_databricks.py +++ b/tests/posit/connect/external/test_databricks.py @@ -2,7 +2,6 @@ from unittest.mock import patch import responses - from posit.connect import Client from posit.connect.external.databricks import ( CredentialsProvider, @@ -48,9 +47,7 @@ def test_posit_credentials_provider(self): register_mocks() client = Client(api_key="12345", url="https://connect.example/") - cp = PositCredentialsProvider( - client=client, user_session_token="cit" - ) + cp = PositCredentialsProvider(client=client, user_session_token="cit") assert cp() == {"Authorization": f"Bearer dynamic-viewer-access-token"} @responses.activate diff --git a/tests/posit/connect/external/test_snowflake.py b/tests/posit/connect/external/test_snowflake.py index 7c0e2bf1..2dd2437b 100644 --- a/tests/posit/connect/external/test_snowflake.py +++ b/tests/posit/connect/external/test_snowflake.py @@ -1,8 +1,6 @@ -from typing import Dict from unittest.mock import patch import responses - from posit.connect import Client from posit.connect.external.snowflake import PositAuthenticator diff --git a/tests/posit/connect/metrics/test_shiny_usage.py b/tests/posit/connect/metrics/test_shiny_usage.py index 9afcb4e1..29fad126 100644 --- a/tests/posit/connect/metrics/test_shiny_usage.py +++ b/tests/posit/connect/metrics/test_shiny_usage.py @@ -2,10 +2,9 @@ import requests import responses -from responses import matchers - from posit.connect.metrics import shiny_usage from posit.connect.resources import ResourceParameters +from responses import matchers from ..api import load_mock # type: ignore diff --git a/tests/posit/connect/metrics/test_usage.py b/tests/posit/connect/metrics/test_usage.py index 29cb3ecb..a1257a5e 100644 --- a/tests/posit/connect/metrics/test_usage.py +++ b/tests/posit/connect/metrics/test_usage.py @@ -2,10 +2,9 @@ import pytest import responses -from responses import matchers - from posit import connect from posit.connect.metrics import shiny_usage, usage, visits +from responses import matchers from ..api import load_mock # type: ignore diff --git a/tests/posit/connect/metrics/test_visits.py b/tests/posit/connect/metrics/test_visits.py index 26df29d0..cce28f7d 100644 --- a/tests/posit/connect/metrics/test_visits.py +++ b/tests/posit/connect/metrics/test_visits.py @@ -2,10 +2,9 @@ import requests import responses -from responses import matchers - from posit.connect.metrics import visits from posit.connect.resources import ResourceParameters +from responses import matchers from ..api import load_mock # type: ignore diff --git a/tests/posit/connect/test_bundles.py b/tests/posit/connect/test_bundles.py index dcd2b681..16adaa40 100644 --- a/tests/posit/connect/test_bundles.py +++ b/tests/posit/connect/test_bundles.py @@ -3,10 +3,9 @@ import pytest import responses -from responses import matchers - from posit.connect import Client from posit.connect.bundles import Bundle +from responses import matchers from .api import get_path, load_mock # type: ignore diff --git a/tests/posit/connect/test_client.py b/tests/posit/connect/test_client.py index 11942995..efe716e3 100644 --- a/tests/posit/connect/test_client.py +++ b/tests/posit/connect/test_client.py @@ -2,7 +2,6 @@ import pytest import responses - from posit.connect import Client from .api import load_mock # type: ignore diff --git a/tests/posit/connect/test_config.py b/tests/posit/connect/test_config.py index 3f1dacf4..a8f24195 100644 --- a/tests/posit/connect/test_config.py +++ b/tests/posit/connect/test_config.py @@ -1,7 +1,6 @@ from unittest.mock import patch import pytest - from posit.connect.config import Config, _get_api_key, _get_url diff --git a/tests/posit/connect/test_content.py b/tests/posit/connect/test_content.py index 609fa92f..b846ad98 100644 --- a/tests/posit/connect/test_content.py +++ b/tests/posit/connect/test_content.py @@ -2,11 +2,10 @@ import pytest import responses -from responses import matchers - from posit.connect.client import Client from posit.connect.content import ContentItem, ContentItemOwner from posit.connect.permissions import Permissions +from responses import matchers from .api import load_mock # type: ignore diff --git a/tests/posit/connect/test_env.py b/tests/posit/connect/test_env.py index c4c76dcd..418e5127 100644 --- a/tests/posit/connect/test_env.py +++ b/tests/posit/connect/test_env.py @@ -1,8 +1,7 @@ import pytest import responses -from responses import matchers - from posit.connect import Client +from responses import matchers from .api import load_mock # type: ignore diff --git a/tests/posit/connect/test_errors.py b/tests/posit/connect/test_errors.py index 429e4dd9..9443f035 100644 --- a/tests/posit/connect/test_errors.py +++ b/tests/posit/connect/test_errors.py @@ -1,5 +1,4 @@ import pytest - from posit.connect.errors import ClientError diff --git a/tests/posit/connect/test_hooks.py b/tests/posit/connect/test_hooks.py index 6945bc7a..a3c32176 100644 --- a/tests/posit/connect/test_hooks.py +++ b/tests/posit/connect/test_hooks.py @@ -3,11 +3,10 @@ import pytest import responses -from requests import HTTPError, Response - from posit.connect import Client from posit.connect.errors import ClientError from posit.connect.hooks import handle_errors +from requests import HTTPError, Response def test_success(): diff --git a/tests/posit/connect/test_oauth.py b/tests/posit/connect/test_oauth.py index 28f77b58..f4b6a4e8 100644 --- a/tests/posit/connect/test_oauth.py +++ b/tests/posit/connect/test_oauth.py @@ -1,5 +1,4 @@ import responses - from posit.connect import Client diff --git a/tests/posit/connect/test_permissions.py b/tests/posit/connect/test_permissions.py index 864169ec..2f37879a 100644 --- a/tests/posit/connect/test_permissions.py +++ b/tests/posit/connect/test_permissions.py @@ -3,11 +3,10 @@ import requests import responses -from responses import matchers - from posit.connect.permissions import Permission, Permissions from posit.connect.resources import ResourceParameters from posit.connect.urls import Url +from responses import matchers from .api import load_mock # type: ignore diff --git a/tests/posit/connect/test_tasks.py b/tests/posit/connect/test_tasks.py index 28cdd718..bd09a34d 100644 --- a/tests/posit/connect/test_tasks.py +++ b/tests/posit/connect/test_tasks.py @@ -1,10 +1,9 @@ from unittest import mock import responses -from responses import matchers - from posit import connect from posit.connect import tasks +from responses import matchers from .api import load_mock # type: ignore diff --git a/tests/posit/connect/test_urls.py b/tests/posit/connect/test_urls.py index 0ce45775..0877dcb1 100644 --- a/tests/posit/connect/test_urls.py +++ b/tests/posit/connect/test_urls.py @@ -1,5 +1,4 @@ import pytest - from posit.connect import urls diff --git a/tests/posit/connect/test_users.py b/tests/posit/connect/test_users.py index 568a5f20..884ed07e 100644 --- a/tests/posit/connect/test_users.py +++ b/tests/posit/connect/test_users.py @@ -4,10 +4,9 @@ import pytest import requests import responses -from responses import matchers - from posit.connect.client import Client from posit.connect.users import User +from responses import matchers from .api import load_mock # type: ignore