databricks-sdk 0.29.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.

@@ -5,8 +5,9 @@ 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)
@@ -162,8 +167,10 @@ class WorkspaceClient:
162
167
  self._config = config.copy()
163
168
  self._dbutils = _make_dbutils(self._config)
164
169
  self._api_client = client.ApiClient(self._config)
170
+ serving_endpoints = ServingEndpointsAPI(self._api_client)
165
171
  self._account_access_control_proxy = AccountAccessControlProxyAPI(self._api_client)
166
172
  self._alerts = AlertsAPI(self._api_client)
173
+ self._alerts_legacy = AlertsLegacyAPI(self._api_client)
167
174
  self._apps = AppsAPI(self._api_client)
168
175
  self._artifact_allowlists = ArtifactAllowlistsAPI(self._api_client)
169
176
  self._catalogs = CatalogsAPI(self._api_client)
@@ -188,6 +195,7 @@ class WorkspaceClient:
188
195
  self._external_locations = ExternalLocationsAPI(self._api_client)
189
196
  self._files = FilesAPI(self._api_client)
190
197
  self._functions = FunctionsAPI(self._api_client)
198
+ self._genie = GenieAPI(self._api_client)
191
199
  self._git_credentials = GitCredentialsAPI(self._api_client)
192
200
  self._global_init_scripts = GlobalInitScriptsAPI(self._api_client)
193
201
  self._grants = GrantsAPI(self._api_client)
@@ -201,6 +209,7 @@ class WorkspaceClient:
201
209
  self._metastores = MetastoresAPI(self._api_client)
202
210
  self._model_registry = ModelRegistryAPI(self._api_client)
203
211
  self._model_versions = ModelVersionsAPI(self._api_client)
212
+ self._notification_destinations = NotificationDestinationsAPI(self._api_client)
204
213
  self._online_tables = OnlineTablesAPI(self._api_client)
205
214
  self._permission_migration = PermissionMigrationAPI(self._api_client)
206
215
  self._permissions = PermissionsAPI(self._api_client)
@@ -217,8 +226,10 @@ class WorkspaceClient:
217
226
  self._providers = ProvidersAPI(self._api_client)
218
227
  self._quality_monitors = QualityMonitorsAPI(self._api_client)
219
228
  self._queries = QueriesAPI(self._api_client)
229
+ self._queries_legacy = QueriesLegacyAPI(self._api_client)
220
230
  self._query_history = QueryHistoryAPI(self._api_client)
221
231
  self._query_visualizations = QueryVisualizationsAPI(self._api_client)
232
+ self._query_visualizations_legacy = QueryVisualizationsLegacyAPI(self._api_client)
222
233
  self._recipient_activation = RecipientActivationAPI(self._api_client)
223
234
  self._recipients = RecipientsAPI(self._api_client)
224
235
  self._registered_models = RegisteredModelsAPI(self._api_client)
@@ -226,7 +237,8 @@ class WorkspaceClient:
226
237
  self._schemas = SchemasAPI(self._api_client)
227
238
  self._secrets = SecretsAPI(self._api_client)
228
239
  self._service_principals = ServicePrincipalsAPI(self._api_client)
229
- 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)
230
242
  self._settings = SettingsAPI(self._api_client)
231
243
  self._shares = SharesAPI(self._api_client)
232
244
  self._statement_execution = StatementExecutionAPI(self._api_client)
@@ -267,6 +279,11 @@ class WorkspaceClient:
267
279
  """The alerts API can be used to perform CRUD operations on alerts."""
268
280
  return self._alerts
269
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
+
270
287
  @property
271
288
  def apps(self) -> AppsAPI:
272
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."""
@@ -387,6 +404,11 @@ class WorkspaceClient:
387
404
  """Functions implement User-Defined Functions (UDFs) in Unity Catalog."""
388
405
  return self._functions
389
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
+
390
412
  @property
391
413
  def git_credentials(self) -> GitCredentialsAPI:
392
414
  """Registers personal access token for Databricks to do operations on behalf of the user."""
@@ -452,6 +474,11 @@ class WorkspaceClient:
452
474
  """Databricks provides a hosted version of MLflow Model Registry in Unity Catalog."""
453
475
  return self._model_versions
454
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
+
455
482
  @property
456
483
  def online_tables(self) -> OnlineTablesAPI:
457
484
  """Online tables provide lower latency and higher QPS access to data from Delta tables."""
@@ -459,7 +486,7 @@ class WorkspaceClient:
459
486
 
460
487
  @property
461
488
  def permission_migration(self) -> PermissionMigrationAPI:
462
- """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."""
463
490
  return self._permission_migration
464
491
 
465
492
  @property
@@ -524,19 +551,29 @@ class WorkspaceClient:
524
551
 
525
552
  @property
526
553
  def queries(self) -> QueriesAPI:
527
- """These endpoints are used for CRUD operations on query definitions."""
554
+ """The queries API can be used to perform CRUD operations on queries."""
528
555
  return self._queries
529
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
+
530
562
  @property
531
563
  def query_history(self) -> QueryHistoryAPI:
532
- """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."""
533
565
  return self._query_history
534
566
 
535
567
  @property
536
568
  def query_visualizations(self) -> QueryVisualizationsAPI:
537
- """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."""
538
570
  return self._query_visualizations
539
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
+
540
577
  @property
541
578
  def recipient_activation(self) -> RecipientActivationAPI:
542
579
  """The Recipient Activation API is only applicable in the open sharing model where the recipient object has the authentication type of `TOKEN`."""
@@ -577,6 +614,11 @@ class WorkspaceClient:
577
614
  """The Serving Endpoints API allows you to create, update, and delete model serving endpoints."""
578
615
  return self._serving_endpoints
579
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
+
580
622
  @property
581
623
  def settings(self) -> SettingsAPI:
582
624
  """Workspace Settings API allows users to manage settings at the workspace level."""
@@ -734,7 +776,6 @@ class AccountClient:
734
776
  self._api_client = client.ApiClient(self._config)
735
777
  self._access_control = AccountAccessControlAPI(self._api_client)
736
778
  self._billable_usage = BillableUsageAPI(self._api_client)
737
- self._budgets = BudgetsAPI(self._api_client)
738
779
  self._credentials = CredentialsAPI(self._api_client)
739
780
  self._custom_app_integration = CustomAppIntegrationAPI(self._api_client)
740
781
  self._encryption_keys = EncryptionKeysAPI(self._api_client)
@@ -753,10 +794,12 @@ class AccountClient:
753
794
  self._settings = AccountSettingsAPI(self._api_client)
754
795
  self._storage = StorageAPI(self._api_client)
755
796
  self._storage_credentials = AccountStorageCredentialsAPI(self._api_client)
797
+ self._usage_dashboards = UsageDashboardsAPI(self._api_client)
756
798
  self._users = AccountUsersAPI(self._api_client)
757
799
  self._vpc_endpoints = VpcEndpointsAPI(self._api_client)
758
800
  self._workspace_assignment = WorkspaceAssignmentAPI(self._api_client)
759
801
  self._workspaces = WorkspacesAPI(self._api_client)
802
+ self._budgets = BudgetsAPI(self._api_client)
760
803
 
761
804
  @property
762
805
  def config(self) -> client.Config:
@@ -776,11 +819,6 @@ class AccountClient:
776
819
  """This API allows you to download billable usage logs for the specified account and date range."""
777
820
  return self._billable_usage
778
821
 
779
- @property
780
- def budgets(self) -> BudgetsAPI:
781
- """These APIs manage budget configuration including notifications for exceeding a budget for a period."""
782
- return self._budgets
783
-
784
822
  @property
785
823
  def credentials(self) -> CredentialsAPI:
786
824
  """These APIs manage credential configurations for this workspace."""
@@ -788,7 +826,7 @@ class AccountClient:
788
826
 
789
827
  @property
790
828
  def custom_app_integration(self) -> CustomAppIntegrationAPI:
791
- """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."""
792
830
  return self._custom_app_integration
793
831
 
794
832
  @property
@@ -843,7 +881,7 @@ class AccountClient:
843
881
 
844
882
  @property
845
883
  def published_app_integration(self) -> PublishedAppIntegrationAPI:
846
- """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."""
847
885
  return self._published_app_integration
848
886
 
849
887
  @property
@@ -871,6 +909,11 @@ class AccountClient:
871
909
  """These APIs manage storage credentials for a particular metastore."""
872
910
  return self._storage_credentials
873
911
 
912
+ @property
913
+ def usage_dashboards(self) -> UsageDashboardsAPI:
914
+ """These APIs manage usage dashboards for this account."""
915
+ return self._usage_dashboards
916
+
874
917
  @property
875
918
  def users(self) -> AccountUsersAPI:
876
919
  """User identities recognized by Databricks and represented by email addresses."""
@@ -891,6 +934,11 @@ class AccountClient:
891
934
  """These APIs manage workspaces for this account."""
892
935
  return self._workspaces
893
936
 
937
+ @property
938
+ def budgets(self) -> BudgetsAPI:
939
+ """These APIs manage budget configurations for this account."""
940
+ return self._budgets
941
+
894
942
  def get_workspace_client(self, workspace: Workspace) -> WorkspaceClient:
895
943
  """Constructs a ``WorkspaceClient`` for the given workspace.
896
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
- from typing import Dict, Iterable, List, Optional, Tuple
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
14
  from .credentials_provider import CredentialsStrategy, DefaultCredentials
15
15
  from .environments import (ALL_ENVS, AzureEnvironment, Cloud,
16
16
  DatabricksEnvironment, get_environment_for_hostname)
17
17
  from .oauth import OidcEndpoints, Token
18
- from .version import __version__
19
18
 
20
19
  logger = logging.getLogger('databricks.sdk')
21
20
 
@@ -44,30 +43,14 @@ class ConfigAttribute:
44
43
  return f"<ConfigAttribute '{self.name}' {self.transform.__name__}>"
45
44
 
46
45
 
47
- _DEFAULT_PRODUCT_NAME = 'unknown'
48
- _DEFAULT_PRODUCT_VERSION = '0.0.0'
49
- _STATIC_USER_AGENT: Tuple[str, str, List[str]] = (_DEFAULT_PRODUCT_NAME, _DEFAULT_PRODUCT_VERSION, [])
50
-
51
-
52
46
  def with_product(product: str, product_version: str):
53
47
  """[INTERNAL API] Change the product name and version used in the User-Agent header."""
54
- global _STATIC_USER_AGENT
55
- prev_product, prev_version, prev_other_info = _STATIC_USER_AGENT
56
- logger.debug(f'Changing product from {prev_product}/{prev_version} to {product}/{product_version}')
57
- _STATIC_USER_AGENT = product, product_version, prev_other_info
48
+ useragent.with_product(product, product_version)
58
49
 
59
50
 
60
51
  def with_user_agent_extra(key: str, value: str):
61
52
  """[INTERNAL API] Add extra metadata to the User-Agent header when developing a library."""
62
- global _STATIC_USER_AGENT
63
- product_name, product_version, other_info = _STATIC_USER_AGENT
64
- for item in other_info:
65
- if item.startswith(f"{key}/"):
66
- # ensure that we don't have duplicates
67
- other_info.remove(item)
68
- break
69
- other_info.append(f"{key}/{value}")
70
- _STATIC_USER_AGENT = product_name, product_version, other_info
53
+ useragent.with_extra(key, value)
71
54
 
72
55
 
73
56
  class Config:
@@ -111,21 +94,12 @@ class Config:
111
94
  # Deprecated. Use credentials_strategy instead.
112
95
  credentials_provider: CredentialsStrategy = None,
113
96
  credentials_strategy: CredentialsStrategy = None,
114
- product=_DEFAULT_PRODUCT_NAME,
115
- product_version=_DEFAULT_PRODUCT_VERSION,
97
+ product=None,
98
+ product_version=None,
116
99
  clock: Clock = None,
117
100
  **kwargs):
118
101
  self._header_factory = None
119
102
  self._inner = {}
120
- # as in SDK for Go, pull information from global static user agent context,
121
- # so that we can track additional metadata for mid-stream libraries, as well
122
- # as for cases, when the downstream product is used as a library and is not
123
- # configured with a proper product name and version.
124
- static_product, static_version, _ = _STATIC_USER_AGENT
125
- if product == _DEFAULT_PRODUCT_NAME:
126
- product = static_product
127
- if product_version == _DEFAULT_PRODUCT_VERSION:
128
- product_version = static_version
129
103
  self._user_agent_other_info = []
130
104
  if credentials_strategy and credentials_provider:
131
105
  raise ValueError(
@@ -147,8 +121,7 @@ class Config:
147
121
  self._fix_host_if_needed()
148
122
  self._validate()
149
123
  self.init_auth()
150
- self._product = product
151
- self._product_version = product_version
124
+ self._init_product(product, product_version)
152
125
  except ValueError as e:
153
126
  message = self.wrap_debug_info(str(e))
154
127
  raise ValueError(message) from e
@@ -260,47 +233,19 @@ class Config:
260
233
  @property
261
234
  def user_agent(self):
262
235
  """ Returns User-Agent header used by this SDK """
263
- py_version = platform.python_version()
264
- os_name = platform.uname().system.lower()
265
-
266
- ua = [
267
- f"{self._product}/{self._product_version}", f"databricks-sdk-py/{__version__}",
268
- f"python/{py_version}", f"os/{os_name}", f"auth/{self.auth_type}",
269
- ]
270
- if len(self._user_agent_other_info) > 0:
271
- ua.append(' '.join(self._user_agent_other_info))
272
- # as in SDK for Go, pull information from global static user agent context,
273
- # so that we can track additional metadata for mid-stream libraries. this value
274
- # is shared across all instances of Config objects intentionally.
275
- _, _, static_info = _STATIC_USER_AGENT
276
- if len(static_info) > 0:
277
- ua.append(' '.join(static_info))
278
- if len(self._upstream_user_agent) > 0:
279
- ua.append(self._upstream_user_agent)
280
- if 'DATABRICKS_RUNTIME_VERSION' in os.environ:
281
- runtime_version = os.environ['DATABRICKS_RUNTIME_VERSION']
282
- if runtime_version != '':
283
- runtime_version = self._sanitize_header_value(runtime_version)
284
- ua.append(f'runtime/{runtime_version}')
285
-
286
- return ' '.join(ua)
287
236
 
288
- @staticmethod
289
- def _sanitize_header_value(value: str) -> str:
290
- value = value.replace(' ', '-')
291
- value = value.replace('/', '-')
292
- 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)
293
242
 
294
243
  @property
295
244
  def _upstream_user_agent(self) -> str:
296
- product = os.environ.get('DATABRICKS_SDK_UPSTREAM', None)
297
- product_version = os.environ.get('DATABRICKS_SDK_UPSTREAM_VERSION', None)
298
- if product is not None and product_version is not None:
299
- return f"upstream/{product} upstream-version/{product_version}"
300
- return ""
245
+ return " ".join(f"{k}/{v}" for k, v in useragent._get_upstream_user_agent_info())
301
246
 
302
247
  def with_user_agent_extra(self, key: str, value: str) -> 'Config':
303
- self._user_agent_other_info.append(f"{key}/{value}")
248
+ self._user_agent_other_info.append((key, value))
304
249
  return self
305
250
 
306
251
  @property
@@ -403,13 +348,47 @@ class Config:
403
348
  def _fix_host_if_needed(self):
404
349
  if not self.host:
405
350
  return
406
- # 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
+
407
356
  o = urllib.parse.urlparse(self.host)
408
- if not o.hostname:
409
- # only hostname is specified
410
- self.host = f"https://{self.host}"
411
- else:
412
- 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}')
413
392
 
414
393
  def _set_inner_config(self, keyword_args: Dict[str, any]):
415
394
  for attr in self.attributes():
@@ -498,6 +477,13 @@ class Config:
498
477
  except ValueError as e:
499
478
  raise ValueError(f'{self._credentials_strategy.auth_type()} auth: {e}') from e
500
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
486
+
501
487
  def __repr__(self):
502
488
  return f'<{self.debug_string()}>'
503
489
 
databricks/sdk/core.py CHANGED
@@ -133,31 +133,36 @@ class ApiClient:
133
133
 
134
134
  def do(self,
135
135
  method: str,
136
- path: str,
136
+ path: str = None,
137
+ url: str = None,
137
138
  query: dict = None,
138
139
  headers: dict = None,
139
140
  body: dict = None,
140
141
  raw: bool = False,
141
142
  files=None,
142
143
  data=None,
144
+ auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None,
143
145
  response_headers: List[str] = None) -> Union[dict, BinaryIO]:
144
- # Remove extra `/` from path for Files API
145
- # Once we've fixed the OpenAPI spec, we can remove this
146
- path = re.sub('^/api/2.0/fs/files//', '/api/2.0/fs/files/', path)
147
146
  if headers is None:
148
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}"
149
153
  headers['User-Agent'] = self._user_agent_base
150
154
  retryable = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
151
155
  is_retryable=self._is_retryable,
152
156
  clock=self._cfg.clock)
153
157
  response = retryable(self._perform)(method,
154
- path,
158
+ url,
155
159
  query=query,
156
160
  headers=headers,
157
161
  body=body,
158
162
  raw=raw,
159
163
  files=files,
160
- data=data)
164
+ data=data,
165
+ auth=auth)
161
166
 
162
167
  resp = dict()
163
168
  for header in response_headers if response_headers else []:
@@ -239,20 +244,22 @@ class ApiClient:
239
244
 
240
245
  def _perform(self,
241
246
  method: str,
242
- path: str,
247
+ url: str,
243
248
  query: dict = None,
244
249
  headers: dict = None,
245
250
  body: dict = None,
246
251
  raw: bool = False,
247
252
  files=None,
248
- data=None):
253
+ data=None,
254
+ auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None):
249
255
  response = self._session.request(method,
250
- f"{self._cfg.host}{path}",
256
+ url,
251
257
  params=self._fix_query_string(query),
252
258
  json=body,
253
259
  headers=headers,
254
260
  files=files,
255
261
  data=data,
262
+ auth=auth,
256
263
  stream=raw,
257
264
  timeout=self._http_timeout_seconds)
258
265
  try:
@@ -233,8 +233,7 @@ def _ensure_host_present(cfg: 'Config', token_source_for: Callable[[str], TokenS
233
233
  cfg.host = f"https://{resp.json()['properties']['workspaceUrl']}"
234
234
 
235
235
 
236
- @oauth_credentials_strategy('azure-client-secret',
237
- ['is_azure', 'azure_client_id', 'azure_client_secret', 'azure_tenant_id'])
236
+ @oauth_credentials_strategy('azure-client-secret', ['is_azure', 'azure_client_id', 'azure_client_secret'])
238
237
  def azure_service_principal(cfg: 'Config') -> CredentialsProvider:
239
238
  """ Adds refreshed Azure Active Directory (AAD) Service Principal OAuth tokens
240
239
  to every request, while automatically resolving different Azure environment endpoints. """
@@ -248,6 +247,7 @@ def azure_service_principal(cfg: 'Config') -> CredentialsProvider:
248
247
  use_params=True)
249
248
 
250
249
  _ensure_host_present(cfg, token_source_for)
250
+ cfg.load_azure_tenant_id()
251
251
  logger.info("Configured AAD token for Service Principal (%s)", cfg.azure_client_id)
252
252
  inner = token_source_for(cfg.effective_azure_login_app_id)
253
253
  cloud = token_source_for(cfg.arm_environment.service_management_endpoint)
@@ -432,11 +432,13 @@ class CliTokenSource(Refreshable):
432
432
  class AzureCliTokenSource(CliTokenSource):
433
433
  """ Obtain the token granted by `az login` CLI command """
434
434
 
435
- def __init__(self, resource: str, subscription: str = ""):
435
+ def __init__(self, resource: str, subscription: Optional[str] = None, tenant: Optional[str] = None):
436
436
  cmd = ["az", "account", "get-access-token", "--resource", resource, "--output", "json"]
437
- if subscription != "":
437
+ if subscription is not None:
438
438
  cmd.append("--subscription")
439
439
  cmd.append(subscription)
440
+ if tenant:
441
+ cmd.extend(["--tenant", tenant])
440
442
  super().__init__(cmd=cmd,
441
443
  token_type_field='tokenType',
442
444
  access_token_field='accessToken',
@@ -464,8 +466,10 @@ class AzureCliTokenSource(CliTokenSource):
464
466
  @staticmethod
465
467
  def for_resource(cfg: 'Config', resource: str) -> 'AzureCliTokenSource':
466
468
  subscription = AzureCliTokenSource.get_subscription(cfg)
467
- if subscription != "":
468
- token_source = AzureCliTokenSource(resource, subscription)
469
+ if subscription is not None:
470
+ token_source = AzureCliTokenSource(resource,
471
+ subscription=subscription,
472
+ tenant=cfg.azure_tenant_id)
469
473
  try:
470
474
  # This will fail if the user has access to the workspace, but not to the subscription
471
475
  # itself.
@@ -475,25 +479,26 @@ class AzureCliTokenSource(CliTokenSource):
475
479
  except OSError:
476
480
  logger.warning("Failed to get token for subscription. Using resource only token.")
477
481
 
478
- token_source = AzureCliTokenSource(resource)
482
+ token_source = AzureCliTokenSource(resource, subscription=None, tenant=cfg.azure_tenant_id)
479
483
  token_source.token()
480
484
  return token_source
481
485
 
482
486
  @staticmethod
483
- def get_subscription(cfg: 'Config') -> str:
487
+ def get_subscription(cfg: 'Config') -> Optional[str]:
484
488
  resource = cfg.azure_workspace_resource_id
485
489
  if resource is None or resource == "":
486
- return ""
490
+ return None
487
491
  components = resource.split('/')
488
492
  if len(components) < 3:
489
493
  logger.warning("Invalid azure workspace resource ID")
490
- return ""
494
+ return None
491
495
  return components[2]
492
496
 
493
497
 
494
498
  @credentials_strategy('azure-cli', ['is_azure'])
495
499
  def azure_cli(cfg: 'Config') -> Optional[CredentialsProvider]:
496
500
  """ Adds refreshed OAuth token granted by `az login` command to every request. """
501
+ cfg.load_azure_tenant_id()
497
502
  token_source = None
498
503
  mgmt_token_source = None
499
504
  try:
@@ -517,11 +522,6 @@ def azure_cli(cfg: 'Config') -> Optional[CredentialsProvider]:
517
522
 
518
523
  _ensure_host_present(cfg, lambda resource: AzureCliTokenSource.for_resource(cfg, resource))
519
524
  logger.info("Using Azure CLI authentication with AAD tokens")
520
- if not cfg.is_account_client and AzureCliTokenSource.get_subscription(cfg) == "":
521
- logger.warning(
522
- "azure_workspace_resource_id field not provided. "
523
- "It is recommended to specify this field in the Databricks configuration to avoid authentication errors."
524
- )
525
525
 
526
526
  def inner() -> Dict[str, str]:
527
527
  token = token_source.token()