@@ -82,6 +82,19 @@ def _get_auth_type(local_auth_type: str) -> str:
8282 return POSIT_OAUTH_INTEGRATION_AUTH_TYPE
8383
8484class PositLocalContentCredentialsProvider :
85+ """`CredentialsProvider` implementation which provides a fallback for local development using a client credentials flow.
86+
87+ There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
88+ https://github.com/databricks/cli/issues/1939
89+
90+ Until the CLI issue is resolved, this CredentialsProvider implements the approach described in the Databricks documentation
91+ for manually generating a workspace-level access token using OAuth M2M authentication. Once it has acquired an access token,
92+ it returns it as a Bearer authorization header like other `CredentialsProvider` implementations.
93+
94+ See Also
95+ --------
96+ * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
97+ """
8598
8699 def __init__ (self , token_endpoint_url : str , client_id : str , client_secret : str ):
87100 self ._token_endpoint_url = token_endpoint_url
@@ -142,6 +155,73 @@ def __call__(self) -> Dict[str, str]:
142155 return _new_bearer_authorization_header (credentials )
143156
144157class PositLocalContentCredentialsStrategy (CredentialsStrategy ):
158+ """`CredentialsStrategy` implementation which supports local development using OAuth M2M authentication against databricks.
159+
160+ There is an open issue against the Databricks CLI which prevents it from returning service principal access tokens.
161+ https://github.com/databricks/cli/issues/1939
162+
163+ Until the CLI issue is resolved, this CredentialsStrategy provides a drop-in replacement as a local_strategy that can be used
164+ to develop applications which target Service Account OAuth integrations on Connect.
165+
166+ Examples
167+ --------
168+ In the example below, the PositContentCredentialsStrategy can be initialized anywhere that
169+ the Python process can read environment variables.
170+
171+ CLIENT_ID and CLIENT_SECRET credentials associated with the Databricks Service Principal.
172+
173+ ```python
174+ import os
175+
176+ from posit.connect.external.databricks import PositContentCredentialsStrategy, PositLocalContentCredentialsStrategy
177+
178+ import pandas as pd
179+ from databricks import sql
180+ from databricks.sdk.core import ApiClient, Config
181+ from databricks.sdk.service.iam import CurrentUserAPI
182+
183+ DATABRICKS_HOST = "<REDACTED>"
184+ DATABRICKS_HOST_URL = f"https://{DATABRICKS_HOST}"
185+ SQL_HTTP_PATH = "<REDACTED>"
186+ TOKEN_ENDPOINT_URL = f"https://{DATABRICKS_HOST}/oidc/v1/token"
187+
188+ CLIENT_ID = "<REDACTED>"
189+ CLIENT_SECRET = "<REDACTED>"
190+
191+ # Rather than relying on the Databricks CLI as a local strategy, we use
192+ # PositLocalContentCredentialsStragtegy as a drop-in replacement.
193+ # Can be replaced with the Databricks CLI implementation when
194+ # https://github.com/databricks/cli/issues/1939 is resolved.
195+ local_strategy = PositLocalContentCredentialsStrategy(
196+ token_endpoint_url=TOKEN_ENDPOINT_URL,
197+ client_id=CLIENT_ID,
198+ client_secret=CLIENT_SECRET,
199+ )
200+
201+ posit_strategy = PositContentCredentialsStrategy(local_strategy=local_strategy)
202+
203+ cfg = Config(host=DATABRICKS_HOST_URL, credentials_strategy=posit_strategy)
204+
205+ databricks_user_info = CurrentUserAPI(ApiClient(cfg)).me()
206+ print(f"Hello, {databricks_user_info.display_name}!")
207+
208+ query = "SELECT * FROM samples.nyctaxi.trips LIMIT 10;"
209+ with sql.connect(
210+ server_hostname=DATABRICKS_HOST,
211+ http_path=SQL_HTTP_PATH,
212+ credentials_provider=posit_strategy.sql_credentials_provider(cfg),
213+ ) as connection:
214+ with connection.cursor() as cursor:
215+ cursor.execute(query)
216+ rows = cursor.fetchall()
217+ print(pd.DataFrame([row.asDict() for row in rows]))
218+ ```
219+
220+ See Also
221+ --------
222+ * https://docs.databricks.com/en/dev-tools/auth/oauth-m2m.html#manually-generate-a-workspace-level-access-token
223+ """
224+
145225 def __init__ (self , token_endpoint_url : str , client_id : str , client_secret : str ):
146226 self ._token_endpoint_url = token_endpoint_url
147227 self ._client_id = client_id
0 commit comments