Repository: posit-dev/posit-sdk-py
Pull request #384: feat: implement posit workbench credentials strategy and make credentials strategy fallback options more explicit
Author: login: dbkegley
Created: 2025-02-14T21:10:18Z | Merged: 2025-03-18T17:28:13Z

Closes #382

These changes make the separation between the different credential strategies more explicit and tries to be better about detecting the difference between local and workbench content.

It also combines the `Content` and `Viewer` strategy into a single new class: `ConnectStrategy`. If `user-session-token` is provided then we use the viewer implementation. If not then we should fall back to the content credentials (service account) implementation.

I also modified some of the naming in the hopes of the choice of which strategy to use more obvious to end users of these helpers. 


This example shows how to construct a Databricks SDK `Config` that is compatible with:
- Databricks CLI authentication for local development
- Workbench-managed Databricks Credentials in Posit Workbench
- Viewer OAuth integration authentication in Posit Connect

```python
import os

from databricks.sdk.core import ApiClient
from databricks.sdk.core.credentials_provider import databricks_cli
from databricks.sdk.service.iam import CurrentUserAPI
from shiny import reactive
from shiny.express import render, session

from posit.workbench.external.databricks import WorkbenchStrategy
from posit.connect.external.databricks import (
    ConnectStrategy,
    databricks_config,
)


@reactive.calc
def cfg():
    session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
    return databricks_config(
        posit_default_strategy=databricks_cli,
        posit_workbench_strategy=WorkbenchStrategy(),
        posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
        host=os.getenv("DATABRICKS_HOST"),
    )


@render.text
def text():
    databricks_user_info = CurrentUserAPI(ApiClient(cfg())).me()
    return f"Hello, {databricks_user_info.display_name}!"
```

### Testing 

This needs QA in Connect and Workbench if possible. 

Workbench should test using the `WorkbenchStrategy()` with Workbench-managed credentials.

Connect should test with `ConnectStrategy()` with both a Viewer and a Service Account OAuth integration.

It would be nice to test the `azure_service_principal` and `oauth_service_principal` strategies as a replacement for the deprecated `PositLocalContentCredentialsProvider` helper that was removed.

Linked issue #382: Implement `WorkbenchManagedCredentialsStrategy` for Databricks helpers
### Problem

The external databricks helpers aren't intuitive to use with workbench yet. The `local_strategy=databricks_cli` fallback option does not work because workbench can't do an external browser redirect flow. A working alternative is to use `pat_auth` instead, but that's not obvious because workbench managed credentials use an oauth token, not a PAT.  ([`pat_auth`](https://github.com/databricks/databricks-sdk-py/blob/6f094aafffc13fbe5c236e6a7735b1b239b1ab78/databricks/sdk/credentials_provider.py#L128-L136) just sets a static bearer token on the auth header so it works the same, but how is a user supposed to know that?)


### Proposal 

We should implement a `posit.connect.external.databricks.PositWorkbenchManagedCredentialsStrategy` to make it more obvious to users how they should use managed workbench credentials in a piece of content that is going to be deployed to Connect.

#### Option 1:

```python
session_token = headers.get("Posit-Connect-User-Session-Token")

cfg = Config("workbench")
workbench_strategy = PositWorkbenchManagedCredentialsStrategy(cfg)

posit_strategy = PositCredentialsStrategy(
    local_strategy=workbench_strategy,
    user_session_token=session_token,
)
cfg.credentials_strategy = posit_strategy
```

There's still some awkwardness here though. Using `local_strategy=workbench_strategy` would not work locally because the `"workbench"` managed databricks profile wouldn't exist in the config. Also the need to modify `cfg.credentials_strategy` feels bad.

#### Option 2: prefer a more explicit configuration

```python
session_token = headers.get("Posit-Connect-User-Session-Token")

posit_strategy = PositCredentialsStrategy(
    local_strategy=databricks_cli,
    workbench_strategy=PositWorkbenchManagedCredentialsStrategy(Config("workbench"))
    user_session_token=session_token,
)

cfg = Config(
    host=DATABRICKS_HOST_URL,
    credentials_strategy=posit_strategy,
)
...
```

This is better but the use of the Connect strategy is still non-obvious. How does the caller know if it will do a client credentials flow or a viewer auth flow?

#### Option 3: an even more explicit configuration

```python
posit_strategy = PositCredentialsStrategy(
    local_strategy=databricks_cli,
    workbench_strategy=PositWorkbenchManagedCredentialsStrategy(Config("workbench"))
    connect_strategy=PositViewerCredentialsStrategy(headers.get("Posit-Connect-User-Session-Token"))
)

cfg = Config(
    host=DATABRICKS_HOST_URL,
    credentials_strategy=posit_strategy,
)
...
```
Linked issue comments: user: login: tdstein
created_at: 2025-01-31T17:47:41Z
body: I prefer option 3 since I assume most people will copy and paste from our documentation.
author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-01-31T17:57:19Z
body: I think that's my preference as well. It does require the most code changes and maybe isn't backwards compatible (not sure yet) but I like that it's the most explicit and obvious to the caller which strategy will be used in which environment. It also provides the most control to mix different options. There's nothing stopping the user from doing `pat_auth` for both local and workbench if that's what they want to do
author_association: COLLABORATOR
user: login: kmasiello
created_at: 2025-02-01T05:23:06Z
body: I like option three as well. It was confusing to me before why the Connection strategy was just implied. Thank you for putting these thoughts together!
author_association: NONE
user: login: plascaray
created_at: 2025-03-17T14:38:19Z
body: # Testing Notes

- Updated existing SDK examples to make sure they work with the latest code (git+https://github.com/posit-dev/posit-sdk-py@kegs-databricks-workbench)
- Validated that when using `Config()` running locally generates an error.
- Use `databricks_config()` to validate content can access DataBricks resources locally, from Connect and from Workbench without code changes.
author_association: COLLABORATOR

Diff:
diff --git a/examples/README.md b/examples/README.md
index a405c460..22b8642f 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -5,7 +5,6 @@ For more in-depth SDK examples, covering a variety of use cases, check out the
 
 > [!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/)
+> Please see the updated examples in the [OAuth Integrations](https://docs.posit.co/connect/cookbook/oauth-integrations/)
 > section of the Connect Cookbook.
 
diff --git a/examples/connect/.gitignore b/examples/connect/.gitignore
deleted file mode 100644
index 5ac2a70e..00000000
--- a/examples/connect/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-**/rsconnect-python/
diff --git a/examples/connect/databricks/dash/README.md b/examples/connect/databricks/dash/README.md
deleted file mode 100644
index afc19968..00000000
--- a/examples/connect/databricks/dash/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Dash Example
-
-## Start the app locally
-
-```bash
-export DATABRICKS_HOST="<databricks-sql-warehouse-server-hostname>"
-export DATABRICKS_PATH="<databricks-sql-warehouse-http-path>"
-python 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`, `DATABRICKS_HOST`, `DATABRICKS_PATH` have valid values. Then, on a terminal session, enter the following command:
-
-```bash
-rsconnect deploy dash . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}" \
-  --environment DATABRICKS_HOST \
-  --environment DATABRICKS_PATH
-```
-
-Note that the Databricks environment variables do not need to be resolved by the shell, so they do not include the `$` prefix.
-
-The Databricks 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.
-
-```
-rsconnect deploy dash . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}"
-```
diff --git a/examples/connect/databricks/dash/app.py b/examples/connect/databricks/dash/app.py
deleted file mode 100644
index 4fc1a5dd..00000000
--- a/examples/connect/databricks/dash/app.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-# mypy: ignore-errors
-import os
-
-import flask
-import pandas as pd
-from dash import Dash, Input, Output, dash_table, html
-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")
-DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-SQL_HTTP_PATH = os.getenv("DATABRICKS_PATH")
-
-df = None
-app = Dash(__name__)
-
-app.layout = html.Div(
-    children=[
-        html.Div(id="greeting", children="Loading..."),
-        html.Div(id="table-container"),
-        html.Div(id="dummy"),  # dummy element to trigger callback on page load
-    ],
-)
-
-
-@app.callback(
-    [Output("table-container", "children"), Output("greeting", "children")],
-    Input("dummy", "children"),
-)
-def update_page(_):
-    """
-    Dash example application that shows user information and
-    the first few rows from a table hosted in Databricks.
-    """
-    session_token = flask.request.headers.get("Posit-Connect-User-Session-Token")
-    posit_strategy = PositCredentialsStrategy(
-        local_strategy=databricks_cli,
-        user_session_token=session_token,
-    )
-    cfg = Config(
-        host=DATABRICKS_HOST_URL,
-        # uses Posit's custom credential_strategy if running on Connect,
-        # otherwise falls back to the strategy defined by local_strategy
-        credentials_strategy=posit_strategy,
-    )
-
-    def get_greeting():
-        databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-        return f"Hello, {databricks_user_info.display_name}!"
-
-    def get_table():
-        global df
-
-        if df is None:
-            query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-            with sql.connect(
-                server_hostname=DATABRICKS_HOST,
-                http_path=SQL_HTTP_PATH,
-                # https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-                credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-            ) as connection:
-                with connection.cursor() as cursor:
-                    cursor.execute(query)
-                    rows = cursor.fetchall()
-                    df = pd.DataFrame(rows, columns=[col[0] for col in cursor.description])
-
-        table = dash_table.DataTable(
-            id="table",
-            columns=[{"name": i, "id": i} for i in df.columns],
-            data=df.to_dict("records"),
-            style_table={"overflowX": "scroll"},
-        )
-        return table
-
-    return get_table(), get_greeting()
-
-
-if __name__ == "__main__":
-    app.run(debug=True)
diff --git a/examples/connect/databricks/dash/requirements.txt b/examples/connect/databricks/dash/requirements.txt
deleted file mode 100644
index 090523a4..00000000
--- a/examples/connect/databricks/dash/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-databricks-sql-connector==3.3.0
-databricks-sdk==0.29.0
-dash==2.15.0
-posit-sdk>=0.4.0
diff --git a/examples/connect/databricks/fastapi/README.md b/examples/connect/databricks/fastapi/README.md
deleted file mode 100644
index 8b5d9df1..00000000
--- a/examples/connect/databricks/fastapi/README.md
+++ /dev/null
@@ -1,46 +0,0 @@
-# FastAPI Example
-
-## Start the app locally
-
-```bash
-# install uvicorn
-pip install "uvicorn[standard]"
-
-# start the app
-export DATABRICKS_HOST="<databricks-sql-warehouse-server-hostname>"
-export DATABRICKS_PATH="<databricks-sql-warehouse-http-path>"
-uvicorn app:app --reload
-
-# send a request to the rest api
-curl -X 'GET' 'http://127.0.0.1:8000/fares'
-```
-
-## 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`, `DATABRICKS_HOST`, `DATABRICKS_PATH` have valid values. Then, on a terminal session, enter the following command:
-
-```bash
-rsconnect deploy fastapi . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}" \
-  --environment DATABRICKS_HOST \
-  --environment DATABRICKS_PATH
-```
-
-Note that the Databricks environment variables do not need to be resolved by the shell, so they do not include the `$` prefix.
-
-The Databricks 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.
-
-```
-rsconnect deploy fastapi . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}"
-```
diff --git a/examples/connect/databricks/fastapi/app.py b/examples/connect/databricks/fastapi/app.py
deleted file mode 100644
index a3e49e6a..00000000
--- a/examples/connect/databricks/fastapi/app.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-# mypy: ignore-errors
-from __future__ import annotations
-
-import os
-
-from databricks import sql
-from databricks.sdk.core import Config, databricks_cli
-from fastapi import FastAPI, Header
-from typing_extensions import TYPE_CHECKING, Annotated
-
-from posit.connect.external.databricks import PositCredentialsStrategy
-
-if TYPE_CHECKING:
-    from fastapi.responses import JSONResponse
-
-DATABRICKS_HOST = os.getenv("DATABRICKS_HOST")
-DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-SQL_HTTP_PATH = os.getenv("DATABRICKS_PATH")
-
-rows = None
-app = FastAPI()
-
-
-@app.get("/fares")
-async def get_fares(
-    posit_connect_user_session_token: Annotated[str | None, Header()] = None,
-) -> JSONResponse:
-    """
-    FastAPI example API that returns the first few rows from
-    a table hosted in Databricks.
-    """
-    global rows
-
-    posit_strategy = PositCredentialsStrategy(
-        local_strategy=databricks_cli,
-        user_session_token=posit_connect_user_session_token,
-    )
-    cfg = Config(
-        host=DATABRICKS_HOST_URL,
-        # uses Posit's custom credential_strategy if running on Connect,
-        # otherwise falls back to the strategy defined by local_strategy
-        credentials_strategy=posit_strategy,
-    )
-
-    if rows is None:
-        query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-        with sql.connect(
-            server_hostname=DATABRICKS_HOST,
-            http_path=SQL_HTTP_PATH,
-            # https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-            credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-        ) as connection:
-            with connection.cursor() as cursor:
-                cursor.execute(query)
-                rows = cursor.fetchall()
-
-    return [row.asDict() for row in rows]
diff --git a/examples/connect/databricks/fastapi/requirements.txt b/examples/connect/databricks/fastapi/requirements.txt
deleted file mode 100644
index 262e5868..00000000
--- a/examples/connect/databricks/fastapi/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-databricks-sql-connector==3.3.0
-databricks-sdk==0.29.0
-fastapi==0.110.0
-posit-sdk>=0.4.0
diff --git a/examples/connect/databricks/flask/README.md b/examples/connect/databricks/flask/README.md
deleted file mode 100644
index 8bac29a7..00000000
--- a/examples/connect/databricks/flask/README.md
+++ /dev/null
@@ -1,43 +0,0 @@
-# Flask Example
-
-## Start the app locally
-
-```bash
-# start the app
-export DATABRICKS_HOST="<databricks-sql-warehouse-server-hostname>"
-export DATABRICKS_PATH="<databricks-sql-warehouse-http-path>"
-flask --app app run
-
-# send a request to the rest api
-curl -X 'GET' 'http://127.0.0.1:5000/fares'
-```
-
-## 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`, `DATABRICKS_HOST`, `DATABRICKS_PATH` have valid values. Then, on a terminal session, enter the following command:
-
-```bash
-rsconnect deploy api . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}" \
-  --environment DATABRICKS_HOST \
-  --environment DATABRICKS_PATH
-```
-
-Note that the Databricks environment variables do not need to be resolved by the shell, so they do not include the `$` prefix.
-
-The Databricks 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.
-
-```
-rsconnect deploy api . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}"
-```
diff --git a/examples/connect/databricks/flask/app.py b/examples/connect/databricks/flask/app.py
deleted file mode 100644
index dd4054e6..00000000
--- a/examples/connect/databricks/flask/app.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-# mypy: ignore-errors
-import os
-
-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")
-DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-SQL_HTTP_PATH = os.getenv("DATABRICKS_PATH")
-
-rows = None
-app = Flask(__name__)
-
-
-@app.route("/")
-def usage():
-    return "<p>Try: <pre>GET /fares<pre></p>"
-
-
-@app.route("/fares")
-def get_fares():
-    """
-    Flask example API that returns the first few rows from
-    a table hosted in Databricks.
-    """
-    global rows
-
-    session_token = request.headers.get("Posit-Connect-User-Session-Token")
-    posit_strategy = PositCredentialsStrategy(
-        local_strategy=databricks_cli,
-        user_session_token=session_token,
-    )
-    cfg = Config(
-        host=DATABRICKS_HOST_URL,
-        # uses Posit's custom credential_strategy if running on Connect,
-        # otherwise falls back to the strategy defined by local_strategy
-        credentials_strategy=posit_strategy,
-    )
-
-    if rows is None:
-        query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-        with sql.connect(
-            server_hostname=DATABRICKS_HOST,
-            http_path=SQL_HTTP_PATH,
-            # https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-            credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-        ) as connection:
-            with connection.cursor() as cursor:
-                cursor.execute(query)
-                rows = cursor.fetchall()
-
-    return [row.asDict() for row in rows]
-
-
-if __name__ == "__main__":
-    app.run(debug=True)
diff --git a/examples/connect/databricks/flask/requirements.txt b/examples/connect/databricks/flask/requirements.txt
deleted file mode 100644
index c0b12941..00000000
--- a/examples/connect/databricks/flask/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-databricks-sql-connector==3.3.0
-databricks-sdk==0.29.0
-flask==3.0.2
-posit-sdk>=0.4.0
diff --git a/examples/connect/databricks/shiny/README.md b/examples/connect/databricks/shiny/README.md
deleted file mode 100644
index e03d1217..00000000
--- a/examples/connect/databricks/shiny/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Shiny for Python Example
-
-## Start the app locally
-
-```bash
-export DATABRICKS_HOST="<databricks-sql-warehouse-server-hostname>"
-export DATABRICKS_PATH="<databricks-sql-warehouse-http-path>"
-shiny 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`, `DATABRICKS_HOST`, `DATABRICKS_PATH` have valid values. Then, on a terminal session, enter the following command:
-
-```bash
-rsconnect deploy shiny . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}" \
-  --environment DATABRICKS_HOST \
-  --environment DATABRICKS_PATH
-```
-
-Note that the Databricks environment variables do not need to be resolved by the shell, so they do not include the `$` prefix.
-
-The Databricks 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.
-
-```
-rsconnect deploy shiny . \
-  --server "${CONNECT_SERVER}" \
-  --api-key "${CONNECT_API_KEY}"
-```
diff --git a/examples/connect/databricks/shiny/app.py b/examples/connect/databricks/shiny/app.py
deleted file mode 100644
index 32891b35..00000000
--- a/examples/connect/databricks/shiny/app.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-# mypy: ignore-errors
-import os
-
-import pandas as pd
-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
-
-DATABRICKS_HOST = os.getenv("DATABRICKS_HOST")
-DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-SQL_HTTP_PATH = os.getenv("DATABRICKS_PATH")
-
-app_ui = ui.page_fluid(ui.output_text("text"), ui.output_data_frame("result"))
-
-
-def server(i: Inputs, o: Outputs, session: Session):
-    """
-    Shiny for Python example application that shows user information and
-    the first few rows from a table hosted in Databricks.
-    """
-    session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
-    posit_strategy = PositCredentialsStrategy(
-        local_strategy=databricks_cli,
-        user_session_token=session_token,
-    )
-    cfg = Config(
-        host=DATABRICKS_HOST_URL,
-        # uses Posit's custom credential_strategy if running on Connect,
-        # otherwise falls back to the strategy defined by local_strategy
-        credentials_strategy=posit_strategy,
-    )
-
-    @render.data_frame
-    def result():
-        query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-        with sql.connect(
-            server_hostname=DATABRICKS_HOST,
-            http_path=SQL_HTTP_PATH,
-            # https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-            credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-        ) as connection:
-            with connection.cursor() as cursor:
-                cursor.execute(query)
-                rows = cursor.fetchall()
-                df = pd.DataFrame(rows, columns=[col[0] for col in cursor.description])
-                return df
-
-    @render.text
-    def text():
-        databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-        return f"Hello, {databricks_user_info.display_name}!"
-
-
-app = App(app_ui, server)
diff --git a/examples/connect/databricks/shiny/requirements.txt b/examples/connect/databricks/shiny/requirements.txt
deleted file mode 100644
index eedba131..00000000
--- a/examples/connect/databricks/shiny/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-databricks-sql-connector==3.3.0
-databricks-sdk==0.29.0
-shiny==0.7.1
-posit-sdk>=0.4.0
diff --git a/examples/connect/databricks/streamlit/README.md b/examples/connect/databricks/streamlit/README.md
deleted file mode 100644
index 6d7a5121..00000000
--- a/examples/connect/databricks/streamlit/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# Streamlit Example
-
-## Start the app locally
-
-```bash
-export DATABRICKS_HOST="<databricks-sql-warehouse-server-hostname>"
-export DATABRICKS_PATH="<databricks-sql-warehouse-http-path>"
-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`, `DATABRICKS_HOST`, `DATABRICKS_PATH` 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 DATABRICKS_HOST \
-  --environment DATABRICKS_PATH
-```
-
-Note that the Databricks environment variables do not need to be resolved by the shell, so they do not include the `$` prefix.
-
-The Databricks 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/databricks/streamlit/app.py b/examples/connect/databricks/streamlit/app.py
deleted file mode 100644
index 0738f606..00000000
--- a/examples/connect/databricks/streamlit/app.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-# mypy: ignore-errors
-import os
-
-import pandas as pd
-import streamlit as st
-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")
-DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-SQL_HTTP_PATH = os.getenv("DATABRICKS_PATH")
-
-session_token = st.context.headers.get("Posit-Connect-User-Session-Token")
-posit_strategy = PositCredentialsStrategy(
-    local_strategy=databricks_cli,
-    user_session_token=session_token,
-)
-cfg = Config(
-    host=DATABRICKS_HOST_URL,
-    # uses Posit's custom credential_strategy if running on Connect,
-    # otherwise falls back to the strategy defined by local_strategy
-    credentials_strategy=posit_strategy,
-)
-
-databricks_user = CurrentUserAPI(ApiClient(cfg)).me()
-st.write(f"Hello, {databricks_user.display_name}!")
-
-with sql.connect(
-    server_hostname=DATABRICKS_HOST,
-    http_path=SQL_HTTP_PATH,
-    # https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-    credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-) as connection:
-    with connection.cursor() as cursor:
-        cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
-        result = cursor.fetchall()
-        st.table(pd.DataFrame(result))
diff --git a/examples/connect/databricks/streamlit/requirements.txt b/examples/connect/databricks/streamlit/requirements.txt
deleted file mode 100644
index 8395b2c1..00000000
--- a/examples/connect/databricks/streamlit/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-databricks-sql-connector==3.3.0
-databricks-sdk==0.29.0
-streamlit==1.37.0
-posit-sdk>=0.4.0
diff --git a/examples/connect/snowflake/streamlit/README.md b/examples/connect/snowflake/streamlit/README.md
deleted file mode 100644
index 96c0a70b..00000000
--- a/examples/connect/snowflake/streamlit/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# Streamlit Example
-
-## Start the app locally
-
-```bash
-SNOWFLAKE_ACCOUNT = "<snowflake-account-identifier>"
-SNOWFLAKE_WAREHOUSE = "<snowflake-warehouse-name>"
-
-# USER is only required when running the example locally with external browser auth
-SNOWFLAKE_USER="<snowflake-username>" 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
deleted file mode 100644
index e6912ad9..00000000
--- a/examples/connect/snowflake/streamlit/app.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-# mypy: ignore-errors
-import os
-
-import pandas as pd
-import snowflake.connector
-import streamlit as st
-
-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
deleted file mode 100644
index 4a064186..00000000
--- a/examples/connect/snowflake/streamlit/requirements.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-snowflake-connector-python==3.12.3
-streamlit==1.37.0
-posit-sdk>=0.4.1
diff --git a/pyproject.toml b/pyproject.toml
index 79985b04..deb0a378 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -131,7 +131,7 @@ exclude = ".*"
 [dependency-groups]
 build = ["build"]
 coverage = ["coverage"]
-examples = ["rsconnect-python", "pandas", "databricks", "shiny"]
+external = ["databricks-sdk"]
 git = ["pre-commit"]
 lint = ["ruff", "pyright"]
 test = ["rsconnect-python", "responses", "pytest", "pyjson5"]
@@ -139,10 +139,7 @@ test = ["rsconnect-python", "responses", "pytest", "pyjson5"]
 dev = [
     { include-group = "build" },
     { include-group = "coverage" },
-    # # Must comment out `examples` group until only python>3.8 is supported
-    # # `numpy==1.24.4` requires `distutils`, which is not available in python3.12
-    # # `numpy>1.24.4` does not require `distutils`
-    # { include-group = "examples" },
+    { include-group = "external" },
     { include-group = "git" },
     { include-group = "lint" },
     { include-group = "test" },
diff --git a/src/posit/__init__.py b/src/posit/__init__.py
index 1791d60e..9a6a49ad 100644
--- a/src/posit/__init__.py
+++ b/src/posit/__init__.py
@@ -1,3 +1,4 @@
 """The Posit SDK."""
 
 from . import connect as connect
+from . import workbench as workbench
diff --git a/src/posit/connect/_utils.py b/src/posit/connect/_utils.py
index d9c1b083..1cfa90d8 100644
--- a/src/posit/connect/_utils.py
+++ b/src/posit/connect/_utils.py
@@ -32,11 +32,27 @@ def update_dict_values(obj: dict[str, Any], /, **kwargs: Any) -> None:
     dict.update(obj, **kwargs)
 
 
-def is_local() -> bool:
-    """Returns true if called from a piece of content running on a Connect server.
+def is_workbench() -> bool:
+    """Attempts to return true if called from a piece of content running on Posit Workbench.
+
+    There is not yet a definitive way to determine if the content is running on Workbench. This method is best-effort.
+    """
+    return (
+        "RSW_LAUNCHER" in os.environ
+        or "RSTUDIO_MULTI_SESSION" in os.environ
+        or "RS_SERVER_ADDRESS" in os.environ
+        or "RS_SERVER_URL" in os.environ
+    )
+
 
-    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.
+def is_connect() -> bool:
+    """Returns true if called from a piece of content running on Posit Connect.
+
+    The Connect content will always set the environment variable `RSTUDIO_PRODUCT=CONNECT`.
     """
-    return os.getenv("RSTUDIO_PRODUCT") != "CONNECT"
+    return os.getenv("RSTUDIO_PRODUCT") == "CONNECT"
+
+
+def is_local() -> bool:
+    """Returns true if called from a piece of content running locally."""
+    return not is_connect() and not is_workbench()
diff --git a/src/posit/connect/client.py b/src/posit/connect/client.py
index 1804caec..8665c9a0 100644
--- a/src/posit/connect/client.py
+++ b/src/posit/connect/client.py
@@ -208,6 +208,7 @@ def with_user_session_token(self, token: str) -> Client:
         --------
         ```python
         from posit.connect import Client
+
         client = Client().with_user_session_token("my-user-session-token")
         ```
 
@@ -218,13 +219,14 @@ def with_user_session_token(self, token: str) -> Client:
 
         client = Client()
 
+
         @reactive.calc
         def visitor_client():
             ## read the user session token and generate a new client
-            user_session_token = session.http_conn.headers.get(
-                "Posit-Connect-User-Session-Token"
-            )
+            user_session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
             return client.with_user_session_token(user_session_token)
+
+
         @render.text
         def user_profile():
             # fetch the viewer's profile information
diff --git a/src/posit/connect/external/databricks.py b/src/posit/connect/external/databricks.py
index 1f3b0895..b49736e7 100644
--- a/src/posit/connect/external/databricks.py
+++ b/src/posit/connect/external/databricks.py
@@ -8,38 +8,31 @@
 https://github.com/databricks/databricks-sdk-py#interface-stability
 """
 
-import abc
+from __future__ import annotations
 
-import requests
-from typing_extensions import Callable, Dict, Optional
+import logging
 
-from .._utils import is_local
-from ..client import Client
-from ..oauth import Credentials
+from typing_extensions import TYPE_CHECKING, Dict, Optional
 
-POSIT_OAUTH_INTEGRATION_AUTH_TYPE = "posit-oauth-integration"
-POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE = "posit-local-client-credentials"
+from .._utils import is_connect, is_workbench
+from ..client import Client
 
-# The Databricks SDK CredentialsProvider == Databricks SQL HeaderFactory
-CredentialsProvider = Callable[[], Dict[str, str]]
+try:
+    from databricks.sdk.core import Config, DefaultCredentials
+    from databricks.sdk.credentials_provider import (
+        CredentialsProvider,
+        CredentialsStrategy,
+    )
+except ImportError as e:
+    raise ImportError("The 'databricks-sdk' package is required to use this module.") from e
 
+if TYPE_CHECKING:
+    from ..oauth import Credentials
 
-class CredentialsStrategy(abc.ABC):
-    """Maintain compatibility with the Databricks SQL/SDK client libraries.
 
-    See Also
-    --------
-    * https://github.com/databricks/databricks-sql-python/blob/v3.3.0/src/databricks/sql/auth/authenticators.py#L19-L33
-    * https://github.com/databricks/databricks-sdk-py/blob/v0.29.0/databricks/sdk/credentials_provider.py#L44-L54
-    """
-
-    @abc.abstractmethod
-    def auth_type(self) -> str:
-        raise NotImplementedError
+POSIT_OAUTH_INTEGRATION_AUTH_TYPE = "posit-oauth-integration"
 
-    @abc.abstractmethod
-    def __call__(self, *args, **kwargs) -> CredentialsProvider:
-        raise NotImplementedError
+logger = logging.getLogger(__name__)
 
 
 def _new_bearer_authorization_header(credentials: Credentials) -> Dict[str, str]:
@@ -59,68 +52,7 @@ def _new_bearer_authorization_header(credentials: Credentials) -> Dict[str, str]
     return {"Authorization": f"Bearer {access_token}"}
 
 
-def _get_auth_type(local_auth_type: str) -> str:
-    """Returns the auth type currently in use.
-
-    The databricks-sdk client uses the configured auth_type to create
-    a user-agent string which is used for attribution. We should only
-    overwrite the auth_type if we are using the PositCredentialsStrategy (non-local),
-    otherwise, we should return the auth_type of the configured local_strategy instead
-    to avoid breaking someone elses attribution.
-
-    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
-
-    See Also
-    --------
-    * https://github.com/databricks/databricks-sdk-py/blob/v0.29.0/databricks/sdk/config.py#L261-L269
-
-    Returns
-    -------
-        str
-    """
-    if is_local():
-        return local_auth_type
-
-    return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
-
-
-class PositLocalContentCredentialsProvider:
-    """`CredentialsProvider` implementation which provides a fallback for local development using a client credentials flow.
-
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
-
-    Until the CLI issue is resolved, this CredentialsProvider implements the approach described in the Databricks documentation
-    for manually generating a workspace-level access token using OAuth M2M authentication. Once it has acquired an access token,
-    it returns it as a Bearer authorization header like other `CredentialsProvider` implementations.
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
-    """
-
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def __call__(self) -> Dict[str, str]:
-        response = requests.post(
-            self._token_endpoint_url,
-            auth=(self._client_id, self._client_secret),
-            data={
-                "grant_type": "client_credentials",
-                "scope": "all-apis",
-            },
-        )
-        response.raise_for_status()
-
-        credentials = Credentials(**response.json())
-        return _new_bearer_authorization_header(credentials)
-
-
-class PositContentCredentialsProvider:
+class _PositConnectContentCredentialsProvider:
     """`CredentialsProvider` implementation which initiates a credential exchange using a content-session-token.
 
     The content-session-token is provided by Connect through the environment variable `CONNECT_CONTENT_SESSION_TOKEN`.
@@ -128,7 +60,6 @@ class PositContentCredentialsProvider:
     See Also
     --------
     * https://github.com/posit-dev/posit-sdk-py/blob/main/src/posit/connect/oauth/oauth.py
-
     """
 
     def __init__(self, client: Client):
@@ -139,7 +70,7 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositCredentialsProvider:
+class _PositConnectViewerCredentialsProvider:
     """`CredentialsProvider` implementation which initiates a credential exchange using a user-session-token.
 
     The user-session-token is provided by Connect through the HTTP session header
@@ -148,7 +79,6 @@ class PositCredentialsProvider:
     See Also
     --------
     * https://github.com/posit-dev/posit-sdk-py/blob/main/src/posit/connect/oauth/oauth.py
-
     """
 
     def __init__(self, client: Client, user_session_token: str):
@@ -160,277 +90,340 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    Examples
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import ConnectStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
 
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
-    """
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
+    ```python
+    import os
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+    """
+
+    def __init__(
+        self,
+        client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
+    ):
+        self._cp: Optional[CredentialsProvider] = None
+        self._client = client
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
+        if self._client is None:
+            self._client = Client()
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
 
-    Examples
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
+
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
+
+    See Also
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
-
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    from posit.connect.external.databricks import sql_credentials
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
     with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
     ) as connection:
         with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
     ```
     """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
 
-    def __init__(
-        self,
-        local_strategy: CredentialsStrategy,
-        client: Optional[Client] = None,
-    ):
-        self._local_strategy = local_strategy
-        self._client = client
+    Returns
+    -------
+        databricks.sdk.core.Config
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
 
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+    ```python
+    import os
 
-    def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+    from databricks.sdk.core import ApiClient, databricks_cli
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    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)
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
+    from posit.workbench.external.databricks import WorkbenchStrategy
 
-        if self._client is None:
-            self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return databricks_config(
+            posit_default_strategy=databricks_cli,
+            posit_workbench_strategy=WorkbenchStrategy(),
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This example shows how to construct a databricks Config that is compatible with:
+    - OAuth Service Principal (oauth-m2m) in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
 
-    Examples
-    --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `DATABRICKS_CLIENT_ID` and `DATABRICKS_CLIENT_SECRET` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
     ```python
     import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, oauth_service_principal
     from databricks.sdk.service.iam import CurrentUserAPI
-    from posit.connect.external.databricks import PositCredentialsStrategy
-    from shiny import App, Inputs, Outputs, Session, render, ui
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-
-    app_ui = ui.page_fluid(ui.output_text("text"), ui.output_data_frame("result"))
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
 
 
-    def server(i: Inputs, o: Outputs, session: Session):
-        # HTTP session headers are available in this context.
+    @reactive.calc
+    def cfg():
         session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
-        posit_strategy = PositCredentialsStrategy(
-            local_strategy=databricks_cli, user_session_token=session_token
+        return databricks_config(
+            posit_workbench_strategy=oauth_service_principal,
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            client_id=os.getenv("DATABRICKS_CLIENT_ID"),
+            client_secret=os.getenv("DATABRICKS_CLIENT_SECRET"),
         )
-        cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
-
-        @render.data_frame
-        def result():
-            query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-            with sql.connect(
-                server_hostname=DATABRICKS_HOST,
-                http_path=SQL_HTTP_PATH,
-                credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-            ) as connection:
-                with connection.cursor() as cursor:
-                    cursor.execute(query)
-                    rows = cursor.fetchall()
-                    df = pd.DataFrame(rows, columns=[col[0] for col in cursor.description])
-                    return df
-
-        @render.text
-        def text():
-            databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-            return f"Hello, {databricks_user_info.display_name}!"
-
-
-    app = App(app_ui, server)
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-    """
 
-    def __init__(
-        self,
-        local_strategy: CredentialsStrategy,
-        client: Optional[Client] = None,
-        user_session_token: Optional[str] = None,
-    ):
-        self._local_strategy = local_strategy
-        self._client = client
-        self._user_session_token = user_session_token
+    This example shows how to construct a databricks Config that is compatible with:
+    - Azure Service Principal credentials in Posit Workbench
+    - Service Account OAuth integration authentication in Posit Connect
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `ARM_CLIENT_ID`, `ARM_CLIENT_SECRET`, and `ARM_TENANT_ID` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
+    ```python
+    import os
 
-        See Also
-        --------
-        * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+    from databricks.sdk.core import ApiClient, azure_service_principal
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
 
-    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)
 
-        # 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.")
+    @reactive.calc
+    def cfg():
+        return databricks_config(
+            posit_workbench_strategy=azure_service_principal,
+            posit_connect_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+            azure_client_id=os.getenv("ARM_CLIENT_ID"),
+            azure_client_secret=os.getenv("ARM_CLIENT_SECRET"),
+            azure_tenant_id=os.getenv("ARM_TENANT_ID"),
+        )
 
-        if self._client is None:
-            self._client = Client()
 
-        return PositCredentialsProvider(self._client, self._user_session_token)
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+    """
+    credentials_strategy = None
+    if "credentials_strategy" in kwargs:
+        del kwargs["credentials_strategy"]
+
+    if is_connect():
+        logger.info("Using Posit Connect credentials strategy")
+        if posit_connect_strategy is not None:
+            credentials_strategy = posit_connect_strategy
+        else:
+            credentials_strategy = ConnectStrategy()
+    elif is_workbench():
+        logger.info("Using Posit Workbench credentials strategy")
+        if posit_workbench_strategy is not None:
+            credentials_strategy = posit_workbench_strategy
+        else:
+            credentials_strategy = DefaultCredentials()
+    else:
+        logger.info(
+            "Using default credentials strategy because neither Posit Connect nor Posit Workbench were detected"
+        )
+        if posit_default_strategy is not None:
+            credentials_strategy = posit_default_strategy
+        else:
+            credentials_strategy = DefaultCredentials()
+
+    return Config(credentials_strategy=credentials_strategy, **kwargs)  # pyright: ignore[reportArgumentType]
diff --git a/src/posit/workbench/__init__.py b/src/posit/workbench/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/src/posit/workbench/external/__init__.py b/src/posit/workbench/external/__init__.py
new file mode 100644
index 00000000..efc68376
--- /dev/null
+++ b/src/posit/workbench/external/__init__.py
@@ -0,0 +1,6 @@
+"""External integrations.
+
+Notes
+-----
+The APIs in this module are provided as a convenience and are subject to breaking changes.
+"""
diff --git a/src/posit/workbench/external/databricks.py b/src/posit/workbench/external/databricks.py
new file mode 100644
index 00000000..5394ca80
--- /dev/null
+++ b/src/posit/workbench/external/databricks.py
@@ -0,0 +1,86 @@
+"""Databricks SDK integration.
+
+Databricks SDK credentials implementations which support interacting with Posit Workbench-managed Databricks credentials.
+
+Notes
+-----
+These APIs are provided as a convenience and are subject to breaking changes:
+https://github.com/databricks/databricks-sdk-py#interface-stability
+"""
+
+from __future__ import annotations
+
+from typing_extensions import Optional
+
+try:
+    from databricks.sdk.core import Config
+    from databricks.sdk.credentials_provider import (
+        CredentialsProvider,
+        CredentialsStrategy,
+    )
+except ImportError as e:
+    raise ImportError("The 'databricks-sdk' package is required to use this module.") from e
+
+
+POSIT_WORKBENCH_AUTH_TYPE = "posit-workbench"
+
+
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
+
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
+
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `posit.connect.external.databricks.databricks_config()` helper method.
+
+    See Also
+    --------
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
+
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.workbench.external.databricks import WorkbenchStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+    """
+
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
+
+    def auth_type(self) -> str:
+        return POSIT_WORKBENCH_AUTH_TYPE
+
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
diff --git a/tests/posit/connect/external/test_databricks.py b/tests/posit/connect/external/test_databricks.py
index 134911b1..22f296bc 100644
--- a/tests/posit/connect/external/test_databricks.py
+++ b/tests/posit/connect/external/test_databricks.py
@@ -1,4 +1,5 @@
-import base64
+from __future__ import annotations
+
 from unittest.mock import patch
 
 import pytest
@@ -8,27 +9,41 @@
 from posit.connect import Client
 from posit.connect.external.databricks import (
     POSIT_OAUTH_INTEGRATION_AUTH_TYPE,
-    CredentialsProvider,
-    CredentialsStrategy,
-    PositContentCredentialsProvider,
-    PositContentCredentialsStrategy,
-    PositCredentialsProvider,
-    PositCredentialsStrategy,
-    PositLocalContentCredentialsProvider,
-    PositLocalContentCredentialsStrategy,
-    _get_auth_type,
+    ConnectStrategy,
     _new_bearer_authorization_header,
+    _PositConnectContentCredentialsProvider,
+    _PositConnectViewerCredentialsProvider,
+    databricks_config,
 )
 from posit.connect.oauth import Credentials
 
+try:
+    from databricks.sdk.core import Config, DefaultCredentials
+    from databricks.sdk.credentials_provider import (
+        CredentialsProvider,
+        CredentialsStrategy,
+    )
+
+    # construct a DefaultCredentials CredentialsStrategy
+    # weirdly, you have to call `__call__()` at least once in order to initialize `auth_type()`
+    # This is the expected credentials strategy when none is provided to our databricks_config() helper
+    expected_credentials = DefaultCredentials()  # pyright: ignore[reportPossiblyUnboundVariable]
+    expected_credentials(Config(auth_type="pat", token="asdf", host="https://databricks.com/"))  # pyright: ignore[reportPossiblyUnboundVariable]
+
+except ImportError:
+    pytestmark = pytest.mark.skipif(True, reason="requires the Databricks SDK")
+
+
+class mock_strategy(CredentialsStrategy):  # pyright: ignore[reportPossiblyUnboundVariable]
+    def __init__(self, name: str):
+        self.name = name
 
-class mock_strategy(CredentialsStrategy):
     def auth_type(self) -> str:
-        return "local"
+        return self.name
 
-    def __call__(self) -> CredentialsProvider:
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:
         def inner() -> Dict[str, str]:
-            return {"Authorization": "Bearer static-pat-token"}
+            return {"Authorization": f"Bearer {self.name}"}
 
         return inner
 
@@ -84,42 +99,6 @@ def test_new_bearer_authorization_header(self):
         result = _new_bearer_authorization_header(credential)
         assert result == {"Authorization": "Bearer access_token"}
 
-    def test_get_auth_type_local(self):
-        assert _get_auth_type("local-auth") == "local-auth"
-
-    @patch.dict("os.environ", {"RSTUDIO_PRODUCT": "CONNECT"})
-    def test_get_auth_type_connect(self):
-        assert _get_auth_type("local-auth") == POSIT_OAUTH_INTEGRATION_AUTH_TYPE
-
-    @responses.activate
-    def test_local_content_credentials_provider(self):
-        token_url = "https://my-token/url"
-        client_id = "client_id"
-        client_secret = "client_secret_123"
-        basic_auth = f"{client_id}:{client_secret}"
-        b64_basic_auth = base64.b64encode(basic_auth.encode("utf-8")).decode("utf-8")
-
-        responses.post(
-            token_url,
-            match=[
-                responses.matchers.urlencoded_params_matcher(
-                    {
-                        "grant_type": "client_credentials",
-                        "scope": "all-apis",
-                    },
-                ),
-                responses.matchers.header_matcher({"Authorization": f"Basic {b64_basic_auth}"}),
-            ],
-            json={
-                "access_token": "oauth2-m2m-access-token",
-                "token_type": "Bearer",
-                "expires_in": 3600,
-            },
-        )
-
-        cp = PositLocalContentCredentialsProvider(token_url, client_id, client_secret)
-        assert cp() == {"Authorization": "Bearer oauth2-m2m-access-token"}
-
     @patch.dict("os.environ", {"CONNECT_CONTENT_SESSION_TOKEN": "cit"})
     @responses.activate
     def test_posit_content_credentials_provider(self):
@@ -127,7 +106,7 @@ def test_posit_content_credentials_provider(self):
 
         client = Client(api_key="12345", url="https://connect.example/")
         client._ctx.version = None
-        cp = PositContentCredentialsProvider(client=client)
+        cp = _PositConnectContentCredentialsProvider(client=client)
         assert cp() == {"Authorization": "Bearer content-access-token"}
 
     @responses.activate
@@ -136,95 +115,86 @@ def test_posit_credentials_provider(self):
 
         client = Client(api_key="12345", url="https://connect.example/")
         client._ctx.version = None
-        cp = PositCredentialsProvider(client=client, user_session_token="cit")
+        cp = _PositConnectViewerCredentialsProvider(client=client, user_session_token="cit")
         assert cp() == {"Authorization": "Bearer dynamic-viewer-access-token"}
 
-    @responses.activate
-    def test_local_content_credentials_strategy(self):
-        token_url = "https://my-token/url"
-        client_id = "client_id"
-        client_secret = "client_secret_123"
-        basic_auth = f"{client_id}:{client_secret}"
-        b64_basic_auth = base64.b64encode(basic_auth.encode("utf-8")).decode("utf-8")
-
-        responses.post(
-            token_url,
-            match=[
-                responses.matchers.urlencoded_params_matcher(
-                    {
-                        "grant_type": "client_credentials",
-                        "scope": "all-apis",
-                    },
-                ),
-                responses.matchers.header_matcher({"Authorization": f"Basic {b64_basic_auth}"}),
-            ],
-            json={
-                "access_token": "oauth2-m2m-access-token",
-                "token_type": "Bearer",
-                "expires_in": 3600,
-            },
-        )
-
-        cs = PositLocalContentCredentialsStrategy(
-            token_url,
-            client_id,
-            client_secret,
-        )
-        cp = cs()
-        assert cs.auth_type() == "posit-local-client-credentials"
-        assert cp() == {"Authorization": "Bearer oauth2-m2m-access-token"}
-
+    @patch.dict("os.environ", {"RSTUDIO_PRODUCT": "CONNECT"})
     @patch.dict("os.environ", {"CONNECT_CONTENT_SESSION_TOKEN": "cit"})
     @responses.activate
-    @patch.dict("os.environ", {"RSTUDIO_PRODUCT": "CONNECT"})
-    def test_posit_content_credentials_strategy(self):
+    def test_connect_strategy(self):
         register_mocks()
-
         client = Client(api_key="12345", url="https://connect.example/")
         client._ctx.version = None
-        cs = PositContentCredentialsStrategy(
-            local_strategy=mock_strategy(),
-            client=client,
-        )
+
+        # the default implementation uses Service Account authentication
+        cs = ConnectStrategy(client=client)
+        assert cs.auth_type() == POSIT_OAUTH_INTEGRATION_AUTH_TYPE
         cp = cs()
-        assert cs.auth_type() == "posit-oauth-integration"
         assert cp() == {"Authorization": "Bearer content-access-token"}
 
-    @responses.activate
-    @patch.dict("os.environ", {"RSTUDIO_PRODUCT": "CONNECT"})
-    def test_posit_credentials_strategy(self):
-        register_mocks()
-
-        client = Client(api_key="12345", url="https://connect.example/")
-        client._ctx.version = None
-        cs = PositCredentialsStrategy(
-            local_strategy=mock_strategy(),
-            user_session_token="cit",
-            client=client,
-        )
+        # if a session token is provided then Viewer auth is used
+        cs = ConnectStrategy(client=client, user_session_token="cit")
         cp = cs()
-        assert cs.auth_type() == "posit-oauth-integration"
         assert cp() == {"Authorization": "Bearer dynamic-viewer-access-token"}
 
-    def test_posit_content_credentials_strategy_fallback(self):
-        # local_strategy is used when the content is running locally
-        client = Client(api_key="12345", url="https://connect.example/")
-        cs = PositContentCredentialsStrategy(
-            local_strategy=mock_strategy(),
-            client=client,
+    def test_databricks_config(self):
+        # credentials_strategy is removed if it is provided
+        cfg = databricks_config(
+            credentials_strategy=mock_strategy("mock"),
+            auth_type="pat",
+            token="asdf",
+            host="https://databricks.com/",
         )
-        cp = cs()
-        assert cs.auth_type() == "local"
-        assert cp() == {"Authorization": "Bearer static-pat-token"}
+        assert cfg._credentials_strategy.auth_type() == expected_credentials.auth_type()
+
+        # kwargs are passed through to the Config() constructor
+        cfg = databricks_config(
+            host="https://databricks.com",
+            cluster_id="cluster_id",
+            warehouse_id="warehouse_id",
+            token="token",
+        )
+        assert cfg.host == "https://databricks.com"
+        assert cfg.cluster_id == "cluster_id"
+        assert cfg.warehouse_id == "warehouse_id"
+        assert cfg.token == "token"
+
+    def test_databricks_config_default(self):
+        cfg = databricks_config(
+            posit_default_strategy=mock_strategy("default"),
+            posit_workbench_strategy=mock_strategy("workbench"),
+            posit_connect_strategy=mock_strategy("connect"),
+        )
+        assert cfg._credentials_strategy.auth_type() == "default"
+
+        # default fallback defaults to DefaultCredentials() when none is provided
+        cfg = databricks_config(auth_type="pat", token="asdf", host="https://databricks.com/")
+        assert cfg._credentials_strategy.auth_type() == expected_credentials.auth_type()
+
+    @patch.dict("os.environ", {"RS_SERVER_ADDRESS": "https://workbench.posit.co/"})
+    def test_databricks_config_workbench(self):
+        cfg = databricks_config(
+            posit_default_strategy=mock_strategy("default"),
+            posit_workbench_strategy=mock_strategy("workbench"),
+            posit_connect_strategy=mock_strategy("connect"),
+        )
+        assert cfg._credentials_strategy.auth_type() == "workbench"
 
-    def test_posit_credentials_strategy_fallback(self):
-        # local_strategy is used when the content is running locally
-        client = Client(api_key="12345", url="https://connect.example/")
-        cs = PositCredentialsStrategy(
-            local_strategy=mock_strategy(),
-            user_session_token="cit",
-            client=client,
+        # workbench defaults to DefaultCredentials() when none is provided
+        cfg = databricks_config(auth_type="pat", token="asdf", host="https://databricks.com/")
+        assert cfg._credentials_strategy.auth_type() == expected_credentials.auth_type()
+
+    @patch.dict("os.environ", {"CONNECT_API_KEY": "API_KEY"})
+    @patch.dict("os.environ", {"CONNECT_SERVER": "https://connect.posit.co/"})
+    @patch.dict("os.environ", {"RSTUDIO_PRODUCT": "CONNECT"})
+    def test_databricks_config_connect(self):
+        cfg = databricks_config(
+            posit_default_strategy=mock_strategy("default"),
+            posit_workbench_strategy=mock_strategy("workbench"),
+            posit_connect_strategy=mock_strategy("connect"),
         )
-        cp = cs()
-        assert cs.auth_type() == "local"
-        assert cp() == {"Authorization": "Bearer static-pat-token"}
+        assert cfg._credentials_strategy.auth_type() == "connect"
+
+        # connect defaults to ConnectStrategy() when none is provided
+        cfg = databricks_config()
+        assert cfg._credentials_strategy.auth_type() == ConnectStrategy().auth_type()
diff --git a/tests/posit/workbench/external/test_databricks.py b/tests/posit/workbench/external/test_databricks.py
new file mode 100644
index 00000000..d90a6181
--- /dev/null
+++ b/tests/posit/workbench/external/test_databricks.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+import pytest
+
+from posit.workbench.external.databricks import (
+    POSIT_WORKBENCH_AUTH_TYPE,
+    WorkbenchStrategy,
+)
+
+try:
+    from databricks.sdk.core import Config
+except ImportError:
+    pytestmark = pytest.mark.skipif(True, reason="requires the Databricks SDK")
+
+
+class TestPositCredentialsHelpers:
+    def test_workbench_strategy(self):
+        # default will attempt to load the workbench profile
+        with pytest.raises(ValueError, match="profile=workbench"):
+            WorkbenchStrategy()
+
+        # providing a Config is allowed
+        cs = WorkbenchStrategy(
+            config=Config(host="https://databricks.com/workspace", token="token")  # pyright: ignore[reportPossiblyUnboundVariable]
+        )
+        assert cs.auth_type() == POSIT_WORKBENCH_AUTH_TYPE
+        cp = cs()
+
+        # token from the Config is passed through to the auth header
+        assert cp() == {"Authorization": "Bearer token"}


Reviews:
user: login: dbkegley
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T21:13:27Z
user: login: dbkegley
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T21:14:19Z
user: login: mconflitti-pbc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T21:44:29Z
user: login: mconflitti-pbc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T21:44:34Z
user: login: kmasiello
state: COMMENTED
author_association: NONE
submitted_at: 2025-02-15T00:49:52Z
user: login: nealrichardson
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-15T22:02:28Z
user: login: dbkegley
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-18T14:44:48Z
user: login: dbkegley
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-18T14:49:22Z
user: login: mconflitti-pbc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-18T14:55:52Z
user: login: mconflitti-pbc
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-18T14:57:39Z
user: login: tdstein
body: Looking good! Just a few minor questions.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T13:34:27Z
user: login: dbkegley
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T16:06:17Z
user: login: tdstein
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-05T16:12:34Z
user: login: plascaray
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T20:06:06Z
user: login: plascaray
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T20:07:24Z
user: login: plascaray
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T20:07:55Z
user: login: plascaray
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T20:16:30Z
user: login: plascaray
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T20:17:06Z
user: login: plascaray
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-15T20:17:47Z

Review comments:
diff_hunk: @@ -7,24 +7,28 @@
 These APIs are provided as a convenience and are subject to breaking changes:
 https://github.com/databricks/databricks-sdk-py#interface-stability
 """
-
-import abc
+from __future__ import annotations
 
 import requests
-from typing_extensions import Callable, Dict, Optional
+from typing_extensions import TYPE_CHECKING, Dict, Optional
 
-from .._utils import is_local
+from .._utils import is_local, is_connect, is_workbench
 from ..client import Client
 from ..oauth import Credentials
 
+if TYPE_CHECKING:
+    from databricks.sdk.core import Config
+    from databricks.sdk.credentials_provider import (
+            CredentialsStrategy,
+            CredentialsProvider
+    )
+
 POSIT_OAUTH_INTEGRATION_AUTH_TYPE = "posit-oauth-integration"
 POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE = "posit-local-client-credentials"
-
-# The Databricks SDK CredentialsProvider == Databricks SQL HeaderFactory
-CredentialsProvider = Callable[[], Dict[str, str]]
+POSIT_WORKBENCH_AUTH_TYPE = "posit-workbench"
 
 
-class CredentialsStrategy(abc.ABC):
+class CredentialsStrategyWrapper(CredentialsStrategy):
path: src/posit/connect/external/databricks.py
user: login: dbkegley
body: Need to revisit this. I'd rather not depend on the databricks sdk but this violates the `TYPE_CHECKING` import above.
created_at: 2025-02-14T21:13:27Z
author_association: COLLABORATOR
diff_hunk: @@ -395,42 +345,59 @@ def text():
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
+        local_strategy: Optional[CredentialsStrategy] = None,
+        workbench_strategy: Optional[CredentialsStrategy] = None,
+        connect_strategy: Optional[CredentialsStrategy] = None,
         client: Optional[Client] = None,
-        user_session_token: Optional[str] = None,
     ):
+        self._auth_type = "posit-connect-default"
         self._local_strategy = local_strategy
+        self._workbench_strategy = workbench_strategy
+        self._connect_strategy = connect_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.
+        # TODO: set self._strategy during init and log the one that we picked
 
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
+    def auth_type(self) -> str:
+        """Returns the auth type currently in use.
+
+        The databricks-sdk client uses the configured auth_type to create
+        a user-agent string which is used for attribution. We should only
+        overwrite the auth_type if we are using the PositWorkbenchCredentialsStrategy
+        or PositConnectCredentialsStrategy (non-local), otherwise, we should return the auth_type
+        of the configured local_strategy instead to avoid breaking someone elses attribution.
+
+        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
 
         See Also
         --------
-        * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        * https://github.com/databricks/databricks-sdk-py/blob/v0.29.0/databricks/sdk/config.py#L261-L269
 
-    def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        Returns
+        -------
+            str
+        """
+        return self._auth_type
 
     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_connect() and self._connect_strategy is not None:
+            self._auth_type = self._connect_strategy.auth_type()
+            return self._connect_strategy(*args, **kwargs)
+        else:
+            print()
+            # log and continue
path: src/posit/connect/external/databricks.py
user: login: dbkegley
body: I'd like move these log messages into the init so that they aren't printed everytime the user requests credentials
created_at: 2025-02-14T21:14:19Z
author_association: COLLABORATOR
diff_hunk: @@ -234,105 +225,64 @@ def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
         self._client_id = client_id
         self._client_secret = client_secret
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
 
     def auth_type(self) -> str:
         return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
+        return positLocalContentCredentialsProvider(
             self._token_endpoint_url,
             self._client_id,
             self._client_secret,
path: src/posit/connect/external/databricks.py
user: login: mconflitti-pbc
body: Can you remind me again why we need this? Shouldn't we be able to just pass the user session token to client.oauth.get_credentials()?
created_at: 2025-02-14T21:44:29Z
author_association: COLLABORATOR
diff_hunk: @@ -234,105 +225,64 @@ def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
         self._client_id = client_id
         self._client_secret = client_secret
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
 
     def auth_type(self) -> str:
         return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
+        return positLocalContentCredentialsProvider(
             self._token_endpoint_url,
             self._client_id,
             self._client_secret,
         )
 
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
-
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
-
-    Examples
-    --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
-
-    ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
-
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
-    from databricks.sdk.service.iam import CurrentUserAPI
+class PositWorkbenchCredentialsStrategy(CredentialsStrategyWrapper):
+    """
+    """
+    def __init__(self, config: Config):
+        self.config = config
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    def auth_type(self) -> str:
+        return POSIT_WORKBENCH_AUTH_TYPE
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider: # noqa: ARG002
+        if self.config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+        def cp():
+            return {"Authorization": f"Bearer {self.config.token}"}
+        return cp
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
-    ```
+class PositConnectCredentialsStrategy(CredentialsStrategyWrapper):
     """
-
-    def __init__(
-        self,
-        local_strategy: CredentialsStrategy,
-        client: Optional[Client] = None,
+    """
+    def __init__(self,
+                 client: Optional[Client] = None,
+                 user_session_token: Optional[str] = None,
     ):
path: src/posit/connect/external/databricks.py
user: login: mconflitti-pbc
body: ```suggestion
    """
    def __init__(self,
                 client: Optional[Client] = None,
                 *,
                 user_session_token: Optional[str] = None,
                 content_session_token: Optional[str] = None,
    ):
```

Would keep these explicitly keyword arguments and then allows the dev to pass the token with an appropriately named param.
created_at: 2025-02-14T21:44:33Z
author_association: COLLABORATOR
diff_hunk: @@ -25,12 +29,11 @@ def server(i: Inputs, o: Outputs, session: Session):
     session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
path: examples/connect/databricks/shiny/app.py
user: login: kmasiello
body: A nit, and this applies to all the examples:
 From the developer’s POV, I am in local/workbench first. It feels out of order to have the Connect-specific session token defined so early. My mental model is to get through the parts about defining how to handle creds in development vs deployment, then I’d start putting Connect-specific info. 
created_at: 2025-02-15T00:49:52Z
author_association: NONE
diff_hunk: @@ -9,7 +9,11 @@
 from databricks.sdk.core import ApiClient, Config, databricks_cli
 from databricks.sdk.service.iam import CurrentUserAPI
 
-from posit.connect.external.databricks import PositCredentialsStrategy
+from posit.connect.external.databricks import (
+    PositCredentialsStrategy,
+    PositConnectCredentialsStrategy,
+    PositWorkbenchCredentialsStrategy,
path: examples/connect/databricks/dash/app.py
user: login: nealrichardson
body: Just a passing observation: it's odd to see `PositWorkbenchCredentialsStrategy` inside the `posit.connect` module. They aren't really relevant for Connect, right? Is it time to create `posit.workbench` to contain these?
created_at: 2025-02-15T22:02:28Z
author_association: COLLABORATOR
diff_hunk: @@ -234,105 +225,64 @@ def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
         self._client_id = client_id
         self._client_secret = client_secret
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
 
     def auth_type(self) -> str:
         return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
+        return positLocalContentCredentialsProvider(
             self._token_endpoint_url,
             self._client_id,
             self._client_secret,
path: src/posit/connect/external/databricks.py
user: login: dbkegley
body: > Shouldn't we be able to just pass the user session token to client.oauth.get_credentials()?

`cient.oauth.get_credentials` and `client.oauth.get_content_credentials` are only available when the content is running on Connect. This helper would allow content running locally (or in Workbench) to obtain the same service principal credential that it would use when running on a Connect server.

Originally we had this because the databricks-cli [doesn't support service principal auth](https://github.com/databricks/cli/issues/1939) however after looking more closely at the databricks-sdk, I think we should be able to use some of the [provided service_principal credentials strategies](https://github.com/databricks/databricks-sdk-py/blob/998a117c43a7bc901710d263a7a7ab0d66ae8b8c/databricks/sdk/credentials_provider.py#L164-L185) rather than implementing this ourselves. 
created_at: 2025-02-18T14:44:47Z
author_association: COLLABORATOR
diff_hunk: @@ -9,7 +9,11 @@
 from databricks.sdk.core import ApiClient, Config, databricks_cli
 from databricks.sdk.service.iam import CurrentUserAPI
 
-from posit.connect.external.databricks import PositCredentialsStrategy
+from posit.connect.external.databricks import (
+    PositCredentialsStrategy,
+    PositConnectCredentialsStrategy,
+    PositWorkbenchCredentialsStrategy,
path: examples/connect/databricks/dash/app.py
user: login: dbkegley
body: I'll move this before the PR becomes final. For now it's easier to iterate with everything inside a single module.
created_at: 2025-02-18T14:49:22Z
author_association: COLLABORATOR
diff_hunk: @@ -25,12 +29,11 @@ def server(i: Inputs, o: Outputs, session: Session):
     session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
path: examples/connect/databricks/shiny/app.py
user: login: mconflitti-pbc
body: This could be a constraint given the custom nature of this integration. The way it is shown here, this code can be written once and then adapt to the environment it is running it. But that means there is a lowest common denominator in terms of DX as a tradeoff. Ideally, devs are thinking about how their code would work in production as well if that is where they plan to deploy to.

Referencing your other comments though, if we had a way to grab the user session token for the dev if/when it is needed then that could be done behind the scenes at the appropriate time simplifying this immensely.
created_at: 2025-02-18T14:55:52Z
author_association: COLLABORATOR
diff_hunk: @@ -25,12 +29,11 @@ def server(i: Inputs, o: Outputs, session: Session):
     session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
path: examples/connect/databricks/shiny/app.py
user: login: mconflitti-pbc
body: ```python
posit_strategy = PositCredentialsStrategy(
        local_strategy=databricks_cli,
        workbench_strategy=PositWorkbenchCredentialsStrategy(Config(profile="workbench")),
        connect_strategy=PositConnectCredentialsStrategy(user_session_token=session_token),
    )
```
could become:
```python
posit_strategy = PositCredentialsStrategy()
```
allowing for overrides to defaults, but otherwise it just grabs what it needs under the hood.
created_at: 2025-02-18T14:57:39Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,401 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        databricks_user_info = CurrentUserAPI(ApiClient(cfg())).me()
+        return f"Hello, {databricks_user_info.display_name}!"
path: src/posit/connect/external/databricks.py
user: login: tdstein
body: nit; extract variables to improve readability.

```suggestion
        current_user_api = CurrentUserAPI(ApiClient(cfg()))
        databricks_user_info = current_user_api.me()
        return f"Hello, {databricks_user_info.display_name}!"
```
created_at: 2025-03-05T13:11:39Z
author_association: COLLABORATOR
diff_hunk: @@ -59,76 +53,14 @@ def _new_bearer_authorization_header(credentials: Credentials) -> Dict[str, str]
     return {"Authorization": f"Bearer {access_token}"}
 
 
-def _get_auth_type(local_auth_type: str) -> str:
-    """Returns the auth type currently in use.
-
-    The databricks-sdk client uses the configured auth_type to create
-    a user-agent string which is used for attribution. We should only
-    overwrite the auth_type if we are using the PositCredentialsStrategy (non-local),
-    otherwise, we should return the auth_type of the configured local_strategy instead
-    to avoid breaking someone elses attribution.
-
-    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
-
-    See Also
-    --------
-    * https://github.com/databricks/databricks-sdk-py/blob/v0.29.0/databricks/sdk/config.py#L261-L269
-
-    Returns
-    -------
-        str
-    """
-    if is_local():
-        return local_auth_type
-
-    return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
-
-
-class PositLocalContentCredentialsProvider:
-    """`CredentialsProvider` implementation which provides a fallback for local development using a client credentials flow.
-
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
-
-    Until the CLI issue is resolved, this CredentialsProvider implements the approach described in the Databricks documentation
-    for manually generating a workspace-level access token using OAuth M2M authentication. Once it has acquired an access token,
-    it returns it as a Bearer authorization header like other `CredentialsProvider` implementations.
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
-    """
-
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def __call__(self) -> Dict[str, str]:
-        response = requests.post(
-            self._token_endpoint_url,
-            auth=(self._client_id, self._client_secret),
-            data={
-                "grant_type": "client_credentials",
-                "scope": "all-apis",
-            },
-        )
-        response.raise_for_status()
-
-        credentials = Credentials(**response.json())
-        return _new_bearer_authorization_header(credentials)
-
-
-class PositContentCredentialsProvider:
+class _PositConnectContentCredentialsProvider:
path: src/posit/connect/external/databricks.py
user: login: tdstein
body: Should this inherit from `CredentialsProvider`? 
created_at: 2025-03-05T13:16:27Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,401 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        databricks_user_info = CurrentUserAPI(ApiClient(cfg())).me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        databricks_user_info = CurrentUserAPI(ApiClient(cfg())).me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        databricks_user_info = CurrentUserAPI(ApiClient(cfg())).me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp = None
path: src/posit/connect/external/databricks.py
user: login: tdstein
body: nit; add a type signature for clarity on the non-null state.

```suggestion
        self._cp: CredentialsProvider | None = None
```
created_at: 2025-03-05T13:18:03Z
author_association: COLLABORATOR
diff_hunk: @@ -8,38 +8,32 @@
 https://github.com/databricks/databricks-sdk-py#interface-stability
 """
 
-import abc
+from __future__ import annotations
 
-import requests
-from typing_extensions import Callable, Dict, Optional
+import logging
 
-from .._utils import is_local
-from ..client import Client
-from ..oauth import Credentials
-
-POSIT_OAUTH_INTEGRATION_AUTH_TYPE = "posit-oauth-integration"
-POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE = "posit-local-client-credentials"
+from typing_extensions import TYPE_CHECKING, Dict, Optional
 
-# The Databricks SDK CredentialsProvider == Databricks SQL HeaderFactory
-CredentialsProvider = Callable[[], Dict[str, str]]
+from .._utils import is_connect, is_workbench
+from ..client import Client
 
+try:
+    from databricks.sdk.core import Config, DefaultCredentials
+    from databricks.sdk.credentials_provider import (
+        CredentialsProvider,
+        CredentialsStrategy,
+    )
+except ImportError as e:
+    raise ImportError("The 'databricks-sdk' package is required to use this module.") from e
 
-class CredentialsStrategy(abc.ABC):
-    """Maintain compatibility with the Databricks SQL/SDK client libraries.
+if TYPE_CHECKING:
+    from ..oauth import Credentials
 
-    See Also
-    --------
-    * https://github.com/databricks/databricks-sql-python/blob/v3.3.0/src/databricks/sql/auth/authenticators.py#L19-L33
-    * https://github.com/databricks/databricks-sdk-py/blob/v0.29.0/databricks/sdk/credentials_provider.py#L44-L54
-    """
 
-    @abc.abstractmethod
-    def auth_type(self) -> str:
-        raise NotImplementedError
+POSIT_OAUTH_INTEGRATION_AUTH_TYPE = "posit-oauth-integration"
+POSIT_WORKBENCH_AUTH_TYPE = "posit-workbench"
 
-    @abc.abstractmethod
-    def __call__(self, *args, **kwargs) -> CredentialsProvider:
-        raise NotImplementedError
+logger = logging.getLogger("posit.sdk")
path: src/posit/connect/external/databricks.py
user: login: tdstein
body: I'm pretty sure we can use `logger = logging.getLogger(__name__)` here. Which will be equivalent to `getLogger("posit.sdk.external.databricks")`.

https://docs.python.org/3/howto/logging.html#advanced-logging-tutorial
created_at: 2025-03-05T13:26:31Z
author_association: COLLABORATOR
diff_hunk: @@ -59,76 +53,14 @@ def _new_bearer_authorization_header(credentials: Credentials) -> Dict[str, str]
     return {"Authorization": f"Bearer {access_token}"}
 
 
-def _get_auth_type(local_auth_type: str) -> str:
-    """Returns the auth type currently in use.
-
-    The databricks-sdk client uses the configured auth_type to create
-    a user-agent string which is used for attribution. We should only
-    overwrite the auth_type if we are using the PositCredentialsStrategy (non-local),
-    otherwise, we should return the auth_type of the configured local_strategy instead
-    to avoid breaking someone elses attribution.
-
-    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
-
-    See Also
-    --------
-    * https://github.com/databricks/databricks-sdk-py/blob/v0.29.0/databricks/sdk/config.py#L261-L269
-
-    Returns
-    -------
-        str
-    """
-    if is_local():
-        return local_auth_type
-
-    return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
-
-
-class PositLocalContentCredentialsProvider:
-    """`CredentialsProvider` implementation which provides a fallback for local development using a client credentials flow.
-
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
-
-    Until the CLI issue is resolved, this CredentialsProvider implements the approach described in the Databricks documentation
-    for manually generating a workspace-level access token using OAuth M2M authentication. Once it has acquired an access token,
-    it returns it as a Bearer authorization header like other `CredentialsProvider` implementations.
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
-    """
-
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def __call__(self) -> Dict[str, str]:
-        response = requests.post(
-            self._token_endpoint_url,
-            auth=(self._client_id, self._client_secret),
-            data={
-                "grant_type": "client_credentials",
-                "scope": "all-apis",
-            },
-        )
-        response.raise_for_status()
-
-        credentials = Credentials(**response.json())
-        return _new_bearer_authorization_header(credentials)
-
-
-class PositContentCredentialsProvider:
+class _PositConnectContentCredentialsProvider:
path: src/posit/connect/external/databricks.py
user: login: dbkegley
body: `CredentialsProvider` is a type not a base class

```python
CredentialsProvider = Callable[[], Dict[str, str]]
```
created_at: 2025-03-05T16:06:17Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,407 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp: Optional[CredentialsProvider] = None
         self._client = client
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
-
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
-
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
-    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)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
         if self._client is None:
             self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
 
-    Examples
+    See Also
     --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
     import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+
+    from posit.connect.external.databricks import sql_credentials
+
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
+
+    with sql.connect(
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
+    ) as connection:
+        with connection.cursor() as cursor:
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
+    ```
+    """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
+
+    Returns
+    -------
+        databricks.sdk.core.Config
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
+
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import databricks_cli
     from databricks.sdk.service.iam import CurrentUserAPI
-    from posit.connect.external.databricks import PositCredentialsStrategy
-    from shiny import App, Inputs, Outputs, Session, render, ui
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        WorkbenchStrategy,
+        ConnectStrategy,
+        databricks_config,
+    )
+
+
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return databricks_config(
+            posit_default_strategy=databricks_cli,
+            posit_workbench_strategy=WorkbenchStrategy(),
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
path: src/posit/connect/external/databricks.py
user: login: plascaray
body: ```suggestion
```
I don't think this is needed for the example to work.
created_at: 2025-03-15T20:06:06Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,407 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp: Optional[CredentialsProvider] = None
         self._client = client
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
-
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
-
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
-    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)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
         if self._client is None:
             self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
 
-    Examples
+    See Also
     --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
     import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+
+    from posit.connect.external.databricks import sql_credentials
+
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
+
+    with sql.connect(
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
+    ) as connection:
+        with connection.cursor() as cursor:
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
+    ```
+    """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
+
+    Returns
+    -------
+        databricks.sdk.core.Config
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
+
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import databricks_cli
     from databricks.sdk.service.iam import CurrentUserAPI
-    from posit.connect.external.databricks import PositCredentialsStrategy
-    from shiny import App, Inputs, Outputs, Session, render, ui
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        WorkbenchStrategy,
+        ConnectStrategy,
+        databricks_config,
+    )
+
+
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return databricks_config(
+            posit_default_strategy=databricks_cli,
+            posit_workbench_strategy=WorkbenchStrategy(),
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to construct a databricks Config that is compatible with:
+    - OAuth Service Principal (oauth-m2m) in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `DATABRICKS_CLIENT_ID` and `DATABRICKS_CLIENT_SECRET` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-    app_ui = ui.page_fluid(ui.output_text("text"), ui.output_data_frame("result"))
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import oauth_service_principal
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
 
 
-    def server(i: Inputs, o: Outputs, session: Session):
-        # HTTP session headers are available in this context.
+    @reactive.calc
+    def cfg():
         session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
-        posit_strategy = PositCredentialsStrategy(
-            local_strategy=databricks_cli, user_session_token=session_token
+        return databricks_config(
+            posit_workbench_strategy=oauth_service_principal,
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
path: src/posit/connect/external/databricks.py
user: login: plascaray
body: ```suggestion
```
I don't think this is needed for the example to work.
created_at: 2025-03-15T20:07:24Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,407 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp: Optional[CredentialsProvider] = None
         self._client = client
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
-
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
-
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
-    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)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
         if self._client is None:
             self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
 
-    Examples
+    See Also
     --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
     import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+
+    from posit.connect.external.databricks import sql_credentials
+
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
+
+    with sql.connect(
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
+    ) as connection:
+        with connection.cursor() as cursor:
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
+    ```
+    """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
+
+    Returns
+    -------
+        databricks.sdk.core.Config
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
+
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import databricks_cli
     from databricks.sdk.service.iam import CurrentUserAPI
-    from posit.connect.external.databricks import PositCredentialsStrategy
-    from shiny import App, Inputs, Outputs, Session, render, ui
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        WorkbenchStrategy,
+        ConnectStrategy,
+        databricks_config,
+    )
+
+
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return databricks_config(
+            posit_default_strategy=databricks_cli,
+            posit_workbench_strategy=WorkbenchStrategy(),
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to construct a databricks Config that is compatible with:
+    - OAuth Service Principal (oauth-m2m) in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `DATABRICKS_CLIENT_ID` and `DATABRICKS_CLIENT_SECRET` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-    app_ui = ui.page_fluid(ui.output_text("text"), ui.output_data_frame("result"))
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import oauth_service_principal
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
 
 
-    def server(i: Inputs, o: Outputs, session: Session):
-        # HTTP session headers are available in this context.
+    @reactive.calc
+    def cfg():
         session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
-        posit_strategy = PositCredentialsStrategy(
-            local_strategy=databricks_cli, user_session_token=session_token
+        return databricks_config(
+            posit_workbench_strategy=oauth_service_principal,
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+            client_id=os.getenv("DATABRICKS_CLIENT_ID"),
+            client_secret=os.getenv("DATABRICKS_CLIENT_SECRET"),
         )
-        cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
-
-        @render.data_frame
-        def result():
-            query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-            with sql.connect(
-                server_hostname=DATABRICKS_HOST,
-                http_path=SQL_HTTP_PATH,
-                credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-            ) as connection:
-                with connection.cursor() as cursor:
-                    cursor.execute(query)
-                    rows = cursor.fetchall()
-                    df = pd.DataFrame(rows, columns=[col[0] for col in cursor.description])
-                    return df
-
-        @render.text
-        def text():
-            databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-            return f"Hello, {databricks_user_info.display_name}!"
-
-
-    app = App(app_ui, server)
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-    """
 
-    def __init__(
-        self,
-        local_strategy: CredentialsStrategy,
-        client: Optional[Client] = None,
-        user_session_token: Optional[str] = None,
-    ):
-        self._local_strategy = local_strategy
-        self._client = client
-        self._user_session_token = user_session_token
+    This example shows how to construct a databricks Config that is compatible with:
+    - Azure Service Principal credentials in Posit Workbench
+    - Service Account OAuth integration authentication in Posit Connect
+
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `ARM_CLIENT_ID`, `ARM_CLIENT_SECRET`, and `ARM_TENANT_ID` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
+    ```python
+    import os
 
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import azure_service_principal
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-        See Also
-        --------
-        * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
 
-    def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
 
-    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)
+    @reactive.calc
+    def cfg():
+        return databricks_config(
+            posit_workbench_strategy=azure_service_principal,
+            posit_connect_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
path: src/posit/connect/external/databricks.py
user: login: plascaray
body: ```suggestion
```
I don't think this is needed for the example to work.
created_at: 2025-03-15T20:07:55Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,407 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp: Optional[CredentialsProvider] = None
         self._client = client
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
-
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
-
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
-    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)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
         if self._client is None:
             self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
 
-    Examples
+    See Also
     --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
     import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+
+    from posit.connect.external.databricks import sql_credentials
+
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
+
+    with sql.connect(
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
+    ) as connection:
+        with connection.cursor() as cursor:
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
+    ```
+    """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
+
+    Returns
+    -------
+        databricks.sdk.core.Config
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
+
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import databricks_cli
     from databricks.sdk.service.iam import CurrentUserAPI
-    from posit.connect.external.databricks import PositCredentialsStrategy
-    from shiny import App, Inputs, Outputs, Session, render, ui
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        WorkbenchStrategy,
+        ConnectStrategy,
+        databricks_config,
+    )
+
+
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return databricks_config(
+            posit_default_strategy=databricks_cli,
+            posit_workbench_strategy=WorkbenchStrategy(),
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to construct a databricks Config that is compatible with:
+    - OAuth Service Principal (oauth-m2m) in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `DATABRICKS_CLIENT_ID` and `DATABRICKS_CLIENT_SECRET` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-    app_ui = ui.page_fluid(ui.output_text("text"), ui.output_data_frame("result"))
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import oauth_service_principal
path: src/posit/connect/external/databricks.py
user: login: plascaray
body: ```suggestion
    from databricks.sdk.core import ApiClient, oauth_service_principal
```
created_at: 2025-03-15T20:16:29Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,407 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp: Optional[CredentialsProvider] = None
         self._client = client
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
-
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
-
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
-    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)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
         if self._client is None:
             self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
 
-    Examples
+    See Also
     --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
     import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+
+    from posit.connect.external.databricks import sql_credentials
+
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
+
+    with sql.connect(
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
+    ) as connection:
+        with connection.cursor() as cursor:
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
+    ```
+    """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
+
+    Returns
+    -------
+        databricks.sdk.core.Config
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
+
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import databricks_cli
path: src/posit/connect/external/databricks.py
user: login: plascaray
body: ```suggestion
    from databricks.sdk.core import ApiClient, databricks_cli
```
created_at: 2025-03-15T20:17:05Z
author_association: COLLABORATOR
diff_hunk: @@ -160,277 +91,407 @@ def __call__(self) -> Dict[str, str]:
         return _new_bearer_authorization_header(credentials)
 
 
-class PositLocalContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against Databricks.
+class WorkbenchStrategy(CredentialsStrategy):
+    """`CredentialsStrategy` implementation which uses a bearer token authentication provider for Workbench environments.
 
-    There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
-    https://github.com/databricks/cli/issues/1939
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-    Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
-    to develop applications which target Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Workbench server needs to access a Databricks token
+    that is manged by Posit Workbench-managed Databricks Credentials. If you need to author content that can
+    run in multiple environments (local content, Posit Workbench, _and_ Posit Connect), consider using the
+    `databricks_config()` helper method in this module.
 
-    Examples
+    See Also
     --------
-    In the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    * https://docs.posit.co/ide/server-pro/user/posit-workbench/guide/databricks.html#databricks-with-python
 
-    CLIENT_ID and CLIENT_SECRET are credentials associated with the Databricks service principal.
+    Examples
+    --------
+    This example shows how authenticate to Databricks using Posit Workbench-managed Databricks Credentials.
 
     ```python
-    from posit.connect.external.databricks import (
-        PositContentCredentialsStrategy,
-        PositLocalContentCredentialsStrategy,
-    )
+    import os
 
-    import pandas as pd
-    from databricks import sql
     from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
-    TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
-
-    CLIENT_ID = "<REDACTED>"
-    CLIENT_SECRET = "<REDACTED>"
-
-    # Rather than relying on the Databricks CLI as a local strategy, we use
-    # PositLocalContentCredentialsStrategy as a drop-in replacement.
-    # Can be replaced with the Databricks CLI implementation when
-    # https://github.com/databricks/cli/issues/1939 is resolved.
-    local_strategy = PositLocalContentCredentialsStrategy(
-        token_endpoint_url=TOKEN_ENDPOINT_URL,
-        client_id=CLIENT_ID,
-        client_secret=CLIENT_SECRET,
-    )
+    from posit.connect.external.databricks import WorkbenchStrategy
 
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=WorkbenchStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-
-    See Also
-    --------
-    * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
     """
 
-    def __init__(self, token_endpoint_url: str, client_id: str, client_secret: str):
-        self._token_endpoint_url = token_endpoint_url
-        self._client_id = client_id
-        self._client_secret = client_secret
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        return lambda: self.__call__(*args, **kwargs)
+    def __init__(self, config: Optional[Config] = None):
+        self._config = config or Config(profile="workbench")
 
     def auth_type(self) -> str:
-        return POSIT_LOCAL_CLIENT_CREDENTIALS_AUTH_TYPE
+        return POSIT_WORKBENCH_AUTH_TYPE
 
     def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
-        return PositLocalContentCredentialsProvider(
-            self._token_endpoint_url,
-            self._client_id,
-            self._client_secret,
-        )
+        if self._config.token is None:
+            raise ValueError("Missing value for field 'token' in Config.")
+
+        def cp():
+            return {"Authorization": f"Bearer {self._config.token}"}
+
+        return cp
+
+
+class ConnectStrategy(CredentialsStrategy):
+    """A `CredentialsStrategy` implementation which supports content running on Posit Connect.
 
+    This strategy can be used as a valid `credentials_strategy` when constructing a [](`databricks.sdk.core.Config`).
 
-class PositContentCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Service Account OAuth integrations on Connect.
+    It should be used when content running on a Posit Connect server needs to access a Databricks token
+    that is managed by a Posit Connect OAuth integration. This strategy is _only_ valid when running
+    on Posit Connect. If you need to author content that can run in multiple environments
+    (local content, Posit Workbench, _and_ Posit Connect), consider using the `databricks_config()`
+    helper method in this module.
 
-    This strategy callable class returns a `PositContentCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    This strategy can be used for both Viewer authentication and Service Account authentication
+    (sometimes referred to as Content credentials). When `user_session_token` is provided then
+    Viewer authentication is used, otherwise the strategy attempts to fall back to Service Account
+    authentication by reading the `CONNECT_CONTENT_SESSION_TOKEN` environment var.
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
 
     Examples
     --------
-    NOTE: in the example below, the `PositContentCredentialsStrategy` can be initialized anywhere that
-    the Python process can read environment variables.
+    This example shows how to do Viewer authentication with a Viewer Databricks OAuth integration.
 
     ```python
-    from posit.connect.external.databricks import PositContentCredentialsStrategy
+    import os
 
-    import pandas as pd
-    from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
     from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    from posit.connect.external.databricks import ConnectStrategy
 
-    # NOTE: currently the databricks_cli local strategy only supports auth code OAuth flows.
-    # https://github.com/databricks/cli/issues/1939
-    #
-    # This means that the databricks_cli supports local development using the developer's
-    # databricks credentials, but not the credentials for a service principal.
-    # To fallback to service principal credentials in local development, use
-    # `PositLocalContentCredentialsStrategy` as a drop-in replacement.
-    posit_strategy = PositContentCredentialsStrategy(local_strategy=databricks_cli)
 
-    cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return Config(
+            credentials_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
 
-    databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-    print(f"Hello, {databricks_user_info.display_name}!")
 
-    query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-    with sql.connect(
-        server_hostname=DATABRICKS_HOST,
-        http_path=SQL_HTTP_PATH,
-        credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-    ) as connection:
-        with connection.cursor() as cursor:
-            cursor.execute(query)
-            rows = cursor.fetchall()
-            print(pd.DataFrame([row.asDict() for row in rows]))
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to do Service Account authentication with a Service Account Databricks OAuth integration.
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render
+
+    from posit.connect.external.databricks import ConnectStrategy
+
+
+    @reactive.calc
+    def cfg():
+        return Config(
+            credentials_strategy=ConnectStrategy(),
+            host=os.getenv("DATABRICKS_HOST"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
     """
 
     def __init__(
         self,
-        local_strategy: CredentialsStrategy,
         client: Optional[Client] = None,
+        user_session_token: Optional[str] = None,
     ):
-        self._local_strategy = local_strategy
+        self._cp: Optional[CredentialsProvider] = None
         self._client = client
-
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
-
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
-
-        https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
-        """
-        return lambda: self.__call__(*args, **kwargs)
+        self._user_session_token = user_session_token
 
     def auth_type(self) -> str:
-        return _get_auth_type(self._local_strategy.auth_type())
+        return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
 
-    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)
+    def __call__(self, *args, **kwargs) -> CredentialsProvider:  # noqa: ARG002
+        if not is_connect():
+            raise ValueError(
+                "ConnectStrategy is not supported for content running outside of Posit Connect"
+            )
 
         if self._client is None:
             self._client = Client()
 
-        return PositContentCredentialsProvider(self._client)
+        if self._cp is None:
+            if self._user_session_token:
+                self._cp = _PositConnectViewerCredentialsProvider(
+                    self._client, self._user_session_token
+                )
+            else:
+                logger.info(
+                    "ConnectStrategy will attempt to use OAuth Service Account credentials because user_session_token is not set"
+                )
+                self._cp = _PositConnectContentCredentialsProvider(self._client)
+        return self._cp
 
 
-class PositCredentialsStrategy(CredentialsStrategy):
-    """`CredentialsStrategy` implementation which supports interacting with Viewer OAuth integrations on Connect.
+def sql_credentials(cfg: Config) -> CredentialsProvider:
+    """A helper method for making a [](`databricks.sdk.core.Config`) object compatible with `databricks.sql.connect`.
 
-    This strategy callable class returns a `PositCredentialsProvider` when hosted on Connect, and
-    its `local_strategy` strategy otherwise.
+    The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
+    `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
+    This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
+    implementations from where `sql.connect` is called.
 
-    Examples
+    See Also
     --------
-    NOTE: In the example below, the PositCredentialsProvider *must* be initialized within the context of the
-    shiny `server` function, which provides access to the HTTP session headers.
+    * https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
 
+    Examples
+    --------
     ```python
     import os
 
-    import pandas as pd
     from databricks import sql
-    from databricks.sdk.core import ApiClient, Config, databricks_cli
+    from databricks.sdk.core import ApiClient, Config
+    from databricks.sdk.service.iam import CurrentUserAPI
+
+    from posit.connect.external.databricks import sql_credentials
+
+    cfg = Config(
+        host=os.getenv("DATABRICKS_HOST"),
+        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+    )
+
+    with sql.connect(
+        server_hostname=cfg.host,
+        http_path=cfg.sql_http_path,
+        credentials_provider=sql_credentials(cfg),
+    ) as connection:
+        with connection.cursor() as cursor:
+            cursor.execute("SELECT * FROM samples.nyctaxi.trips LIMIT 10;")
+            print(cursor.fetchall())
+    ```
+    """
+    return lambda: cfg._credentials_strategy(cfg)
+
+
+def databricks_config(
+    posit_default_strategy: Optional[CredentialsStrategy] = None,
+    posit_workbench_strategy: Optional[CredentialsStrategy] = None,
+    posit_connect_strategy: Optional[CredentialsStrategy] = None,
+    **kwargs,
+) -> Config:
+    """A helper to aid with constructing a valid [](`databricks.sdk.core.Config`) object.
+
+    The returned `Config` is configured with a credentials_strategy which should be compatible with the current
+    execution environment. This is useful when authoring content that needs to run in multiple environments
+    without changing code. This constructor allows you mix-and-match Databricks SDK credentials_strategies.
+    For example, content executing on a developer laptop can use the `databricks_cli` strategy, while
+    in Workbench the same content can use the `pat_auth` strategy, and in Connect the content uses
+    the custom `ConnectStrategy()` with a Databricks OAuth integration.
+
+    Parameters
+    ----------
+    posit_default_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The default credentials_strategy to use when neither Posit Connect nor Posit Workbench are detected
+    posit_workbench_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Workbench is detected
+    posit_connect_strategy : databricks.sdk.credentials_provider.CredentialsStrategy, optional
+        The credentials_strategy to use when Posit Connect is detected
+    kwargs : Dict[str, Any], optional
+        Additional keyword arguments. kwargs is passed directly to the databricks.sdk.core.Config constructor.
+
+    Returns
+    -------
+        databricks.sdk.core.Config
+
+    See Also
+    --------
+    * https://docs.posit.co/connect/admin/integrations/oauth-integrations/databricks/
+
+    Examples
+    --------
+    This example shows how to construct a databricks Config that is compatible with:
+    - Databricks CLI authentication for local development
+    - Workbench-managed Databricks Credentials in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
+
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import databricks_cli
     from databricks.sdk.service.iam import CurrentUserAPI
-    from posit.connect.external.databricks import PositCredentialsStrategy
-    from shiny import App, Inputs, Outputs, Session, render, ui
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        WorkbenchStrategy,
+        ConnectStrategy,
+        databricks_config,
+    )
+
+
+    @reactive.calc
+    def cfg():
+        session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
+        return databricks_config(
+            posit_default_strategy=databricks_cli,
+            posit_workbench_strategy=WorkbenchStrategy(),
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+        )
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
+    ```
+
+    This example shows how to construct a databricks Config that is compatible with:
+    - OAuth Service Principal (oauth-m2m) in Posit Workbench
+    - Viewer OAuth integration authentication in Posit Connect
 
-    DATABRICKS_HOST = "<REDACTED>"
-    DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
-    SQL_HTTP_PATH = "<REDACTED>"
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `DATABRICKS_CLIENT_ID` and `DATABRICKS_CLIENT_SECRET` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-    app_ui = ui.page_fluid(ui.output_text("text"), ui.output_data_frame("result"))
+    ```python
+    import os
+
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import oauth_service_principal
+    from databricks.sdk.service.iam import CurrentUserAPI
+    from shiny import reactive
+    from shiny.express import render, session
+
+    from posit.connect.external.databricks import (
+        ConnectStrategy,
+        databricks_config,
+    )
 
 
-    def server(i: Inputs, o: Outputs, session: Session):
-        # HTTP session headers are available in this context.
+    @reactive.calc
+    def cfg():
         session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")
-        posit_strategy = PositCredentialsStrategy(
-            local_strategy=databricks_cli, user_session_token=session_token
+        return databricks_config(
+            posit_workbench_strategy=oauth_service_principal,
+            posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
+            host=os.getenv("DATABRICKS_HOST"),
+            warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
+            client_id=os.getenv("DATABRICKS_CLIENT_ID"),
+            client_secret=os.getenv("DATABRICKS_CLIENT_SECRET"),
         )
-        cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
-
-        @render.data_frame
-        def result():
-            query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
-
-            with sql.connect(
-                server_hostname=DATABRICKS_HOST,
-                http_path=SQL_HTTP_PATH,
-                credentials_provider=posit_strategy.sql_credentials_provider(cfg),
-            ) as connection:
-                with connection.cursor() as cursor:
-                    cursor.execute(query)
-                    rows = cursor.fetchall()
-                    df = pd.DataFrame(rows, columns=[col[0] for col in cursor.description])
-                    return df
-
-        @render.text
-        def text():
-            databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
-            return f"Hello, {databricks_user_info.display_name}!"
-
-
-    app = App(app_ui, server)
+
+
+    @render.text
+    def text():
+        current_user_api = CurrentUserAPI(ApiClient(cfg()))
+        databricks_user_info = current_user_api.me()
+        return f"Hello, {databricks_user_info.display_name}!"
     ```
-    """
 
-    def __init__(
-        self,
-        local_strategy: CredentialsStrategy,
-        client: Optional[Client] = None,
-        user_session_token: Optional[str] = None,
-    ):
-        self._local_strategy = local_strategy
-        self._client = client
-        self._user_session_token = user_session_token
+    This example shows how to construct a databricks Config that is compatible with:
+    - Azure Service Principal credentials in Posit Workbench
+    - Service Account OAuth integration authentication in Posit Connect
+
+    This examples uses environment variables to load the correct configurations at runtime. Make sure to set
+    the `ARM_CLIENT_ID`, `ARM_CLIENT_SECRET`, and `ARM_TENANT_ID` as environment variables in Posit Workbench, but do
+    not set them inside of Posit Connect.
 
-    def sql_credentials_provider(self, *args, **kwargs):
-        """The sql connector attempts to call the credentials provider w/o any args.
+    ```python
+    import os
 
-        The SQL client's `ExternalAuthProvider` is not compatible w/ the SDK's implementation of
-        `CredentialsProvider`, so create a no-arg lambda that wraps the args defined by the real caller.
-        This way we can pass in a databricks `Config` object required by most of the SDK's `CredentialsProvider`
-        implementations from where `sql.connect` is called.
+    from databricks.sdk.core import ApiClient
+    from databricks.sdk.core.credentials_provider import azure_service_principal
path: src/posit/connect/external/databricks.py
user: login: plascaray
body: ```suggestion
    from databricks.sdk.core import ApiClient, azure_service_principal
```
created_at: 2025-03-15T20:17:47Z
author_association: COLLABORATOR

PR comments:
user: login: kmasiello
created_at: 2025-02-15T00:27:39Z
body: My review is not of the code itself, but from the perspective of the developer/publisher trying to understand the correct usage of this helper. Some of my comments may reflect naivety in Python, but this may be representative of many of our users and we want them to be able to use this helper without significant mental overhead.  

Here's a markup of the questions and confusion I had in trying to understand the shiny example.  I will summarize each main point (numbered) separately below for discussion.  (and no, i don't usually do PR comments with images 😂)
![image](https://github.com/user-attachments/assets/984cf371-dfa0-4ca3-b692-d2cbc3f19edf)

author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:30:37Z
body: (1). "Posit", "Credentials", "Strategy", "posit_strategy", "credentials_strategy", "connect_strategy", "PositCredentials", "PositConnectCredentials", "PositWorkbenchCredentials" ...
This is very distracting and overwhelming, making it difficult to follow the logic path for what we're doing here.
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:32:57Z
body: (2). `session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")` or `session_token = flask.request.headers.get("Posit-Connect-User-Session-Token")`.  The viewer's session token is always going to be retrieved at this header, so don't make me have to write the code to go get it.  
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:35:55Z
body: (3). `workbench_strategy=PositWorkbenchCredentialsStrategy(Config(profile="workbench")),` I don't love this because it's a lot to type out.  Why not just `workbench_strategy=Config(profile="workbench")` or set this by default so I can just do `workbench_strategy=PositWorkbenchCredentialsStrategy()` but if I happen to have a different profile defined (maybe because I'm using a service account and need M2M) then I can change to a different profile.
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:38:30Z
body: (4) `connect_strategy=PositConnectCredentialsStrategy(user_session_token=session_token)`. Again, this is a lot to type out.  and it would align better with my mental model of credential methods on connect if I could instead specify `connect_strategy=PositConnectCredentialsStrategy(type=[viewer_oath | service_account_oath | envvars])`
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:40:37Z
body: (5). `PositWorkbenchCredentialsStrategy` and `PositConnectCredentialsStrategy` - again, word soup. Maybe we call these credential methods or credential types?  I think the helper should have one "Strategy", not a PositStrategy and a <Product>Strategy
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:41:33Z
body: (6). `posit_strategy` isn't descriptive of what it actually is. It's a strategy for handling credentials, not for handling Posit.
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:47:55Z
body: (7) here's where I completely lost the logic trail. 
Part of my confusion was not realizing that `credentials_provider` is a valid argument to `sql.connect`.  I had only used `access_token=` before. So among the word soup from posit_sdk and now the databricks sql connector adding a related term, it was hard to follow. I've stared at this for an hour and I still don't understand the flow here.
author_association: NONE
user: login: kmasiello
created_at: 2025-02-15T00:56:38Z
body: 🪱 🥫
Databricks SQL Connector is great. But what about authenticating to compute clusters using Spark and `databricks-connect` ?
author_association: NONE
user: login: mconflitti-pbc
created_at: 2025-02-18T14:32:22Z
body: > (2). `session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")` or `session_token = flask.request.headers.get("Posit-Connect-User-Session-Token")`. The viewer's session token is always going to be retrieved at this header, so don't make me have to write the code to go get it.

Great point! I think the trouble with doing this for people though is that its location is dependent on the app/framework they are using. Would be neat to automatically find it though, I agree.
author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-02-18T14:55:10Z
body: > Databricks SQL Connector is great. But what about authenticating to compute clusters using Spark and databricks-connect ?

Any of the various `CredentialsStrategy` implementations from this PR or from the databricks-sdk should also work with `databricks-connect`
author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-02-18T15:27:30Z
body: > (1). "Posit", "Credentials", "Strategy", "posit_strategy", "credentials_strategy", "connect_strategy", "PositCredentials", "PositConnectCredentials", "PositWorkbenchCredentials" ...
This is very distracting and overwhelming, making it difficult to follow the logic path for what we're doing here.

We can do my best to hide some of this from the user but this is an artifact of the databricks SDK, not something we are imposing in our client. 

https://github.com/databricks/databricks-sdk-py/blob/998a117c43a7bc901710d263a7a7ab0d66ae8b8c/databricks/sdk/config.py#L103-L121

> (2). `session_token = session.http_conn.headers.get("Posit-Connect-User-Session-Token")` or `session_token = flask.request.headers.get("Posit-Connect-User-Session-Token")`. The viewer's session token is always going to be retrieved at this header, so don't make me have to write the code to go get it.

Right. As Matt said, this is framework-dependent. We could consider adding a helper for each framework but that feels like it will lead to even more confusion like we see with `(1)`

> (3). `workbench_strategy=PositWorkbenchCredentialsStrategy(Config(profile="workbench"))`, I don't love this because it's a lot to type out. Why not just `workbench_strategy=Config(profile="workbench")` or set this by default so I can just do `workbench_strategy=PositWorkbenchCredentialsStrategy()` but if I happen to have a different profile defined (maybe because I'm using a service account and need M2M) then I can change to a different profile.

Good call. I think we can do `workbench_strategy=PositWorkbenchCredentialsStrategy()` pretty easily.

> (4) `connect_strategy=PositConnectCredentialsStrategy(user_session_token=session_token)`. Again, this is a lot to type out. and it would align better with my mental model of credential methods on connect if I could instead specify `connect_strategy=PositConnectCredentialsStrategy(type=[viewer_oath | service_account_oath | envvars])`

We can revisit this but if the choice is `viewer_oauth` then _someone_ has to pass the token from the header into the `PositConnectCredentialsStrategy`.

The way this is implemented at the moment, if `user_session_token` is not provided then we attempt to default to Service Account auth, so the presence of `user_session_token` is what drives the type of auth used. This way if the publisher changes the oauth integration association in Connect from a Viewer integration to a Service Account integration, then they don't need to update any of their content code. If we want to make this an explicit choice in the code then we can definitely do that instead. I tend to prefer explicit options but this was one area where I tried to make the user do less typing.

> (5). `PositWorkbenchCredentialsStrategy` and `PositConnectCredentialsStrategy` - again, word soup. Maybe we call these credential methods or credential types? I think the helper should have one "Strategy", not a PositStrategy and a Strategy

If you want to write content that _only_ works on workbench then you don't need the `PositStrategy`, simply pass a `PositWorkbenchStrategy` when constructing the `Config(credentials_strategy=PositWorkbenchStrategy())`. The `PositStrategy` is useful when you want to author content that works in all 3 environments without making any code changes.

More broadly, the word choices "CredentialsStrategy" and "CredentialsProvider" are constructs from the databricks-sdk, not something we came up with here. We tried to be consistent with their naming in the SDK to avoid confusion but we can call these things whatever we want.

> (6). `posit_strategy` isn't descriptive of what it actually is. It's a strategy for handling credentials, not for handling Posit.

This one is just a var name so an easy fix. How about we just call this `creds` or `credentials`? edit: Although I do think it's a little confusing to call it "posit_credentials" - These aren't posit's credentials, they are using posit's strategy when obtaining the user's databricks credentials.

> (7) here's where I completely lost the logic trail.
Part of my confusion was not realizing that `credentials_provider` is a valid argument to `sql.connect`. I had only used `access_token=` before. So among the word soup from posit_sdk and now the databricks sql connector adding a related term, it was hard to follow. I've stared at this for an hour and I still don't understand the flow here.

This is a major part of the friction with implementing these helpers. We are trying to make something easy to do for our users when some of these libraries (databricks-sdk and databricks-sql) aren't even compatible inside of Databricks' own ecosystem of tools.

https://github.com/databricks/databricks-sql-python/issues/148#issuecomment-2271561365
author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-02-18T15:44:30Z
body: Oh an regarding the circular reference mentioned in `(7)`. Yes. It's another reason this is so challenging. `Config.credentials_strategy` is a `Callable` which is called with `Config` as an argument.


author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-02-18T21:17:42Z
body: @kmasiello could you take another look at this when you have a minute? 

I've done some refactoring based on your feedback. One of the main issues we had before was that there was this circular dependency between a `databricks.Config` and the `credentials_strategy`. I've tried to remove this by adding a `new_config` helper for constructing a databricks config that is all set up with the right strategy. The defaults should now work in _most_ environments but can be overridden explicitly if desired. I still need to test all the different combinations but the basic gist is:

```python
import streamlit as st
from databricks.sdk.core import ApiClient
from databricks.sdk.service.iam import CurrentUserAPI

from posit.connect.external.databricks import (
    new_config,
    ConnectStrategy,
)

session_token = st.context.headers.get("Posit-Connect-User-Session-Token")
cfg = new_config(
    posit_connect_strategy=ConnectStrategy(
        user_session_token=session_token
    ),
)

databricks_user = CurrentUserAPI(ApiClient(cfg)).me()
st.write(f"Hello, {databricks_user.display_name}!")
``` 

Unfortunately we can't really get rid of the need to pass in the session_token arg when specifying the connect strategy using viewer auth but hopefully this is an improvement.

If you want to use a Service Account oauth integration when running on Connect then the empty default configuration should be sufficient. This code should work locally, on workbench, and on Connect:

```python
import streamlit as st
from databricks.sdk.core import ApiClient
from databricks.sdk.service.iam import CurrentUserAPI
from posit.connect.external.databricks import new_config

cfg = new_config()

databricks_user = CurrentUserAPI(ApiClient(cfg)).me()
st.write(f"Hello, {databricks_user.display_name}!")
```
author_association: COLLABORATOR
user: login: github-actions[bot]
created_at: 2025-03-04T23:17:51Z
body: # ☂️ Python Coverage
> current status: ✅
## Overall Coverage
| Lines | Covered | Coverage | Threshold | Status |
| :---: | :-----: | :------: | :-------: | :----: |
|  1860 |   1751  |    94%   |     0%    |   🟢   |
## New Files
| File                                       | Coverage | Status |
| :----------------------------------------- | :------: | :----: |
| src/posit/workbench/\__init__.py           |   100%   |   🟢   |
| src/posit/workbench/external/\__init__.py  |   100%   |   🟢   |
| src/posit/workbench/external/databricks.py |    84%   |   🟢   |
| **TOTAL**                                  |  **95%** |   🟢   |
## Modified Files
| File                                     | Coverage | Status |
| :--------------------------------------- | :------: | :----: |
| src/posit/\__init__.py                   |   100%   |   🟢   |
| src/posit/connect/\_utils.py             |   100%   |   🟢   |
| src/posit/connect/client.py              |    99%   |   🟢   |
| src/posit/connect/external/databricks.py |    94%   |   🟢   |
| **TOTAL**                                |  **98%** |   🟢   |


> **updated for commit: `6d5cfe5` by [action](https://github.com/marketplace/actions/python-coverage)🐍**
author_association: NONE
user: login: nealrichardson
created_at: 2025-03-05T13:56:10Z
body: This still feels like a lot of complexity for an app developer to have to wrestle with (or, more likely, just cargo cult):

```
    return databricks_config(
        posit_default_strategy=databricks_cli,
        posit_workbench_strategy=WorkbenchStrategy(),
        posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
        host=os.getenv("DATABRICKS_HOST"),
        warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
    )
```

What if instead it were

```
    return databricks_config(user_session_token = session_token)
```

and everything else were set by defaults in the function signature? Seems totally reasonable to use conventions for env vars for defaults, and as for `posit_default_strategy` and `posit_workbench_strategy`, would anyone ever want something other than the default you provided? (If so, they could still pass it as an argument.) 
author_association: COLLABORATOR
user: login: tdstein
created_at: 2025-03-05T14:11:16Z
body: > This still feels like a lot of complexity for an app developer to have to wrestle with

Agreed. You have all the building blocks now. Applying sensible defaults to `databricks_config` would be a nice UX.
author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-03-05T14:57:53Z
body: > This still feels like a lot of complexity for an app developer to have to wrestle with (or, more likely, just cargo cult):
> 
> ```
>     return databricks_config(
>         posit_default_strategy=databricks_cli,
>         posit_workbench_strategy=WorkbenchStrategy(),
>         posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
>         host=os.getenv("DATABRICKS_HOST"),
>         warehouse_id=os.getenv("DATABRICKS_WAREHOUSE_ID"),
>     )
> ```
> 
> What if instead it were
> 
> ```
>     return databricks_config(user_session_token = session_token)
> ```
> 
> and everything else were set by defaults in the function signature? Seems totally reasonable to use conventions for env vars for defaults, and as for `posit_default_strategy` and `posit_workbench_strategy`, would anyone ever want something other than the default you provided? (If so, they could still pass it as an argument.)

That's basically how this is implemented with a minor difference. The example in the PR description shows how to override the default values. The env vars also also already handled the way that you describe, but it's useful to show them in our docs because if they aren't set then you get a rather opaque error message from Databricks SDK/SQL.

The minimal viewer auth example would be:

```python
return databricks_config(
  posit_connect_strategy=ConnectStrategy(user_session_token=session_token)
)
```

And the minimal service account auth example would be:

```python
return databricks_config()
```

However note that the minimal examples would not work when Workbench-managed Databricks credentials are used. You have to enable it by passing `WorkbenchStrategy(). There's some awkwardness with how the Databricks Config object is initialized.
author_association: COLLABORATOR
user: login: dbkegley
created_at: 2025-03-18T16:14:30Z
body: @tdstein FYI - I've moved the workbench credentials helper into a new module `posit.workbench.external`

Planning to merge this later today and update the release notes for the next release.
author_association: COLLABORATOR