databricks-sdk 0.60.0__py3-none-any.whl → 0.61.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.
@@ -3,11 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ import random
7
+ import time
6
8
  from dataclasses import dataclass
9
+ from datetime import timedelta
7
10
  from enum import Enum
8
- from typing import Any, Dict, Iterator, List, Optional
11
+ from typing import Any, Callable, Dict, Iterator, List, Optional
9
12
 
10
- from ._internal import _enum, _from_dict, _repeated_dict
13
+ from ._internal import Wait, _enum, _from_dict, _repeated_dict
11
14
 
12
15
  _LOG = logging.getLogger("databricks.sdk")
13
16
 
@@ -540,6 +543,83 @@ class CleanRoomAssetVolumeLocalDetails:
540
543
  return cls(local_name=d.get("local_name", None))
541
544
 
542
545
 
546
+ @dataclass
547
+ class CleanRoomAutoApprovalRule:
548
+ author_collaborator_alias: Optional[str] = None
549
+
550
+ author_scope: Optional[CleanRoomAutoApprovalRuleAuthorScope] = None
551
+
552
+ clean_room_name: Optional[str] = None
553
+ """The name of the clean room this auto-approval rule belongs to."""
554
+
555
+ created_at: Optional[int] = None
556
+ """Timestamp of when the rule was created, in epoch milliseconds."""
557
+
558
+ rule_id: Optional[str] = None
559
+ """A generated UUID identifying the rule."""
560
+
561
+ rule_owner_collaborator_alias: Optional[str] = None
562
+ """The owner of the rule to whom the rule applies."""
563
+
564
+ runner_collaborator_alias: Optional[str] = None
565
+
566
+ def as_dict(self) -> dict:
567
+ """Serializes the CleanRoomAutoApprovalRule into a dictionary suitable for use as a JSON request body."""
568
+ body = {}
569
+ if self.author_collaborator_alias is not None:
570
+ body["author_collaborator_alias"] = self.author_collaborator_alias
571
+ if self.author_scope is not None:
572
+ body["author_scope"] = self.author_scope.value
573
+ if self.clean_room_name is not None:
574
+ body["clean_room_name"] = self.clean_room_name
575
+ if self.created_at is not None:
576
+ body["created_at"] = self.created_at
577
+ if self.rule_id is not None:
578
+ body["rule_id"] = self.rule_id
579
+ if self.rule_owner_collaborator_alias is not None:
580
+ body["rule_owner_collaborator_alias"] = self.rule_owner_collaborator_alias
581
+ if self.runner_collaborator_alias is not None:
582
+ body["runner_collaborator_alias"] = self.runner_collaborator_alias
583
+ return body
584
+
585
+ def as_shallow_dict(self) -> dict:
586
+ """Serializes the CleanRoomAutoApprovalRule into a shallow dictionary of its immediate attributes."""
587
+ body = {}
588
+ if self.author_collaborator_alias is not None:
589
+ body["author_collaborator_alias"] = self.author_collaborator_alias
590
+ if self.author_scope is not None:
591
+ body["author_scope"] = self.author_scope
592
+ if self.clean_room_name is not None:
593
+ body["clean_room_name"] = self.clean_room_name
594
+ if self.created_at is not None:
595
+ body["created_at"] = self.created_at
596
+ if self.rule_id is not None:
597
+ body["rule_id"] = self.rule_id
598
+ if self.rule_owner_collaborator_alias is not None:
599
+ body["rule_owner_collaborator_alias"] = self.rule_owner_collaborator_alias
600
+ if self.runner_collaborator_alias is not None:
601
+ body["runner_collaborator_alias"] = self.runner_collaborator_alias
602
+ return body
603
+
604
+ @classmethod
605
+ def from_dict(cls, d: Dict[str, Any]) -> CleanRoomAutoApprovalRule:
606
+ """Deserializes the CleanRoomAutoApprovalRule from a dictionary."""
607
+ return cls(
608
+ author_collaborator_alias=d.get("author_collaborator_alias", None),
609
+ author_scope=_enum(d, "author_scope", CleanRoomAutoApprovalRuleAuthorScope),
610
+ clean_room_name=d.get("clean_room_name", None),
611
+ created_at=d.get("created_at", None),
612
+ rule_id=d.get("rule_id", None),
613
+ rule_owner_collaborator_alias=d.get("rule_owner_collaborator_alias", None),
614
+ runner_collaborator_alias=d.get("runner_collaborator_alias", None),
615
+ )
616
+
617
+
618
+ class CleanRoomAutoApprovalRuleAuthorScope(Enum):
619
+
620
+ ANY_AUTHOR = "ANY_AUTHOR"
621
+
622
+
543
623
  @dataclass
544
624
  class CleanRoomCollaborator:
545
625
  """Publicly visible clean room collaborator."""
@@ -1017,6 +1097,41 @@ class ComplianceSecurityProfile:
1017
1097
  )
1018
1098
 
1019
1099
 
1100
+ @dataclass
1101
+ class CreateCleanRoomAssetReviewResponse:
1102
+ notebook_review_state: Optional[CleanRoomNotebookReviewNotebookReviewState] = None
1103
+ """top-level status derived from all reviews"""
1104
+
1105
+ notebook_reviews: Optional[List[CleanRoomNotebookReview]] = None
1106
+ """All existing notebook approvals or rejections"""
1107
+
1108
+ def as_dict(self) -> dict:
1109
+ """Serializes the CreateCleanRoomAssetReviewResponse into a dictionary suitable for use as a JSON request body."""
1110
+ body = {}
1111
+ if self.notebook_review_state is not None:
1112
+ body["notebook_review_state"] = self.notebook_review_state.value
1113
+ if self.notebook_reviews:
1114
+ body["notebook_reviews"] = [v.as_dict() for v in self.notebook_reviews]
1115
+ return body
1116
+
1117
+ def as_shallow_dict(self) -> dict:
1118
+ """Serializes the CreateCleanRoomAssetReviewResponse into a shallow dictionary of its immediate attributes."""
1119
+ body = {}
1120
+ if self.notebook_review_state is not None:
1121
+ body["notebook_review_state"] = self.notebook_review_state
1122
+ if self.notebook_reviews:
1123
+ body["notebook_reviews"] = self.notebook_reviews
1124
+ return body
1125
+
1126
+ @classmethod
1127
+ def from_dict(cls, d: Dict[str, Any]) -> CreateCleanRoomAssetReviewResponse:
1128
+ """Deserializes the CreateCleanRoomAssetReviewResponse from a dictionary."""
1129
+ return cls(
1130
+ notebook_review_state=_enum(d, "notebook_review_state", CleanRoomNotebookReviewNotebookReviewState),
1131
+ notebook_reviews=_repeated_dict(d, "notebook_reviews", CleanRoomNotebookReview),
1132
+ )
1133
+
1134
+
1020
1135
  @dataclass
1021
1136
  class CreateCleanRoomOutputCatalogResponse:
1022
1137
  output_catalog: Optional[CleanRoomOutputCatalog] = None
@@ -1062,6 +1177,38 @@ class DeleteCleanRoomAssetResponse:
1062
1177
  return cls()
1063
1178
 
1064
1179
 
1180
+ @dataclass
1181
+ class ListCleanRoomAssetRevisionsResponse:
1182
+ next_page_token: Optional[str] = None
1183
+
1184
+ revisions: Optional[List[CleanRoomAsset]] = None
1185
+
1186
+ def as_dict(self) -> dict:
1187
+ """Serializes the ListCleanRoomAssetRevisionsResponse into a dictionary suitable for use as a JSON request body."""
1188
+ body = {}
1189
+ if self.next_page_token is not None:
1190
+ body["next_page_token"] = self.next_page_token
1191
+ if self.revisions:
1192
+ body["revisions"] = [v.as_dict() for v in self.revisions]
1193
+ return body
1194
+
1195
+ def as_shallow_dict(self) -> dict:
1196
+ """Serializes the ListCleanRoomAssetRevisionsResponse into a shallow dictionary of its immediate attributes."""
1197
+ body = {}
1198
+ if self.next_page_token is not None:
1199
+ body["next_page_token"] = self.next_page_token
1200
+ if self.revisions:
1201
+ body["revisions"] = self.revisions
1202
+ return body
1203
+
1204
+ @classmethod
1205
+ def from_dict(cls, d: Dict[str, Any]) -> ListCleanRoomAssetRevisionsResponse:
1206
+ """Deserializes the ListCleanRoomAssetRevisionsResponse from a dictionary."""
1207
+ return cls(
1208
+ next_page_token=d.get("next_page_token", None), revisions=_repeated_dict(d, "revisions", CleanRoomAsset)
1209
+ )
1210
+
1211
+
1065
1212
  @dataclass
1066
1213
  class ListCleanRoomAssetsResponse:
1067
1214
  assets: Optional[List[CleanRoomAsset]] = None
@@ -1095,6 +1242,40 @@ class ListCleanRoomAssetsResponse:
1095
1242
  return cls(assets=_repeated_dict(d, "assets", CleanRoomAsset), next_page_token=d.get("next_page_token", None))
1096
1243
 
1097
1244
 
1245
+ @dataclass
1246
+ class ListCleanRoomAutoApprovalRulesResponse:
1247
+ next_page_token: Optional[str] = None
1248
+ """Opaque token to retrieve the next page of results. Absent if there are no more pages. page_token
1249
+ should be set to this value for the next request (for the next page of results)."""
1250
+
1251
+ rules: Optional[List[CleanRoomAutoApprovalRule]] = None
1252
+
1253
+ def as_dict(self) -> dict:
1254
+ """Serializes the ListCleanRoomAutoApprovalRulesResponse into a dictionary suitable for use as a JSON request body."""
1255
+ body = {}
1256
+ if self.next_page_token is not None:
1257
+ body["next_page_token"] = self.next_page_token
1258
+ if self.rules:
1259
+ body["rules"] = [v.as_dict() for v in self.rules]
1260
+ return body
1261
+
1262
+ def as_shallow_dict(self) -> dict:
1263
+ """Serializes the ListCleanRoomAutoApprovalRulesResponse into a shallow dictionary of its immediate attributes."""
1264
+ body = {}
1265
+ if self.next_page_token is not None:
1266
+ body["next_page_token"] = self.next_page_token
1267
+ if self.rules:
1268
+ body["rules"] = self.rules
1269
+ return body
1270
+
1271
+ @classmethod
1272
+ def from_dict(cls, d: Dict[str, Any]) -> ListCleanRoomAutoApprovalRulesResponse:
1273
+ """Deserializes the ListCleanRoomAutoApprovalRulesResponse from a dictionary."""
1274
+ return cls(
1275
+ next_page_token=d.get("next_page_token", None), rules=_repeated_dict(d, "rules", CleanRoomAutoApprovalRule)
1276
+ )
1277
+
1278
+
1098
1279
  @dataclass
1099
1280
  class ListCleanRoomNotebookTaskRunsResponse:
1100
1281
  next_page_token: Optional[str] = None
@@ -1164,6 +1345,130 @@ class ListCleanRoomsResponse:
1164
1345
  )
1165
1346
 
1166
1347
 
1348
+ @dataclass
1349
+ class NotebookVersionReview:
1350
+ etag: str
1351
+ """etag that identifies the notebook version"""
1352
+
1353
+ review_state: CleanRoomNotebookReviewNotebookReviewState
1354
+ """review outcome"""
1355
+
1356
+ comment: Optional[str] = None
1357
+ """review comment"""
1358
+
1359
+ def as_dict(self) -> dict:
1360
+ """Serializes the NotebookVersionReview into a dictionary suitable for use as a JSON request body."""
1361
+ body = {}
1362
+ if self.comment is not None:
1363
+ body["comment"] = self.comment
1364
+ if self.etag is not None:
1365
+ body["etag"] = self.etag
1366
+ if self.review_state is not None:
1367
+ body["review_state"] = self.review_state.value
1368
+ return body
1369
+
1370
+ def as_shallow_dict(self) -> dict:
1371
+ """Serializes the NotebookVersionReview into a shallow dictionary of its immediate attributes."""
1372
+ body = {}
1373
+ if self.comment is not None:
1374
+ body["comment"] = self.comment
1375
+ if self.etag is not None:
1376
+ body["etag"] = self.etag
1377
+ if self.review_state is not None:
1378
+ body["review_state"] = self.review_state
1379
+ return body
1380
+
1381
+ @classmethod
1382
+ def from_dict(cls, d: Dict[str, Any]) -> NotebookVersionReview:
1383
+ """Deserializes the NotebookVersionReview from a dictionary."""
1384
+ return cls(
1385
+ comment=d.get("comment", None),
1386
+ etag=d.get("etag", None),
1387
+ review_state=_enum(d, "review_state", CleanRoomNotebookReviewNotebookReviewState),
1388
+ )
1389
+
1390
+
1391
+ class CleanRoomAssetRevisionsAPI:
1392
+ """Clean Room Asset Revisions denote new versions of uploaded assets (e.g. notebooks) in the clean room."""
1393
+
1394
+ def __init__(self, api_client):
1395
+ self._api = api_client
1396
+
1397
+ def get(self, clean_room_name: str, asset_type: CleanRoomAssetAssetType, name: str, etag: str) -> CleanRoomAsset:
1398
+ """Get a specific revision of an asset
1399
+
1400
+ :param clean_room_name: str
1401
+ Name of the clean room.
1402
+ :param asset_type: :class:`CleanRoomAssetAssetType`
1403
+ Asset type. Only NOTEBOOK_FILE is supported.
1404
+ :param name: str
1405
+ Name of the asset.
1406
+ :param etag: str
1407
+ Revision etag to fetch. If not provided, the latest revision will be returned.
1408
+
1409
+ :returns: :class:`CleanRoomAsset`
1410
+ """
1411
+
1412
+ headers = {
1413
+ "Accept": "application/json",
1414
+ }
1415
+
1416
+ res = self._api.do(
1417
+ "GET",
1418
+ f"/api/2.0/clean-rooms/{clean_room_name}/assets/{asset_type.value}/{name}/revisions/{etag}",
1419
+ headers=headers,
1420
+ )
1421
+ return CleanRoomAsset.from_dict(res)
1422
+
1423
+ def list(
1424
+ self,
1425
+ clean_room_name: str,
1426
+ asset_type: CleanRoomAssetAssetType,
1427
+ name: str,
1428
+ *,
1429
+ page_size: Optional[int] = None,
1430
+ page_token: Optional[str] = None,
1431
+ ) -> Iterator[CleanRoomAsset]:
1432
+ """List revisions for an asset
1433
+
1434
+ :param clean_room_name: str
1435
+ Name of the clean room.
1436
+ :param asset_type: :class:`CleanRoomAssetAssetType`
1437
+ Asset type. Only NOTEBOOK_FILE is supported.
1438
+ :param name: str
1439
+ Name of the asset.
1440
+ :param page_size: int (optional)
1441
+ Maximum number of asset revisions to return. Defaults to 10.
1442
+ :param page_token: str (optional)
1443
+ Opaque pagination token to go to next page based on the previous query.
1444
+
1445
+ :returns: Iterator over :class:`CleanRoomAsset`
1446
+ """
1447
+
1448
+ query = {}
1449
+ if page_size is not None:
1450
+ query["page_size"] = page_size
1451
+ if page_token is not None:
1452
+ query["page_token"] = page_token
1453
+ headers = {
1454
+ "Accept": "application/json",
1455
+ }
1456
+
1457
+ while True:
1458
+ json = self._api.do(
1459
+ "GET",
1460
+ f"/api/2.0/clean-rooms/{clean_room_name}/assets/{asset_type.value}/{name}/revisions",
1461
+ query=query,
1462
+ headers=headers,
1463
+ )
1464
+ if "revisions" in json:
1465
+ for v in json["revisions"]:
1466
+ yield CleanRoomAsset.from_dict(v)
1467
+ if "next_page_token" not in json or not json["next_page_token"]:
1468
+ return
1469
+ query["page_token"] = json["next_page_token"]
1470
+
1471
+
1167
1472
  class CleanRoomAssetsAPI:
1168
1473
  """Clean room assets are data and code objects — Tables, volumes, and notebooks that are shared with the
1169
1474
  clean room."""
@@ -1193,6 +1498,41 @@ class CleanRoomAssetsAPI:
1193
1498
  res = self._api.do("POST", f"/api/2.0/clean-rooms/{clean_room_name}/assets", body=body, headers=headers)
1194
1499
  return CleanRoomAsset.from_dict(res)
1195
1500
 
1501
+ def create_clean_room_asset_review(
1502
+ self,
1503
+ clean_room_name: str,
1504
+ asset_type: CleanRoomAssetAssetType,
1505
+ name: str,
1506
+ notebook_review: NotebookVersionReview,
1507
+ ) -> CreateCleanRoomAssetReviewResponse:
1508
+ """submit an asset review
1509
+
1510
+ :param clean_room_name: str
1511
+ Name of the clean room
1512
+ :param asset_type: :class:`CleanRoomAssetAssetType`
1513
+ can only be NOTEBOOK_FILE for now
1514
+ :param name: str
1515
+ Name of the asset
1516
+ :param notebook_review: :class:`NotebookVersionReview`
1517
+
1518
+ :returns: :class:`CreateCleanRoomAssetReviewResponse`
1519
+ """
1520
+ body = {}
1521
+ if notebook_review is not None:
1522
+ body["notebook_review"] = notebook_review.as_dict()
1523
+ headers = {
1524
+ "Accept": "application/json",
1525
+ "Content-Type": "application/json",
1526
+ }
1527
+
1528
+ res = self._api.do(
1529
+ "POST",
1530
+ f"/api/2.0/clean-rooms/{clean_room_name}/assets/{asset_type.value}/{name}/reviews",
1531
+ body=body,
1532
+ headers=headers,
1533
+ )
1534
+ return CreateCleanRoomAssetReviewResponse.from_dict(res)
1535
+
1196
1536
  def delete(self, clean_room_name: str, asset_type: CleanRoomAssetAssetType, name: str):
1197
1537
  """Delete a clean room asset - unshare/remove the asset from the clean room
1198
1538
 
@@ -1302,6 +1642,128 @@ class CleanRoomAssetsAPI:
1302
1642
  return CleanRoomAsset.from_dict(res)
1303
1643
 
1304
1644
 
1645
+ class CleanRoomAutoApprovalRulesAPI:
1646
+ """Clean room auto-approval rules automatically create an approval on your behalf when an asset (e.g.
1647
+ notebook) meeting specific criteria is shared in a clean room."""
1648
+
1649
+ def __init__(self, api_client):
1650
+ self._api = api_client
1651
+
1652
+ def create(self, clean_room_name: str, auto_approval_rule: CleanRoomAutoApprovalRule) -> CleanRoomAutoApprovalRule:
1653
+ """Create an auto-approval rule
1654
+
1655
+ :param clean_room_name: str
1656
+ The name of the clean room this auto-approval rule belongs to.
1657
+ :param auto_approval_rule: :class:`CleanRoomAutoApprovalRule`
1658
+
1659
+ :returns: :class:`CleanRoomAutoApprovalRule`
1660
+ """
1661
+ body = {}
1662
+ if auto_approval_rule is not None:
1663
+ body["auto_approval_rule"] = auto_approval_rule.as_dict()
1664
+ headers = {
1665
+ "Accept": "application/json",
1666
+ "Content-Type": "application/json",
1667
+ }
1668
+
1669
+ res = self._api.do(
1670
+ "POST", f"/api/2.0/clean-rooms/{clean_room_name}/auto-approval-rules", body=body, headers=headers
1671
+ )
1672
+ return CleanRoomAutoApprovalRule.from_dict(res)
1673
+
1674
+ def delete(self, clean_room_name: str, rule_id: str):
1675
+ """Delete a auto-approval rule by rule ID
1676
+
1677
+ :param clean_room_name: str
1678
+ :param rule_id: str
1679
+
1680
+
1681
+ """
1682
+
1683
+ headers = {
1684
+ "Accept": "application/json",
1685
+ }
1686
+
1687
+ self._api.do("DELETE", f"/api/2.0/clean-rooms/{clean_room_name}/auto-approval-rules/{rule_id}", headers=headers)
1688
+
1689
+ def get(self, clean_room_name: str, rule_id: str) -> CleanRoomAutoApprovalRule:
1690
+ """Get a auto-approval rule by rule ID
1691
+
1692
+ :param clean_room_name: str
1693
+ :param rule_id: str
1694
+
1695
+ :returns: :class:`CleanRoomAutoApprovalRule`
1696
+ """
1697
+
1698
+ headers = {
1699
+ "Accept": "application/json",
1700
+ }
1701
+
1702
+ res = self._api.do(
1703
+ "GET", f"/api/2.0/clean-rooms/{clean_room_name}/auto-approval-rules/{rule_id}", headers=headers
1704
+ )
1705
+ return CleanRoomAutoApprovalRule.from_dict(res)
1706
+
1707
+ def list(
1708
+ self, clean_room_name: str, *, page_size: Optional[int] = None, page_token: Optional[str] = None
1709
+ ) -> Iterator[CleanRoomAutoApprovalRule]:
1710
+ """List all auto-approval rules for the caller
1711
+
1712
+ :param clean_room_name: str
1713
+ :param page_size: int (optional)
1714
+ Maximum number of auto-approval rules to return. Defaults to 100.
1715
+ :param page_token: str (optional)
1716
+ Opaque pagination token to go to next page based on previous query.
1717
+
1718
+ :returns: Iterator over :class:`CleanRoomAutoApprovalRule`
1719
+ """
1720
+
1721
+ query = {}
1722
+ if page_size is not None:
1723
+ query["page_size"] = page_size
1724
+ if page_token is not None:
1725
+ query["page_token"] = page_token
1726
+ headers = {
1727
+ "Accept": "application/json",
1728
+ }
1729
+
1730
+ while True:
1731
+ json = self._api.do(
1732
+ "GET", f"/api/2.0/clean-rooms/{clean_room_name}/auto-approval-rules", query=query, headers=headers
1733
+ )
1734
+ if "rules" in json:
1735
+ for v in json["rules"]:
1736
+ yield CleanRoomAutoApprovalRule.from_dict(v)
1737
+ if "next_page_token" not in json or not json["next_page_token"]:
1738
+ return
1739
+ query["page_token"] = json["next_page_token"]
1740
+
1741
+ def update(
1742
+ self, clean_room_name: str, rule_id: str, auto_approval_rule: CleanRoomAutoApprovalRule
1743
+ ) -> CleanRoomAutoApprovalRule:
1744
+ """Update a auto-approval rule by rule ID
1745
+
1746
+ :param clean_room_name: str
1747
+ The name of the clean room this auto-approval rule belongs to.
1748
+ :param rule_id: str
1749
+ A generated UUID identifying the rule.
1750
+ :param auto_approval_rule: :class:`CleanRoomAutoApprovalRule`
1751
+ The auto-approval rule to update. The rule_id field is used to identify the rule to update.
1752
+
1753
+ :returns: :class:`CleanRoomAutoApprovalRule`
1754
+ """
1755
+ body = auto_approval_rule.as_dict()
1756
+ headers = {
1757
+ "Accept": "application/json",
1758
+ "Content-Type": "application/json",
1759
+ }
1760
+
1761
+ res = self._api.do(
1762
+ "PATCH", f"/api/2.0/clean-rooms/{clean_room_name}/auto-approval-rules/{rule_id}", body=body, headers=headers
1763
+ )
1764
+ return CleanRoomAutoApprovalRule.from_dict(res)
1765
+
1766
+
1305
1767
  class CleanRoomTaskRunsAPI:
1306
1768
  """Clean room task runs are the executions of notebooks in a clean room."""
1307
1769
 
@@ -1359,7 +1821,32 @@ class CleanRoomsAPI:
1359
1821
  def __init__(self, api_client):
1360
1822
  self._api = api_client
1361
1823
 
1362
- def create(self, clean_room: CleanRoom) -> CleanRoom:
1824
+ def wait_get_clean_room_active(
1825
+ self, name: str, timeout=timedelta(minutes=20), callback: Optional[Callable[[CleanRoom], None]] = None
1826
+ ) -> CleanRoom:
1827
+ deadline = time.time() + timeout.total_seconds()
1828
+ target_states = (CleanRoomStatusEnum.ACTIVE,)
1829
+ status_message = "polling..."
1830
+ attempt = 1
1831
+ while time.time() < deadline:
1832
+ poll = self.get(name=name)
1833
+ status = poll.status
1834
+ status_message = f"current status: {status}"
1835
+ if status in target_states:
1836
+ return poll
1837
+ if callback:
1838
+ callback(poll)
1839
+ prefix = f"name={name}"
1840
+ sleep = attempt
1841
+ if sleep > 10:
1842
+ # sleep 10s max per attempt
1843
+ sleep = 10
1844
+ _LOG.debug(f"{prefix}: ({status}) {status_message} (sleeping ~{sleep}s)")
1845
+ time.sleep(sleep + random.random())
1846
+ attempt += 1
1847
+ raise TimeoutError(f"timed out after {timeout}: {status_message}")
1848
+
1849
+ def create(self, clean_room: CleanRoom) -> Wait[CleanRoom]:
1363
1850
  """Create a new clean room with the specified collaborators. This method is asynchronous; the returned
1364
1851
  name field inside the clean_room field can be used to poll the clean room status, using the
1365
1852
  :method:cleanrooms/get method. When this method returns, the clean room will be in a PROVISIONING
@@ -1370,7 +1857,9 @@ class CleanRoomsAPI:
1370
1857
 
1371
1858
  :param clean_room: :class:`CleanRoom`
1372
1859
 
1373
- :returns: :class:`CleanRoom`
1860
+ :returns:
1861
+ Long-running operation waiter for :class:`CleanRoom`.
1862
+ See :method:wait_get_clean_room_active for more details.
1374
1863
  """
1375
1864
  body = clean_room.as_dict()
1376
1865
  headers = {
@@ -1378,8 +1867,13 @@ class CleanRoomsAPI:
1378
1867
  "Content-Type": "application/json",
1379
1868
  }
1380
1869
 
1381
- res = self._api.do("POST", "/api/2.0/clean-rooms", body=body, headers=headers)
1382
- return CleanRoom.from_dict(res)
1870
+ op_response = self._api.do("POST", "/api/2.0/clean-rooms", body=body, headers=headers)
1871
+ return Wait(
1872
+ self.wait_get_clean_room_active, response=CleanRoom.from_dict(op_response), name=op_response["name"]
1873
+ )
1874
+
1875
+ def create_and_wait(self, clean_room: CleanRoom, timeout=timedelta(minutes=20)) -> CleanRoom:
1876
+ return self.create(clean_room=clean_room).result(timeout=timeout)
1383
1877
 
1384
1878
  def create_output_catalog(
1385
1879
  self, clean_room_name: str, output_catalog: CleanRoomOutputCatalog
@@ -1034,6 +1034,7 @@ class MessageErrorType(Enum):
1034
1034
  INVALID_SQL_UNKNOWN_TABLE_EXCEPTION = "INVALID_SQL_UNKNOWN_TABLE_EXCEPTION"
1035
1035
  INVALID_TABLE_IDENTIFIER_EXCEPTION = "INVALID_TABLE_IDENTIFIER_EXCEPTION"
1036
1036
  LOCAL_CONTEXT_EXCEEDED_EXCEPTION = "LOCAL_CONTEXT_EXCEEDED_EXCEPTION"
1037
+ MESSAGE_ATTACHMENT_TOO_LONG_ERROR = "MESSAGE_ATTACHMENT_TOO_LONG_ERROR"
1037
1038
  MESSAGE_CANCELLED_WHILE_EXECUTING_EXCEPTION = "MESSAGE_CANCELLED_WHILE_EXECUTING_EXCEPTION"
1038
1039
  MESSAGE_DELETED_WHILE_EXECUTING_EXCEPTION = "MESSAGE_DELETED_WHILE_EXECUTING_EXCEPTION"
1039
1040
  MESSAGE_UPDATED_WHILE_EXECUTING_EXCEPTION = "MESSAGE_UPDATED_WHILE_EXECUTING_EXCEPTION"
@@ -3,11 +3,14 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import logging
6
+ import random
7
+ import time
6
8
  from dataclasses import dataclass
9
+ from datetime import timedelta
7
10
  from enum import Enum
8
- from typing import Any, Dict, Iterator, List, Optional
11
+ from typing import Any, Callable, Dict, Iterator, List, Optional
9
12
 
10
- from ._internal import _enum, _from_dict, _repeated_dict
13
+ from ._internal import Wait, _enum, _from_dict, _repeated_dict
11
14
 
12
15
  _LOG = logging.getLogger("databricks.sdk")
13
16
 
@@ -1358,6 +1361,31 @@ class DatabaseAPI:
1358
1361
  def __init__(self, api_client):
1359
1362
  self._api = api_client
1360
1363
 
1364
+ def wait_get_database_instance_database_available(
1365
+ self, name: str, timeout=timedelta(minutes=20), callback: Optional[Callable[[DatabaseInstance], None]] = None
1366
+ ) -> DatabaseInstance:
1367
+ deadline = time.time() + timeout.total_seconds()
1368
+ target_states = (DatabaseInstanceState.AVAILABLE,)
1369
+ status_message = "polling..."
1370
+ attempt = 1
1371
+ while time.time() < deadline:
1372
+ poll = self.get_database_instance(name=name)
1373
+ status = poll.state
1374
+ status_message = f"current status: {status}"
1375
+ if status in target_states:
1376
+ return poll
1377
+ if callback:
1378
+ callback(poll)
1379
+ prefix = f"name={name}"
1380
+ sleep = attempt
1381
+ if sleep > 10:
1382
+ # sleep 10s max per attempt
1383
+ sleep = 10
1384
+ _LOG.debug(f"{prefix}: ({status}) {status_message} (sleeping ~{sleep}s)")
1385
+ time.sleep(sleep + random.random())
1386
+ attempt += 1
1387
+ raise TimeoutError(f"timed out after {timeout}: {status_message}")
1388
+
1361
1389
  def create_database_catalog(self, catalog: DatabaseCatalog) -> DatabaseCatalog:
1362
1390
  """Create a Database Catalog.
1363
1391
 
@@ -1374,13 +1402,15 @@ class DatabaseAPI:
1374
1402
  res = self._api.do("POST", "/api/2.0/database/catalogs", body=body, headers=headers)
1375
1403
  return DatabaseCatalog.from_dict(res)
1376
1404
 
1377
- def create_database_instance(self, database_instance: DatabaseInstance) -> DatabaseInstance:
1405
+ def create_database_instance(self, database_instance: DatabaseInstance) -> Wait[DatabaseInstance]:
1378
1406
  """Create a Database Instance.
1379
1407
 
1380
1408
  :param database_instance: :class:`DatabaseInstance`
1381
1409
  Instance to create.
1382
1410
 
1383
- :returns: :class:`DatabaseInstance`
1411
+ :returns:
1412
+ Long-running operation waiter for :class:`DatabaseInstance`.
1413
+ See :method:wait_get_database_instance_database_available for more details.
1384
1414
  """
1385
1415
  body = database_instance.as_dict()
1386
1416
  headers = {
@@ -1388,8 +1418,17 @@ class DatabaseAPI:
1388
1418
  "Content-Type": "application/json",
1389
1419
  }
1390
1420
 
1391
- res = self._api.do("POST", "/api/2.0/database/instances", body=body, headers=headers)
1392
- return DatabaseInstance.from_dict(res)
1421
+ op_response = self._api.do("POST", "/api/2.0/database/instances", body=body, headers=headers)
1422
+ return Wait(
1423
+ self.wait_get_database_instance_database_available,
1424
+ response=DatabaseInstance.from_dict(op_response),
1425
+ name=op_response["name"],
1426
+ )
1427
+
1428
+ def create_database_instance_and_wait(
1429
+ self, database_instance: DatabaseInstance, timeout=timedelta(minutes=20)
1430
+ ) -> DatabaseInstance:
1431
+ return self.create_database_instance(database_instance=database_instance).result(timeout=timeout)
1393
1432
 
1394
1433
  def create_database_instance_role(
1395
1434
  self, instance_name: str, database_instance_role: DatabaseInstanceRole
@@ -205,6 +205,7 @@ class AiGatewayGuardrailPiiBehavior:
205
205
  class AiGatewayGuardrailPiiBehaviorBehavior(Enum):
206
206
 
207
207
  BLOCK = "BLOCK"
208
+ MASK = "MASK"
208
209
  NONE = "NONE"
209
210
 
210
211