databricks-sdk 0.43.0__py3-none-any.whl → 0.44.1__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.

@@ -9,6 +9,7 @@ import pathlib
9
9
  import platform
10
10
  import subprocess
11
11
  import sys
12
+ import threading
12
13
  import time
13
14
  from datetime import datetime
14
15
  from typing import Callable, Dict, List, Optional, Tuple, Union
@@ -723,14 +724,17 @@ def metadata_service(cfg: 'Config') -> Optional[CredentialsProvider]:
723
724
  # This Code is derived from Mlflow DatabricksModelServingConfigProvider
724
725
  # https://github.com/mlflow/mlflow/blob/1219e3ef1aac7d337a618a352cd859b336cf5c81/mlflow/legacy_databricks_cli/configure/provider.py#L332
725
726
  class ModelServingAuthProvider():
727
+ USER_CREDENTIALS = "user_credentials"
728
+
726
729
  _MODEL_DEPENDENCY_OAUTH_TOKEN_FILE_PATH = "/var/credentials-secret/model-dependencies-oauth-token"
727
730
 
728
- def __init__(self):
731
+ def __init__(self, credential_type: Optional[str]):
729
732
  self.expiry_time = -1
730
733
  self.current_token = None
731
734
  self.refresh_duration = 300 # 300 Seconds
735
+ self.credential_type = credential_type
732
736
 
733
- def should_fetch_model_serving_environment_oauth(self) -> bool:
737
+ def should_fetch_model_serving_environment_oauth() -> bool:
734
738
  """
735
739
  Check whether this is the model serving environment
736
740
  Additionally check if the oauth token file path exists
@@ -739,15 +743,15 @@ class ModelServingAuthProvider():
739
743
  is_in_model_serving_env = (os.environ.get("IS_IN_DB_MODEL_SERVING_ENV")
740
744
  or os.environ.get("IS_IN_DATABRICKS_MODEL_SERVING_ENV") or "false")
741
745
  return (is_in_model_serving_env == "true"
742
- and os.path.isfile(self._MODEL_DEPENDENCY_OAUTH_TOKEN_FILE_PATH))
746
+ and os.path.isfile(ModelServingAuthProvider._MODEL_DEPENDENCY_OAUTH_TOKEN_FILE_PATH))
743
747
 
744
- def get_model_dependency_oauth_token(self, should_retry=True) -> str:
748
+ def _get_model_dependency_oauth_token(self, should_retry=True) -> str:
745
749
  # Use Cached value if it is valid
746
750
  if self.current_token is not None and self.expiry_time > time.time():
747
751
  return self.current_token
748
752
 
749
753
  try:
750
- with open(self._MODEL_DEPENDENCY_OAUTH_TOKEN_FILE_PATH) as f:
754
+ with open(ModelServingAuthProvider._MODEL_DEPENDENCY_OAUTH_TOKEN_FILE_PATH) as f:
751
755
  oauth_dict = json.load(f)
752
756
  self.current_token = oauth_dict["OAUTH_TOKEN"][0]["oauthTokenValue"]
753
757
  self.expiry_time = time.time() + self.refresh_duration
@@ -757,32 +761,43 @@ class ModelServingAuthProvider():
757
761
  logger.warning("Unable to read oauth token on first attmept in Model Serving Environment",
758
762
  exc_info=e)
759
763
  time.sleep(0.5)
760
- return self.get_model_dependency_oauth_token(should_retry=False)
764
+ return self._get_model_dependency_oauth_token(should_retry=False)
761
765
  else:
762
766
  raise RuntimeError(
763
767
  "Unable to read OAuth credentials from the file mounted in Databricks Model Serving"
764
768
  ) from e
765
769
  return self.current_token
766
770
 
771
+ def _get_invokers_token(self):
772
+ current_thread = threading.current_thread()
773
+ thread_data = current_thread.__dict__
774
+ invokers_token = None
775
+ if "invokers_token" in thread_data:
776
+ invokers_token = thread_data["invokers_token"]
777
+
778
+ if invokers_token is None:
779
+ raise RuntimeError("Unable to read Invokers Token in Databricks Model Serving")
780
+
781
+ return invokers_token
782
+
767
783
  def get_databricks_host_token(self) -> Optional[Tuple[str, str]]:
768
- if not self.should_fetch_model_serving_environment_oauth():
784
+ if not ModelServingAuthProvider.should_fetch_model_serving_environment_oauth():
769
785
  return None
770
786
 
771
787
  # read from DB_MODEL_SERVING_HOST_ENV_VAR if available otherwise MODEL_SERVING_HOST_ENV_VAR
772
788
  host = os.environ.get("DATABRICKS_MODEL_SERVING_HOST_URL") or os.environ.get(
773
789
  "DB_MODEL_SERVING_HOST_URL")
774
- token = self.get_model_dependency_oauth_token()
775
790
 
776
- return (host, token)
791
+ if self.credential_type == ModelServingAuthProvider.USER_CREDENTIALS:
792
+ return (host, self._get_invokers_token())
793
+ else:
794
+ return (host, self._get_model_dependency_oauth_token())
777
795
 
778
796
 
779
- @credentials_strategy('model-serving', [])
780
- def model_serving_auth(cfg: 'Config') -> Optional[CredentialsProvider]:
797
+ def model_serving_auth_visitor(cfg: 'Config',
798
+ credential_type: Optional[str] = None) -> Optional[CredentialsProvider]:
781
799
  try:
782
- model_serving_auth_provider = ModelServingAuthProvider()
783
- if not model_serving_auth_provider.should_fetch_model_serving_environment_oauth():
784
- logger.debug("model-serving: Not in Databricks Model Serving, skipping")
785
- return None
800
+ model_serving_auth_provider = ModelServingAuthProvider(credential_type)
786
801
  host, token = model_serving_auth_provider.get_databricks_host_token()
787
802
  if token is None:
788
803
  raise ValueError(
@@ -793,7 +808,6 @@ def model_serving_auth(cfg: 'Config') -> Optional[CredentialsProvider]:
793
808
  except Exception as e:
794
809
  logger.warning("Unable to get auth from Databricks Model Serving Environment", exc_info=e)
795
810
  return None
796
-
797
811
  logger.info("Using Databricks Model Serving Authentication")
798
812
 
799
813
  def inner() -> Dict[str, str]:
@@ -804,6 +818,15 @@ def model_serving_auth(cfg: 'Config') -> Optional[CredentialsProvider]:
804
818
  return inner
805
819
 
806
820
 
821
+ @credentials_strategy('model-serving', [])
822
+ def model_serving_auth(cfg: 'Config') -> Optional[CredentialsProvider]:
823
+ if not ModelServingAuthProvider.should_fetch_model_serving_environment_oauth():
824
+ logger.debug("model-serving: Not in Databricks Model Serving, skipping")
825
+ return None
826
+
827
+ return model_serving_auth_visitor(cfg)
828
+
829
+
807
830
  class DefaultCredentials:
808
831
  """ Select the first applicable credential provider from the chain """
809
832
 
@@ -846,3 +869,35 @@ class DefaultCredentials:
846
869
  raise ValueError(
847
870
  f'cannot configure default credentials, please check {auth_flow_url} to configure credentials for your preferred authentication method.'
848
871
  )
872
+
873
+
874
+ class ModelServingUserCredentials(CredentialsStrategy):
875
+ """
876
+ This credential strategy is designed for authenticating the Databricks SDK in the model serving environment using user-specific rights.
877
+ In the model serving environment, the strategy retrieves a downscoped user token from the thread-local variable.
878
+ In any other environments, the class defaults to the DefaultCredentialStrategy.
879
+ To use this credential strategy, instantiate the WorkspaceClient with the ModelServingUserCredentials strategy as follows:
880
+
881
+ invokers_client = WorkspaceClient(credential_strategy = ModelServingUserCredentials())
882
+ """
883
+
884
+ def __init__(self):
885
+ self.credential_type = ModelServingAuthProvider.USER_CREDENTIALS
886
+ self.default_credentials = DefaultCredentials()
887
+
888
+ def auth_type(self):
889
+ if ModelServingAuthProvider.should_fetch_model_serving_environment_oauth():
890
+ return "model_serving_" + self.credential_type
891
+ else:
892
+ return self.default_credentials.auth_type()
893
+
894
+ def __call__(self, cfg: 'Config') -> CredentialsProvider:
895
+ if ModelServingAuthProvider.should_fetch_model_serving_environment_oauth():
896
+ header_factory = model_serving_auth_visitor(cfg, self.credential_type)
897
+ if not header_factory:
898
+ raise ValueError(
899
+ f"Unable to authenticate using {self.credential_type} in Databricks Model Serving Environment"
900
+ )
901
+ return header_factory
902
+ else:
903
+ return self.default_credentials(cfg)
@@ -11,9 +11,10 @@ class JobsExt(jobs.JobsAPI):
11
11
  include_history: Optional[bool] = None,
12
12
  include_resolved_values: Optional[bool] = None,
13
13
  page_token: Optional[str] = None) -> jobs.Run:
14
- """
15
- This method fetches the details of a run identified by `run_id`. If the run has multiple pages of tasks or iterations,
16
- it will paginate through all pages and aggregate the results.
14
+ """Get a single job run.
15
+
16
+ Retrieve the metadata of a run. If a run has multiple pages of tasks, it will paginate through all pages of tasks, iterations, job_clusters, job_parameters, and repair history.
17
+
17
18
  :param run_id: int
18
19
  The canonical identifier of the run for which to retrieve the metadata. This field is required.
19
20
  :param include_history: bool (optional)
@@ -21,8 +22,9 @@ class JobsExt(jobs.JobsAPI):
21
22
  :param include_resolved_values: bool (optional)
22
23
  Whether to include resolved parameter values in the response.
23
24
  :param page_token: str (optional)
24
- To list the next page or the previous page of job tasks, set this field to the value of the
25
- `next_page_token` or `prev_page_token` returned in the GetJob response.
25
+ To list the next page of job tasks, set this field to the value of the `next_page_token` returned in
26
+ the GetJob response.
27
+
26
28
  :returns: :class:`Run`
27
29
  """
28
30
  run = super().get_run(run_id,
@@ -34,6 +36,7 @@ class JobsExt(jobs.JobsAPI):
34
36
  # When querying a ForEach task run, a page token is returned when there are more than 100 iterations. Only a single task is returned, corresponding to the ForEach task itself. Therefore, the client only reads the iterations from the next page and not the tasks.
35
37
  is_paginating_iterations = run.iterations is not None and len(run.iterations) > 0
36
38
 
39
+ # runs/get response includes next_page_token as long as there are more pages to fetch.
37
40
  while run.next_page_token is not None:
38
41
  next_run = super().get_run(run_id,
39
42
  include_history=include_history,
@@ -43,7 +46,10 @@ class JobsExt(jobs.JobsAPI):
43
46
  run.iterations.extend(next_run.iterations)
44
47
  else:
45
48
  run.tasks.extend(next_run.tasks)
49
+ # Each new page of runs/get response includes the next page of the job_clusters, job_parameters, and repair history.
50
+ run.job_clusters.extend(next_run.job_clusters)
51
+ run.job_parameters.extend(next_run.job_parameters)
52
+ run.repair_history.extend(next_run.repair_history)
46
53
  run.next_page_token = next_run.next_page_token
47
54
 
48
- run.prev_page_token = None
49
55
  return run
@@ -45,6 +45,9 @@ class App:
45
45
  description: Optional[str] = None
46
46
  """The description of the app."""
47
47
 
48
+ id: Optional[str] = None
49
+ """The unique identifier of the app."""
50
+
48
51
  pending_deployment: Optional[AppDeployment] = None
49
52
  """The pending deployment of the app. A deployment is considered pending when it is being prepared
50
53
  for deployment to the app compute."""
@@ -78,6 +81,7 @@ class App:
78
81
  if self.default_source_code_path is not None:
79
82
  body['default_source_code_path'] = self.default_source_code_path
80
83
  if self.description is not None: body['description'] = self.description
84
+ if self.id is not None: body['id'] = self.id
81
85
  if self.name is not None: body['name'] = self.name
82
86
  if self.pending_deployment: body['pending_deployment'] = self.pending_deployment.as_dict()
83
87
  if self.resources: body['resources'] = [v.as_dict() for v in self.resources]
@@ -102,6 +106,7 @@ class App:
102
106
  if self.default_source_code_path is not None:
103
107
  body['default_source_code_path'] = self.default_source_code_path
104
108
  if self.description is not None: body['description'] = self.description
109
+ if self.id is not None: body['id'] = self.id
105
110
  if self.name is not None: body['name'] = self.name
106
111
  if self.pending_deployment: body['pending_deployment'] = self.pending_deployment
107
112
  if self.resources: body['resources'] = self.resources
@@ -125,6 +130,7 @@ class App:
125
130
  creator=d.get('creator', None),
126
131
  default_source_code_path=d.get('default_source_code_path', None),
127
132
  description=d.get('description', None),
133
+ id=d.get('id', None),
128
134
  name=d.get('name', None),
129
135
  pending_deployment=_from_dict(d, 'pending_deployment', AppDeployment),
130
136
  resources=_repeated_dict(d, 'resources', AppResource),
@@ -894,6 +894,27 @@ class GetBudgetConfigurationResponse:
894
894
  return cls(budget=_from_dict(d, 'budget', BudgetConfiguration))
895
895
 
896
896
 
897
+ @dataclass
898
+ class LimitConfig:
899
+ """The limit configuration of the policy. Limit configuration provide a budget policy level cost
900
+ control by enforcing the limit."""
901
+
902
+ def as_dict(self) -> dict:
903
+ """Serializes the LimitConfig into a dictionary suitable for use as a JSON request body."""
904
+ body = {}
905
+ return body
906
+
907
+ def as_shallow_dict(self) -> dict:
908
+ """Serializes the LimitConfig into a shallow dictionary of its immediate attributes."""
909
+ body = {}
910
+ return body
911
+
912
+ @classmethod
913
+ def from_dict(cls, d: Dict[str, any]) -> LimitConfig:
914
+ """Deserializes the LimitConfig from a dictionary."""
915
+ return cls()
916
+
917
+
897
918
  @dataclass
898
919
  class ListBudgetConfigurationsResponse:
899
920
  budgets: Optional[List[BudgetConfiguration]] = None
@@ -1641,23 +1662,32 @@ class BudgetPolicyAPI:
1641
1662
  return
1642
1663
  query['page_token'] = json['next_page_token']
1643
1664
 
1644
- def update(self, policy_id: str, *, policy: Optional[BudgetPolicy] = None) -> BudgetPolicy:
1665
+ def update(self,
1666
+ policy_id: str,
1667
+ *,
1668
+ limit_config: Optional[LimitConfig] = None,
1669
+ policy: Optional[BudgetPolicy] = None) -> BudgetPolicy:
1645
1670
  """Update a budget policy.
1646
1671
 
1647
1672
  Updates a policy
1648
1673
 
1649
1674
  :param policy_id: str
1650
1675
  The Id of the policy. This field is generated by Databricks and globally unique.
1676
+ :param limit_config: :class:`LimitConfig` (optional)
1677
+ DEPRECATED. This is redundant field as LimitConfig is part of the BudgetPolicy
1651
1678
  :param policy: :class:`BudgetPolicy` (optional)
1652
1679
  Contains the BudgetPolicy details.
1653
1680
 
1654
1681
  :returns: :class:`BudgetPolicy`
1655
1682
  """
1656
1683
  body = policy.as_dict()
1684
+ query = {}
1685
+ if limit_config is not None: query['limit_config'] = limit_config.as_dict()
1657
1686
  headers = {'Accept': 'application/json', 'Content-Type': 'application/json', }
1658
1687
 
1659
1688
  res = self._api.do('PATCH',
1660
1689
  f'/api/2.1/accounts/{self._api.account_id}/budget-policies/{policy_id}',
1690
+ query=query,
1661
1691
  body=body,
1662
1692
  headers=headers)
1663
1693
  return BudgetPolicy.from_dict(res)
@@ -8983,6 +8983,7 @@ class CatalogsAPI:
8983
8983
  if page_token is not None: query['page_token'] = page_token
8984
8984
  headers = {'Accept': 'application/json', }
8985
8985
 
8986
+ if "max_results" not in query: query['max_results'] = 0
8986
8987
  while True:
8987
8988
  json = self._api.do('GET', '/api/2.1/unity-catalog/catalogs', query=query, headers=headers)
8988
8989
  if 'catalogs' in json:
@@ -9151,6 +9152,7 @@ class ConnectionsAPI:
9151
9152
  if page_token is not None: query['page_token'] = page_token
9152
9153
  headers = {'Accept': 'application/json', }
9153
9154
 
9155
+ if "max_results" not in query: query['max_results'] = 0
9154
9156
  while True:
9155
9157
  json = self._api.do('GET', '/api/2.1/unity-catalog/connections', query=query, headers=headers)
9156
9158
  if 'connections' in json:
@@ -9656,6 +9658,7 @@ class ExternalLocationsAPI:
9656
9658
  if page_token is not None: query['page_token'] = page_token
9657
9659
  headers = {'Accept': 'application/json', }
9658
9660
 
9661
+ if "max_results" not in query: query['max_results'] = 0
9659
9662
  while True:
9660
9663
  json = self._api.do('GET',
9661
9664
  '/api/2.1/unity-catalog/external-locations',
@@ -11389,6 +11392,7 @@ class SchemasAPI:
11389
11392
  if page_token is not None: query['page_token'] = page_token
11390
11393
  headers = {'Accept': 'application/json', }
11391
11394
 
11395
+ if "max_results" not in query: query['max_results'] = 0
11392
11396
  while True:
11393
11397
  json = self._api.do('GET', '/api/2.1/unity-catalog/schemas', query=query, headers=headers)
11394
11398
  if 'schemas' in json:
@@ -11578,6 +11582,7 @@ class StorageCredentialsAPI:
11578
11582
  if page_token is not None: query['page_token'] = page_token
11579
11583
  headers = {'Accept': 'application/json', }
11580
11584
 
11585
+ if "max_results" not in query: query['max_results'] = 0
11581
11586
  while True:
11582
11587
  json = self._api.do('GET',
11583
11588
  '/api/2.1/unity-catalog/storage-credentials',
@@ -11802,6 +11807,7 @@ class SystemSchemasAPI:
11802
11807
  if page_token is not None: query['page_token'] = page_token
11803
11808
  headers = {'Accept': 'application/json', }
11804
11809
 
11810
+ if "max_results" not in query: query['max_results'] = 0
11805
11811
  while True:
11806
11812
  json = self._api.do('GET',
11807
11813
  f'/api/2.1/unity-catalog/metastores/{metastore_id}/systemschemas',
@@ -12044,6 +12050,7 @@ class TablesAPI:
12044
12050
  if schema_name is not None: query['schema_name'] = schema_name
12045
12051
  headers = {'Accept': 'application/json', }
12046
12052
 
12053
+ if "max_results" not in query: query['max_results'] = 0
12047
12054
  while True:
12048
12055
  json = self._api.do('GET', '/api/2.1/unity-catalog/tables', query=query, headers=headers)
12049
12056
  if 'tables' in json:
@@ -12104,6 +12111,7 @@ class TablesAPI:
12104
12111
  if table_name_pattern is not None: query['table_name_pattern'] = table_name_pattern
12105
12112
  headers = {'Accept': 'application/json', }
12106
12113
 
12114
+ if "max_results" not in query: query['max_results'] = 0
12107
12115
  while True:
12108
12116
  json = self._api.do('GET', '/api/2.1/unity-catalog/table-summaries', query=query, headers=headers)
12109
12117
  if 'tables' in json:
@@ -289,24 +289,11 @@ class CleanRoomAssetNotebook:
289
289
  """Base 64 representation of the notebook contents. This is the same format as returned by
290
290
  :method:workspace/export with the format of **HTML**."""
291
291
 
292
- review_state: Optional[CleanRoomNotebookReviewNotebookReviewState] = None
293
- """top-level status derived from all reviews"""
294
-
295
- reviews: Optional[List[CleanRoomNotebookReview]] = None
296
- """All existing approvals or rejections"""
297
-
298
- runner_collaborators: Optional[List[CleanRoomCollaborator]] = None
299
- """collaborators that can run the notebook"""
300
-
301
292
  def as_dict(self) -> dict:
302
293
  """Serializes the CleanRoomAssetNotebook into a dictionary suitable for use as a JSON request body."""
303
294
  body = {}
304
295
  if self.etag is not None: body['etag'] = self.etag
305
296
  if self.notebook_content is not None: body['notebook_content'] = self.notebook_content
306
- if self.review_state is not None: body['review_state'] = self.review_state.value
307
- if self.reviews: body['reviews'] = [v.as_dict() for v in self.reviews]
308
- if self.runner_collaborators:
309
- body['runner_collaborators'] = [v.as_dict() for v in self.runner_collaborators]
310
297
  return body
311
298
 
312
299
  def as_shallow_dict(self) -> dict:
@@ -314,19 +301,12 @@ class CleanRoomAssetNotebook:
314
301
  body = {}
315
302
  if self.etag is not None: body['etag'] = self.etag
316
303
  if self.notebook_content is not None: body['notebook_content'] = self.notebook_content
317
- if self.review_state is not None: body['review_state'] = self.review_state
318
- if self.reviews: body['reviews'] = self.reviews
319
- if self.runner_collaborators: body['runner_collaborators'] = self.runner_collaborators
320
304
  return body
321
305
 
322
306
  @classmethod
323
307
  def from_dict(cls, d: Dict[str, any]) -> CleanRoomAssetNotebook:
324
308
  """Deserializes the CleanRoomAssetNotebook from a dictionary."""
325
- return cls(etag=d.get('etag', None),
326
- notebook_content=d.get('notebook_content', None),
327
- review_state=_enum(d, 'review_state', CleanRoomNotebookReviewNotebookReviewState),
328
- reviews=_repeated_dict(d, 'reviews', CleanRoomNotebookReview),
329
- runner_collaborators=_repeated_dict(d, 'runner_collaborators', CleanRoomCollaborator))
309
+ return cls(etag=d.get('etag', None), notebook_content=d.get('notebook_content', None))
330
310
 
331
311
 
332
312
  class CleanRoomAssetStatusEnum(Enum):
@@ -531,56 +511,6 @@ class CleanRoomCollaborator:
531
511
  organization_name=d.get('organization_name', None))
532
512
 
533
513
 
534
- @dataclass
535
- class CleanRoomNotebookReview:
536
- comment: Optional[str] = None
537
- """review comment"""
538
-
539
- created_at_millis: Optional[int] = None
540
- """timestamp of when the review was submitted"""
541
-
542
- review_state: Optional[CleanRoomNotebookReviewNotebookReviewState] = None
543
- """review outcome"""
544
-
545
- reviewer_collaborator_alias: Optional[str] = None
546
- """collaborator alias of the reviewer"""
547
-
548
- def as_dict(self) -> dict:
549
- """Serializes the CleanRoomNotebookReview into a dictionary suitable for use as a JSON request body."""
550
- body = {}
551
- if self.comment is not None: body['comment'] = self.comment
552
- if self.created_at_millis is not None: body['created_at_millis'] = self.created_at_millis
553
- if self.review_state is not None: body['review_state'] = self.review_state.value
554
- if self.reviewer_collaborator_alias is not None:
555
- body['reviewer_collaborator_alias'] = self.reviewer_collaborator_alias
556
- return body
557
-
558
- def as_shallow_dict(self) -> dict:
559
- """Serializes the CleanRoomNotebookReview into a shallow dictionary of its immediate attributes."""
560
- body = {}
561
- if self.comment is not None: body['comment'] = self.comment
562
- if self.created_at_millis is not None: body['created_at_millis'] = self.created_at_millis
563
- if self.review_state is not None: body['review_state'] = self.review_state
564
- if self.reviewer_collaborator_alias is not None:
565
- body['reviewer_collaborator_alias'] = self.reviewer_collaborator_alias
566
- return body
567
-
568
- @classmethod
569
- def from_dict(cls, d: Dict[str, any]) -> CleanRoomNotebookReview:
570
- """Deserializes the CleanRoomNotebookReview from a dictionary."""
571
- return cls(comment=d.get('comment', None),
572
- created_at_millis=d.get('created_at_millis', None),
573
- review_state=_enum(d, 'review_state', CleanRoomNotebookReviewNotebookReviewState),
574
- reviewer_collaborator_alias=d.get('reviewer_collaborator_alias', None))
575
-
576
-
577
- class CleanRoomNotebookReviewNotebookReviewState(Enum):
578
-
579
- APPROVED = 'APPROVED'
580
- PENDING = 'PENDING'
581
- REJECTED = 'REJECTED'
582
-
583
-
584
514
  @dataclass
585
515
  class CleanRoomNotebookTaskRun:
586
516
  """Stores information about a single task run."""
@@ -637,11 +637,11 @@ class ClusterAttributes:
637
637
  a set of default values will be used."""
638
638
 
639
639
  cluster_log_conf: Optional[ClusterLogConf] = None
640
- """The configuration for delivering spark logs to a long-term storage destination. Two kinds of
641
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster.
642
- If the conf is given, the logs will be delivered to the destination every `5 mins`. The
643
- destination of driver logs is `$destination/$clusterId/driver`, while the destination of
644
- executor logs is `$destination/$clusterId/executor`."""
640
+ """The configuration for delivering spark logs to a long-term storage destination. Three kinds of
641
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
642
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination
643
+ every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the
644
+ destination of executor logs is `$destination/$clusterId/executor`."""
645
645
 
646
646
  cluster_name: Optional[str] = None
647
647
  """Cluster name requested by the user. This doesn't have to be unique. If not specified at
@@ -947,11 +947,11 @@ class ClusterDetails:
947
947
  while each new cluster has a globally unique id."""
948
948
 
949
949
  cluster_log_conf: Optional[ClusterLogConf] = None
950
- """The configuration for delivering spark logs to a long-term storage destination. Two kinds of
951
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster.
952
- If the conf is given, the logs will be delivered to the destination every `5 mins`. The
953
- destination of driver logs is `$destination/$clusterId/driver`, while the destination of
954
- executor logs is `$destination/$clusterId/executor`."""
950
+ """The configuration for delivering spark logs to a long-term storage destination. Three kinds of
951
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
952
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination
953
+ every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the
954
+ destination of executor logs is `$destination/$clusterId/executor`."""
955
955
 
956
956
  cluster_log_status: Optional[LogSyncStatus] = None
957
957
  """Cluster log delivery status."""
@@ -1428,11 +1428,16 @@ class ClusterLogConf:
1428
1428
  access s3, please make sure the cluster iam role in `instance_profile_arn` has permission to
1429
1429
  write data to the s3 destination."""
1430
1430
 
1431
+ volumes: Optional[VolumesStorageInfo] = None
1432
+ """destination needs to be provided. e.g. `{ "volumes" : { "destination" :
1433
+ "/Volumes/catalog/schema/volume/cluster_log" } }`"""
1434
+
1431
1435
  def as_dict(self) -> dict:
1432
1436
  """Serializes the ClusterLogConf into a dictionary suitable for use as a JSON request body."""
1433
1437
  body = {}
1434
1438
  if self.dbfs: body['dbfs'] = self.dbfs.as_dict()
1435
1439
  if self.s3: body['s3'] = self.s3.as_dict()
1440
+ if self.volumes: body['volumes'] = self.volumes.as_dict()
1436
1441
  return body
1437
1442
 
1438
1443
  def as_shallow_dict(self) -> dict:
@@ -1440,12 +1445,15 @@ class ClusterLogConf:
1440
1445
  body = {}
1441
1446
  if self.dbfs: body['dbfs'] = self.dbfs
1442
1447
  if self.s3: body['s3'] = self.s3
1448
+ if self.volumes: body['volumes'] = self.volumes
1443
1449
  return body
1444
1450
 
1445
1451
  @classmethod
1446
1452
  def from_dict(cls, d: Dict[str, any]) -> ClusterLogConf:
1447
1453
  """Deserializes the ClusterLogConf from a dictionary."""
1448
- return cls(dbfs=_from_dict(d, 'dbfs', DbfsStorageInfo), s3=_from_dict(d, 's3', S3StorageInfo))
1454
+ return cls(dbfs=_from_dict(d, 'dbfs', DbfsStorageInfo),
1455
+ s3=_from_dict(d, 's3', S3StorageInfo),
1456
+ volumes=_from_dict(d, 'volumes', VolumesStorageInfo))
1449
1457
 
1450
1458
 
1451
1459
  @dataclass
@@ -1918,11 +1926,11 @@ class ClusterSpec:
1918
1926
  a set of default values will be used."""
1919
1927
 
1920
1928
  cluster_log_conf: Optional[ClusterLogConf] = None
1921
- """The configuration for delivering spark logs to a long-term storage destination. Two kinds of
1922
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster.
1923
- If the conf is given, the logs will be delivered to the destination every `5 mins`. The
1924
- destination of driver logs is `$destination/$clusterId/driver`, while the destination of
1925
- executor logs is `$destination/$clusterId/executor`."""
1929
+ """The configuration for delivering spark logs to a long-term storage destination. Three kinds of
1930
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
1931
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination
1932
+ every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the
1933
+ destination of executor logs is `$destination/$clusterId/executor`."""
1926
1934
 
1927
1935
  cluster_name: Optional[str] = None
1928
1936
  """Cluster name requested by the user. This doesn't have to be unique. If not specified at
@@ -2334,11 +2342,11 @@ class CreateCluster:
2334
2342
  cluster."""
2335
2343
 
2336
2344
  cluster_log_conf: Optional[ClusterLogConf] = None
2337
- """The configuration for delivering spark logs to a long-term storage destination. Two kinds of
2338
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster.
2339
- If the conf is given, the logs will be delivered to the destination every `5 mins`. The
2340
- destination of driver logs is `$destination/$clusterId/driver`, while the destination of
2341
- executor logs is `$destination/$clusterId/executor`."""
2345
+ """The configuration for delivering spark logs to a long-term storage destination. Three kinds of
2346
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
2347
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination
2348
+ every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the
2349
+ destination of executor logs is `$destination/$clusterId/executor`."""
2342
2350
 
2343
2351
  cluster_name: Optional[str] = None
2344
2352
  """Cluster name requested by the user. This doesn't have to be unique. If not specified at
@@ -3469,11 +3477,11 @@ class EditCluster:
3469
3477
  a set of default values will be used."""
3470
3478
 
3471
3479
  cluster_log_conf: Optional[ClusterLogConf] = None
3472
- """The configuration for delivering spark logs to a long-term storage destination. Two kinds of
3473
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster.
3474
- If the conf is given, the logs will be delivered to the destination every `5 mins`. The
3475
- destination of driver logs is `$destination/$clusterId/driver`, while the destination of
3476
- executor logs is `$destination/$clusterId/executor`."""
3480
+ """The configuration for delivering spark logs to a long-term storage destination. Three kinds of
3481
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
3482
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination
3483
+ every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the
3484
+ destination of executor logs is `$destination/$clusterId/executor`."""
3477
3485
 
3478
3486
  cluster_name: Optional[str] = None
3479
3487
  """Cluster name requested by the user. This doesn't have to be unique. If not specified at
@@ -7773,11 +7781,11 @@ class UpdateClusterResource:
7773
7781
  a set of default values will be used."""
7774
7782
 
7775
7783
  cluster_log_conf: Optional[ClusterLogConf] = None
7776
- """The configuration for delivering spark logs to a long-term storage destination. Two kinds of
7777
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster.
7778
- If the conf is given, the logs will be delivered to the destination every `5 mins`. The
7779
- destination of driver logs is `$destination/$clusterId/driver`, while the destination of
7780
- executor logs is `$destination/$clusterId/executor`."""
7784
+ """The configuration for delivering spark logs to a long-term storage destination. Three kinds of
7785
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
7786
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination
7787
+ every `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the
7788
+ destination of executor logs is `$destination/$clusterId/executor`."""
7781
7789
 
7782
7790
  cluster_name: Optional[str] = None
7783
7791
  """Cluster name requested by the user. This doesn't have to be unique. If not specified at
@@ -8077,7 +8085,7 @@ class UpdateResponse:
8077
8085
  @dataclass
8078
8086
  class VolumesStorageInfo:
8079
8087
  destination: str
8080
- """Unity Catalog Volumes file destination, e.g. `/Volumes/my-init.sh`"""
8088
+ """Unity Catalog volumes file destination, e.g. `/Volumes/catalog/schema/volume/dir/file`"""
8081
8089
 
8082
8090
  def as_dict(self) -> dict:
8083
8091
  """Serializes the VolumesStorageInfo into a dictionary suitable for use as a JSON request body."""
@@ -8619,11 +8627,11 @@ class ClustersAPI:
8619
8627
  :param clone_from: :class:`CloneCluster` (optional)
8620
8628
  When specified, this clones libraries from a source cluster during the creation of a new cluster.
8621
8629
  :param cluster_log_conf: :class:`ClusterLogConf` (optional)
8622
- The configuration for delivering spark logs to a long-term storage destination. Two kinds of
8623
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster. If
8624
- the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of
8625
- driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is
8626
- `$destination/$clusterId/executor`.
8630
+ The configuration for delivering spark logs to a long-term storage destination. Three kinds of
8631
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
8632
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination every
8633
+ `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination
8634
+ of executor logs is `$destination/$clusterId/executor`.
8627
8635
  :param cluster_name: str (optional)
8628
8636
  Cluster name requested by the user. This doesn't have to be unique. If not specified at creation,
8629
8637
  the cluster name will be an empty string.
@@ -8952,11 +8960,11 @@ class ClustersAPI:
8952
8960
  Attributes related to clusters running on Microsoft Azure. If not specified at cluster creation, a
8953
8961
  set of default values will be used.
8954
8962
  :param cluster_log_conf: :class:`ClusterLogConf` (optional)
8955
- The configuration for delivering spark logs to a long-term storage destination. Two kinds of
8956
- destinations (dbfs and s3) are supported. Only one destination can be specified for one cluster. If
8957
- the conf is given, the logs will be delivered to the destination every `5 mins`. The destination of
8958
- driver logs is `$destination/$clusterId/driver`, while the destination of executor logs is
8959
- `$destination/$clusterId/executor`.
8963
+ The configuration for delivering spark logs to a long-term storage destination. Three kinds of
8964
+ destinations (DBFS, S3 and Unity Catalog volumes) are supported. Only one destination can be
8965
+ specified for one cluster. If the conf is given, the logs will be delivered to the destination every
8966
+ `5 mins`. The destination of driver logs is `$destination/$clusterId/driver`, while the destination
8967
+ of executor logs is `$destination/$clusterId/executor`.
8960
8968
  :param cluster_name: str (optional)
8961
8969
  Cluster name requested by the user. This doesn't have to be unique. If not specified at creation,
8962
8970
  the cluster name will be an empty string.