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

@@ -159,16 +159,29 @@ class _BaseClient:
159
159
  if isinstance(data, (str, bytes)):
160
160
  data = io.BytesIO(data.encode('utf-8') if isinstance(data, str) else data)
161
161
 
162
- # Only retry if the request is not a stream or if the stream is seekable and
163
- # we can rewind it. This is necessary to avoid bugs where the retry doesn't
164
- # re-read already read data from the body.
165
- if data is not None and not self._is_seekable_stream(data):
166
- logger.debug(f"Retry disabled for non-seekable stream: type={type(data)}")
167
- call = self._perform
168
- else:
162
+ if not data:
163
+ # The request is not a stream.
169
164
  call = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
170
165
  is_retryable=self._is_retryable,
171
166
  clock=self._clock)(self._perform)
167
+ elif self._is_seekable_stream(data):
168
+ # Keep track of the initial position of the stream so that we can rewind to it
169
+ # if we need to retry the request.
170
+ initial_data_position = data.tell()
171
+
172
+ def rewind():
173
+ logger.debug(f"Rewinding input data to offset {initial_data_position} before retry")
174
+ data.seek(initial_data_position)
175
+
176
+ call = retried(timeout=timedelta(seconds=self._retry_timeout_seconds),
177
+ is_retryable=self._is_retryable,
178
+ clock=self._clock,
179
+ before_retry=rewind)(self._perform)
180
+ else:
181
+ # Do not retry if the stream is not seekable. This is necessary to avoid bugs
182
+ # where the retry doesn't re-read already read data from the stream.
183
+ logger.debug(f"Retry disabled for non-seekable stream: type={type(data)}")
184
+ call = self._perform
172
185
 
173
186
  response = call(method,
174
187
  url,
@@ -249,12 +262,6 @@ class _BaseClient:
249
262
  files=None,
250
263
  data=None,
251
264
  auth: Callable[[requests.PreparedRequest], requests.PreparedRequest] = None):
252
- # Keep track of the initial position of the stream so that we can rewind it if
253
- # we need to retry the request.
254
- initial_data_position = 0
255
- if self._is_seekable_stream(data):
256
- initial_data_position = data.tell()
257
-
258
265
  response = self._session.request(method,
259
266
  url,
260
267
  params=self._fix_query_string(query),
@@ -266,16 +273,8 @@ class _BaseClient:
266
273
  stream=raw,
267
274
  timeout=self._http_timeout_seconds)
268
275
  self._record_request_log(response, raw=raw or data is not None or files is not None)
269
-
270
276
  error = self._error_parser.get_api_error(response)
271
277
  if error is not None:
272
- # If the request body is a seekable stream, rewind it so that it is ready
273
- # to be read again in case of a retry.
274
- #
275
- # TODO: This should be moved into a "before-retry" hook to avoid one
276
- # unnecessary seek on the last failed retry before aborting.
277
- if self._is_seekable_stream(data):
278
- data.seek(initial_data_position)
279
278
  raise error from None
280
279
 
281
280
  return response
@@ -676,12 +676,18 @@ class MetadataServiceTokenSource(Refreshable):
676
676
  self.host = cfg.host
677
677
 
678
678
  def refresh(self) -> Token:
679
- resp = requests.get(self.url,
680
- timeout=self._metadata_service_timeout,
681
- headers={
682
- self.METADATA_SERVICE_VERSION_HEADER: self.METADATA_SERVICE_VERSION,
683
- self.METADATA_SERVICE_HOST_HEADER: self.host
684
- })
679
+ resp = requests.get(
680
+ self.url,
681
+ timeout=self._metadata_service_timeout,
682
+ headers={
683
+ self.METADATA_SERVICE_VERSION_HEADER: self.METADATA_SERVICE_VERSION,
684
+ self.METADATA_SERVICE_HOST_HEADER: self.host
685
+ },
686
+ proxies={
687
+ # Explicitly exclude localhost from being proxied. This is necessary
688
+ # for Metadata URLs which typically point to localhost.
689
+ "no_proxy": "localhost,127.0.0.1"
690
+ })
685
691
  json_resp: dict[str, Union[str, float]] = resp.json()
686
692
  access_token = json_resp.get("access_token", None)
687
693
  if access_token is None:
@@ -1,8 +1,9 @@
1
1
  import json as js
2
2
  from typing import Dict, Optional
3
3
 
4
+ from requests import Response
5
+
4
6
  from databricks.sdk.service.serving import (ExternalFunctionRequestHttpMethod,
5
- ExternalFunctionResponse,
6
7
  ServingEndpointsAPI)
7
8
 
8
9
 
@@ -63,7 +64,7 @@ class ServingEndpointsExt(ServingEndpointsAPI):
63
64
  *,
64
65
  headers: Optional[Dict[str, str]] = None,
65
66
  json: Optional[Dict[str, str]] = None,
66
- params: Optional[Dict[str, str]] = None) -> ExternalFunctionResponse:
67
+ params: Optional[Dict[str, str]] = None) -> Response:
67
68
  """Make external services call using the credentials stored in UC Connection.
68
69
  **NOTE:** Experimental: This API may change or be removed in a future release without warning.
69
70
  :param conn: str
@@ -79,13 +80,27 @@ class ServingEndpointsExt(ServingEndpointsAPI):
79
80
  JSON payload for the request.
80
81
  :param params: Dict[str,str] (optional)
81
82
  Query parameters for the request.
82
- :returns: :class:`ExternalFunctionResponse`
83
+ :returns: :class:`Response`
83
84
  """
85
+ response = Response()
86
+ response.status_code = 200
87
+ server_response = super().http_request(connection_name=conn,
88
+ method=method,
89
+ path=path,
90
+ headers=js.dumps(headers) if headers is not None else None,
91
+ json=js.dumps(json) if json is not None else None,
92
+ params=js.dumps(params) if params is not None else None)
93
+
94
+ # Read the content from the HttpRequestResponse object
95
+ if hasattr(server_response, "contents") and hasattr(server_response.contents, "read"):
96
+ raw_content = server_response.contents.read() # Read the bytes
97
+ else:
98
+ raise ValueError("Invalid response from the server.")
99
+
100
+ # Set the raw content
101
+ if isinstance(raw_content, bytes):
102
+ response._content = raw_content
103
+ else:
104
+ raise ValueError("Contents must be bytes.")
84
105
 
85
- return super.http_request(connection_name=conn,
86
- method=method,
87
- path=path,
88
- headers=js.dumps(headers),
89
- json=js.dumps(json),
90
- params=js.dumps(params),
91
- )
106
+ return response
databricks/sdk/retries.py CHANGED
@@ -13,7 +13,8 @@ def retried(*,
13
13
  on: Sequence[Type[BaseException]] = None,
14
14
  is_retryable: Callable[[BaseException], Optional[str]] = None,
15
15
  timeout=timedelta(minutes=20),
16
- clock: Clock = None):
16
+ clock: Clock = None,
17
+ before_retry: Callable = None):
17
18
  has_allowlist = on is not None
18
19
  has_callback = is_retryable is not None
19
20
  if not (has_allowlist or has_callback) or (has_allowlist and has_callback):
@@ -54,6 +55,9 @@ def retried(*,
54
55
  raise err
55
56
 
56
57
  logger.debug(f'Retrying: {retry_reason} (sleeping ~{sleep}s)')
58
+ if before_retry:
59
+ before_retry()
60
+
57
61
  clock.sleep(sleep + random())
58
62
  attempt += 1
59
63
  raise TimeoutError(f'Timed out after {timeout}') from last_err
@@ -11,6 +11,8 @@ from ._internal import _enum, _from_dict, _repeated_dict
11
11
 
12
12
  _LOG = logging.getLogger('databricks.sdk')
13
13
 
14
+ from databricks.sdk.service import compute
15
+
14
16
  # all definitions in this file are in alphabetical order
15
17
 
16
18
 
@@ -311,6 +313,44 @@ class BudgetConfigurationFilterWorkspaceIdClause:
311
313
  values=d.get('values', None))
312
314
 
313
315
 
316
+ @dataclass
317
+ class BudgetPolicy:
318
+ """Contains the BudgetPolicy details."""
319
+
320
+ policy_id: str
321
+ """The Id of the policy. This field is generated by Databricks and globally unique."""
322
+
323
+ custom_tags: Optional[List[compute.CustomPolicyTag]] = None
324
+ """A list of tags defined by the customer. At most 20 entries are allowed per policy."""
325
+
326
+ policy_name: Optional[str] = None
327
+ """The name of the policy. - Must be unique among active policies. - Can contain only characters
328
+ from the ISO 8859-1 (latin1) set."""
329
+
330
+ def as_dict(self) -> dict:
331
+ """Serializes the BudgetPolicy into a dictionary suitable for use as a JSON request body."""
332
+ body = {}
333
+ if self.custom_tags: body['custom_tags'] = [v.as_dict() for v in self.custom_tags]
334
+ if self.policy_id is not None: body['policy_id'] = self.policy_id
335
+ if self.policy_name is not None: body['policy_name'] = self.policy_name
336
+ return body
337
+
338
+ def as_shallow_dict(self) -> dict:
339
+ """Serializes the BudgetPolicy into a shallow dictionary of its immediate attributes."""
340
+ body = {}
341
+ if self.custom_tags: body['custom_tags'] = self.custom_tags
342
+ if self.policy_id is not None: body['policy_id'] = self.policy_id
343
+ if self.policy_name is not None: body['policy_name'] = self.policy_name
344
+ return body
345
+
346
+ @classmethod
347
+ def from_dict(cls, d: Dict[str, any]) -> BudgetPolicy:
348
+ """Deserializes the BudgetPolicy from a dictionary."""
349
+ return cls(custom_tags=_repeated_dict(d, 'custom_tags', compute.CustomPolicyTag),
350
+ policy_id=d.get('policy_id', None),
351
+ policy_name=d.get('policy_name', None))
352
+
353
+
314
354
  @dataclass
315
355
  class CreateBillingUsageDashboardRequest:
316
356
  dashboard_type: Optional[UsageDashboardType] = None
@@ -536,6 +576,45 @@ class CreateBudgetConfigurationResponse:
536
576
  return cls(budget=_from_dict(d, 'budget', BudgetConfiguration))
537
577
 
538
578
 
579
+ @dataclass
580
+ class CreateBudgetPolicyRequest:
581
+ """A request to create a BudgetPolicy."""
582
+
583
+ custom_tags: Optional[List[compute.CustomPolicyTag]] = None
584
+ """A list of tags defined by the customer. At most 40 entries are allowed per policy."""
585
+
586
+ policy_name: Optional[str] = None
587
+ """The name of the policy. - Must be unique among active policies. - Can contain only characters of
588
+ 0-9, a-z, A-Z, -, =, ., :, /, @, _, +, whitespace."""
589
+
590
+ request_id: Optional[str] = None
591
+ """A unique identifier for this request. Restricted to 36 ASCII characters. A random UUID is
592
+ recommended. This request is only idempotent if a `request_id` is provided."""
593
+
594
+ def as_dict(self) -> dict:
595
+ """Serializes the CreateBudgetPolicyRequest into a dictionary suitable for use as a JSON request body."""
596
+ body = {}
597
+ if self.custom_tags: body['custom_tags'] = [v.as_dict() for v in self.custom_tags]
598
+ if self.policy_name is not None: body['policy_name'] = self.policy_name
599
+ if self.request_id is not None: body['request_id'] = self.request_id
600
+ return body
601
+
602
+ def as_shallow_dict(self) -> dict:
603
+ """Serializes the CreateBudgetPolicyRequest into a shallow dictionary of its immediate attributes."""
604
+ body = {}
605
+ if self.custom_tags: body['custom_tags'] = self.custom_tags
606
+ if self.policy_name is not None: body['policy_name'] = self.policy_name
607
+ if self.request_id is not None: body['request_id'] = self.request_id
608
+ return body
609
+
610
+ @classmethod
611
+ def from_dict(cls, d: Dict[str, any]) -> CreateBudgetPolicyRequest:
612
+ """Deserializes the CreateBudgetPolicyRequest from a dictionary."""
613
+ return cls(custom_tags=_repeated_dict(d, 'custom_tags', compute.CustomPolicyTag),
614
+ policy_name=d.get('policy_name', None),
615
+ request_id=d.get('request_id', None))
616
+
617
+
539
618
  @dataclass
540
619
  class CreateLogDeliveryConfigurationParams:
541
620
  log_type: LogType
@@ -670,6 +749,25 @@ class DeleteBudgetConfigurationResponse:
670
749
  return cls()
671
750
 
672
751
 
752
+ @dataclass
753
+ class DeleteResponse:
754
+
755
+ def as_dict(self) -> dict:
756
+ """Serializes the DeleteResponse into a dictionary suitable for use as a JSON request body."""
757
+ body = {}
758
+ return body
759
+
760
+ def as_shallow_dict(self) -> dict:
761
+ """Serializes the DeleteResponse into a shallow dictionary of its immediate attributes."""
762
+ body = {}
763
+ return body
764
+
765
+ @classmethod
766
+ def from_dict(cls, d: Dict[str, any]) -> DeleteResponse:
767
+ """Deserializes the DeleteResponse from a dictionary."""
768
+ return cls()
769
+
770
+
673
771
  class DeliveryStatus(Enum):
674
772
  """The status string for log delivery. Possible values are: * `CREATED`: There were no log delivery
675
773
  attempts since the config was created. * `SUCCEEDED`: The latest attempt of log delivery has
@@ -708,6 +806,44 @@ class DownloadResponse:
708
806
  return cls(contents=d.get('contents', None))
709
807
 
710
808
 
809
+ @dataclass
810
+ class Filter:
811
+ """Structured representation of a filter to be applied to a list of policies. All specified filters
812
+ will be applied in conjunction."""
813
+
814
+ creator_user_id: Optional[int] = None
815
+ """The policy creator user id to be filtered on. If unspecified, all policies will be returned."""
816
+
817
+ creator_user_name: Optional[str] = None
818
+ """The policy creator user name to be filtered on. If unspecified, all policies will be returned."""
819
+
820
+ policy_name: Optional[str] = None
821
+ """The partial name of policies to be filtered on. If unspecified, all policies will be returned."""
822
+
823
+ def as_dict(self) -> dict:
824
+ """Serializes the Filter into a dictionary suitable for use as a JSON request body."""
825
+ body = {}
826
+ if self.creator_user_id is not None: body['creator_user_id'] = self.creator_user_id
827
+ if self.creator_user_name is not None: body['creator_user_name'] = self.creator_user_name
828
+ if self.policy_name is not None: body['policy_name'] = self.policy_name
829
+ return body
830
+
831
+ def as_shallow_dict(self) -> dict:
832
+ """Serializes the Filter into a shallow dictionary of its immediate attributes."""
833
+ body = {}
834
+ if self.creator_user_id is not None: body['creator_user_id'] = self.creator_user_id
835
+ if self.creator_user_name is not None: body['creator_user_name'] = self.creator_user_name
836
+ if self.policy_name is not None: body['policy_name'] = self.policy_name
837
+ return body
838
+
839
+ @classmethod
840
+ def from_dict(cls, d: Dict[str, any]) -> Filter:
841
+ """Deserializes the Filter from a dictionary."""
842
+ return cls(creator_user_id=d.get('creator_user_id', None),
843
+ creator_user_name=d.get('creator_user_name', None),
844
+ policy_name=d.get('policy_name', None))
845
+
846
+
711
847
  @dataclass
712
848
  class GetBillingUsageDashboardResponse:
713
849
  dashboard_id: Optional[str] = None
@@ -787,6 +923,44 @@ class ListBudgetConfigurationsResponse:
787
923
  next_page_token=d.get('next_page_token', None))
788
924
 
789
925
 
926
+ @dataclass
927
+ class ListBudgetPoliciesResponse:
928
+ """A list of policies."""
929
+
930
+ next_page_token: Optional[str] = None
931
+ """A token that can be sent as `page_token` to retrieve the next page. If this field is omitted,
932
+ there are no subsequent pages."""
933
+
934
+ policies: Optional[List[BudgetPolicy]] = None
935
+
936
+ previous_page_token: Optional[str] = None
937
+ """A token that can be sent as `page_token` to retrieve the previous page. In this field is
938
+ omitted, there are no previous pages."""
939
+
940
+ def as_dict(self) -> dict:
941
+ """Serializes the ListBudgetPoliciesResponse into a dictionary suitable for use as a JSON request body."""
942
+ body = {}
943
+ if self.next_page_token is not None: body['next_page_token'] = self.next_page_token
944
+ if self.policies: body['policies'] = [v.as_dict() for v in self.policies]
945
+ if self.previous_page_token is not None: body['previous_page_token'] = self.previous_page_token
946
+ return body
947
+
948
+ def as_shallow_dict(self) -> dict:
949
+ """Serializes the ListBudgetPoliciesResponse into a shallow dictionary of its immediate attributes."""
950
+ body = {}
951
+ if self.next_page_token is not None: body['next_page_token'] = self.next_page_token
952
+ if self.policies: body['policies'] = self.policies
953
+ if self.previous_page_token is not None: body['previous_page_token'] = self.previous_page_token
954
+ return body
955
+
956
+ @classmethod
957
+ def from_dict(cls, d: Dict[str, any]) -> ListBudgetPoliciesResponse:
958
+ """Deserializes the ListBudgetPoliciesResponse from a dictionary."""
959
+ return cls(next_page_token=d.get('next_page_token', None),
960
+ policies=_repeated_dict(d, 'policies', BudgetPolicy),
961
+ previous_page_token=d.get('previous_page_token', None))
962
+
963
+
790
964
  class LogDeliveryConfigStatus(Enum):
791
965
  """Status of log delivery configuration. Set to `ENABLED` (enabled) or `DISABLED` (disabled).
792
966
  Defaults to `ENABLED`. You can [enable or disable the
@@ -1046,6 +1220,39 @@ class PatchStatusResponse:
1046
1220
  return cls()
1047
1221
 
1048
1222
 
1223
+ @dataclass
1224
+ class SortSpec:
1225
+ descending: Optional[bool] = None
1226
+ """Whether to sort in descending order."""
1227
+
1228
+ field: Optional[SortSpecField] = None
1229
+ """The filed to sort by"""
1230
+
1231
+ def as_dict(self) -> dict:
1232
+ """Serializes the SortSpec into a dictionary suitable for use as a JSON request body."""
1233
+ body = {}
1234
+ if self.descending is not None: body['descending'] = self.descending
1235
+ if self.field is not None: body['field'] = self.field.value
1236
+ return body
1237
+
1238
+ def as_shallow_dict(self) -> dict:
1239
+ """Serializes the SortSpec into a shallow dictionary of its immediate attributes."""
1240
+ body = {}
1241
+ if self.descending is not None: body['descending'] = self.descending
1242
+ if self.field is not None: body['field'] = self.field
1243
+ return body
1244
+
1245
+ @classmethod
1246
+ def from_dict(cls, d: Dict[str, any]) -> SortSpec:
1247
+ """Deserializes the SortSpec from a dictionary."""
1248
+ return cls(descending=d.get('descending', None), field=_enum(d, 'field', SortSpecField))
1249
+
1250
+
1251
+ class SortSpecField(Enum):
1252
+
1253
+ POLICY_NAME = 'POLICY_NAME'
1254
+
1255
+
1049
1256
  @dataclass
1050
1257
  class UpdateBudgetConfigurationBudget:
1051
1258
  account_id: Optional[str] = None
@@ -1315,6 +1522,147 @@ class BillableUsageAPI:
1315
1522
  return DownloadResponse.from_dict(res)
1316
1523
 
1317
1524
 
1525
+ class BudgetPolicyAPI:
1526
+ """A service serves REST API about Budget policies"""
1527
+
1528
+ def __init__(self, api_client):
1529
+ self._api = api_client
1530
+
1531
+ def create(self,
1532
+ *,
1533
+ custom_tags: Optional[List[compute.CustomPolicyTag]] = None,
1534
+ policy_name: Optional[str] = None,
1535
+ request_id: Optional[str] = None) -> BudgetPolicy:
1536
+ """Create a budget policy.
1537
+
1538
+ Creates a new policy.
1539
+
1540
+ :param custom_tags: List[:class:`CustomPolicyTag`] (optional)
1541
+ A list of tags defined by the customer. At most 40 entries are allowed per policy.
1542
+ :param policy_name: str (optional)
1543
+ The name of the policy. - Must be unique among active policies. - Can contain only characters of
1544
+ 0-9, a-z, A-Z, -, =, ., :, /, @, _, +, whitespace.
1545
+ :param request_id: str (optional)
1546
+ A unique identifier for this request. Restricted to 36 ASCII characters. A random UUID is
1547
+ recommended. This request is only idempotent if a `request_id` is provided.
1548
+
1549
+ :returns: :class:`BudgetPolicy`
1550
+ """
1551
+ body = {}
1552
+ if custom_tags is not None: body['custom_tags'] = [v.as_dict() for v in custom_tags]
1553
+ if policy_name is not None: body['policy_name'] = policy_name
1554
+ if request_id is not None: body['request_id'] = request_id
1555
+ headers = {'Accept': 'application/json', 'Content-Type': 'application/json', }
1556
+
1557
+ res = self._api.do('POST',
1558
+ f'/api/2.1/accounts/{self._api.account_id}/budget-policies',
1559
+ body=body,
1560
+ headers=headers)
1561
+ return BudgetPolicy.from_dict(res)
1562
+
1563
+ def delete(self, policy_id: str):
1564
+ """Delete a budget policy.
1565
+
1566
+ Deletes a policy
1567
+
1568
+ :param policy_id: str
1569
+ The Id of the policy.
1570
+
1571
+
1572
+ """
1573
+
1574
+ headers = {'Accept': 'application/json', }
1575
+
1576
+ self._api.do('DELETE',
1577
+ f'/api/2.1/accounts/{self._api.account_id}/budget-policies/{policy_id}',
1578
+ headers=headers)
1579
+
1580
+ def get(self, policy_id: str) -> BudgetPolicy:
1581
+ """Get a budget policy.
1582
+
1583
+ Retrieves a policy by it's ID.
1584
+
1585
+ :param policy_id: str
1586
+ The Id of the policy.
1587
+
1588
+ :returns: :class:`BudgetPolicy`
1589
+ """
1590
+
1591
+ headers = {'Accept': 'application/json', }
1592
+
1593
+ res = self._api.do('GET',
1594
+ f'/api/2.1/accounts/{self._api.account_id}/budget-policies/{policy_id}',
1595
+ headers=headers)
1596
+ return BudgetPolicy.from_dict(res)
1597
+
1598
+ def list(self,
1599
+ *,
1600
+ filter_by: Optional[Filter] = None,
1601
+ page_size: Optional[int] = None,
1602
+ page_token: Optional[str] = None,
1603
+ sort_spec: Optional[SortSpec] = None) -> Iterator[BudgetPolicy]:
1604
+ """List policies.
1605
+
1606
+ Lists all policies. Policies are returned in the alphabetically ascending order of their names.
1607
+
1608
+ :param filter_by: :class:`Filter` (optional)
1609
+ A filter to apply to the list of policies.
1610
+ :param page_size: int (optional)
1611
+ The maximum number of budget policies to return. If unspecified, at most 100 budget policies will be
1612
+ returned. The maximum value is 1000; values above 1000 will be coerced to 1000.
1613
+ :param page_token: str (optional)
1614
+ A page token, received from a previous `ListServerlessPolicies` call. Provide this to retrieve the
1615
+ subsequent page. If unspecified, the first page will be returned.
1616
+
1617
+ When paginating, all other parameters provided to `ListServerlessPoliciesRequest` must match the
1618
+ call that provided the page token.
1619
+ :param sort_spec: :class:`SortSpec` (optional)
1620
+ The sort specification.
1621
+
1622
+ :returns: Iterator over :class:`BudgetPolicy`
1623
+ """
1624
+
1625
+ query = {}
1626
+ if filter_by is not None: query['filter_by'] = filter_by.as_dict()
1627
+ if page_size is not None: query['page_size'] = page_size
1628
+ if page_token is not None: query['page_token'] = page_token
1629
+ if sort_spec is not None: query['sort_spec'] = sort_spec.as_dict()
1630
+ headers = {'Accept': 'application/json', }
1631
+
1632
+ while True:
1633
+ json = self._api.do('GET',
1634
+ f'/api/2.1/accounts/{self._api.account_id}/budget-policies',
1635
+ query=query,
1636
+ headers=headers)
1637
+ if 'policies' in json:
1638
+ for v in json['policies']:
1639
+ yield BudgetPolicy.from_dict(v)
1640
+ if 'next_page_token' not in json or not json['next_page_token']:
1641
+ return
1642
+ query['page_token'] = json['next_page_token']
1643
+
1644
+ def update(self, policy_id: str, *, policy: Optional[BudgetPolicy] = None) -> BudgetPolicy:
1645
+ """Update a budget policy.
1646
+
1647
+ Updates a policy
1648
+
1649
+ :param policy_id: str
1650
+ The Id of the policy. This field is generated by Databricks and globally unique.
1651
+ :param policy: :class:`BudgetPolicy` (optional)
1652
+ Contains the BudgetPolicy details.
1653
+
1654
+ :returns: :class:`BudgetPolicy`
1655
+ """
1656
+ body = policy.as_dict()
1657
+ headers = {'Accept': 'application/json', 'Content-Type': 'application/json', }
1658
+
1659
+ res = self._api.do('PATCH',
1660
+ f'/api/2.1/accounts/{self._api.account_id}/budget-policies/{policy_id}',
1661
+ body=body,
1662
+ headers=headers)
1663
+ return BudgetPolicy.from_dict(res)
1664
+
1665
+
1318
1666
  class BudgetsAPI:
1319
1667
  """These APIs manage budget configurations for this account. Budgets enable you to monitor usage across your
1320
1668
  account. You can set up budgets to either track account-wide spending, or apply filters to track the