databricks-sdk 0.29.0__py3-none-any.whl → 0.31.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.
- databricks/sdk/__init__.py +89 -21
- databricks/sdk/config.py +61 -75
- databricks/sdk/core.py +16 -9
- databricks/sdk/credentials_provider.py +15 -15
- databricks/sdk/data_plane.py +65 -0
- databricks/sdk/errors/overrides.py +8 -0
- databricks/sdk/errors/platform.py +5 -0
- databricks/sdk/mixins/files.py +12 -4
- databricks/sdk/service/apps.py +977 -0
- databricks/sdk/service/billing.py +602 -218
- databricks/sdk/service/catalog.py +324 -34
- databricks/sdk/service/compute.py +766 -81
- databricks/sdk/service/dashboards.py +628 -18
- databricks/sdk/service/iam.py +99 -88
- databricks/sdk/service/jobs.py +332 -23
- databricks/sdk/service/marketplace.py +2 -122
- databricks/sdk/service/oauth2.py +127 -70
- databricks/sdk/service/pipelines.py +72 -52
- databricks/sdk/service/serving.py +303 -750
- databricks/sdk/service/settings.py +423 -4
- databricks/sdk/service/sharing.py +235 -25
- databricks/sdk/service/sql.py +2328 -544
- databricks/sdk/useragent.py +151 -0
- databricks/sdk/version.py +1 -1
- {databricks_sdk-0.29.0.dist-info → databricks_sdk-0.31.0.dist-info}/METADATA +36 -16
- {databricks_sdk-0.29.0.dist-info → databricks_sdk-0.31.0.dist-info}/RECORD +30 -27
- {databricks_sdk-0.29.0.dist-info → databricks_sdk-0.31.0.dist-info}/WHEEL +1 -1
- {databricks_sdk-0.29.0.dist-info → databricks_sdk-0.31.0.dist-info}/LICENSE +0 -0
- {databricks_sdk-0.29.0.dist-info → databricks_sdk-0.31.0.dist-info}/NOTICE +0 -0
- {databricks_sdk-0.29.0.dist-info → databricks_sdk-0.31.0.dist-info}/top_level.txt +0 -0
|
@@ -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,
|
|
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()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Callable, List
|
|
4
|
+
|
|
5
|
+
from databricks.sdk.oauth import Token
|
|
6
|
+
from databricks.sdk.service.oauth2 import DataPlaneInfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class DataPlaneDetails:
|
|
11
|
+
"""
|
|
12
|
+
Contains details required to query a DataPlane endpoint.
|
|
13
|
+
"""
|
|
14
|
+
endpoint_url: str
|
|
15
|
+
"""URL used to query the endpoint through the DataPlane."""
|
|
16
|
+
token: Token
|
|
17
|
+
"""Token to query the DataPlane endpoint."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DataPlaneService:
|
|
21
|
+
"""Helper class to fetch and manage DataPlane details."""
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self._data_plane_info = {}
|
|
25
|
+
self._tokens = {}
|
|
26
|
+
self._lock = threading.Lock()
|
|
27
|
+
|
|
28
|
+
def get_data_plane_details(self, method: str, params: List[str], info_getter: Callable[[], DataPlaneInfo],
|
|
29
|
+
refresh: Callable[[str], Token]):
|
|
30
|
+
"""Get and cache information required to query a Data Plane endpoint using the provided methods.
|
|
31
|
+
|
|
32
|
+
Returns a cached DataPlaneDetails if the details have already been fetched previously and are still valid.
|
|
33
|
+
If not, it uses the provided functions to fetch the details.
|
|
34
|
+
|
|
35
|
+
:param method: method name. Used to construct a unique key for the cache.
|
|
36
|
+
:param params: path params used in the "get" operation which uniquely determine the object. Used to construct a unique key for the cache.
|
|
37
|
+
:param info_getter: function which returns the DataPlaneInfo. It will only be called if the information is not already present in the cache.
|
|
38
|
+
:param refresh: function to refresh the token. It will only be called if the token is missing or expired.
|
|
39
|
+
"""
|
|
40
|
+
all_elements = params.copy()
|
|
41
|
+
all_elements.insert(0, method)
|
|
42
|
+
map_key = "/".join(all_elements)
|
|
43
|
+
info = self._data_plane_info.get(map_key)
|
|
44
|
+
if not info:
|
|
45
|
+
self._lock.acquire()
|
|
46
|
+
try:
|
|
47
|
+
info = self._data_plane_info.get(map_key)
|
|
48
|
+
if not info:
|
|
49
|
+
info = info_getter()
|
|
50
|
+
self._data_plane_info[map_key] = info
|
|
51
|
+
finally:
|
|
52
|
+
self._lock.release()
|
|
53
|
+
|
|
54
|
+
token = self._tokens.get(map_key)
|
|
55
|
+
if not token or not token.valid:
|
|
56
|
+
self._lock.acquire()
|
|
57
|
+
token = self._tokens.get(map_key)
|
|
58
|
+
try:
|
|
59
|
+
if not token or not token.valid:
|
|
60
|
+
token = refresh(info.authorization_details)
|
|
61
|
+
self._tokens[map_key] = token
|
|
62
|
+
finally:
|
|
63
|
+
self._lock.release()
|
|
64
|
+
|
|
65
|
+
return DataPlaneDetails(endpoint_url=info.endpoint_url, token=token)
|
|
@@ -22,4 +22,12 @@ _ALL_OVERRIDES = [
|
|
|
22
22
|
message_matcher=re.compile(r'Job .* does not exist'),
|
|
23
23
|
custom_error=ResourceDoesNotExist,
|
|
24
24
|
),
|
|
25
|
+
_ErrorOverride(debug_name="Job Runs InvalidParameterValue=>ResourceDoesNotExist",
|
|
26
|
+
path_regex=re.compile(r'^/api/2\.\d/jobs/runs/get'),
|
|
27
|
+
verb="GET",
|
|
28
|
+
status_code_matcher=re.compile(r'^400$'),
|
|
29
|
+
error_code_matcher=re.compile(r'INVALID_PARAMETER_VALUE'),
|
|
30
|
+
message_matcher=re.compile(r'(Run .* does not exist|Run: .* in job: .* doesn\'t exist)'),
|
|
31
|
+
custom_error=ResourceDoesNotExist,
|
|
32
|
+
),
|
|
25
33
|
]
|
|
@@ -47,6 +47,10 @@ class DeadlineExceeded(DatabricksError):
|
|
|
47
47
|
"""the deadline expired before the operation could complete"""
|
|
48
48
|
|
|
49
49
|
|
|
50
|
+
class InvalidState(BadRequest):
|
|
51
|
+
"""unexpected state"""
|
|
52
|
+
|
|
53
|
+
|
|
50
54
|
class InvalidParameterValue(BadRequest):
|
|
51
55
|
"""supplied value for a parameter was invalid"""
|
|
52
56
|
|
|
@@ -99,6 +103,7 @@ STATUS_CODE_MAPPING = {
|
|
|
99
103
|
}
|
|
100
104
|
|
|
101
105
|
ERROR_CODE_MAPPING = {
|
|
106
|
+
'INVALID_STATE': InvalidState,
|
|
102
107
|
'INVALID_PARAMETER_VALUE': InvalidParameterValue,
|
|
103
108
|
'RESOURCE_DOES_NOT_EXIST': ResourceDoesNotExist,
|
|
104
109
|
'ABORTED': Aborted,
|
databricks/sdk/mixins/files.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import base64
|
|
4
4
|
import os
|
|
5
5
|
import pathlib
|
|
6
|
+
import platform
|
|
6
7
|
import shutil
|
|
7
8
|
import sys
|
|
8
9
|
from abc import ABC, abstractmethod
|
|
@@ -266,8 +267,9 @@ class _VolumesIO(BinaryIO):
|
|
|
266
267
|
|
|
267
268
|
class _Path(ABC):
|
|
268
269
|
|
|
269
|
-
|
|
270
|
-
|
|
270
|
+
@abstractmethod
|
|
271
|
+
def __init__(self):
|
|
272
|
+
...
|
|
271
273
|
|
|
272
274
|
@property
|
|
273
275
|
def is_local(self) -> bool:
|
|
@@ -327,6 +329,12 @@ class _Path(ABC):
|
|
|
327
329
|
|
|
328
330
|
class _LocalPath(_Path):
|
|
329
331
|
|
|
332
|
+
def __init__(self, path: str):
|
|
333
|
+
if platform.system() == "Windows":
|
|
334
|
+
self._path = pathlib.Path(str(path).replace('file:///', '').replace('file:', ''))
|
|
335
|
+
else:
|
|
336
|
+
self._path = pathlib.Path(str(path).replace('file:', ''))
|
|
337
|
+
|
|
330
338
|
def _is_local(self) -> bool:
|
|
331
339
|
return True
|
|
332
340
|
|
|
@@ -393,7 +401,7 @@ class _LocalPath(_Path):
|
|
|
393
401
|
class _VolumesPath(_Path):
|
|
394
402
|
|
|
395
403
|
def __init__(self, api: files.FilesAPI, src: Union[str, pathlib.Path]):
|
|
396
|
-
|
|
404
|
+
self._path = pathlib.PurePosixPath(str(src).replace('dbfs:', '').replace('file:', ''))
|
|
397
405
|
self._api = api
|
|
398
406
|
|
|
399
407
|
def _is_local(self) -> bool:
|
|
@@ -462,7 +470,7 @@ class _VolumesPath(_Path):
|
|
|
462
470
|
class _DbfsPath(_Path):
|
|
463
471
|
|
|
464
472
|
def __init__(self, api: files.DbfsAPI, src: str):
|
|
465
|
-
|
|
473
|
+
self._path = pathlib.PurePosixPath(str(src).replace('dbfs:', '').replace('file:', ''))
|
|
466
474
|
self._api = api
|
|
467
475
|
|
|
468
476
|
def _is_local(self) -> bool:
|