databricks-sdk 0.28.0__py3-none-any.whl → 0.30.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of databricks-sdk might be problematic. Click here for more details.

Files changed (31) hide show
  1. databricks/sdk/__init__.py +74 -22
  2. databricks/sdk/config.py +89 -48
  3. databricks/sdk/core.py +38 -9
  4. databricks/sdk/credentials_provider.py +134 -57
  5. databricks/sdk/data_plane.py +65 -0
  6. databricks/sdk/dbutils.py +81 -3
  7. databricks/sdk/mixins/files.py +12 -4
  8. databricks/sdk/oauth.py +8 -6
  9. databricks/sdk/service/apps.py +977 -0
  10. databricks/sdk/service/billing.py +602 -218
  11. databricks/sdk/service/catalog.py +263 -62
  12. databricks/sdk/service/compute.py +515 -94
  13. databricks/sdk/service/dashboards.py +1310 -2
  14. databricks/sdk/service/iam.py +99 -88
  15. databricks/sdk/service/jobs.py +159 -166
  16. databricks/sdk/service/marketplace.py +74 -58
  17. databricks/sdk/service/oauth2.py +149 -70
  18. databricks/sdk/service/pipelines.py +73 -53
  19. databricks/sdk/service/serving.py +332 -694
  20. databricks/sdk/service/settings.py +424 -4
  21. databricks/sdk/service/sharing.py +235 -26
  22. databricks/sdk/service/sql.py +2484 -553
  23. databricks/sdk/service/vectorsearch.py +75 -0
  24. databricks/sdk/useragent.py +144 -0
  25. databricks/sdk/version.py +1 -1
  26. {databricks_sdk-0.28.0.dist-info → databricks_sdk-0.30.0.dist-info}/METADATA +37 -16
  27. {databricks_sdk-0.28.0.dist-info → databricks_sdk-0.30.0.dist-info}/RECORD +31 -28
  28. {databricks_sdk-0.28.0.dist-info → databricks_sdk-0.30.0.dist-info}/WHEEL +1 -1
  29. {databricks_sdk-0.28.0.dist-info → databricks_sdk-0.30.0.dist-info}/LICENSE +0 -0
  30. {databricks_sdk-0.28.0.dist-info → databricks_sdk-0.30.0.dist-info}/NOTICE +0 -0
  31. {databricks_sdk-0.28.0.dist-info → databricks_sdk-0.30.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,13 @@
1
1
  import databricks.sdk.core as client
2
2
  import databricks.sdk.dbutils as dbutils
3
3
  from databricks.sdk import azure
4
- from databricks.sdk.credentials_provider import CredentialsProvider
4
+ from databricks.sdk.credentials_provider import CredentialsStrategy
5
5
  from databricks.sdk.mixins.compute import ClustersExt
6
6
  from databricks.sdk.mixins.files import DbfsExt
7
7
  from databricks.sdk.mixins.workspace import WorkspaceExt
8
+ from databricks.sdk.service.apps import AppsAPI
8
9
  from databricks.sdk.service.billing import (BillableUsageAPI, BudgetsAPI,
9
- LogDeliveryAPI)
10
+ LogDeliveryAPI, UsageDashboardsAPI)
10
11
  from databricks.sdk.service.catalog import (AccountMetastoreAssignmentsAPI,
11
12
  AccountMetastoresAPI,
12
13
  AccountStorageCredentialsAPI,
@@ -27,7 +28,7 @@ from databricks.sdk.service.compute import (ClusterPoliciesAPI, ClustersAPI,
27
28
  InstancePoolsAPI,
28
29
  InstanceProfilesAPI, LibrariesAPI,
29
30
  PolicyFamiliesAPI)
30
- from databricks.sdk.service.dashboards import LakeviewAPI
31
+ from databricks.sdk.service.dashboards import GenieAPI, LakeviewAPI
31
32
  from databricks.sdk.service.files import DbfsAPI, FilesAPI
32
33
  from databricks.sdk.service.iam import (AccountAccessControlAPI,
33
34
  AccountAccessControlProxyAPI,
@@ -55,7 +56,8 @@ from databricks.sdk.service.provisioning import (CredentialsAPI,
55
56
  NetworksAPI, PrivateAccessAPI,
56
57
  StorageAPI, VpcEndpointsAPI,
57
58
  Workspace, WorkspacesAPI)
58
- from databricks.sdk.service.serving import AppsAPI, ServingEndpointsAPI
59
+ from databricks.sdk.service.serving import (ServingEndpointsAPI,
60
+ ServingEndpointsDataPlaneAPI)
59
61
  from databricks.sdk.service.settings import (AccountIpAccessListsAPI,
60
62
  AccountSettingsAPI,
61
63
  AutomaticClusterUpdateAPI,
@@ -67,6 +69,7 @@ from databricks.sdk.service.settings import (AccountIpAccessListsAPI,
67
69
  EsmEnablementAccountAPI,
68
70
  IpAccessListsAPI,
69
71
  NetworkConnectivityAPI,
72
+ NotificationDestinationsAPI,
70
73
  PersonalComputeAPI,
71
74
  RestrictWorkspaceAdminsAPI,
72
75
  SettingsAPI, TokenManagementAPI,
@@ -74,11 +77,13 @@ from databricks.sdk.service.settings import (AccountIpAccessListsAPI,
74
77
  from databricks.sdk.service.sharing import (CleanRoomsAPI, ProvidersAPI,
75
78
  RecipientActivationAPI,
76
79
  RecipientsAPI, SharesAPI)
77
- from databricks.sdk.service.sql import (AlertsAPI, DashboardsAPI,
78
- DashboardWidgetsAPI, DataSourcesAPI,
79
- DbsqlPermissionsAPI, QueriesAPI,
80
+ from databricks.sdk.service.sql import (AlertsAPI, AlertsLegacyAPI,
81
+ DashboardsAPI, DashboardWidgetsAPI,
82
+ DataSourcesAPI, DbsqlPermissionsAPI,
83
+ QueriesAPI, QueriesLegacyAPI,
80
84
  QueryHistoryAPI,
81
85
  QueryVisualizationsAPI,
86
+ QueryVisualizationsLegacyAPI,
82
87
  StatementExecutionAPI, WarehousesAPI)
83
88
  from databricks.sdk.service.vectorsearch import (VectorSearchEndpointsAPI,
84
89
  VectorSearchIndexesAPI)
@@ -131,7 +136,8 @@ class WorkspaceClient:
131
136
  debug_headers: bool = None,
132
137
  product="unknown",
133
138
  product_version="0.0.0",
134
- credentials_provider: CredentialsProvider = None,
139
+ credentials_strategy: CredentialsStrategy = None,
140
+ credentials_provider: CredentialsStrategy = None,
135
141
  config: client.Config = None):
136
142
  if not config:
137
143
  config = client.Config(host=host,
@@ -152,6 +158,7 @@ class WorkspaceClient:
152
158
  cluster_id=cluster_id,
153
159
  google_credentials=google_credentials,
154
160
  google_service_account=google_service_account,
161
+ credentials_strategy=credentials_strategy,
155
162
  credentials_provider=credentials_provider,
156
163
  debug_truncate_bytes=debug_truncate_bytes,
157
164
  debug_headers=debug_headers,
@@ -160,8 +167,10 @@ class WorkspaceClient:
160
167
  self._config = config.copy()
161
168
  self._dbutils = _make_dbutils(self._config)
162
169
  self._api_client = client.ApiClient(self._config)
170
+ serving_endpoints = ServingEndpointsAPI(self._api_client)
163
171
  self._account_access_control_proxy = AccountAccessControlProxyAPI(self._api_client)
164
172
  self._alerts = AlertsAPI(self._api_client)
173
+ self._alerts_legacy = AlertsLegacyAPI(self._api_client)
165
174
  self._apps = AppsAPI(self._api_client)
166
175
  self._artifact_allowlists = ArtifactAllowlistsAPI(self._api_client)
167
176
  self._catalogs = CatalogsAPI(self._api_client)
@@ -186,6 +195,7 @@ class WorkspaceClient:
186
195
  self._external_locations = ExternalLocationsAPI(self._api_client)
187
196
  self._files = FilesAPI(self._api_client)
188
197
  self._functions = FunctionsAPI(self._api_client)
198
+ self._genie = GenieAPI(self._api_client)
189
199
  self._git_credentials = GitCredentialsAPI(self._api_client)
190
200
  self._global_init_scripts = GlobalInitScriptsAPI(self._api_client)
191
201
  self._grants = GrantsAPI(self._api_client)
@@ -199,6 +209,7 @@ class WorkspaceClient:
199
209
  self._metastores = MetastoresAPI(self._api_client)
200
210
  self._model_registry = ModelRegistryAPI(self._api_client)
201
211
  self._model_versions = ModelVersionsAPI(self._api_client)
212
+ self._notification_destinations = NotificationDestinationsAPI(self._api_client)
202
213
  self._online_tables = OnlineTablesAPI(self._api_client)
203
214
  self._permission_migration = PermissionMigrationAPI(self._api_client)
204
215
  self._permissions = PermissionsAPI(self._api_client)
@@ -215,8 +226,10 @@ class WorkspaceClient:
215
226
  self._providers = ProvidersAPI(self._api_client)
216
227
  self._quality_monitors = QualityMonitorsAPI(self._api_client)
217
228
  self._queries = QueriesAPI(self._api_client)
229
+ self._queries_legacy = QueriesLegacyAPI(self._api_client)
218
230
  self._query_history = QueryHistoryAPI(self._api_client)
219
231
  self._query_visualizations = QueryVisualizationsAPI(self._api_client)
232
+ self._query_visualizations_legacy = QueryVisualizationsLegacyAPI(self._api_client)
220
233
  self._recipient_activation = RecipientActivationAPI(self._api_client)
221
234
  self._recipients = RecipientsAPI(self._api_client)
222
235
  self._registered_models = RegisteredModelsAPI(self._api_client)
@@ -224,7 +237,8 @@ class WorkspaceClient:
224
237
  self._schemas = SchemasAPI(self._api_client)
225
238
  self._secrets = SecretsAPI(self._api_client)
226
239
  self._service_principals = ServicePrincipalsAPI(self._api_client)
227
- self._serving_endpoints = ServingEndpointsAPI(self._api_client)
240
+ self._serving_endpoints = serving_endpoints
241
+ self._serving_endpoints_data_plane = ServingEndpointsDataPlaneAPI(self._api_client, serving_endpoints)
228
242
  self._settings = SettingsAPI(self._api_client)
229
243
  self._shares = SharesAPI(self._api_client)
230
244
  self._statement_execution = StatementExecutionAPI(self._api_client)
@@ -265,6 +279,11 @@ class WorkspaceClient:
265
279
  """The alerts API can be used to perform CRUD operations on alerts."""
266
280
  return self._alerts
267
281
 
282
+ @property
283
+ def alerts_legacy(self) -> AlertsLegacyAPI:
284
+ """The alerts API can be used to perform CRUD operations on alerts."""
285
+ return self._alerts_legacy
286
+
268
287
  @property
269
288
  def apps(self) -> AppsAPI:
270
289
  """Apps run directly on a customer’s Databricks instance, integrate with their data, use and extend Databricks services, and enable users to interact through single sign-on."""
@@ -385,6 +404,11 @@ class WorkspaceClient:
385
404
  """Functions implement User-Defined Functions (UDFs) in Unity Catalog."""
386
405
  return self._functions
387
406
 
407
+ @property
408
+ def genie(self) -> GenieAPI:
409
+ """Genie provides a no-code experience for business users, powered by AI/BI."""
410
+ return self._genie
411
+
388
412
  @property
389
413
  def git_credentials(self) -> GitCredentialsAPI:
390
414
  """Registers personal access token for Databricks to do operations on behalf of the user."""
@@ -450,6 +474,11 @@ class WorkspaceClient:
450
474
  """Databricks provides a hosted version of MLflow Model Registry in Unity Catalog."""
451
475
  return self._model_versions
452
476
 
477
+ @property
478
+ def notification_destinations(self) -> NotificationDestinationsAPI:
479
+ """The notification destinations API lets you programmatically manage a workspace's notification destinations."""
480
+ return self._notification_destinations
481
+
453
482
  @property
454
483
  def online_tables(self) -> OnlineTablesAPI:
455
484
  """Online tables provide lower latency and higher QPS access to data from Delta tables."""
@@ -457,7 +486,7 @@ class WorkspaceClient:
457
486
 
458
487
  @property
459
488
  def permission_migration(self) -> PermissionMigrationAPI:
460
- """This spec contains undocumented permission migration APIs used in https://github.com/databrickslabs/ucx."""
489
+ """APIs for migrating acl permissions, used only by the ucx tool: https://github.com/databrickslabs/ucx."""
461
490
  return self._permission_migration
462
491
 
463
492
  @property
@@ -522,19 +551,29 @@ class WorkspaceClient:
522
551
 
523
552
  @property
524
553
  def queries(self) -> QueriesAPI:
525
- """These endpoints are used for CRUD operations on query definitions."""
554
+ """The queries API can be used to perform CRUD operations on queries."""
526
555
  return self._queries
527
556
 
557
+ @property
558
+ def queries_legacy(self) -> QueriesLegacyAPI:
559
+ """These endpoints are used for CRUD operations on query definitions."""
560
+ return self._queries_legacy
561
+
528
562
  @property
529
563
  def query_history(self) -> QueryHistoryAPI:
530
- """Access the history of queries through SQL warehouses."""
564
+ """A service responsible for storing and retrieving the list of queries run against SQL endpoints, serverless compute, and DLT."""
531
565
  return self._query_history
532
566
 
533
567
  @property
534
568
  def query_visualizations(self) -> QueryVisualizationsAPI:
535
- """This is an evolving API that facilitates the addition and removal of vizualisations from existing queries within the Databricks Workspace."""
569
+ """This is an evolving API that facilitates the addition and removal of visualizations from existing queries in the Databricks Workspace."""
536
570
  return self._query_visualizations
537
571
 
572
+ @property
573
+ def query_visualizations_legacy(self) -> QueryVisualizationsLegacyAPI:
574
+ """This is an evolving API that facilitates the addition and removal of vizualisations from existing queries within the Databricks Workspace."""
575
+ return self._query_visualizations_legacy
576
+
538
577
  @property
539
578
  def recipient_activation(self) -> RecipientActivationAPI:
540
579
  """The Recipient Activation API is only applicable in the open sharing model where the recipient object has the authentication type of `TOKEN`."""
@@ -575,6 +614,11 @@ class WorkspaceClient:
575
614
  """The Serving Endpoints API allows you to create, update, and delete model serving endpoints."""
576
615
  return self._serving_endpoints
577
616
 
617
+ @property
618
+ def serving_endpoints_data_plane(self) -> ServingEndpointsDataPlaneAPI:
619
+ """Serving endpoints DataPlane provides a set of operations to interact with data plane endpoints for Serving endpoints service."""
620
+ return self._serving_endpoints_data_plane
621
+
578
622
  @property
579
623
  def settings(self) -> SettingsAPI:
580
624
  """Workspace Settings API allows users to manage settings at the workspace level."""
@@ -700,7 +744,8 @@ class AccountClient:
700
744
  debug_headers: bool = None,
701
745
  product="unknown",
702
746
  product_version="0.0.0",
703
- credentials_provider: CredentialsProvider = None,
747
+ credentials_strategy: CredentialsStrategy = None,
748
+ credentials_provider: CredentialsStrategy = None,
704
749
  config: client.Config = None):
705
750
  if not config:
706
751
  config = client.Config(host=host,
@@ -721,6 +766,7 @@ class AccountClient:
721
766
  cluster_id=cluster_id,
722
767
  google_credentials=google_credentials,
723
768
  google_service_account=google_service_account,
769
+ credentials_strategy=credentials_strategy,
724
770
  credentials_provider=credentials_provider,
725
771
  debug_truncate_bytes=debug_truncate_bytes,
726
772
  debug_headers=debug_headers,
@@ -730,7 +776,6 @@ class AccountClient:
730
776
  self._api_client = client.ApiClient(self._config)
731
777
  self._access_control = AccountAccessControlAPI(self._api_client)
732
778
  self._billable_usage = BillableUsageAPI(self._api_client)
733
- self._budgets = BudgetsAPI(self._api_client)
734
779
  self._credentials = CredentialsAPI(self._api_client)
735
780
  self._custom_app_integration = CustomAppIntegrationAPI(self._api_client)
736
781
  self._encryption_keys = EncryptionKeysAPI(self._api_client)
@@ -749,10 +794,12 @@ class AccountClient:
749
794
  self._settings = AccountSettingsAPI(self._api_client)
750
795
  self._storage = StorageAPI(self._api_client)
751
796
  self._storage_credentials = AccountStorageCredentialsAPI(self._api_client)
797
+ self._usage_dashboards = UsageDashboardsAPI(self._api_client)
752
798
  self._users = AccountUsersAPI(self._api_client)
753
799
  self._vpc_endpoints = VpcEndpointsAPI(self._api_client)
754
800
  self._workspace_assignment = WorkspaceAssignmentAPI(self._api_client)
755
801
  self._workspaces = WorkspacesAPI(self._api_client)
802
+ self._budgets = BudgetsAPI(self._api_client)
756
803
 
757
804
  @property
758
805
  def config(self) -> client.Config:
@@ -772,11 +819,6 @@ class AccountClient:
772
819
  """This API allows you to download billable usage logs for the specified account and date range."""
773
820
  return self._billable_usage
774
821
 
775
- @property
776
- def budgets(self) -> BudgetsAPI:
777
- """These APIs manage budget configuration including notifications for exceeding a budget for a period."""
778
- return self._budgets
779
-
780
822
  @property
781
823
  def credentials(self) -> CredentialsAPI:
782
824
  """These APIs manage credential configurations for this workspace."""
@@ -784,7 +826,7 @@ class AccountClient:
784
826
 
785
827
  @property
786
828
  def custom_app_integration(self) -> CustomAppIntegrationAPI:
787
- """These APIs enable administrators to manage custom oauth app integrations, which is required for adding/using Custom OAuth App Integration like Tableau Cloud for Databricks in AWS cloud."""
829
+ """These APIs enable administrators to manage custom OAuth app integrations, which is required for adding/using Custom OAuth App Integration like Tableau Cloud for Databricks in AWS cloud."""
788
830
  return self._custom_app_integration
789
831
 
790
832
  @property
@@ -839,7 +881,7 @@ class AccountClient:
839
881
 
840
882
  @property
841
883
  def published_app_integration(self) -> PublishedAppIntegrationAPI:
842
- """These APIs enable administrators to manage published oauth app integrations, which is required for adding/using Published OAuth App Integration like Tableau Desktop for Databricks in AWS cloud."""
884
+ """These APIs enable administrators to manage published OAuth app integrations, which is required for adding/using Published OAuth App Integration like Tableau Desktop for Databricks in AWS cloud."""
843
885
  return self._published_app_integration
844
886
 
845
887
  @property
@@ -867,6 +909,11 @@ class AccountClient:
867
909
  """These APIs manage storage credentials for a particular metastore."""
868
910
  return self._storage_credentials
869
911
 
912
+ @property
913
+ def usage_dashboards(self) -> UsageDashboardsAPI:
914
+ """These APIs manage usage dashboards for this account."""
915
+ return self._usage_dashboards
916
+
870
917
  @property
871
918
  def users(self) -> AccountUsersAPI:
872
919
  """User identities recognized by Databricks and represented by email addresses."""
@@ -887,6 +934,11 @@ class AccountClient:
887
934
  """These APIs manage workspaces for this account."""
888
935
  return self._workspaces
889
936
 
937
+ @property
938
+ def budgets(self) -> BudgetsAPI:
939
+ """These APIs manage budget configurations for this account."""
940
+ return self._budgets
941
+
890
942
  def get_workspace_client(self, workspace: Workspace) -> WorkspaceClient:
891
943
  """Constructs a ``WorkspaceClient`` for the given workspace.
892
944
 
databricks/sdk/config.py CHANGED
@@ -3,19 +3,18 @@ import copy
3
3
  import logging
4
4
  import os
5
5
  import pathlib
6
- import platform
7
6
  import sys
8
7
  import urllib.parse
9
8
  from typing import Dict, Iterable, Optional
10
9
 
11
10
  import requests
12
11
 
12
+ from . import useragent
13
13
  from .clock import Clock, RealClock
14
- from .credentials_provider import CredentialsProvider, DefaultCredentials
14
+ from .credentials_provider import CredentialsStrategy, DefaultCredentials
15
15
  from .environments import (ALL_ENVS, AzureEnvironment, Cloud,
16
16
  DatabricksEnvironment, get_environment_for_hostname)
17
- from .oauth import OidcEndpoints
18
- from .version import __version__
17
+ from .oauth import OidcEndpoints, Token
19
18
 
20
19
  logger = logging.getLogger('databricks.sdk')
21
20
 
@@ -44,6 +43,16 @@ class ConfigAttribute:
44
43
  return f"<ConfigAttribute '{self.name}' {self.transform.__name__}>"
45
44
 
46
45
 
46
+ def with_product(product: str, product_version: str):
47
+ """[INTERNAL API] Change the product name and version used in the User-Agent header."""
48
+ useragent.with_product(product, product_version)
49
+
50
+
51
+ def with_user_agent_extra(key: str, value: str):
52
+ """[INTERNAL API] Add extra metadata to the User-Agent header when developing a library."""
53
+ useragent.with_extra(key, value)
54
+
55
+
47
56
  class Config:
48
57
  host: str = ConfigAttribute(env='DATABRICKS_HOST')
49
58
  account_id: str = ConfigAttribute(env='DATABRICKS_ACCOUNT_ID')
@@ -66,6 +75,7 @@ class Config:
66
75
  auth_type: str = ConfigAttribute(env='DATABRICKS_AUTH_TYPE')
67
76
  cluster_id: str = ConfigAttribute(env='DATABRICKS_CLUSTER_ID')
68
77
  warehouse_id: str = ConfigAttribute(env='DATABRICKS_WAREHOUSE_ID')
78
+ serverless_compute_id: str = ConfigAttribute(env='DATABRICKS_SERVERLESS_COMPUTE_ID')
69
79
  skip_verify: bool = ConfigAttribute()
70
80
  http_timeout_seconds: float = ConfigAttribute()
71
81
  debug_truncate_bytes: int = ConfigAttribute(env='DATABRICKS_DEBUG_TRUNCATE_BYTES')
@@ -81,15 +91,25 @@ class Config:
81
91
 
82
92
  def __init__(self,
83
93
  *,
84
- credentials_provider: CredentialsProvider = None,
85
- product="unknown",
86
- product_version="0.0.0",
94
+ # Deprecated. Use credentials_strategy instead.
95
+ credentials_provider: CredentialsStrategy = None,
96
+ credentials_strategy: CredentialsStrategy = None,
97
+ product=None,
98
+ product_version=None,
87
99
  clock: Clock = None,
88
100
  **kwargs):
89
101
  self._header_factory = None
90
102
  self._inner = {}
91
103
  self._user_agent_other_info = []
92
- self._credentials_provider = credentials_provider if credentials_provider else DefaultCredentials()
104
+ if credentials_strategy and credentials_provider:
105
+ raise ValueError(
106
+ "When providing `credentials_strategy` field, `credential_provider` cannot be specified.")
107
+ if credentials_provider:
108
+ logger.warning(
109
+ "parameter 'credentials_provider' is deprecated. Use 'credentials_strategy' instead.")
110
+ self._credentials_strategy = next(
111
+ s for s in [credentials_strategy, credentials_provider,
112
+ DefaultCredentials()] if s is not None)
93
113
  if 'databricks_environment' in kwargs:
94
114
  self.databricks_environment = kwargs['databricks_environment']
95
115
  del kwargs['databricks_environment']
@@ -101,12 +121,14 @@ class Config:
101
121
  self._fix_host_if_needed()
102
122
  self._validate()
103
123
  self.init_auth()
104
- self._product = product
105
- self._product_version = product_version
124
+ self._init_product(product, product_version)
106
125
  except ValueError as e:
107
126
  message = self.wrap_debug_info(str(e))
108
127
  raise ValueError(message) from e
109
128
 
129
+ def oauth_token(self) -> Token:
130
+ return self._credentials_strategy.oauth_token(self)
131
+
110
132
  def wrap_debug_info(self, message: str) -> str:
111
133
  debug_string = self.debug_string()
112
134
  if debug_string:
@@ -211,41 +233,19 @@ class Config:
211
233
  @property
212
234
  def user_agent(self):
213
235
  """ Returns User-Agent header used by this SDK """
214
- py_version = platform.python_version()
215
- os_name = platform.uname().system.lower()
216
-
217
- ua = [
218
- f"{self._product}/{self._product_version}", f"databricks-sdk-py/{__version__}",
219
- f"python/{py_version}", f"os/{os_name}", f"auth/{self.auth_type}",
220
- ]
221
- if len(self._user_agent_other_info) > 0:
222
- ua.append(' '.join(self._user_agent_other_info))
223
- if len(self._upstream_user_agent) > 0:
224
- ua.append(self._upstream_user_agent)
225
- if 'DATABRICKS_RUNTIME_VERSION' in os.environ:
226
- runtime_version = os.environ['DATABRICKS_RUNTIME_VERSION']
227
- if runtime_version != '':
228
- runtime_version = self._sanitize_header_value(runtime_version)
229
- ua.append(f'runtime/{runtime_version}')
230
-
231
- return ' '.join(ua)
232
236
 
233
- @staticmethod
234
- def _sanitize_header_value(value: str) -> str:
235
- value = value.replace(' ', '-')
236
- value = value.replace('/', '-')
237
- return value
237
+ # global user agent includes SDK version, product name & version, platform info,
238
+ # and global extra info. Config can have specific extra info associated with it,
239
+ # such as an override product, auth type, and other user-defined information.
240
+ return useragent.to_string(self._product_info,
241
+ [("auth", self.auth_type)] + self._user_agent_other_info)
238
242
 
239
243
  @property
240
244
  def _upstream_user_agent(self) -> str:
241
- product = os.environ.get('DATABRICKS_SDK_UPSTREAM', None)
242
- product_version = os.environ.get('DATABRICKS_SDK_UPSTREAM_VERSION', None)
243
- if product is not None and product_version is not None:
244
- return f"upstream/{product} upstream-version/{product_version}"
245
- return ""
245
+ return " ".join(f"{k}/{v}" for k, v in useragent._get_upstream_user_agent_info())
246
246
 
247
247
  def with_user_agent_extra(self, key: str, value: str) -> 'Config':
248
- self._user_agent_other_info.append(f"{key}/{value}")
248
+ self._user_agent_other_info.append((key, value))
249
249
  return self
250
250
 
251
251
  @property
@@ -348,13 +348,47 @@ class Config:
348
348
  def _fix_host_if_needed(self):
349
349
  if not self.host:
350
350
  return
351
- # fix url to remove trailing slash
351
+
352
+ # Add a default scheme if it's missing
353
+ if '://' not in self.host:
354
+ self.host = 'https://' + self.host
355
+
352
356
  o = urllib.parse.urlparse(self.host)
353
- if not o.hostname:
354
- # only hostname is specified
355
- self.host = f"https://{self.host}"
356
- else:
357
- self.host = f"{o.scheme}://{o.netloc}"
357
+ # remove trailing slash
358
+ path = o.path.rstrip('/')
359
+ # remove port if 443
360
+ netloc = o.netloc
361
+ if o.port == 443:
362
+ netloc = netloc.split(':')[0]
363
+
364
+ self.host = urllib.parse.urlunparse((o.scheme, netloc, path, o.params, o.query, o.fragment))
365
+
366
+ def load_azure_tenant_id(self):
367
+ """[Internal] Load the Azure tenant ID from the Azure Databricks login page.
368
+
369
+ If the tenant ID is already set, this method does nothing."""
370
+ if not self.is_azure or self.azure_tenant_id is not None or self.host is None:
371
+ return
372
+ login_url = f'{self.host}/aad/auth'
373
+ logger.debug(f'Loading tenant ID from {login_url}')
374
+ resp = requests.get(login_url, allow_redirects=False)
375
+ if resp.status_code // 100 != 3:
376
+ logger.debug(
377
+ f'Failed to get tenant ID from {login_url}: expected status code 3xx, got {resp.status_code}')
378
+ return
379
+ entra_id_endpoint = resp.headers.get('Location')
380
+ if entra_id_endpoint is None:
381
+ logger.debug(f'No Location header in response from {login_url}')
382
+ return
383
+ # The Location header has the following form: https://login.microsoftonline.com/<tenant-id>/oauth2/authorize?...
384
+ # The domain may change depending on the Azure cloud (e.g. login.microsoftonline.us for US Government cloud).
385
+ url = urllib.parse.urlparse(entra_id_endpoint)
386
+ path_segments = url.path.split('/')
387
+ if len(path_segments) < 2:
388
+ logger.debug(f'Invalid path in Location header: {url.path}')
389
+ return
390
+ self.azure_tenant_id = path_segments[1]
391
+ logger.debug(f'Loaded tenant ID: {self.azure_tenant_id}')
358
392
 
359
393
  def _set_inner_config(self, keyword_args: Dict[str, any]):
360
394
  for attr in self.attributes():
@@ -436,12 +470,19 @@ class Config:
436
470
 
437
471
  def init_auth(self):
438
472
  try:
439
- self._header_factory = self._credentials_provider(self)
440
- self.auth_type = self._credentials_provider.auth_type()
473
+ self._header_factory = self._credentials_strategy(self)
474
+ self.auth_type = self._credentials_strategy.auth_type()
441
475
  if not self._header_factory:
442
476
  raise ValueError('not configured')
443
477
  except ValueError as e:
444
- raise ValueError(f'{self._credentials_provider.auth_type()} auth: {e}') from e
478
+ raise ValueError(f'{self._credentials_strategy.auth_type()} auth: {e}') from e
479
+
480
+ def _init_product(self, product, product_version):
481
+ if product is not None or product_version is not None:
482
+ default_product, default_version = useragent.product()
483
+ self._product_info = (product or default_product, product_version or default_version)
484
+ else:
485
+ self._product_info = None
445
486
 
446
487
  def __repr__(self):
447
488
  return f'<{self.debug_string()}>'
databricks/sdk/core.py CHANGED
@@ -4,6 +4,7 @@ from datetime import timedelta
4
4
  from json import JSONDecodeError
5
5
  from types import TracebackType
6
6
  from typing import Any, BinaryIO, Iterator, Type
7
+ from urllib.parse import urlencode
7
8
 
8
9
  from requests.adapters import HTTPAdapter
9
10
 
@@ -13,12 +14,17 @@ from .config import *
13
14
  from .credentials_provider import *
14
15
  from .errors import DatabricksError, error_mapper
15
16
  from .errors.private_link import _is_private_link_redirect
17
+ from .oauth import retrieve_token
16
18
  from .retries import retried
17
19
 
18
20
  __all__ = ['Config', 'DatabricksError']
19
21
 
20
22
  logger = logging.getLogger('databricks.sdk')
21
23
 
24
+ URL_ENCODED_CONTENT_TYPE = "application/x-www-form-urlencoded"
25
+ JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"
26
+ OIDC_TOKEN_PATH = "/oidc/v1/token"
27
+
22
28
 
23
29
  class ApiClient:
24
30
  _cfg: Config
@@ -109,33 +115,54 @@ class ApiClient:
109
115
  flattened = dict(flatten_dict(with_fixed_bools))
110
116
  return flattened
111
117
 
118
+ def get_oauth_token(self, auth_details: str) -> Token:
119
+ if not self._cfg.auth_type:
120
+ self._cfg.authenticate()
121
+ original_token = self._cfg.oauth_token()
122
+ headers = {"Content-Type": URL_ENCODED_CONTENT_TYPE}
123
+ params = urlencode({
124
+ "grant_type": JWT_BEARER_GRANT_TYPE,
125
+ "authorization_details": auth_details,
126
+ "assertion": original_token.access_token
127
+ })
128
+ return retrieve_token(client_id=self._cfg.client_id,
129
+ client_secret=self._cfg.client_secret,
130
+ token_url=self._cfg.host + OIDC_TOKEN_PATH,
131
+ params=params,
132
+ headers=headers)
133
+
112
134
  def do(self,
113
135
  method: str,
114
- path: str,
136
+ path: str = None,
137
+ url: str = None,
115
138
  query: dict = None,
116
139
  headers: dict = None,
117
140
  body: dict = None,
118
141
  raw: bool = False,
119
142
  files=None,
120
143
  data=None,
144
+ auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None,
121
145
  response_headers: List[str] = None) -> Union[dict, BinaryIO]:
122
- # Remove extra `/` from path for Files API
123
- # Once we've fixed the OpenAPI spec, we can remove this
124
- path = re.sub('^/api/2.0/fs/files//', '/api/2.0/fs/files/', path)
125
146
  if headers is None:
126
147
  headers = {}
148
+ if url is None:
149
+ # Remove extra `/` from path for Files API
150
+ # Once we've fixed the OpenAPI spec, we can remove this
151
+ path = re.sub('^/api/2.0/fs/files//', '/api/2.0/fs/files/', path)
152
+ url = f"{self._cfg.host}{path}"
127
153
  headers['User-Agent'] = self._user_agent_base
128
154
  retryable = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
129
155
  is_retryable=self._is_retryable,
130
156
  clock=self._cfg.clock)
131
157
  response = retryable(self._perform)(method,
132
- path,
158
+ url,
133
159
  query=query,
134
160
  headers=headers,
135
161
  body=body,
136
162
  raw=raw,
137
163
  files=files,
138
- data=data)
164
+ data=data,
165
+ auth=auth)
139
166
 
140
167
  resp = dict()
141
168
  for header in response_headers if response_headers else []:
@@ -217,20 +244,22 @@ class ApiClient:
217
244
 
218
245
  def _perform(self,
219
246
  method: str,
220
- path: str,
247
+ url: str,
221
248
  query: dict = None,
222
249
  headers: dict = None,
223
250
  body: dict = None,
224
251
  raw: bool = False,
225
252
  files=None,
226
- data=None):
253
+ data=None,
254
+ auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None):
227
255
  response = self._session.request(method,
228
- f"{self._cfg.host}{path}",
256
+ url,
229
257
  params=self._fix_query_string(query),
230
258
  json=body,
231
259
  headers=headers,
232
260
  files=files,
233
261
  data=data,
262
+ auth=auth,
234
263
  stream=raw,
235
264
  timeout=self._http_timeout_seconds)
236
265
  try: