databricks-sdk 0.27.0__py3-none-any.whl → 0.28.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.

@@ -13,9 +13,9 @@ from databricks.sdk.service.catalog import (AccountMetastoreAssignmentsAPI,
13
13
  ArtifactAllowlistsAPI, CatalogsAPI,
14
14
  ConnectionsAPI,
15
15
  ExternalLocationsAPI, FunctionsAPI,
16
- GrantsAPI, LakehouseMonitorsAPI,
17
- MetastoresAPI, ModelVersionsAPI,
18
- OnlineTablesAPI,
16
+ GrantsAPI, MetastoresAPI,
17
+ ModelVersionsAPI, OnlineTablesAPI,
18
+ QualityMonitorsAPI,
19
19
  RegisteredModelsAPI, SchemasAPI,
20
20
  StorageCredentialsAPI,
21
21
  SystemSchemasAPI,
@@ -194,7 +194,6 @@ class WorkspaceClient:
194
194
  self._instance_profiles = InstanceProfilesAPI(self._api_client)
195
195
  self._ip_access_lists = IpAccessListsAPI(self._api_client)
196
196
  self._jobs = JobsAPI(self._api_client)
197
- self._lakehouse_monitors = LakehouseMonitorsAPI(self._api_client)
198
197
  self._lakeview = LakeviewAPI(self._api_client)
199
198
  self._libraries = LibrariesAPI(self._api_client)
200
199
  self._metastores = MetastoresAPI(self._api_client)
@@ -214,6 +213,7 @@ class WorkspaceClient:
214
213
  self._api_client)
215
214
  self._provider_providers = ProviderProvidersAPI(self._api_client)
216
215
  self._providers = ProvidersAPI(self._api_client)
216
+ self._quality_monitors = QualityMonitorsAPI(self._api_client)
217
217
  self._queries = QueriesAPI(self._api_client)
218
218
  self._query_history = QueryHistoryAPI(self._api_client)
219
219
  self._query_visualizations = QueryVisualizationsAPI(self._api_client)
@@ -425,11 +425,6 @@ class WorkspaceClient:
425
425
  """The Jobs API allows you to create, edit, and delete jobs."""
426
426
  return self._jobs
427
427
 
428
- @property
429
- def lakehouse_monitors(self) -> LakehouseMonitorsAPI:
430
- """A monitor computes and monitors data or model quality metrics for a table over time."""
431
- return self._lakehouse_monitors
432
-
433
428
  @property
434
429
  def lakeview(self) -> LakeviewAPI:
435
430
  """These APIs provide specific management operations for Lakeview dashboards."""
@@ -520,6 +515,11 @@ class WorkspaceClient:
520
515
  """A data provider is an object representing the organization in the real world who shares the data."""
521
516
  return self._providers
522
517
 
518
+ @property
519
+ def quality_monitors(self) -> QualityMonitorsAPI:
520
+ """A monitor computes and monitors data or model quality metrics for a table over time."""
521
+ return self._quality_monitors
522
+
523
523
  @property
524
524
  def queries(self) -> QueriesAPI:
525
525
  """These endpoints are used for CRUD operations on query definitions."""
databricks/sdk/azure.py CHANGED
@@ -1,36 +1,9 @@
1
- from dataclasses import dataclass
2
1
  from typing import Dict
3
2
 
4
3
  from .oauth import TokenSource
5
4
  from .service.provisioning import Workspace
6
5
 
7
6
 
8
- @dataclass
9
- class AzureEnvironment:
10
- name: str
11
- service_management_endpoint: str
12
- resource_manager_endpoint: str
13
- active_directory_endpoint: str
14
-
15
-
16
- ARM_DATABRICKS_RESOURCE_ID = "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d"
17
-
18
- ENVIRONMENTS = dict(
19
- PUBLIC=AzureEnvironment(name="PUBLIC",
20
- service_management_endpoint="https://management.core.windows.net/",
21
- resource_manager_endpoint="https://management.azure.com/",
22
- active_directory_endpoint="https://login.microsoftonline.com/"),
23
- USGOVERNMENT=AzureEnvironment(name="USGOVERNMENT",
24
- service_management_endpoint="https://management.core.usgovcloudapi.net/",
25
- resource_manager_endpoint="https://management.usgovcloudapi.net/",
26
- active_directory_endpoint="https://login.microsoftonline.us/"),
27
- CHINA=AzureEnvironment(name="CHINA",
28
- service_management_endpoint="https://management.core.chinacloudapi.cn/",
29
- resource_manager_endpoint="https://management.chinacloudapi.cn/",
30
- active_directory_endpoint="https://login.chinacloudapi.cn/"),
31
- )
32
-
33
-
34
7
  def add_workspace_id_header(cfg: 'Config', headers: Dict[str, str]):
35
8
  if cfg.azure_workspace_resource_id:
36
9
  headers["X-Databricks-Azure-Workspace-Resource-Id"] = cfg.azure_workspace_resource_id
databricks/sdk/config.py CHANGED
@@ -10,11 +10,10 @@ from typing import Dict, Iterable, Optional
10
10
 
11
11
  import requests
12
12
 
13
- from .azure import AzureEnvironment
14
13
  from .clock import Clock, RealClock
15
14
  from .credentials_provider import CredentialsProvider, DefaultCredentials
16
- from .environments import (ALL_ENVS, DEFAULT_ENVIRONMENT, Cloud,
17
- DatabricksEnvironment)
15
+ from .environments import (ALL_ENVS, AzureEnvironment, Cloud,
16
+ DatabricksEnvironment, get_environment_for_hostname)
18
17
  from .oauth import OidcEndpoints
19
18
  from .version import __version__
20
19
 
@@ -154,11 +153,7 @@ class Config:
154
153
  """Returns the environment based on configuration."""
155
154
  if self.databricks_environment:
156
155
  return self.databricks_environment
157
- if self.host:
158
- for environment in ALL_ENVS:
159
- if self.host.endswith(environment.dns_zone):
160
- return environment
161
- if self.azure_workspace_resource_id:
156
+ if not self.host and self.azure_workspace_resource_id:
162
157
  azure_env = self._get_azure_environment_name()
163
158
  for environment in ALL_ENVS:
164
159
  if environment.cloud != Cloud.AZURE:
@@ -168,10 +163,12 @@ class Config:
168
163
  if environment.dns_zone.startswith(".dev") or environment.dns_zone.startswith(".staging"):
169
164
  continue
170
165
  return environment
171
- return DEFAULT_ENVIRONMENT
166
+ return get_environment_for_hostname(self.host)
172
167
 
173
168
  @property
174
169
  def is_azure(self) -> bool:
170
+ if self.azure_workspace_resource_id:
171
+ return True
175
172
  return self.environment.cloud == Cloud.AZURE
176
173
 
177
174
  @property
databricks/sdk/core.py CHANGED
@@ -12,6 +12,7 @@ from .config import *
12
12
  # To preserve backwards compatibility (as these definitions were previously in this module)
13
13
  from .credentials_provider import *
14
14
  from .errors import DatabricksError, error_mapper
15
+ from .errors.private_link import _is_private_link_redirect
15
16
  from .retries import retried
16
17
 
17
18
  __all__ = ['Config', 'DatabricksError']
@@ -145,11 +146,14 @@ class ApiClient:
145
146
  if not len(response.content):
146
147
  return resp
147
148
 
148
- json = response.json()
149
- if isinstance(json, list):
150
- return json
149
+ jsonResponse = response.json()
150
+ if jsonResponse is None:
151
+ return resp
152
+
153
+ if isinstance(jsonResponse, list):
154
+ return jsonResponse
151
155
 
152
- return {**resp, **json}
156
+ return {**resp, **jsonResponse}
153
157
 
154
158
  @staticmethod
155
159
  def _is_retryable(err: BaseException) -> Optional[str]:
@@ -236,6 +240,10 @@ class ApiClient:
236
240
  # See https://stackoverflow.com/a/58821552/277035
237
241
  payload = response.json()
238
242
  raise self._make_nicer_error(response=response, **payload) from None
243
+ # Private link failures happen via a redirect to the login page. From a requests-perspective, the request
244
+ # is successful, but the response is not what we expect. We need to handle this case separately.
245
+ if _is_private_link_redirect(response):
246
+ raise self._make_nicer_error(response=response) from None
239
247
  return response
240
248
  except requests.exceptions.JSONDecodeError:
241
249
  message = self._make_sense_from_html(response.text)
@@ -2,7 +2,31 @@ from dataclasses import dataclass
2
2
  from enum import Enum
3
3
  from typing import Optional
4
4
 
5
- from .azure import ARM_DATABRICKS_RESOURCE_ID, ENVIRONMENTS, AzureEnvironment
5
+
6
+ @dataclass
7
+ class AzureEnvironment:
8
+ name: str
9
+ service_management_endpoint: str
10
+ resource_manager_endpoint: str
11
+ active_directory_endpoint: str
12
+
13
+
14
+ ARM_DATABRICKS_RESOURCE_ID = "2ff814a6-3304-4ab8-85cb-cd0e6f879c1d"
15
+
16
+ ENVIRONMENTS = dict(
17
+ PUBLIC=AzureEnvironment(name="PUBLIC",
18
+ service_management_endpoint="https://management.core.windows.net/",
19
+ resource_manager_endpoint="https://management.azure.com/",
20
+ active_directory_endpoint="https://login.microsoftonline.com/"),
21
+ USGOVERNMENT=AzureEnvironment(name="USGOVERNMENT",
22
+ service_management_endpoint="https://management.core.usgovcloudapi.net/",
23
+ resource_manager_endpoint="https://management.usgovcloudapi.net/",
24
+ active_directory_endpoint="https://login.microsoftonline.us/"),
25
+ CHINA=AzureEnvironment(name="CHINA",
26
+ service_management_endpoint="https://management.core.chinacloudapi.cn/",
27
+ resource_manager_endpoint="https://management.chinacloudapi.cn/",
28
+ active_directory_endpoint="https://login.chinacloudapi.cn/"),
29
+ )
6
30
 
7
31
 
8
32
  class Cloud(Enum):
@@ -70,3 +94,12 @@ ALL_ENVS = [
70
94
  DatabricksEnvironment(Cloud.GCP, ".staging.gcp.databricks.com"),
71
95
  DatabricksEnvironment(Cloud.GCP, ".gcp.databricks.com")
72
96
  ]
97
+
98
+
99
+ def get_environment_for_hostname(hostname: str) -> DatabricksEnvironment:
100
+ if not hostname:
101
+ return DEFAULT_ENVIRONMENT
102
+ for env in ALL_ENVS:
103
+ if hostname.endswith(env.dns_zone):
104
+ return env
105
+ return DEFAULT_ENVIRONMENT
@@ -1,4 +1,5 @@
1
1
  from .base import DatabricksError, ErrorDetail
2
2
  from .mapper import error_mapper
3
3
  from .platform import *
4
+ from .private_link import PrivateLinkValidationError
4
5
  from .sdk import *
@@ -4,6 +4,8 @@ from databricks.sdk.errors import platform
4
4
  from databricks.sdk.errors.base import DatabricksError
5
5
 
6
6
  from .overrides import _ALL_OVERRIDES
7
+ from .private_link import (_get_private_link_validation_error,
8
+ _is_private_link_redirect)
7
9
 
8
10
 
9
11
  def error_mapper(response: requests.Response, raw: dict) -> DatabricksError:
@@ -21,6 +23,8 @@ def error_mapper(response: requests.Response, raw: dict) -> DatabricksError:
21
23
  # where there's a default exception class per HTTP status code, and we do
22
24
  # rely on Databricks platform exception mapper to do the right thing.
23
25
  return platform.STATUS_CODE_MAPPING[status_code](**raw)
26
+ if _is_private_link_redirect(response):
27
+ return _get_private_link_validation_error(response.url)
24
28
 
25
29
  # backwards-compatible error creation for cases like using older versions of
26
30
  # the SDK on way never releases of the platform.
@@ -0,0 +1,60 @@
1
+ from dataclasses import dataclass
2
+ from urllib import parse
3
+
4
+ import requests
5
+
6
+ from ..environments import Cloud, get_environment_for_hostname
7
+ from .platform import PermissionDenied
8
+
9
+
10
+ @dataclass
11
+ class _PrivateLinkInfo:
12
+ serviceName: str
13
+ endpointName: str
14
+ referencePage: str
15
+
16
+ def error_message(self):
17
+ return (
18
+ f'The requested workspace has {self.serviceName} enabled and is not accessible from the current network. '
19
+ f'Ensure that {self.serviceName} is properly configured and that your device has access to the '
20
+ f'{self.endpointName}. For more information, see {self.referencePage}.')
21
+
22
+
23
+ _private_link_info_map = {
24
+ Cloud.AWS:
25
+ _PrivateLinkInfo(serviceName='AWS PrivateLink',
26
+ endpointName='AWS VPC endpoint',
27
+ referencePage='https://docs.databricks.com/en/security/network/classic/privatelink.html',
28
+ ),
29
+ Cloud.AZURE:
30
+ _PrivateLinkInfo(
31
+ serviceName='Azure Private Link',
32
+ endpointName='Azure Private Link endpoint',
33
+ referencePage='https://learn.microsoft.com/en-us/azure/databricks/security/network/classic/private-link-standard#authentication-troubleshooting',
34
+ ),
35
+ Cloud.GCP:
36
+ _PrivateLinkInfo(
37
+ serviceName='Private Service Connect',
38
+ endpointName='GCP VPC endpoint',
39
+ referencePage='https://docs.gcp.databricks.com/en/security/network/classic/private-service-connect.html',
40
+ )
41
+ }
42
+
43
+
44
+ class PrivateLinkValidationError(PermissionDenied):
45
+ """Raised when a user tries to access a Private Link-enabled workspace, but the user's network does not have access
46
+ to the workspace."""
47
+
48
+
49
+ def _is_private_link_redirect(resp: requests.Response) -> bool:
50
+ parsed = parse.urlparse(resp.url)
51
+ return parsed.path == '/login.html' and 'error=private-link-validation-error' in parsed.query
52
+
53
+
54
+ def _get_private_link_validation_error(url: str) -> _PrivateLinkInfo:
55
+ parsed = parse.urlparse(url)
56
+ env = get_environment_for_hostname(parsed.hostname)
57
+ return PrivateLinkValidationError(message=_private_link_info_map[env.cloud].error_message(),
58
+ error_code='PRIVATE_LINK_VALIDATION_ERROR',
59
+ status_code=403,
60
+ )