From d6e88f4ac75e7e272348e38a3002e9524f806f64 Mon Sep 17 00:00:00 2001 From: Parth Bansal Date: Wed, 13 Aug 2025 16:05:25 +0000 Subject: [PATCH 1/2] update --- .codegen.json | 1 - .codegen/_openapi_sha | 2 +- databricks/sdk/__init__.py | 10 +- databricks/sdk/service/catalog.py | 503 ++++++++++++++++++++++++--- databricks/sdk/service/cleanrooms.py | 6 +- databricks/sdk/service/jobs.py | 40 ++- databricks/sdk/service/sharing.py | 29 +- 7 files changed, 496 insertions(+), 95 deletions(-) diff --git a/.codegen.json b/.codegen.json index 65077c1cc..c5c1a82f3 100644 --- a/.codegen.json +++ b/.codegen.json @@ -15,7 +15,6 @@ ], "post_generate": [ "make fmt", - "pytest -m 'not integration' --cov=databricks --cov-report html tests", "pip install .", "python3.12 docs/gen-client-docs.py" ] diff --git a/.codegen/_openapi_sha b/.codegen/_openapi_sha index 9834ec958..7658c3aec 100644 --- a/.codegen/_openapi_sha +++ b/.codegen/_openapi_sha @@ -1 +1 @@ -3ae6f76120079424c8654263eafbc30ec0551854 \ No newline at end of file +file:/home/parth.bansal/universetwo/bazel-bin/openapi/all-internal.json \ No newline at end of file diff --git a/databricks/sdk/__init__.py b/databricks/sdk/__init__.py index 288762efe..643b7c933 100755 --- a/databricks/sdk/__init__.py +++ b/databricks/sdk/__init__.py @@ -53,8 +53,8 @@ ModelVersionsAPI, OnlineTablesAPI, PoliciesAPI, QualityMonitorsAPI, RegisteredModelsAPI, - ResourceQuotasAPI, SchemasAPI, - StorageCredentialsAPI, + ResourceQuotasAPI, RfaAPI, + SchemasAPI, StorageCredentialsAPI, SystemSchemasAPI, TableConstraintsAPI, TablesAPI, TemporaryPathCredentialsAPI, @@ -322,6 +322,7 @@ def __init__( self._query_history = pkg_sql.QueryHistoryAPI(self._api_client) self._query_visualizations = pkg_sql.QueryVisualizationsAPI(self._api_client) self._query_visualizations_legacy = pkg_sql.QueryVisualizationsLegacyAPI(self._api_client) + self._rfa = pkg_catalog.RfaAPI(self._api_client) self._recipient_activation = pkg_sharing.RecipientActivationAPI(self._api_client) self._recipient_federation_policies = pkg_sharing.RecipientFederationPoliciesAPI(self._api_client) self._recipients = pkg_sharing.RecipientsAPI(self._api_client) @@ -768,6 +769,11 @@ def query_visualizations_legacy(self) -> pkg_sql.QueryVisualizationsLegacyAPI: """This is an evolving API that facilitates the addition and removal of vizualisations from existing queries within the Databricks Workspace.""" return self._query_visualizations_legacy + @property + def rfa(self) -> pkg_catalog.RfaAPI: + """Request for Access enables customers to request access to and manage access request destinations for Unity Catalog securables.""" + return self._rfa + @property def recipient_activation(self) -> pkg_sharing.RecipientActivationAPI: """The Recipient Activation API is only applicable in the open sharing model where the recipient object has the authentication type of `TOKEN`.""" diff --git a/databricks/sdk/service/catalog.py b/databricks/sdk/service/catalog.py index 255fa6fa5..9304a01ec 100755 --- a/databricks/sdk/service/catalog.py +++ b/databricks/sdk/service/catalog.py @@ -19,6 +19,50 @@ # all definitions in this file are in alphabetical order +@dataclass +class AccessRequestDestinations: + destinations: List[NotificationDestination] + """The access request destinations for the securable.""" + + securable: Securable + """The securable for which the access request destinations are being retrieved.""" + + are_any_destinations_hidden: Optional[bool] = None + """Indicates whether any destinations are hidden from the caller due to a lack of permissions. This + value is true if the caller does not have permission to see all destinations.""" + + def as_dict(self) -> dict: + """Serializes the AccessRequestDestinations into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.are_any_destinations_hidden is not None: + body["are_any_destinations_hidden"] = self.are_any_destinations_hidden + if self.destinations: + body["destinations"] = [v.as_dict() for v in self.destinations] + if self.securable: + body["securable"] = self.securable.as_dict() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the AccessRequestDestinations into a shallow dictionary of its immediate attributes.""" + body = {} + if self.are_any_destinations_hidden is not None: + body["are_any_destinations_hidden"] = self.are_any_destinations_hidden + if self.destinations: + body["destinations"] = self.destinations + if self.securable: + body["securable"] = self.securable + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> AccessRequestDestinations: + """Deserializes the AccessRequestDestinations from a dictionary.""" + return cls( + are_any_destinations_hidden=d.get("are_any_destinations_hidden", None), + destinations=_repeated_dict(d, "destinations", NotificationDestination), + securable=_from_dict(d, "securable", Securable), + ) + + @dataclass class AccountsMetastoreAssignment: metastore_assignment: Optional[MetastoreAssignment] = None @@ -706,6 +750,31 @@ def from_dict(cls, d: Dict[str, Any]) -> AzureUserDelegationSas: return cls(sas_token=d.get("sas_token", None)) +@dataclass +class BatchCreateAccessRequestsResponse: + responses: Optional[List[CreateAccessRequestResponse]] = None + """The access request destinations for each securable object the principal requested.""" + + def as_dict(self) -> dict: + """Serializes the BatchCreateAccessRequestsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.responses: + body["responses"] = [v.as_dict() for v in self.responses] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the BatchCreateAccessRequestsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.responses: + body["responses"] = self.responses + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> BatchCreateAccessRequestsResponse: + """Deserializes the BatchCreateAccessRequestsResponse from a dictionary.""" + return cls(responses=_repeated_dict(d, "responses", CreateAccessRequestResponse)) + + @dataclass class CancelRefreshResponse: def as_dict(self) -> dict: @@ -1294,9 +1363,6 @@ class ConnectionInfo: credential_type: Optional[CredentialType] = None """The type of credential.""" - environment_settings: Optional[EnvironmentSettings] = None - """[Create,Update:OPT] Connection environment settings as EnvironmentSettings object.""" - full_name: Optional[str] = None """Full name of connection.""" @@ -1346,8 +1412,6 @@ def as_dict(self) -> dict: body["created_by"] = self.created_by if self.credential_type is not None: body["credential_type"] = self.credential_type.value - if self.environment_settings: - body["environment_settings"] = self.environment_settings.as_dict() if self.full_name is not None: body["full_name"] = self.full_name if self.metastore_id is not None: @@ -1389,8 +1453,6 @@ def as_shallow_dict(self) -> dict: body["created_by"] = self.created_by if self.credential_type is not None: body["credential_type"] = self.credential_type - if self.environment_settings: - body["environment_settings"] = self.environment_settings if self.full_name is not None: body["full_name"] = self.full_name if self.metastore_id is not None: @@ -1427,7 +1489,6 @@ def from_dict(cls, d: Dict[str, Any]) -> ConnectionInfo: created_at=d.get("created_at", None), created_by=d.get("created_by", None), credential_type=_enum(d, "credential_type", CredentialType), - environment_settings=_from_dict(d, "environment_settings", EnvironmentSettings), full_name=d.get("full_name", None), metastore_id=d.get("metastore_id", None), name=d.get("name", None), @@ -1516,6 +1577,93 @@ def from_dict(cls, d: Dict[str, Any]) -> ContinuousUpdateStatus: ) +@dataclass +class CreateAccessRequest: + behalf_of: Optional[Principal] = None + """Optional. The principal this request is for. Empty `behalf_of` defaults to the requester's + identity. + + Principals must be unique across the API call.""" + + comment: Optional[str] = None + """Optional. Comment associated with the request. + + At most 200 characters, can only contain lowercase/uppercase letters (a-z, A-Z), numbers (0-9), + punctuation, and spaces.""" + + securable_permissions: Optional[List[SecurablePermissions]] = None + """List of securables and their corresponding requested UC privileges. + + At most 30 securables can be requested for a principal per batched call. Each securable can only + be requested once per principal.""" + + def as_dict(self) -> dict: + """Serializes the CreateAccessRequest into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.behalf_of: + body["behalf_of"] = self.behalf_of.as_dict() + if self.comment is not None: + body["comment"] = self.comment + if self.securable_permissions: + body["securable_permissions"] = [v.as_dict() for v in self.securable_permissions] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the CreateAccessRequest into a shallow dictionary of its immediate attributes.""" + body = {} + if self.behalf_of: + body["behalf_of"] = self.behalf_of + if self.comment is not None: + body["comment"] = self.comment + if self.securable_permissions: + body["securable_permissions"] = self.securable_permissions + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> CreateAccessRequest: + """Deserializes the CreateAccessRequest from a dictionary.""" + return cls( + behalf_of=_from_dict(d, "behalf_of", Principal), + comment=d.get("comment", None), + securable_permissions=_repeated_dict(d, "securable_permissions", SecurablePermissions), + ) + + +@dataclass +class CreateAccessRequestResponse: + behalf_of: Optional[Principal] = None + """The principal the request was made on behalf of.""" + + request_destinations: Optional[List[AccessRequestDestinations]] = None + """The access request destinations for all the securables the principal requested.""" + + def as_dict(self) -> dict: + """Serializes the CreateAccessRequestResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.behalf_of: + body["behalf_of"] = self.behalf_of.as_dict() + if self.request_destinations: + body["request_destinations"] = [v.as_dict() for v in self.request_destinations] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the CreateAccessRequestResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.behalf_of: + body["behalf_of"] = self.behalf_of + if self.request_destinations: + body["request_destinations"] = self.request_destinations + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> CreateAccessRequestResponse: + """Deserializes the CreateAccessRequestResponse from a dictionary.""" + return cls( + behalf_of=_from_dict(d, "behalf_of", Principal), + request_destinations=_repeated_dict(d, "request_destinations", AccessRequestDestinations), + ) + + @dataclass class CreateFunction: name: str @@ -2635,6 +2783,15 @@ def from_dict(cls, d: Dict[str, Any]) -> DependencyList: return cls(dependencies=_repeated_dict(d, "dependencies", Dependency)) +class DestinationType(Enum): + + EMAIL = "EMAIL" + GENERIC_WEBHOOK = "GENERIC_WEBHOOK" + MICROSOFT_TEAMS = "MICROSOFT_TEAMS" + SLACK = "SLACK" + URL = "URL" + + @dataclass class DisableResponse: def as_dict(self) -> dict: @@ -2871,38 +3028,6 @@ def from_dict(cls, d: Dict[str, Any]) -> EncryptionDetails: return cls(sse_encryption_details=_from_dict(d, "sse_encryption_details", SseEncryptionDetails)) -@dataclass -class EnvironmentSettings: - environment_version: Optional[str] = None - - java_dependencies: Optional[List[str]] = None - - def as_dict(self) -> dict: - """Serializes the EnvironmentSettings into a dictionary suitable for use as a JSON request body.""" - body = {} - if self.environment_version is not None: - body["environment_version"] = self.environment_version - if self.java_dependencies: - body["java_dependencies"] = [v for v in self.java_dependencies] - return body - - def as_shallow_dict(self) -> dict: - """Serializes the EnvironmentSettings into a shallow dictionary of its immediate attributes.""" - body = {} - if self.environment_version is not None: - body["environment_version"] = self.environment_version - if self.java_dependencies: - body["java_dependencies"] = self.java_dependencies - return body - - @classmethod - def from_dict(cls, d: Dict[str, Any]) -> EnvironmentSettings: - """Deserializes the EnvironmentSettings from a dictionary.""" - return cls( - environment_version=d.get("environment_version", None), java_dependencies=d.get("java_dependencies", None) - ) - - @dataclass class ExternalLineageExternalMetadata: name: Optional[str] = None @@ -6763,6 +6888,55 @@ def from_dict(cls, d: Dict[str, Any]) -> NamedTableConstraint: return cls(name=d.get("name", None)) +@dataclass +class NotificationDestination: + destination_id: Optional[str] = None + """The identifier for the destination. This is the email address for EMAIL destinations, the URL + for URL destinations, or the unique Databricks notification destination ID for all other + external destinations.""" + + destination_type: Optional[DestinationType] = None + """The type of the destination.""" + + special_destination: Optional[SpecialDestination] = None + """This field is used to denote whether the destination is the email of the owner of the securable + object. The special destination cannot be assigned to a securable and only represents the + default destination of the securable. The securable types that support default special + destinations are: "catalog", "external_location", "connection", "credential", and "metastore". + The **destination_type** of a **special_destination** is always EMAIL.""" + + def as_dict(self) -> dict: + """Serializes the NotificationDestination into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.destination_id is not None: + body["destination_id"] = self.destination_id + if self.destination_type is not None: + body["destination_type"] = self.destination_type.value + if self.special_destination is not None: + body["special_destination"] = self.special_destination.value + return body + + def as_shallow_dict(self) -> dict: + """Serializes the NotificationDestination into a shallow dictionary of its immediate attributes.""" + body = {} + if self.destination_id is not None: + body["destination_id"] = self.destination_id + if self.destination_type is not None: + body["destination_type"] = self.destination_type + if self.special_destination is not None: + body["special_destination"] = self.special_destination + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> NotificationDestination: + """Deserializes the NotificationDestination from a dictionary.""" + return cls( + destination_id=d.get("destination_id", None), + destination_type=_enum(d, "destination_type", DestinationType), + special_destination=_enum(d, "special_destination", SpecialDestination), + ) + + @dataclass class OnlineTable: """Online Table information.""" @@ -7525,6 +7699,44 @@ def from_dict(cls, d: Dict[str, Any]) -> PrimaryKeyConstraint: ) +@dataclass +class Principal: + id: Optional[str] = None + """Databricks user, group or service principal ID.""" + + principal_type: Optional[PrincipalType] = None + + def as_dict(self) -> dict: + """Serializes the Principal into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.id is not None: + body["id"] = self.id + if self.principal_type is not None: + body["principal_type"] = self.principal_type.value + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Principal into a shallow dictionary of its immediate attributes.""" + body = {} + if self.id is not None: + body["id"] = self.id + if self.principal_type is not None: + body["principal_type"] = self.principal_type + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Principal: + """Deserializes the Principal from a dictionary.""" + return cls(id=d.get("id", None), principal_type=_enum(d, "principal_type", PrincipalType)) + + +class PrincipalType(Enum): + + GROUP_PRINCIPAL = "GROUP_PRINCIPAL" + SERVICE_PRINCIPAL = "SERVICE_PRINCIPAL" + USER_PRINCIPAL = "USER_PRINCIPAL" + + class Privilege(Enum): ACCESS = "ACCESS" @@ -8190,6 +8402,53 @@ def from_dict(cls, d: Dict[str, Any]) -> SchemaInfo: ) +@dataclass +class Securable: + """Generic definition of a securable, which is uniquely defined in a metastore by its type and full + name.""" + + full_name: Optional[str] = None + """Required. The full name of the catalog/schema/table. Optional if resource_name is present.""" + + provider_share: Optional[str] = None + """Optional. The name of the Share object that contains the securable when the securable is getting + shared in D2D Delta Sharing.""" + + type: Optional[SecurableType] = None + """Required. The type of securable (catalog/schema/table). Optional if resource_name is present.""" + + def as_dict(self) -> dict: + """Serializes the Securable into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.full_name is not None: + body["full_name"] = self.full_name + if self.provider_share is not None: + body["provider_share"] = self.provider_share + if self.type is not None: + body["type"] = self.type.value + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Securable into a shallow dictionary of its immediate attributes.""" + body = {} + if self.full_name is not None: + body["full_name"] = self.full_name + if self.provider_share is not None: + body["provider_share"] = self.provider_share + if self.type is not None: + body["type"] = self.type + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Securable: + """Deserializes the Securable from a dictionary.""" + return cls( + full_name=d.get("full_name", None), + provider_share=d.get("provider_share", None), + type=_enum(d, "type", SecurableType), + ) + + class SecurableKind(Enum): TABLE_DB_STORAGE = "TABLE_DB_STORAGE" @@ -8321,6 +8580,38 @@ def from_dict(cls, d: Dict[str, Any]) -> SecurableKindManifest: ) +@dataclass +class SecurablePermissions: + permissions: Optional[List[str]] = None + """List of requested Unity Catalog permissions.""" + + securable: Optional[Securable] = None + """The securable for which the access request destinations are being requested.""" + + def as_dict(self) -> dict: + """Serializes the SecurablePermissions into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.permissions: + body["permissions"] = [v for v in self.permissions] + if self.securable: + body["securable"] = self.securable.as_dict() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the SecurablePermissions into a shallow dictionary of its immediate attributes.""" + body = {} + if self.permissions: + body["permissions"] = self.permissions + if self.securable: + body["securable"] = self.securable + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> SecurablePermissions: + """Deserializes the SecurablePermissions from a dictionary.""" + return cls(permissions=d.get("permissions", None), securable=_from_dict(d, "securable", Securable)) + + class SecurableType(Enum): """The type of Unity Catalog securable.""" @@ -8343,6 +8634,15 @@ class SecurableType(Enum): VOLUME = "VOLUME" +class SpecialDestination(Enum): + + SPECIAL_DESTINATION_CATALOG_OWNER = "SPECIAL_DESTINATION_CATALOG_OWNER" + SPECIAL_DESTINATION_CONNECTION_OWNER = "SPECIAL_DESTINATION_CONNECTION_OWNER" + SPECIAL_DESTINATION_CREDENTIAL_OWNER = "SPECIAL_DESTINATION_CREDENTIAL_OWNER" + SPECIAL_DESTINATION_EXTERNAL_LOCATION_OWNER = "SPECIAL_DESTINATION_EXTERNAL_LOCATION_OWNER" + SPECIAL_DESTINATION_METASTORE_OWNER = "SPECIAL_DESTINATION_METASTORE_OWNER" + + @dataclass class SseEncryptionDetails: """Server-Side Encryption properties for clients communicating with AWS s3.""" @@ -10701,7 +11001,6 @@ def create( options: Dict[str, str], *, comment: Optional[str] = None, - environment_settings: Optional[EnvironmentSettings] = None, properties: Optional[Dict[str, str]] = None, read_only: Optional[bool] = None, ) -> ConnectionInfo: @@ -10718,8 +11017,6 @@ def create( A map of key-value properties attached to the securable. :param comment: str (optional) User-provided free-form text description. - :param environment_settings: :class:`EnvironmentSettings` (optional) - [Create,Update:OPT] Connection environment settings as EnvironmentSettings object. :param properties: Dict[str,str] (optional) A map of key-value properties attached to the securable. :param read_only: bool (optional) @@ -10732,8 +11029,6 @@ def create( body["comment"] = comment if connection_type is not None: body["connection_type"] = connection_type.value - if environment_settings is not None: - body["environment_settings"] = environment_settings.as_dict() if name is not None: body["name"] = name if options is not None: @@ -10814,13 +11109,7 @@ def list(self, *, max_results: Optional[int] = None, page_token: Optional[str] = query["page_token"] = json["next_page_token"] def update( - self, - name: str, - options: Dict[str, str], - *, - environment_settings: Optional[EnvironmentSettings] = None, - new_name: Optional[str] = None, - owner: Optional[str] = None, + self, name: str, options: Dict[str, str], *, new_name: Optional[str] = None, owner: Optional[str] = None ) -> ConnectionInfo: """Updates the connection that matches the supplied name. @@ -10828,8 +11117,6 @@ def update( Name of the connection. :param options: Dict[str,str] A map of key-value properties attached to the securable. - :param environment_settings: :class:`EnvironmentSettings` (optional) - [Create,Update:OPT] Connection environment settings as EnvironmentSettings object. :param new_name: str (optional) New name for the connection. :param owner: str (optional) @@ -10838,8 +11125,6 @@ def update( :returns: :class:`ConnectionInfo` """ body = {} - if environment_settings is not None: - body["environment_settings"] = environment_settings.as_dict() if new_name is not None: body["new_name"] = new_name if options is not None: @@ -13571,6 +13856,112 @@ def list_quotas( query["page_token"] = json["next_page_token"] +class RfaAPI: + """Request for Access enables customers to request access to and manage access request destinations for Unity + Catalog securables. + + These APIs provide a standardized way to update, get, and request to access request destinations. + Fine-grained authorization ensures that only users with appropriate permissions can manage access request + destinations.""" + + def __init__(self, api_client): + self._api = api_client + + def batch_create_access_requests( + self, *, requests: Optional[List[CreateAccessRequest]] = None + ) -> BatchCreateAccessRequestsResponse: + """Creates access requests for Unity Catalog permissions for a specified principal on a securable object. + This Batch API can take in multiple principals, securable objects, and permissions as the input and + returns the access request destinations for each. Principals must be unique across the API call. + + The supported securable types are: "metastore", "catalog", "schema", "table", "external_location", + "connection", "credential", "function", "registered_model", and "volume". + + :param requests: List[:class:`CreateAccessRequest`] (optional) + A list of individual access requests, where each request corresponds to a set of permissions being + requested on a list of securables for a specified principal. + + At most 30 requests per API call. + + :returns: :class:`BatchCreateAccessRequestsResponse` + """ + body = {} + if requests is not None: + body["requests"] = [v.as_dict() for v in requests] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("POST", "/api/3.0/rfa/requests", body=body, headers=headers) + return BatchCreateAccessRequestsResponse.from_dict(res) + + def get_access_request_destinations(self, securable_type: str, full_name: str) -> AccessRequestDestinations: + """Gets an array of access request destinations for the specified securable. Any caller can see URL + destinations or the destinations on the metastore. Otherwise, only those with **BROWSE** permissions + on the securable can see destinations. + + The supported securable types are: "metastore", "catalog", "schema", "table", "external_location", + "connection", "credential", "function", "registered_model", and "volume". + + :param securable_type: str + The type of the securable. + :param full_name: str + The full name of the securable. + + :returns: :class:`AccessRequestDestinations` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", f"/api/3.0/rfa/destinations/{securable_type}/{full_name}", headers=headers) + return AccessRequestDestinations.from_dict(res) + + def update_access_request_destinations( + self, access_request_destinations: AccessRequestDestinations, update_mask: str + ) -> AccessRequestDestinations: + """Updates the access request destinations for the given securable. The caller must be a metastore admin, + the owner of the securable, or a user that has the **MANAGE** privilege on the securable in order to + assign destinations. Destinations cannot be updated for securables underneath schemas (tables, + volumes, functions, and models). For these securable types, destinations are inherited from the parent + securable. A maximum of 5 emails and 5 external notification destinations (Slack, Microsoft Teams, and + Generic Webhook destinations) can be assigned to a securable. If a URL destination is assigned, no + other destinations can be set. + + The supported securable types are: "metastore", "catalog", "schema", "table", "external_location", + "connection", "credential", "function", "registered_model", and "volume". + + :param access_request_destinations: :class:`AccessRequestDestinations` + The access request destinations to assign to the securable. For each destination, a + **destination_id** and **destination_type** must be defined. + :param update_mask: str + The field mask must be a single string, with multiple fields separated by commas (no spaces). The + field path is relative to the resource object, using a dot (`.`) to navigate sub-fields (e.g., + `author.given_name`). Specification of elements in sequence or map fields is not allowed, as only + the entire collection field can be specified. Field names must exactly match the resource field + names. + + A field mask of `*` indicates full replacement. It’s recommended to always explicitly list the + fields being updated and avoid using `*` wildcards, as it can lead to unintended results if the API + changes in the future. + + :returns: :class:`AccessRequestDestinations` + """ + body = access_request_destinations.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("PATCH", "/api/3.0/rfa/destinations", query=query, body=body, headers=headers) + return AccessRequestDestinations.from_dict(res) + + class SchemasAPI: """A schema (also called a database) is the second layer of Unity Catalog’s three-level namespace. A schema organizes tables, views and functions. To access (or list) a table or view in a schema, users must have diff --git a/databricks/sdk/service/cleanrooms.py b/databricks/sdk/service/cleanrooms.py index 678f5bf9a..3840c040c 100755 --- a/databricks/sdk/service/cleanrooms.py +++ b/databricks/sdk/service/cleanrooms.py @@ -45,7 +45,7 @@ class CleanRoom: using the separate CreateCleanRoomOutputCatalog API.""" owner: Optional[str] = None - """This is Databricks username of the owner of the local clean room securable for permission + """This is the Databricks username of the owner of the local clean room securable for permission management.""" remote_detailed_info: Optional[CleanRoomRemoteDetail] = None @@ -358,7 +358,7 @@ class CleanRoomAssetNotebook: """All existing approvals or rejections""" runner_collaborator_aliases: Optional[List[str]] = None - """collaborators that can run the notebook""" + """Aliases of collaborators that can run the notebook.""" def as_dict(self) -> dict: """Serializes the CleanRoomAssetNotebook into a dictionary suitable for use as a JSON request body.""" @@ -643,7 +643,7 @@ class CleanRoomCollaborator: It is not restricted to these values and could change in the future""" global_metastore_id: Optional[str] = None - """The global Unity Catalog metastore id of the collaborator. The identifier is of format + """The global Unity Catalog metastore ID of the collaborator. The identifier is of format cloud:region:metastore-uuid.""" invite_recipient_email: Optional[str] = None diff --git a/databricks/sdk/service/jobs.py b/databricks/sdk/service/jobs.py index d8f4e3122..96ee972bf 100755 --- a/databricks/sdk/service/jobs.py +++ b/databricks/sdk/service/jobs.py @@ -42,6 +42,9 @@ class BaseJob: Jobs UI in the job details page and Jobs API using `budget_policy_id` 3. Inferred default based on accessible budget policies of the run_as identity on job creation or modification.""" + effective_usage_policy_id: Optional[str] = None + """The id of the usage policy used by this job for cost attribution purposes.""" + has_more: Optional[bool] = None """Indicates if the job has more array properties (`tasks`, `job_clusters`) that are not shown. They can be accessed via :method:jobs/get endpoint. It is only relevant for API 2.2 @@ -66,6 +69,8 @@ def as_dict(self) -> dict: body["creator_user_name"] = self.creator_user_name if self.effective_budget_policy_id is not None: body["effective_budget_policy_id"] = self.effective_budget_policy_id + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.has_more is not None: body["has_more"] = self.has_more if self.job_id is not None: @@ -85,6 +90,8 @@ def as_shallow_dict(self) -> dict: body["creator_user_name"] = self.creator_user_name if self.effective_budget_policy_id is not None: body["effective_budget_policy_id"] = self.effective_budget_policy_id + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.has_more is not None: body["has_more"] = self.has_more if self.job_id is not None: @@ -102,6 +109,7 @@ def from_dict(cls, d: Dict[str, Any]) -> BaseJob: created_time=d.get("created_time", None), creator_user_name=d.get("creator_user_name", None), effective_budget_policy_id=d.get("effective_budget_policy_id", None), + effective_usage_policy_id=d.get("effective_usage_policy_id", None), has_more=d.get("has_more", None), job_id=d.get("job_id", None), settings=_from_dict(d, "settings", JobSettings), @@ -147,6 +155,9 @@ class BaseRun: `PERFORMANCE_OPTIMIZED`: Prioritizes fast startup and execution times through rapid scaling and optimized cluster performance.""" + effective_usage_policy_id: Optional[str] = None + """The id of the usage policy used by this run for cost attribution purposes.""" + end_time: Optional[int] = None """The time at which this run ended in epoch milliseconds (milliseconds since 1/1/1970 UTC). This field is set to 0 if the job is still running.""" @@ -267,6 +278,8 @@ def as_dict(self) -> dict: body["description"] = self.description if self.effective_performance_target is not None: body["effective_performance_target"] = self.effective_performance_target.value + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.end_time is not None: body["end_time"] = self.end_time if self.execution_duration is not None: @@ -338,6 +351,8 @@ def as_shallow_dict(self) -> dict: body["description"] = self.description if self.effective_performance_target is not None: body["effective_performance_target"] = self.effective_performance_target + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.end_time is not None: body["end_time"] = self.end_time if self.execution_duration is not None: @@ -403,6 +418,7 @@ def from_dict(cls, d: Dict[str, Any]) -> BaseRun: creator_user_name=d.get("creator_user_name", None), description=d.get("description", None), effective_performance_target=_enum(d, "effective_performance_target", PerformanceTarget), + effective_usage_policy_id=d.get("effective_usage_policy_id", None), end_time=d.get("end_time", None), execution_duration=d.get("execution_duration", None), git_source=_from_dict(d, "git_source", GitSource), @@ -2212,6 +2228,9 @@ class Job: Jobs UI in the job details page and Jobs API using `budget_policy_id` 3. Inferred default based on accessible budget policies of the run_as identity on job creation or modification.""" + effective_usage_policy_id: Optional[str] = None + """The id of the usage policy used by this job for cost attribution purposes.""" + has_more: Optional[bool] = None """Indicates if the job has more array properties (`tasks`, `job_clusters`) that are not shown. They can be accessed via :method:jobs/get endpoint. It is only relevant for API 2.2 @@ -2248,6 +2267,8 @@ def as_dict(self) -> dict: body["creator_user_name"] = self.creator_user_name if self.effective_budget_policy_id is not None: body["effective_budget_policy_id"] = self.effective_budget_policy_id + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.has_more is not None: body["has_more"] = self.has_more if self.job_id is not None: @@ -2271,6 +2292,8 @@ def as_shallow_dict(self) -> dict: body["creator_user_name"] = self.creator_user_name if self.effective_budget_policy_id is not None: body["effective_budget_policy_id"] = self.effective_budget_policy_id + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.has_more is not None: body["has_more"] = self.has_more if self.job_id is not None: @@ -2292,6 +2315,7 @@ def from_dict(cls, d: Dict[str, Any]) -> Job: created_time=d.get("created_time", None), creator_user_name=d.get("creator_user_name", None), effective_budget_policy_id=d.get("effective_budget_policy_id", None), + effective_usage_policy_id=d.get("effective_usage_policy_id", None), has_more=d.get("has_more", None), job_id=d.get("job_id", None), next_page_token=d.get("next_page_token", None), @@ -3039,8 +3063,8 @@ class JobSettings: usage_policy_id: Optional[str] = None """The id of the user specified usage policy to use for this job. If not specified, a default usage - policy may be applied when creating or modifying the job. See `effective_budget_policy_id` for - the budget policy used by this workload.""" + policy may be applied when creating or modifying the job. See `effective_usage_policy_id` for + the usage policy used by this workload.""" webhook_notifications: Optional[WebhookNotifications] = None """A collection of system notification IDs to notify when runs of this job begin or complete.""" @@ -4509,6 +4533,9 @@ class Run: `PERFORMANCE_OPTIMIZED`: Prioritizes fast startup and execution times through rapid scaling and optimized cluster performance.""" + effective_usage_policy_id: Optional[str] = None + """The id of the usage policy used by this run for cost attribution purposes.""" + end_time: Optional[int] = None """The time at which this run ended in epoch milliseconds (milliseconds since 1/1/1970 UTC). This field is set to 0 if the job is still running.""" @@ -4635,6 +4662,8 @@ def as_dict(self) -> dict: body["description"] = self.description if self.effective_performance_target is not None: body["effective_performance_target"] = self.effective_performance_target.value + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.end_time is not None: body["end_time"] = self.end_time if self.execution_duration is not None: @@ -4710,6 +4739,8 @@ def as_shallow_dict(self) -> dict: body["description"] = self.description if self.effective_performance_target is not None: body["effective_performance_target"] = self.effective_performance_target + if self.effective_usage_policy_id is not None: + body["effective_usage_policy_id"] = self.effective_usage_policy_id if self.end_time is not None: body["end_time"] = self.end_time if self.execution_duration is not None: @@ -4779,6 +4810,7 @@ def from_dict(cls, d: Dict[str, Any]) -> Run: creator_user_name=d.get("creator_user_name", None), description=d.get("description", None), effective_performance_target=_enum(d, "effective_performance_target", PerformanceTarget), + effective_usage_policy_id=d.get("effective_usage_policy_id", None), end_time=d.get("end_time", None), execution_duration=d.get("execution_duration", None), git_source=_from_dict(d, "git_source", GitSource), @@ -8546,8 +8578,8 @@ def create( `runNow`. :param usage_policy_id: str (optional) The id of the user specified usage policy to use for this job. If not specified, a default usage - policy may be applied when creating or modifying the job. See `effective_budget_policy_id` for the - budget policy used by this workload. + policy may be applied when creating or modifying the job. See `effective_usage_policy_id` for the + usage policy used by this workload. :param webhook_notifications: :class:`WebhookNotifications` (optional) A collection of system notification IDs to notify when runs of this job begin or complete. diff --git a/databricks/sdk/service/sharing.py b/databricks/sdk/service/sharing.py index f9bde5646..fd8063c3d 100755 --- a/databricks/sdk/service/sharing.py +++ b/databricks/sdk/service/sharing.py @@ -1827,59 +1827,32 @@ def from_dict(cls, d: Dict[str, Any]) -> SecurablePropertiesKvPairs: @dataclass class Share: - comment: Optional[str] = None - """The comment of the share.""" - - display_name: Optional[str] = None - """The display name of the share. If defined, it will be shown in the UI.""" - id: Optional[str] = None name: Optional[str] = None - tags: Optional[List[catalog.TagKeyValue]] = None - """The tags of the share.""" - def as_dict(self) -> dict: """Serializes the Share into a dictionary suitable for use as a JSON request body.""" body = {} - if self.comment is not None: - body["comment"] = self.comment - if self.display_name is not None: - body["display_name"] = self.display_name if self.id is not None: body["id"] = self.id if self.name is not None: body["name"] = self.name - if self.tags: - body["tags"] = [v.as_dict() for v in self.tags] return body def as_shallow_dict(self) -> dict: """Serializes the Share into a shallow dictionary of its immediate attributes.""" body = {} - if self.comment is not None: - body["comment"] = self.comment - if self.display_name is not None: - body["display_name"] = self.display_name if self.id is not None: body["id"] = self.id if self.name is not None: body["name"] = self.name - if self.tags: - body["tags"] = self.tags return body @classmethod def from_dict(cls, d: Dict[str, Any]) -> Share: """Deserializes the Share from a dictionary.""" - return cls( - comment=d.get("comment", None), - display_name=d.get("display_name", None), - id=d.get("id", None), - name=d.get("name", None), - tags=_repeated_dict(d, "tags", catalog.TagKeyValue), - ) + return cls(id=d.get("id", None), name=d.get("name", None)) @dataclass From 3722e47541f17bd9c230357a4835be3851afa7bd Mon Sep 17 00:00:00 2001 From: Parth Bansal Date: Thu, 14 Aug 2025 21:20:16 +0000 Subject: [PATCH 2/2] update scim --- databricks/sdk/__init__.py | 85 +- databricks/sdk/service/iam.py | 2935 +++++++++++++++++++++++++++------ 2 files changed, 2536 insertions(+), 484 deletions(-) diff --git a/databricks/sdk/__init__.py b/databricks/sdk/__init__.py index 643b7c933..253138250 100755 --- a/databricks/sdk/__init__.py +++ b/databricks/sdk/__init__.py @@ -79,12 +79,15 @@ from databricks.sdk.service.iam import (AccessControlAPI, AccountAccessControlAPI, AccountAccessControlProxyAPI, - AccountGroupsAPI, + AccountGroupsAPI, AccountGroupsV2API, AccountServicePrincipalsAPI, - AccountUsersAPI, CurrentUserAPI, - GroupsAPI, PermissionMigrationAPI, - PermissionsAPI, ServicePrincipalsAPI, - UsersAPI, WorkspaceAssignmentAPI) + AccountServicePrincipalsV2API, + AccountUsersAPI, AccountUsersV2API, + CurrentUserAPI, GroupsAPI, GroupsV2API, + PermissionMigrationAPI, PermissionsAPI, + ServicePrincipalsAPI, + ServicePrincipalsV2API, UsersAPI, + UsersV2API, WorkspaceAssignmentAPI) from databricks.sdk.service.jobs import JobsAPI, PolicyComplianceForJobsAPI from databricks.sdk.service.marketplace import ( ConsumerFulfillmentsAPI, ConsumerInstallationsAPI, ConsumerListingsAPI, @@ -284,7 +287,7 @@ def __init__( self._git_credentials = pkg_workspace.GitCredentialsAPI(self._api_client) self._global_init_scripts = pkg_compute.GlobalInitScriptsAPI(self._api_client) self._grants = pkg_catalog.GrantsAPI(self._api_client) - self._groups = pkg_iam.GroupsAPI(self._api_client) + self._groups_v2 = pkg_iam.GroupsV2API(self._api_client) self._instance_pools = pkg_compute.InstancePoolsAPI(self._api_client) self._instance_profiles = pkg_compute.InstanceProfilesAPI(self._api_client) self._ip_access_lists = pkg_settings.IpAccessListsAPI(self._api_client) @@ -333,7 +336,7 @@ def __init__( self._schemas = pkg_catalog.SchemasAPI(self._api_client) self._secrets = pkg_workspace.SecretsAPI(self._api_client) self._service_principal_secrets_proxy = pkg_oauth2.ServicePrincipalSecretsProxyAPI(self._api_client) - self._service_principals = pkg_iam.ServicePrincipalsAPI(self._api_client) + self._service_principals_v2 = pkg_iam.ServicePrincipalsV2API(self._api_client) self._serving_endpoints = serving_endpoints serving_endpoints_data_plane_token_source = DataPlaneTokenSource( self._config.host, self._config.oauth_token, self._config.disable_async_token_refresh @@ -352,7 +355,7 @@ def __init__( self._temporary_table_credentials = pkg_catalog.TemporaryTableCredentialsAPI(self._api_client) self._token_management = pkg_settings.TokenManagementAPI(self._api_client) self._tokens = pkg_settings.TokensAPI(self._api_client) - self._users = pkg_iam.UsersAPI(self._api_client) + self._users_v2 = pkg_iam.UsersV2API(self._api_client) self._vector_search_endpoints = pkg_vectorsearch.VectorSearchEndpointsAPI(self._api_client) self._vector_search_indexes = pkg_vectorsearch.VectorSearchIndexesAPI(self._api_client) self._volumes = pkg_catalog.VolumesAPI(self._api_client) @@ -361,6 +364,9 @@ def __init__( self._workspace_bindings = pkg_catalog.WorkspaceBindingsAPI(self._api_client) self._workspace_conf = pkg_settings.WorkspaceConfAPI(self._api_client) self._forecasting = pkg_ml.ForecastingAPI(self._api_client) + self._groups = pkg_iam.GroupsAPI(self._api_client) + self._service_principals = pkg_iam.ServicePrincipalsAPI(self._api_client) + self._users = pkg_iam.UsersAPI(self._api_client) @property def config(self) -> client.Config: @@ -590,9 +596,9 @@ def grants(self) -> pkg_catalog.GrantsAPI: return self._grants @property - def groups(self) -> pkg_iam.GroupsAPI: + def groups_v2(self) -> pkg_iam.GroupsV2API: """Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects.""" - return self._groups + return self._groups_v2 @property def instance_pools(self) -> pkg_compute.InstancePoolsAPI: @@ -825,9 +831,9 @@ def service_principal_secrets_proxy(self) -> pkg_oauth2.ServicePrincipalSecretsP return self._service_principal_secrets_proxy @property - def service_principals(self) -> pkg_iam.ServicePrincipalsAPI: + def service_principals_v2(self) -> pkg_iam.ServicePrincipalsV2API: """Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms.""" - return self._service_principals + return self._service_principals_v2 @property def serving_endpoints(self) -> ServingEndpointsExt: @@ -895,9 +901,9 @@ def tokens(self) -> pkg_settings.TokensAPI: return self._tokens @property - def users(self) -> pkg_iam.UsersAPI: + def users_v2(self) -> pkg_iam.UsersV2API: """User identities recognized by Databricks and represented by email addresses.""" - return self._users + return self._users_v2 @property def vector_search_endpoints(self) -> pkg_vectorsearch.VectorSearchEndpointsAPI: @@ -939,6 +945,21 @@ def forecasting(self) -> pkg_ml.ForecastingAPI: """The Forecasting API allows you to create and get serverless forecasting experiments.""" return self._forecasting + @property + def groups(self) -> pkg_iam.GroupsAPI: + """Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects.""" + return self._groups + + @property + def service_principals(self) -> pkg_iam.ServicePrincipalsAPI: + """Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms.""" + return self._service_principals + + @property + def users(self) -> pkg_iam.UsersAPI: + """User identities recognized by Databricks and represented by email addresses.""" + return self._users + def get_workspace_id(self) -> int: """Get the workspace ID of the workspace that this client is connected to.""" response = self._api_client.do("GET", "/api/2.0/preview/scim/v2/Me", response_headers=["X-Databricks-Org-Id"]) @@ -1020,7 +1041,7 @@ def __init__( self._custom_app_integration = pkg_oauth2.CustomAppIntegrationAPI(self._api_client) self._encryption_keys = pkg_provisioning.EncryptionKeysAPI(self._api_client) self._federation_policy = pkg_oauth2.AccountFederationPolicyAPI(self._api_client) - self._groups = pkg_iam.AccountGroupsAPI(self._api_client) + self._groups_v2 = pkg_iam.AccountGroupsV2API(self._api_client) self._ip_access_lists = pkg_settings.AccountIpAccessListsAPI(self._api_client) self._log_delivery = pkg_billing.LogDeliveryAPI(self._api_client) self._metastore_assignments = pkg_catalog.AccountMetastoreAssignmentsAPI(self._api_client) @@ -1033,17 +1054,20 @@ def __init__( self._published_app_integration = pkg_oauth2.PublishedAppIntegrationAPI(self._api_client) self._service_principal_federation_policy = pkg_oauth2.ServicePrincipalFederationPolicyAPI(self._api_client) self._service_principal_secrets = pkg_oauth2.ServicePrincipalSecretsAPI(self._api_client) - self._service_principals = pkg_iam.AccountServicePrincipalsAPI(self._api_client) + self._service_principals_v2 = pkg_iam.AccountServicePrincipalsV2API(self._api_client) self._settings = pkg_settings.AccountSettingsAPI(self._api_client) self._storage = pkg_provisioning.StorageAPI(self._api_client) self._storage_credentials = pkg_catalog.AccountStorageCredentialsAPI(self._api_client) self._usage_dashboards = pkg_billing.UsageDashboardsAPI(self._api_client) - self._users = pkg_iam.AccountUsersAPI(self._api_client) + self._users_v2 = pkg_iam.AccountUsersV2API(self._api_client) self._vpc_endpoints = pkg_provisioning.VpcEndpointsAPI(self._api_client) self._workspace_assignment = pkg_iam.WorkspaceAssignmentAPI(self._api_client) self._workspace_network_configuration = pkg_settings.WorkspaceNetworkConfigurationAPI(self._api_client) self._workspaces = pkg_provisioning.WorkspacesAPI(self._api_client) self._budgets = pkg_billing.BudgetsAPI(self._api_client) + self._groups = pkg_iam.AccountGroupsAPI(self._api_client) + self._service_principals = pkg_iam.AccountServicePrincipalsAPI(self._api_client) + self._users = pkg_iam.AccountUsersAPI(self._api_client) @property def config(self) -> client.Config: @@ -1089,9 +1113,9 @@ def federation_policy(self) -> pkg_oauth2.AccountFederationPolicyAPI: return self._federation_policy @property - def groups(self) -> pkg_iam.AccountGroupsAPI: + def groups_v2(self) -> pkg_iam.AccountGroupsV2API: """Groups simplify identity management, making it easier to assign access to Databricks account, data, and other securable objects.""" - return self._groups + return self._groups_v2 @property def ip_access_lists(self) -> pkg_settings.AccountIpAccessListsAPI: @@ -1154,9 +1178,9 @@ def service_principal_secrets(self) -> pkg_oauth2.ServicePrincipalSecretsAPI: return self._service_principal_secrets @property - def service_principals(self) -> pkg_iam.AccountServicePrincipalsAPI: + def service_principals_v2(self) -> pkg_iam.AccountServicePrincipalsV2API: """Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms.""" - return self._service_principals + return self._service_principals_v2 @property def settings(self) -> pkg_settings.AccountSettingsAPI: @@ -1179,9 +1203,9 @@ def usage_dashboards(self) -> pkg_billing.UsageDashboardsAPI: return self._usage_dashboards @property - def users(self) -> pkg_iam.AccountUsersAPI: + def users_v2(self) -> pkg_iam.AccountUsersV2API: """User identities recognized by Databricks and represented by email addresses.""" - return self._users + return self._users_v2 @property def vpc_endpoints(self) -> pkg_provisioning.VpcEndpointsAPI: @@ -1208,6 +1232,21 @@ def budgets(self) -> pkg_billing.BudgetsAPI: """These APIs manage budget configurations for this account.""" return self._budgets + @property + def groups(self) -> pkg_iam.AccountGroupsAPI: + """Groups simplify identity management, making it easier to assign access to Databricks account, data, and other securable objects.""" + return self._groups + + @property + def service_principals(self) -> pkg_iam.AccountServicePrincipalsAPI: + """Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms.""" + return self._service_principals + + @property + def users(self) -> pkg_iam.AccountUsersAPI: + """User identities recognized by Databricks and represented by email addresses.""" + return self._users + def get_workspace_client(self, workspace: Workspace) -> WorkspaceClient: """Constructs a ``WorkspaceClient`` for the given workspace. diff --git a/databricks/sdk/service/iam.py b/databricks/sdk/service/iam.py index 09166b04f..9741af96f 100755 --- a/databricks/sdk/service/iam.py +++ b/databricks/sdk/service/iam.py @@ -124,6 +124,243 @@ def from_dict(cls, d: Dict[str, Any]) -> AccessControlResponse: ) +@dataclass +class AccountGroup: + account_id: Optional[str] = None + """Databricks account ID""" + + display_name: Optional[str] = None + """String that represents a human-readable group name""" + + external_id: Optional[str] = None + + id: Optional[str] = None + """Databricks group ID""" + + members: Optional[List[ComplexValue]] = None + + meta: Optional[ResourceMeta] = None + """Container for the group identifier. Workspace local versus account.""" + + roles: Optional[List[ComplexValue]] = None + """Indicates if the group has the admin role.""" + + def as_dict(self) -> dict: + """Serializes the AccountGroup into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.account_id is not None: + body["account_id"] = self.account_id + if self.display_name is not None: + body["displayName"] = self.display_name + if self.external_id is not None: + body["externalId"] = self.external_id + if self.id is not None: + body["id"] = self.id + if self.members: + body["members"] = [v.as_dict() for v in self.members] + if self.meta: + body["meta"] = self.meta.as_dict() + if self.roles: + body["roles"] = [v.as_dict() for v in self.roles] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the AccountGroup into a shallow dictionary of its immediate attributes.""" + body = {} + if self.account_id is not None: + body["account_id"] = self.account_id + if self.display_name is not None: + body["displayName"] = self.display_name + if self.external_id is not None: + body["externalId"] = self.external_id + if self.id is not None: + body["id"] = self.id + if self.members: + body["members"] = self.members + if self.meta: + body["meta"] = self.meta + if self.roles: + body["roles"] = self.roles + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> AccountGroup: + """Deserializes the AccountGroup from a dictionary.""" + return cls( + account_id=d.get("account_id", None), + display_name=d.get("displayName", None), + external_id=d.get("externalId", None), + id=d.get("id", None), + members=_repeated_dict(d, "members", ComplexValue), + meta=_from_dict(d, "meta", ResourceMeta), + roles=_repeated_dict(d, "roles", ComplexValue), + ) + + +@dataclass +class AccountServicePrincipal: + account_id: Optional[str] = None + """Databricks account ID""" + + active: Optional[bool] = None + """If this user is active""" + + application_id: Optional[str] = None + """UUID relating to the service principal""" + + display_name: Optional[str] = None + """String that represents a concatenation of given and family names.""" + + external_id: Optional[str] = None + + id: Optional[str] = None + """Databricks service principal ID.""" + + roles: Optional[List[ComplexValue]] = None + """Indicates if the group has the admin role.""" + + def as_dict(self) -> dict: + """Serializes the AccountServicePrincipal into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.account_id is not None: + body["account_id"] = self.account_id + if self.active is not None: + body["active"] = self.active + if self.application_id is not None: + body["applicationId"] = self.application_id + if self.display_name is not None: + body["displayName"] = self.display_name + if self.external_id is not None: + body["externalId"] = self.external_id + if self.id is not None: + body["id"] = self.id + if self.roles: + body["roles"] = [v.as_dict() for v in self.roles] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the AccountServicePrincipal into a shallow dictionary of its immediate attributes.""" + body = {} + if self.account_id is not None: + body["account_id"] = self.account_id + if self.active is not None: + body["active"] = self.active + if self.application_id is not None: + body["applicationId"] = self.application_id + if self.display_name is not None: + body["displayName"] = self.display_name + if self.external_id is not None: + body["externalId"] = self.external_id + if self.id is not None: + body["id"] = self.id + if self.roles: + body["roles"] = self.roles + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> AccountServicePrincipal: + """Deserializes the AccountServicePrincipal from a dictionary.""" + return cls( + account_id=d.get("account_id", None), + active=d.get("active", None), + application_id=d.get("applicationId", None), + display_name=d.get("displayName", None), + external_id=d.get("externalId", None), + id=d.get("id", None), + roles=_repeated_dict(d, "roles", ComplexValue), + ) + + +@dataclass +class AccountUser: + account_id: Optional[str] = None + """Databricks account ID""" + + active: Optional[bool] = None + """If this user is active""" + + display_name: Optional[str] = None + """String that represents a concatenation of given and family names. For example `John Smith`.""" + + emails: Optional[List[ComplexValue]] = None + """All the emails associated with the Databricks user.""" + + external_id: Optional[str] = None + """External ID is not currently supported. It is reserved for future use.""" + + id: Optional[str] = None + """Databricks user ID.""" + + name: Optional[Name] = None + + roles: Optional[List[ComplexValue]] = None + """Indicates if the group has the admin role.""" + + user_name: Optional[str] = None + """Email address of the Databricks user.""" + + def as_dict(self) -> dict: + """Serializes the AccountUser into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.account_id is not None: + body["account_id"] = self.account_id + if self.active is not None: + body["active"] = self.active + if self.display_name is not None: + body["displayName"] = self.display_name + if self.emails: + body["emails"] = [v.as_dict() for v in self.emails] + if self.external_id is not None: + body["externalId"] = self.external_id + if self.id is not None: + body["id"] = self.id + if self.name: + body["name"] = self.name.as_dict() + if self.roles: + body["roles"] = [v.as_dict() for v in self.roles] + if self.user_name is not None: + body["userName"] = self.user_name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the AccountUser into a shallow dictionary of its immediate attributes.""" + body = {} + if self.account_id is not None: + body["account_id"] = self.account_id + if self.active is not None: + body["active"] = self.active + if self.display_name is not None: + body["displayName"] = self.display_name + if self.emails: + body["emails"] = self.emails + if self.external_id is not None: + body["externalId"] = self.external_id + if self.id is not None: + body["id"] = self.id + if self.name: + body["name"] = self.name + if self.roles: + body["roles"] = self.roles + if self.user_name is not None: + body["userName"] = self.user_name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> AccountUser: + """Deserializes the AccountUser from a dictionary.""" + return cls( + account_id=d.get("account_id", None), + active=d.get("active", None), + display_name=d.get("displayName", None), + emails=_repeated_dict(d, "emails", ComplexValue), + external_id=d.get("externalId", None), + id=d.get("id", None), + name=_from_dict(d, "name", Name), + roles=_repeated_dict(d, "roles", ComplexValue), + user_name=d.get("userName", None), + ) + + @dataclass class Actor: """represents an identity trying to access a resource - user or a service principal group can be a @@ -510,16 +747,13 @@ class GroupSchema(Enum): @dataclass -class ListGroupsResponse: +class ListAccountGroupsResponse: items_per_page: Optional[int] = None """Total results returned in the response.""" - resources: Optional[List[Group]] = None + resources: Optional[List[AccountGroup]] = None """User objects returned in the response.""" - schemas: Optional[List[ListResponseSchema]] = None - """The schema of the service principal.""" - start_index: Optional[int] = None """Starting index of all the results that matched the request filters. First item is number 1.""" @@ -527,14 +761,12 @@ class ListGroupsResponse: """Total results that match the request filters.""" def as_dict(self) -> dict: - """Serializes the ListGroupsResponse into a dictionary suitable for use as a JSON request body.""" + """Serializes the ListAccountGroupsResponse into a dictionary suitable for use as a JSON request body.""" body = {} if self.items_per_page is not None: body["itemsPerPage"] = self.items_per_page if self.resources: body["Resources"] = [v.as_dict() for v in self.resources] - if self.schemas: - body["schemas"] = [v.value for v in self.schemas] if self.start_index is not None: body["startIndex"] = self.start_index if self.total_results is not None: @@ -542,14 +774,12 @@ def as_dict(self) -> dict: return body def as_shallow_dict(self) -> dict: - """Serializes the ListGroupsResponse into a shallow dictionary of its immediate attributes.""" + """Serializes the ListAccountGroupsResponse into a shallow dictionary of its immediate attributes.""" body = {} if self.items_per_page is not None: body["itemsPerPage"] = self.items_per_page if self.resources: body["Resources"] = self.resources - if self.schemas: - body["schemas"] = self.schemas if self.start_index is not None: body["startIndex"] = self.start_index if self.total_results is not None: @@ -557,33 +787,24 @@ def as_shallow_dict(self) -> dict: return body @classmethod - def from_dict(cls, d: Dict[str, Any]) -> ListGroupsResponse: - """Deserializes the ListGroupsResponse from a dictionary.""" + def from_dict(cls, d: Dict[str, Any]) -> ListAccountGroupsResponse: + """Deserializes the ListAccountGroupsResponse from a dictionary.""" return cls( items_per_page=d.get("itemsPerPage", None), - resources=_repeated_dict(d, "Resources", Group), - schemas=_repeated_enum(d, "schemas", ListResponseSchema), + resources=_repeated_dict(d, "Resources", AccountGroup), start_index=d.get("startIndex", None), total_results=d.get("totalResults", None), ) -class ListResponseSchema(Enum): - - URN_IETF_PARAMS_SCIM_API_MESSAGES_2_0_LIST_RESPONSE = "urn:ietf:params:scim:api:messages:2.0:ListResponse" - - @dataclass -class ListServicePrincipalResponse: +class ListAccountServicePrincipalsResponse: items_per_page: Optional[int] = None """Total results returned in the response.""" - resources: Optional[List[ServicePrincipal]] = None + resources: Optional[List[AccountServicePrincipal]] = None """User objects returned in the response.""" - schemas: Optional[List[ListResponseSchema]] = None - """The schema of the List response.""" - start_index: Optional[int] = None """Starting index of all the results that matched the request filters. First item is number 1.""" @@ -591,14 +812,12 @@ class ListServicePrincipalResponse: """Total results that match the request filters.""" def as_dict(self) -> dict: - """Serializes the ListServicePrincipalResponse into a dictionary suitable for use as a JSON request body.""" + """Serializes the ListAccountServicePrincipalsResponse into a dictionary suitable for use as a JSON request body.""" body = {} if self.items_per_page is not None: body["itemsPerPage"] = self.items_per_page if self.resources: body["Resources"] = [v.as_dict() for v in self.resources] - if self.schemas: - body["schemas"] = [v.value for v in self.schemas] if self.start_index is not None: body["startIndex"] = self.start_index if self.total_results is not None: @@ -606,14 +825,12 @@ def as_dict(self) -> dict: return body def as_shallow_dict(self) -> dict: - """Serializes the ListServicePrincipalResponse into a shallow dictionary of its immediate attributes.""" + """Serializes the ListAccountServicePrincipalsResponse into a shallow dictionary of its immediate attributes.""" body = {} if self.items_per_page is not None: body["itemsPerPage"] = self.items_per_page if self.resources: body["Resources"] = self.resources - if self.schemas: - body["schemas"] = self.schemas if self.start_index is not None: body["startIndex"] = self.start_index if self.total_results is not None: @@ -621,33 +838,26 @@ def as_shallow_dict(self) -> dict: return body @classmethod - def from_dict(cls, d: Dict[str, Any]) -> ListServicePrincipalResponse: - """Deserializes the ListServicePrincipalResponse from a dictionary.""" + def from_dict(cls, d: Dict[str, Any]) -> ListAccountServicePrincipalsResponse: + """Deserializes the ListAccountServicePrincipalsResponse from a dictionary.""" return cls( items_per_page=d.get("itemsPerPage", None), - resources=_repeated_dict(d, "Resources", ServicePrincipal), - schemas=_repeated_enum(d, "schemas", ListResponseSchema), + resources=_repeated_dict(d, "Resources", AccountServicePrincipal), start_index=d.get("startIndex", None), total_results=d.get("totalResults", None), ) -class ListSortOrder(Enum): - - ASCENDING = "ascending" - DESCENDING = "descending" - - @dataclass -class ListUsersResponse: +class ListGroupsResponse: items_per_page: Optional[int] = None """Total results returned in the response.""" - resources: Optional[List[User]] = None + resources: Optional[List[Group]] = None """User objects returned in the response.""" schemas: Optional[List[ListResponseSchema]] = None - """The schema of the List response.""" + """The schema of the service principal.""" start_index: Optional[int] = None """Starting index of all the results that matched the request filters. First item is number 1.""" @@ -656,7 +866,7 @@ class ListUsersResponse: """Total results that match the request filters.""" def as_dict(self) -> dict: - """Serializes the ListUsersResponse into a dictionary suitable for use as a JSON request body.""" + """Serializes the ListGroupsResponse into a dictionary suitable for use as a JSON request body.""" body = {} if self.items_per_page is not None: body["itemsPerPage"] = self.items_per_page @@ -671,7 +881,7 @@ def as_dict(self) -> dict: return body def as_shallow_dict(self) -> dict: - """Serializes the ListUsersResponse into a shallow dictionary of its immediate attributes.""" + """Serializes the ListGroupsResponse into a shallow dictionary of its immediate attributes.""" body = {} if self.items_per_page is not None: body["itemsPerPage"] = self.items_per_page @@ -686,19 +896,148 @@ def as_shallow_dict(self) -> dict: return body @classmethod - def from_dict(cls, d: Dict[str, Any]) -> ListUsersResponse: - """Deserializes the ListUsersResponse from a dictionary.""" + def from_dict(cls, d: Dict[str, Any]) -> ListGroupsResponse: + """Deserializes the ListGroupsResponse from a dictionary.""" return cls( items_per_page=d.get("itemsPerPage", None), - resources=_repeated_dict(d, "Resources", User), + resources=_repeated_dict(d, "Resources", Group), schemas=_repeated_enum(d, "schemas", ListResponseSchema), start_index=d.get("startIndex", None), total_results=d.get("totalResults", None), ) -@dataclass -class MigratePermissionsResponse: +class ListResponseSchema(Enum): + + URN_IETF_PARAMS_SCIM_API_MESSAGES_2_0_LIST_RESPONSE = "urn:ietf:params:scim:api:messages:2.0:ListResponse" + + +@dataclass +class ListServicePrincipalResponse: + items_per_page: Optional[int] = None + """Total results returned in the response.""" + + resources: Optional[List[ServicePrincipal]] = None + """User objects returned in the response.""" + + schemas: Optional[List[ListResponseSchema]] = None + """The schema of the List response.""" + + start_index: Optional[int] = None + """Starting index of all the results that matched the request filters. First item is number 1.""" + + total_results: Optional[int] = None + """Total results that match the request filters.""" + + def as_dict(self) -> dict: + """Serializes the ListServicePrincipalResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.items_per_page is not None: + body["itemsPerPage"] = self.items_per_page + if self.resources: + body["Resources"] = [v.as_dict() for v in self.resources] + if self.schemas: + body["schemas"] = [v.value for v in self.schemas] + if self.start_index is not None: + body["startIndex"] = self.start_index + if self.total_results is not None: + body["totalResults"] = self.total_results + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListServicePrincipalResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.items_per_page is not None: + body["itemsPerPage"] = self.items_per_page + if self.resources: + body["Resources"] = self.resources + if self.schemas: + body["schemas"] = self.schemas + if self.start_index is not None: + body["startIndex"] = self.start_index + if self.total_results is not None: + body["totalResults"] = self.total_results + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListServicePrincipalResponse: + """Deserializes the ListServicePrincipalResponse from a dictionary.""" + return cls( + items_per_page=d.get("itemsPerPage", None), + resources=_repeated_dict(d, "Resources", ServicePrincipal), + schemas=_repeated_enum(d, "schemas", ListResponseSchema), + start_index=d.get("startIndex", None), + total_results=d.get("totalResults", None), + ) + + +class ListSortOrder(Enum): + + ASCENDING = "ascending" + DESCENDING = "descending" + + +@dataclass +class ListUsersResponse: + items_per_page: Optional[int] = None + """Total results returned in the response.""" + + resources: Optional[List[User]] = None + """User objects returned in the response.""" + + schemas: Optional[List[ListResponseSchema]] = None + """The schema of the List response.""" + + start_index: Optional[int] = None + """Starting index of all the results that matched the request filters. First item is number 1.""" + + total_results: Optional[int] = None + """Total results that match the request filters.""" + + def as_dict(self) -> dict: + """Serializes the ListUsersResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.items_per_page is not None: + body["itemsPerPage"] = self.items_per_page + if self.resources: + body["Resources"] = [v.as_dict() for v in self.resources] + if self.schemas: + body["schemas"] = [v.value for v in self.schemas] + if self.start_index is not None: + body["startIndex"] = self.start_index + if self.total_results is not None: + body["totalResults"] = self.total_results + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListUsersResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.items_per_page is not None: + body["itemsPerPage"] = self.items_per_page + if self.resources: + body["Resources"] = self.resources + if self.schemas: + body["schemas"] = self.schemas + if self.start_index is not None: + body["startIndex"] = self.start_index + if self.total_results is not None: + body["totalResults"] = self.total_results + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListUsersResponse: + """Deserializes the ListUsersResponse from a dictionary.""" + return cls( + items_per_page=d.get("itemsPerPage", None), + resources=_repeated_dict(d, "Resources", User), + schemas=_repeated_enum(d, "schemas", ListResponseSchema), + start_index=d.get("startIndex", None), + total_results=d.get("totalResults", None), + ) + + +@dataclass +class MigratePermissionsResponse: permissions_migrated: Optional[int] = None """Number of permissions migrated.""" @@ -2077,6 +2416,1993 @@ def update_rule_set(self, name: str, rule_set: RuleSetUpdateRequest) -> RuleSetR return RuleSetResponse.from_dict(res) +class AccountGroupsV2API: + """Groups simplify identity management, making it easier to assign access to Databricks account, data, and + other securable objects. + + It is best practice to assign access to workspaces and access-control policies in Unity Catalog to groups, + instead of to users individually. All Databricks account identities can be assigned as members of groups, + and members inherit permissions that are assigned to their group.""" + + def __init__(self, api_client): + self._api = api_client + + def create( + self, + *, + display_name: Optional[str] = None, + external_id: Optional[str] = None, + id: Optional[str] = None, + members: Optional[List[ComplexValue]] = None, + meta: Optional[ResourceMeta] = None, + roles: Optional[List[ComplexValue]] = None, + ) -> AccountGroup: + """Creates a group in the Databricks account with a unique name, using the supplied group details. + + :param display_name: str (optional) + String that represents a human-readable group name + :param external_id: str (optional) + :param id: str (optional) + Databricks group ID + :param members: List[:class:`ComplexValue`] (optional) + :param meta: :class:`ResourceMeta` (optional) + Container for the group identifier. Workspace local versus account. + :param roles: List[:class:`ComplexValue`] (optional) + Indicates if the group has the admin role. + + :returns: :class:`AccountGroup` + """ + body = {} + if display_name is not None: + body["displayName"] = display_name + if external_id is not None: + body["externalId"] = external_id + if id is not None: + body["id"] = id + if members is not None: + body["members"] = [v.as_dict() for v in members] + if meta is not None: + body["meta"] = meta.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do( + "POST", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Groups", body=body, headers=headers + ) + return AccountGroup.from_dict(res) + + def delete(self, id: str): + """Deletes a group from the Databricks account. + + :param id: str + Unique ID for a group in the Databricks account. + + + """ + + headers = {} + + self._api.do("DELETE", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Groups/{id}", headers=headers) + + def get(self, id: str) -> AccountGroup: + """Gets the information for a specific group in the Databricks account. + + :param id: str + Unique ID for a group in the Databricks account. + + :returns: :class:`AccountGroup` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Groups/{id}", headers=headers) + return AccountGroup.from_dict(res) + + def list( + self, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[ListSortOrder] = None, + start_index: Optional[int] = None, + ) -> Iterator[AccountGroup]: + """Gets all details of the groups associated with the Databricks account. + + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. Default is 10000. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: Iterator over :class:`AccountGroup` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do( + "GET", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Groups", query=query, headers=headers + ) + if "Resources" in json: + for v in json["Resources"]: + yield AccountGroup.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) + + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates the details of a group. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. + + + """ + body = {} + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do( + "PATCH", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Groups/{id}", body=body, headers=headers + ) + + def update( + self, + id: str, + *, + display_name: Optional[str] = None, + external_id: Optional[str] = None, + members: Optional[List[ComplexValue]] = None, + meta: Optional[ResourceMeta] = None, + roles: Optional[List[ComplexValue]] = None, + ): + """Updates the details of a group by replacing the entire group entity. + + :param id: str + Databricks group ID + :param display_name: str (optional) + String that represents a human-readable group name + :param external_id: str (optional) + :param members: List[:class:`ComplexValue`] (optional) + :param meta: :class:`ResourceMeta` (optional) + Container for the group identifier. Workspace local versus account. + :param roles: List[:class:`ComplexValue`] (optional) + Indicates if the group has the admin role. + + + """ + body = {} + if display_name is not None: + body["displayName"] = display_name + if external_id is not None: + body["externalId"] = external_id + if members is not None: + body["members"] = [v.as_dict() for v in members] + if meta is not None: + body["meta"] = meta.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + headers = { + "Content-Type": "application/json", + } + + self._api.do("PUT", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Groups/{id}", body=body, headers=headers) + + +class AccountServicePrincipalsV2API: + """Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. + Databricks recommends creating service principals to run production jobs or modify production data. If all + processes that act on production data run with service principals, interactive users do not need any + write, delete, or modify privileges in production. This eliminates the risk of a user overwriting + production data by accident.""" + + def __init__(self, api_client): + self._api = api_client + + def create( + self, + *, + active: Optional[bool] = None, + application_id: Optional[str] = None, + display_name: Optional[str] = None, + external_id: Optional[str] = None, + id: Optional[str] = None, + roles: Optional[List[ComplexValue]] = None, + ) -> AccountServicePrincipal: + """Creates a new service principal in the Databricks account. + + :param active: bool (optional) + If this user is active + :param application_id: str (optional) + UUID relating to the service principal + :param display_name: str (optional) + String that represents a concatenation of given and family names. + :param external_id: str (optional) + :param id: str (optional) + Databricks service principal ID. + :param roles: List[:class:`ComplexValue`] (optional) + Indicates if the group has the admin role. + + :returns: :class:`AccountServicePrincipal` + """ + body = {} + if active is not None: + body["active"] = active + if application_id is not None: + body["applicationId"] = application_id + if display_name is not None: + body["displayName"] = display_name + if external_id is not None: + body["externalId"] = external_id + if id is not None: + body["id"] = id + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do( + "POST", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/ServicePrincipals", body=body, headers=headers + ) + return AccountServicePrincipal.from_dict(res) + + def delete(self, id: str): + """Delete a single service principal in the Databricks account. + + :param id: str + Unique ID for a service principal in the Databricks account. + + + """ + + headers = {} + + self._api.do( + "DELETE", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/ServicePrincipals/{id}", headers=headers + ) + + def get(self, id: str) -> AccountServicePrincipal: + """Gets the details for a single service principal define in the Databricks account. + + :param id: str + Unique ID for a service principal in the Databricks account. + + :returns: :class:`AccountServicePrincipal` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do( + "GET", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/ServicePrincipals/{id}", headers=headers + ) + return AccountServicePrincipal.from_dict(res) + + def list( + self, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[ListSortOrder] = None, + start_index: Optional[int] = None, + ) -> Iterator[AccountServicePrincipal]: + """Gets the set of service principals associated with a Databricks account. + + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. Default is 10000. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: Iterator over :class:`AccountServicePrincipal` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do( + "GET", + f"/api/2.0/accounts/{self._api.account_id}/scim/v2/ServicePrincipals", + query=query, + headers=headers, + ) + if "Resources" in json: + for v in json["Resources"]: + yield AccountServicePrincipal.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) + + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates the details of a single service principal in the Databricks account. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. + + + """ + body = {} + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do( + "PATCH", + f"/api/2.0/accounts/{self._api.account_id}/scim/v2/ServicePrincipals/{id}", + body=body, + headers=headers, + ) + + def update( + self, + id: str, + *, + active: Optional[bool] = None, + application_id: Optional[str] = None, + display_name: Optional[str] = None, + external_id: Optional[str] = None, + roles: Optional[List[ComplexValue]] = None, + ): + """Updates the details of a single service principal. + + This action replaces the existing service principal with the same name. + + :param id: str + Databricks service principal ID. + :param active: bool (optional) + If this user is active + :param application_id: str (optional) + UUID relating to the service principal + :param display_name: str (optional) + String that represents a concatenation of given and family names. + :param external_id: str (optional) + :param roles: List[:class:`ComplexValue`] (optional) + Indicates if the group has the admin role. + + + """ + body = {} + if active is not None: + body["active"] = active + if application_id is not None: + body["applicationId"] = application_id + if display_name is not None: + body["displayName"] = display_name + if external_id is not None: + body["externalId"] = external_id + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + headers = { + "Content-Type": "application/json", + } + + self._api.do( + "PUT", + f"/api/2.0/accounts/{self._api.account_id}/scim/v2/ServicePrincipals/{id}", + body=body, + headers=headers, + ) + + +class AccountUsersV2API: + """User identities recognized by Databricks and represented by email addresses. + + Databricks recommends using SCIM provisioning to sync users and groups automatically from your identity + provider to your Databricks account. SCIM streamlines onboarding a new employee or team by using your + identity provider to create users and groups in Databricks account and give them the proper level of + access. When a user leaves your organization or no longer needs access to Databricks account, admins can + terminate the user in your identity provider and that user’s account will also be removed from + Databricks account. This ensures a consistent offboarding process and prevents unauthorized users from + accessing sensitive data.""" + + def __init__(self, api_client): + self._api = api_client + + def create( + self, + *, + active: Optional[bool] = None, + display_name: Optional[str] = None, + emails: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + id: Optional[str] = None, + name: Optional[Name] = None, + roles: Optional[List[ComplexValue]] = None, + user_name: Optional[str] = None, + ) -> AccountUser: + """Creates a new user in the Databricks account. This new user will also be added to the Databricks + account. + + :param active: bool (optional) + If this user is active + :param display_name: str (optional) + String that represents a concatenation of given and family names. For example `John Smith`. + :param emails: List[:class:`ComplexValue`] (optional) + All the emails associated with the Databricks user. + :param external_id: str (optional) + External ID is not currently supported. It is reserved for future use. + :param id: str (optional) + Databricks user ID. + :param name: :class:`Name` (optional) + :param roles: List[:class:`ComplexValue`] (optional) + Indicates if the group has the admin role. + :param user_name: str (optional) + Email address of the Databricks user. + + :returns: :class:`AccountUser` + """ + body = {} + if active is not None: + body["active"] = active + if display_name is not None: + body["displayName"] = display_name + if emails is not None: + body["emails"] = [v.as_dict() for v in emails] + if external_id is not None: + body["externalId"] = external_id + if id is not None: + body["id"] = id + if name is not None: + body["name"] = name.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if user_name is not None: + body["userName"] = user_name + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do( + "POST", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users", body=body, headers=headers + ) + return AccountUser.from_dict(res) + + def delete(self, id: str): + """Deletes a user. Deleting a user from a Databricks account also removes objects associated with the + user. + + :param id: str + Unique ID for a user in the Databricks account. + + + """ + + headers = {} + + self._api.do("DELETE", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users/{id}", headers=headers) + + def get( + self, + id: str, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[GetSortOrder] = None, + start_index: Optional[int] = None, + ) -> AccountUser: + """Gets information for a specific user in Databricks account. + + :param id: str + Unique ID for a user in the Databricks account. + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. Default is 10000. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. Multi-part paths are supported. For example, `userName`, + `name.givenName`, and `emails`. + :param sort_order: :class:`GetSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: :class:`AccountUser` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + res = self._api.do( + "GET", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users/{id}", query=query, headers=headers + ) + return AccountUser.from_dict(res) + + def list( + self, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[ListSortOrder] = None, + start_index: Optional[int] = None, + ) -> Iterator[AccountGroup]: + """Gets details for all the users associated with a Databricks account. + + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. Default is 10000. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. Multi-part paths are supported. For example, `userName`, + `name.givenName`, and `emails`. + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: Iterator over :class:`AccountGroup` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do( + "GET", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users", query=query, headers=headers + ) + if "Resources" in json: + for v in json["Resources"]: + yield AccountGroup.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) + + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates a user resource by applying the supplied operations on specific user attributes. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. + + + """ + body = {} + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do( + "PATCH", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users/{id}", body=body, headers=headers + ) + + def update( + self, + id: str, + *, + active: Optional[bool] = None, + display_name: Optional[str] = None, + emails: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + name: Optional[Name] = None, + roles: Optional[List[ComplexValue]] = None, + user_name: Optional[str] = None, + ): + """Replaces a user's information with the data supplied in request. + + :param id: str + Databricks user ID. + :param active: bool (optional) + If this user is active + :param display_name: str (optional) + String that represents a concatenation of given and family names. For example `John Smith`. + :param emails: List[:class:`ComplexValue`] (optional) + All the emails associated with the Databricks user. + :param external_id: str (optional) + External ID is not currently supported. It is reserved for future use. + :param name: :class:`Name` (optional) + :param roles: List[:class:`ComplexValue`] (optional) + Indicates if the group has the admin role. + :param user_name: str (optional) + Email address of the Databricks user. + + + """ + body = {} + if active is not None: + body["active"] = active + if display_name is not None: + body["displayName"] = display_name + if emails is not None: + body["emails"] = [v.as_dict() for v in emails] + if external_id is not None: + body["externalId"] = external_id + if name is not None: + body["name"] = name.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if user_name is not None: + body["userName"] = user_name + headers = { + "Content-Type": "application/json", + } + + self._api.do("PUT", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users/{id}", body=body, headers=headers) + + +class CurrentUserAPI: + """This API allows retrieving information about currently authenticated user or service principal.""" + + def __init__(self, api_client): + self._api = api_client + + def me(self) -> User: + """Get details about the current method caller's identity. + + + :returns: :class:`User` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", "/api/2.0/preview/scim/v2/Me", headers=headers) + return User.from_dict(res) + + +class GroupsV2API: + """Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and + other securable objects. + + It is best practice to assign access to workspaces and access-control policies in Unity Catalog to groups, + instead of to users individually. All Databricks workspace identities can be assigned as members of + groups, and members inherit permissions that are assigned to their group.""" + + def __init__(self, api_client): + self._api = api_client + + def create( + self, + *, + display_name: Optional[str] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + id: Optional[str] = None, + members: Optional[List[ComplexValue]] = None, + meta: Optional[ResourceMeta] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[GroupSchema]] = None, + ) -> Group: + """Creates a group in the Databricks workspace with a unique name, using the supplied group details. + + :param display_name: str (optional) + String that represents a human-readable group name + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the group. See [assigning entitlements] for a full list of supported + values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + :param groups: List[:class:`ComplexValue`] (optional) + :param id: str (optional) + Databricks group ID + :param members: List[:class:`ComplexValue`] (optional) + :param meta: :class:`ResourceMeta` (optional) + Container for the group identifier. Workspace local versus account. + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`GroupSchema`] (optional) + The schema of the group. + + :returns: :class:`Group` + """ + body = {} + if display_name is not None: + body["displayName"] = display_name + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if id is not None: + body["id"] = id + if members is not None: + body["members"] = [v.as_dict() for v in members] + if meta is not None: + body["meta"] = meta.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("POST", "/api/2.0/preview/scim/v2/Groups", body=body, headers=headers) + return Group.from_dict(res) + + def delete(self, id: str): + """Deletes a group from the Databricks workspace. + + :param id: str + Unique ID for a group in the Databricks workspace. + + + """ + + headers = {} + + self._api.do("DELETE", f"/api/2.0/preview/scim/v2/Groups/{id}", headers=headers) + + def get(self, id: str) -> Group: + """Gets the information for a specific group in the Databricks workspace. + + :param id: str + Unique ID for a group in the Databricks workspace. + + :returns: :class:`Group` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", f"/api/2.0/preview/scim/v2/Groups/{id}", headers=headers) + return Group.from_dict(res) + + def list( + self, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[ListSortOrder] = None, + start_index: Optional[int] = None, + ) -> Iterator[Group]: + """Gets all details of the groups associated with the Databricks workspace. + + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: Iterator over :class:`Group` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do("GET", "/api/2.0/preview/scim/v2/Groups", query=query, headers=headers) + if "Resources" in json: + for v in json["Resources"]: + yield Group.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) + + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates the details of a group. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. + + + """ + body = {} + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do("PATCH", f"/api/2.0/preview/scim/v2/Groups/{id}", body=body, headers=headers) + + def update( + self, + id: str, + *, + display_name: Optional[str] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + members: Optional[List[ComplexValue]] = None, + meta: Optional[ResourceMeta] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[GroupSchema]] = None, + ): + """Updates the details of a group by replacing the entire group entity. + + :param id: str + Databricks group ID + :param display_name: str (optional) + String that represents a human-readable group name + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the group. See [assigning entitlements] for a full list of supported + values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + :param groups: List[:class:`ComplexValue`] (optional) + :param members: List[:class:`ComplexValue`] (optional) + :param meta: :class:`ResourceMeta` (optional) + Container for the group identifier. Workspace local versus account. + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`GroupSchema`] (optional) + The schema of the group. + + + """ + body = {} + if display_name is not None: + body["displayName"] = display_name + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if members is not None: + body["members"] = [v.as_dict() for v in members] + if meta is not None: + body["meta"] = meta.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do("PUT", f"/api/2.0/preview/scim/v2/Groups/{id}", body=body, headers=headers) + + +class PermissionMigrationAPI: + """APIs for migrating acl permissions, used only by the ucx tool: https://github.com/databrickslabs/ucx""" + + def __init__(self, api_client): + self._api = api_client + + def migrate_permissions( + self, + workspace_id: int, + from_workspace_group_name: str, + to_account_group_name: str, + *, + size: Optional[int] = None, + ) -> MigratePermissionsResponse: + """Migrate Permissions. + + :param workspace_id: int + WorkspaceId of the associated workspace where the permission migration will occur. + :param from_workspace_group_name: str + The name of the workspace group that permissions will be migrated from. + :param to_account_group_name: str + The name of the account group that permissions will be migrated to. + :param size: int (optional) + The maximum number of permissions that will be migrated. + + :returns: :class:`MigratePermissionsResponse` + """ + body = {} + if from_workspace_group_name is not None: + body["from_workspace_group_name"] = from_workspace_group_name + if size is not None: + body["size"] = size + if to_account_group_name is not None: + body["to_account_group_name"] = to_account_group_name + if workspace_id is not None: + body["workspace_id"] = workspace_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("POST", "/api/2.0/permissionmigration", body=body, headers=headers) + return MigratePermissionsResponse.from_dict(res) + + +class PermissionsAPI: + """Permissions API are used to create read, write, edit, update and manage access for various users on + different objects and endpoints. * **[Apps permissions](:service:apps)** — Manage which users can manage + or use apps. * **[Cluster permissions](:service:clusters)** — Manage which users can manage, restart, or + attach to clusters. * **[Cluster policy permissions](:service:clusterpolicies)** — Manage which users + can use cluster policies. * **[Delta Live Tables pipeline permissions](:service:pipelines)** — Manage + which users can view, manage, run, cancel, or own a Delta Live Tables pipeline. * **[Job + permissions](:service:jobs)** — Manage which users can view, manage, trigger, cancel, or own a job. * + **[MLflow experiment permissions](:service:experiments)** — Manage which users can read, edit, or manage + MLflow experiments. * **[MLflow registered model permissions](:service:modelregistry)** — Manage which + users can read, edit, or manage MLflow registered models. * **[Instance Pool + permissions](:service:instancepools)** — Manage which users can manage or attach to pools. * **[Repo + permissions](repos)** — Manage which users can read, run, edit, or manage a repo. * **[Serving endpoint + permissions](:service:servingendpoints)** — Manage which users can view, query, or manage a serving + endpoint. * **[SQL warehouse permissions](:service:warehouses)** — Manage which users can use or manage + SQL warehouses. * **[Token permissions](:service:tokenmanagement)** — Manage which users can create or + use tokens. * **[Workspace object permissions](:service:workspace)** — Manage which users can read, run, + edit, or manage alerts, dbsql-dashboards, directories, files, notebooks and queries. For the mapping of + the required permissions for specific actions or abilities and other important information, see [Access + Control]. Note that to manage access control on service principals, use **[Account Access Control + Proxy](:service:accountaccesscontrolproxy)**. + + [Access Control]: https://docs.databricks.com/security/auth-authz/access-control/index.html""" + + def __init__(self, api_client): + self._api = api_client + + def get(self, request_object_type: str, request_object_id: str) -> ObjectPermissions: + """Gets the permissions of an object. Objects can inherit permissions from their parent objects or root + object. + + :param request_object_type: str + The type of the request object. Can be one of the following: alerts, alertsv2, authorization, + clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, + instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or + warehouses. + :param request_object_id: str + The id of the request object. + + :returns: :class:`ObjectPermissions` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", f"/api/2.0/permissions/{request_object_type}/{request_object_id}", headers=headers) + return ObjectPermissions.from_dict(res) + + def get_permission_levels(self, request_object_type: str, request_object_id: str) -> GetPermissionLevelsResponse: + """Gets the permission levels that a user can have on an object. + + :param request_object_type: str + The type of the request object. Can be one of the following: alerts, alertsv2, authorization, + clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, + instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or + warehouses. + :param request_object_id: str + + :returns: :class:`GetPermissionLevelsResponse` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do( + "GET", f"/api/2.0/permissions/{request_object_type}/{request_object_id}/permissionLevels", headers=headers + ) + return GetPermissionLevelsResponse.from_dict(res) + + def set( + self, + request_object_type: str, + request_object_id: str, + *, + access_control_list: Optional[List[AccessControlRequest]] = None, + ) -> ObjectPermissions: + """Sets permissions on an object, replacing existing permissions if they exist. Deletes all direct + permissions if none are specified. Objects can inherit permissions from their parent objects or root + object. + + :param request_object_type: str + The type of the request object. Can be one of the following: alerts, alertsv2, authorization, + clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, + instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or + warehouses. + :param request_object_id: str + The id of the request object. + :param access_control_list: List[:class:`AccessControlRequest`] (optional) + + :returns: :class:`ObjectPermissions` + """ + body = {} + if access_control_list is not None: + body["access_control_list"] = [v.as_dict() for v in access_control_list] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do( + "PUT", f"/api/2.0/permissions/{request_object_type}/{request_object_id}", body=body, headers=headers + ) + return ObjectPermissions.from_dict(res) + + def update( + self, + request_object_type: str, + request_object_id: str, + *, + access_control_list: Optional[List[AccessControlRequest]] = None, + ) -> ObjectPermissions: + """Updates the permissions on an object. Objects can inherit permissions from their parent objects or + root object. + + :param request_object_type: str + The type of the request object. Can be one of the following: alerts, alertsv2, authorization, + clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, + instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or + warehouses. + :param request_object_id: str + The id of the request object. + :param access_control_list: List[:class:`AccessControlRequest`] (optional) + + :returns: :class:`ObjectPermissions` + """ + body = {} + if access_control_list is not None: + body["access_control_list"] = [v.as_dict() for v in access_control_list] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do( + "PATCH", f"/api/2.0/permissions/{request_object_type}/{request_object_id}", body=body, headers=headers + ) + return ObjectPermissions.from_dict(res) + + +class ServicePrincipalsV2API: + """Identities for use with jobs, automated tools, and systems such as scripts, apps, and CI/CD platforms. + Databricks recommends creating service principals to run production jobs or modify production data. If all + processes that act on production data run with service principals, interactive users do not need any + write, delete, or modify privileges in production. This eliminates the risk of a user overwriting + production data by accident.""" + + def __init__(self, api_client): + self._api = api_client + + def create( + self, + *, + active: Optional[bool] = None, + application_id: Optional[str] = None, + display_name: Optional[str] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + id: Optional[str] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[ServicePrincipalSchema]] = None, + ) -> ServicePrincipal: + """Creates a new service principal in the Databricks workspace. + + :param active: bool (optional) + If this user is active + :param application_id: str (optional) + UUID relating to the service principal + :param display_name: str (optional) + String that represents a concatenation of given and family names. + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the service principal. See [assigning entitlements] for a full list of + supported values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + :param groups: List[:class:`ComplexValue`] (optional) + :param id: str (optional) + Databricks service principal ID. + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`ServicePrincipalSchema`] (optional) + The schema of the List response. + + :returns: :class:`ServicePrincipal` + """ + body = {} + if active is not None: + body["active"] = active + if application_id is not None: + body["applicationId"] = application_id + if display_name is not None: + body["displayName"] = display_name + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if id is not None: + body["id"] = id + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("POST", "/api/2.0/preview/scim/v2/ServicePrincipals", body=body, headers=headers) + return ServicePrincipal.from_dict(res) + + def delete(self, id: str): + """Delete a single service principal in the Databricks workspace. + + :param id: str + Unique ID for a service principal in the Databricks workspace. + + + """ + + headers = {} + + self._api.do("DELETE", f"/api/2.0/preview/scim/v2/ServicePrincipals/{id}", headers=headers) + + def get(self, id: str) -> ServicePrincipal: + """Gets the details for a single service principal define in the Databricks workspace. + + :param id: str + Unique ID for a service principal in the Databricks workspace. + + :returns: :class:`ServicePrincipal` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", f"/api/2.0/preview/scim/v2/ServicePrincipals/{id}", headers=headers) + return ServicePrincipal.from_dict(res) + + def list( + self, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[ListSortOrder] = None, + start_index: Optional[int] = None, + ) -> Iterator[ServicePrincipal]: + """Gets the set of service principals associated with a Databricks workspace. + + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: Iterator over :class:`ServicePrincipal` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do("GET", "/api/2.0/preview/scim/v2/ServicePrincipals", query=query, headers=headers) + if "Resources" in json: + for v in json["Resources"]: + yield ServicePrincipal.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) + + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates the details of a single service principal in the Databricks workspace. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. + + + """ + body = {} + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do("PATCH", f"/api/2.0/preview/scim/v2/ServicePrincipals/{id}", body=body, headers=headers) + + def update( + self, + id: str, + *, + active: Optional[bool] = None, + application_id: Optional[str] = None, + display_name: Optional[str] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[ServicePrincipalSchema]] = None, + ): + """Updates the details of a single service principal. + + This action replaces the existing service principal with the same name. + + :param id: str + Databricks service principal ID. + :param active: bool (optional) + If this user is active + :param application_id: str (optional) + UUID relating to the service principal + :param display_name: str (optional) + String that represents a concatenation of given and family names. + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the service principal. See [assigning entitlements] for a full list of + supported values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + :param groups: List[:class:`ComplexValue`] (optional) + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`ServicePrincipalSchema`] (optional) + The schema of the List response. + + + """ + body = {} + if active is not None: + body["active"] = active + if application_id is not None: + body["applicationId"] = application_id + if display_name is not None: + body["displayName"] = display_name + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do("PUT", f"/api/2.0/preview/scim/v2/ServicePrincipals/{id}", body=body, headers=headers) + + +class UsersV2API: + """User identities recognized by Databricks and represented by email addresses. + + Databricks recommends using SCIM provisioning to sync users and groups automatically from your identity + provider to your Databricks workspace. SCIM streamlines onboarding a new employee or team by using your + identity provider to create users and groups in Databricks workspace and give them the proper level of + access. When a user leaves your organization or no longer needs access to Databricks workspace, admins can + terminate the user in your identity provider and that user’s account will also be removed from + Databricks workspace. This ensures a consistent offboarding process and prevents unauthorized users from + accessing sensitive data.""" + + def __init__(self, api_client): + self._api = api_client + + def create( + self, + *, + active: Optional[bool] = None, + display_name: Optional[str] = None, + emails: Optional[List[ComplexValue]] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + id: Optional[str] = None, + name: Optional[Name] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[UserSchema]] = None, + user_name: Optional[str] = None, + ) -> User: + """Creates a new user in the Databricks workspace. This new user will also be added to the Databricks + account. + + :param active: bool (optional) + If this user is active + :param display_name: str (optional) + String that represents a concatenation of given and family names. For example `John Smith`. This + field cannot be updated through the Workspace SCIM APIs when [identity federation is enabled]. Use + Account SCIM APIs to update `displayName`. + + [identity federation is enabled]: https://docs.databricks.com/administration-guide/users-groups/best-practices.html#enable-identity-federation + :param emails: List[:class:`ComplexValue`] (optional) + All the emails associated with the Databricks user. + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the user. See [assigning entitlements] for a full list of supported values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + External ID is not currently supported. It is reserved for future use. + :param groups: List[:class:`ComplexValue`] (optional) + :param id: str (optional) + Databricks user ID. + :param name: :class:`Name` (optional) + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`UserSchema`] (optional) + The schema of the user. + :param user_name: str (optional) + Email address of the Databricks user. + + :returns: :class:`User` + """ + body = {} + if active is not None: + body["active"] = active + if display_name is not None: + body["displayName"] = display_name + if emails is not None: + body["emails"] = [v.as_dict() for v in emails] + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if id is not None: + body["id"] = id + if name is not None: + body["name"] = name.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + if user_name is not None: + body["userName"] = user_name + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("POST", "/api/2.0/preview/scim/v2/Users", body=body, headers=headers) + return User.from_dict(res) + + def delete(self, id: str): + """Deletes a user. Deleting a user from a Databricks workspace also removes objects associated with the + user. + + :param id: str + Unique ID for a user in the Databricks workspace. + + + """ + + headers = {} + + self._api.do("DELETE", f"/api/2.0/preview/scim/v2/Users/{id}", headers=headers) + + def get( + self, + id: str, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[GetSortOrder] = None, + start_index: Optional[int] = None, + ) -> User: + """Gets information for a specific user in Databricks workspace. + + :param id: str + Unique ID for a user in the Databricks workspace. + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. Multi-part paths are supported. For example, `userName`, + `name.givenName`, and `emails`. + :param sort_order: :class:`GetSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: :class:`User` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", f"/api/2.0/preview/scim/v2/Users/{id}", query=query, headers=headers) + return User.from_dict(res) + + def get_permission_levels(self) -> GetPasswordPermissionLevelsResponse: + """Gets the permission levels that a user can have on an object. + + + :returns: :class:`GetPasswordPermissionLevelsResponse` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", "/api/2.0/permissions/authorization/passwords/permissionLevels", headers=headers) + return GetPasswordPermissionLevelsResponse.from_dict(res) + + def get_permissions(self) -> PasswordPermissions: + """Gets the permissions of all passwords. Passwords can inherit permissions from their root object. + + + :returns: :class:`PasswordPermissions` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do("GET", "/api/2.0/permissions/authorization/passwords", headers=headers) + return PasswordPermissions.from_dict(res) + + def list( + self, + *, + attributes: Optional[str] = None, + count: Optional[int] = None, + excluded_attributes: Optional[str] = None, + filter: Optional[str] = None, + sort_by: Optional[str] = None, + sort_order: Optional[ListSortOrder] = None, + start_index: Optional[int] = None, + ) -> Iterator[User]: + """Gets details for all the users associated with a Databricks workspace. + + :param attributes: str (optional) + Comma-separated list of attributes to return in response. + :param count: int (optional) + Desired number of results per page. + :param excluded_attributes: str (optional) + Comma-separated list of attributes to exclude in response. + :param filter: str (optional) + Query by which the results have to be filtered. Supported operators are equals(`eq`), + contains(`co`), starts with(`sw`) and not equals(`ne`). Additionally, simple expressions can be + formed using logical operators - `and` and `or`. The [SCIM RFC] has more details but we currently + only support simple expressions. + + [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 + :param sort_by: str (optional) + Attribute to sort the results. Multi-part paths are supported. For example, `userName`, + `name.givenName`, and `emails`. + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. + + :returns: Iterator over :class:`User` + """ + + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index + headers = { + "Accept": "application/json", + } + + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do("GET", "/api/2.0/preview/scim/v2/Users", query=query, headers=headers) + if "Resources" in json: + for v in json["Resources"]: + yield User.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) + + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates a user resource by applying the supplied operations on specific user attributes. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. + + + """ + body = {} + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + headers = { + "Content-Type": "application/json", + } + + self._api.do("PATCH", f"/api/2.0/preview/scim/v2/Users/{id}", body=body, headers=headers) + + def set_permissions( + self, *, access_control_list: Optional[List[PasswordAccessControlRequest]] = None + ) -> PasswordPermissions: + """Sets permissions on an object, replacing existing permissions if they exist. Deletes all direct + permissions if none are specified. Objects can inherit permissions from their root object. + + :param access_control_list: List[:class:`PasswordAccessControlRequest`] (optional) + + :returns: :class:`PasswordPermissions` + """ + body = {} + if access_control_list is not None: + body["access_control_list"] = [v.as_dict() for v in access_control_list] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("PUT", "/api/2.0/permissions/authorization/passwords", body=body, headers=headers) + return PasswordPermissions.from_dict(res) + + def update( + self, + id: str, + *, + active: Optional[bool] = None, + display_name: Optional[str] = None, + emails: Optional[List[ComplexValue]] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + name: Optional[Name] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[UserSchema]] = None, + user_name: Optional[str] = None, + ): + """Replaces a user's information with the data supplied in request. + + :param id: str + Databricks user ID. + :param active: bool (optional) + If this user is active + :param display_name: str (optional) + String that represents a concatenation of given and family names. For example `John Smith`. This + field cannot be updated through the Workspace SCIM APIs when [identity federation is enabled]. Use + Account SCIM APIs to update `displayName`. + + [identity federation is enabled]: https://docs.databricks.com/administration-guide/users-groups/best-practices.html#enable-identity-federation + :param emails: List[:class:`ComplexValue`] (optional) + All the emails associated with the Databricks user. + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the user. See [assigning entitlements] for a full list of supported values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + External ID is not currently supported. It is reserved for future use. + :param groups: List[:class:`ComplexValue`] (optional) + :param name: :class:`Name` (optional) + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`UserSchema`] (optional) + The schema of the user. + :param user_name: str (optional) + Email address of the Databricks user. + + + """ + body = {} + if active is not None: + body["active"] = active + if display_name is not None: + body["displayName"] = display_name + if emails is not None: + body["emails"] = [v.as_dict() for v in emails] + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if name is not None: + body["name"] = name.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] + if user_name is not None: + body["userName"] = user_name + headers = { + "Content-Type": "application/json", + } + + self._api.do("PUT", f"/api/2.0/preview/scim/v2/Users/{id}", body=body, headers=headers) + + def update_permissions( + self, *, access_control_list: Optional[List[PasswordAccessControlRequest]] = None + ) -> PasswordPermissions: + """Updates the permissions on all passwords. Passwords can inherit permissions from their root object. + + :param access_control_list: List[:class:`PasswordAccessControlRequest`] (optional) + + :returns: :class:`PasswordPermissions` + """ + body = {} + if access_control_list is not None: + body["access_control_list"] = [v.as_dict() for v in access_control_list] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do("PATCH", "/api/2.0/permissions/authorization/passwords", body=body, headers=headers) + return PasswordPermissions.from_dict(res) + + +class WorkspaceAssignmentAPI: + """The Workspace Permission Assignment API allows you to manage workspace permissions for principals in your + account.""" + + def __init__(self, api_client): + self._api = api_client + + def delete(self, workspace_id: int, principal_id: int): + """Deletes the workspace permissions assignment in a given account and workspace for the specified + principal. + + :param workspace_id: int + The workspace ID for the account. + :param principal_id: int + The ID of the user, service principal, or group. + + + """ + + headers = { + "Accept": "application/json", + } + + self._api.do( + "DELETE", + f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments/principals/{principal_id}", + headers=headers, + ) + + def get(self, workspace_id: int) -> WorkspacePermissions: + """Get an array of workspace permissions for the specified account and workspace. + + :param workspace_id: int + The workspace ID. + + :returns: :class:`WorkspacePermissions` + """ + + headers = { + "Accept": "application/json", + } + + res = self._api.do( + "GET", + f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments/permissions", + headers=headers, + ) + return WorkspacePermissions.from_dict(res) + + def list(self, workspace_id: int) -> Iterator[PermissionAssignment]: + """Get the permission assignments for the specified Databricks account and Databricks workspace. + + :param workspace_id: int + The workspace ID for the account. + + :returns: Iterator over :class:`PermissionAssignment` + """ + + headers = { + "Accept": "application/json", + } + + json = self._api.do( + "GET", + f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments", + headers=headers, + ) + parsed = PermissionAssignments.from_dict(json).permission_assignments + return parsed if parsed is not None else [] + + def update( + self, workspace_id: int, principal_id: int, *, permissions: Optional[List[WorkspacePermission]] = None + ) -> PermissionAssignment: + """Creates or updates the workspace permissions assignment in a given account and workspace for the + specified principal. + + :param workspace_id: int + The workspace ID. + :param principal_id: int + The ID of the user, service principal, or group. + :param permissions: List[:class:`WorkspacePermission`] (optional) + Array of permissions assignments to update on the workspace. Valid values are "USER" and "ADMIN" + (case-sensitive). If both "USER" and "ADMIN" are provided, "ADMIN" takes precedence. Other values + will be ignored. Note that excluding this field, or providing unsupported values, will have the same + effect as providing an empty list, which will result in the deletion of all permissions for the + principal. + + :returns: :class:`PermissionAssignment` + """ + body = {} + if permissions is not None: + body["permissions"] = [v.value for v in permissions] + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + res = self._api.do( + "PUT", + f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments/principals/{principal_id}", + body=body, + headers=headers, + ) + return PermissionAssignment.from_dict(res) + + class AccountGroupsAPI: """Groups simplify identity management, making it easier to assign access to Databricks account, data, and other securable objects. @@ -2969,27 +5295,6 @@ def update( self._api.do("PUT", f"/api/2.0/accounts/{self._api.account_id}/scim/v2/Users/{id}", body=body, headers=headers) -class CurrentUserAPI: - """This API allows retrieving information about currently authenticated user or service principal.""" - - def __init__(self, api_client): - self._api = api_client - - def me(self) -> User: - """Get details about the current method caller's identity. - - - :returns: :class:`User` - """ - - headers = { - "Accept": "application/json", - } - - res = self._api.do("GET", "/api/2.0/preview/scim/v2/Me", headers=headers) - return User.from_dict(res) - - class GroupsAPI: """Groups simplify identity management, making it easier to assign access to Databricks workspace, data, and other securable objects. @@ -3121,317 +5426,131 @@ def list( [SCIM RFC]: https://tools.ietf.org/html/rfc7644#section-3.4.2.2 :param sort_by: str (optional) Attribute to sort the results. - :param sort_order: :class:`ListSortOrder` (optional) - The order to sort the results. - :param start_index: int (optional) - Specifies the index of the first result. First item is number 1. - - :returns: Iterator over :class:`Group` - """ - - query = {} - if attributes is not None: - query["attributes"] = attributes - if count is not None: - query["count"] = count - if excluded_attributes is not None: - query["excludedAttributes"] = excluded_attributes - if filter is not None: - query["filter"] = filter - if sort_by is not None: - query["sortBy"] = sort_by - if sort_order is not None: - query["sortOrder"] = sort_order.value - if start_index is not None: - query["startIndex"] = start_index - headers = { - "Accept": "application/json", - } - - # deduplicate items that may have been added during iteration - seen = set() - query["startIndex"] = 1 - if "count" not in query: - query["count"] = 10000 - while True: - json = self._api.do("GET", "/api/2.0/preview/scim/v2/Groups", query=query, headers=headers) - if "Resources" in json: - for v in json["Resources"]: - i = v["id"] - if i in seen: - continue - seen.add(i) - yield Group.from_dict(v) - if "Resources" not in json or not json["Resources"]: - return - query["startIndex"] += len(json["Resources"]) - - def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): - """Partially updates the details of a group. - - :param id: str - Unique ID in the Databricks workspace. - :param operations: List[:class:`Patch`] (optional) - :param schemas: List[:class:`PatchSchema`] (optional) - The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. - - - """ - body = {} - if operations is not None: - body["Operations"] = [v.as_dict() for v in operations] - if schemas is not None: - body["schemas"] = [v.value for v in schemas] - headers = { - "Content-Type": "application/json", - } - - self._api.do("PATCH", f"/api/2.0/preview/scim/v2/Groups/{id}", body=body, headers=headers) - - def update( - self, - id: str, - *, - display_name: Optional[str] = None, - entitlements: Optional[List[ComplexValue]] = None, - external_id: Optional[str] = None, - groups: Optional[List[ComplexValue]] = None, - members: Optional[List[ComplexValue]] = None, - meta: Optional[ResourceMeta] = None, - roles: Optional[List[ComplexValue]] = None, - schemas: Optional[List[GroupSchema]] = None, - ): - """Updates the details of a group by replacing the entire group entity. - - :param id: str - Databricks group ID - :param display_name: str (optional) - String that represents a human-readable group name - :param entitlements: List[:class:`ComplexValue`] (optional) - Entitlements assigned to the group. See [assigning entitlements] for a full list of supported - values. - - [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements - :param external_id: str (optional) - :param groups: List[:class:`ComplexValue`] (optional) - :param members: List[:class:`ComplexValue`] (optional) - :param meta: :class:`ResourceMeta` (optional) - Container for the group identifier. Workspace local versus account. - :param roles: List[:class:`ComplexValue`] (optional) - Corresponds to AWS instance profile/arn role. - :param schemas: List[:class:`GroupSchema`] (optional) - The schema of the group. - - - """ - body = {} - if display_name is not None: - body["displayName"] = display_name - if entitlements is not None: - body["entitlements"] = [v.as_dict() for v in entitlements] - if external_id is not None: - body["externalId"] = external_id - if groups is not None: - body["groups"] = [v.as_dict() for v in groups] - if members is not None: - body["members"] = [v.as_dict() for v in members] - if meta is not None: - body["meta"] = meta.as_dict() - if roles is not None: - body["roles"] = [v.as_dict() for v in roles] - if schemas is not None: - body["schemas"] = [v.value for v in schemas] - headers = { - "Content-Type": "application/json", - } - - self._api.do("PUT", f"/api/2.0/preview/scim/v2/Groups/{id}", body=body, headers=headers) - - -class PermissionMigrationAPI: - """APIs for migrating acl permissions, used only by the ucx tool: https://github.com/databrickslabs/ucx""" - - def __init__(self, api_client): - self._api = api_client - - def migrate_permissions( - self, - workspace_id: int, - from_workspace_group_name: str, - to_account_group_name: str, - *, - size: Optional[int] = None, - ) -> MigratePermissionsResponse: - """Migrate Permissions. - - :param workspace_id: int - WorkspaceId of the associated workspace where the permission migration will occur. - :param from_workspace_group_name: str - The name of the workspace group that permissions will be migrated from. - :param to_account_group_name: str - The name of the account group that permissions will be migrated to. - :param size: int (optional) - The maximum number of permissions that will be migrated. - - :returns: :class:`MigratePermissionsResponse` - """ - body = {} - if from_workspace_group_name is not None: - body["from_workspace_group_name"] = from_workspace_group_name - if size is not None: - body["size"] = size - if to_account_group_name is not None: - body["to_account_group_name"] = to_account_group_name - if workspace_id is not None: - body["workspace_id"] = workspace_id - headers = { - "Accept": "application/json", - "Content-Type": "application/json", - } - - res = self._api.do("POST", "/api/2.0/permissionmigration", body=body, headers=headers) - return MigratePermissionsResponse.from_dict(res) - - -class PermissionsAPI: - """Permissions API are used to create read, write, edit, update and manage access for various users on - different objects and endpoints. * **[Apps permissions](:service:apps)** — Manage which users can manage - or use apps. * **[Cluster permissions](:service:clusters)** — Manage which users can manage, restart, or - attach to clusters. * **[Cluster policy permissions](:service:clusterpolicies)** — Manage which users - can use cluster policies. * **[Delta Live Tables pipeline permissions](:service:pipelines)** — Manage - which users can view, manage, run, cancel, or own a Delta Live Tables pipeline. * **[Job - permissions](:service:jobs)** — Manage which users can view, manage, trigger, cancel, or own a job. * - **[MLflow experiment permissions](:service:experiments)** — Manage which users can read, edit, or manage - MLflow experiments. * **[MLflow registered model permissions](:service:modelregistry)** — Manage which - users can read, edit, or manage MLflow registered models. * **[Instance Pool - permissions](:service:instancepools)** — Manage which users can manage or attach to pools. * **[Repo - permissions](repos)** — Manage which users can read, run, edit, or manage a repo. * **[Serving endpoint - permissions](:service:servingendpoints)** — Manage which users can view, query, or manage a serving - endpoint. * **[SQL warehouse permissions](:service:warehouses)** — Manage which users can use or manage - SQL warehouses. * **[Token permissions](:service:tokenmanagement)** — Manage which users can create or - use tokens. * **[Workspace object permissions](:service:workspace)** — Manage which users can read, run, - edit, or manage alerts, dbsql-dashboards, directories, files, notebooks and queries. For the mapping of - the required permissions for specific actions or abilities and other important information, see [Access - Control]. Note that to manage access control on service principals, use **[Account Access Control - Proxy](:service:accountaccesscontrolproxy)**. - - [Access Control]: https://docs.databricks.com/security/auth-authz/access-control/index.html""" - - def __init__(self, api_client): - self._api = api_client - - def get(self, request_object_type: str, request_object_id: str) -> ObjectPermissions: - """Gets the permissions of an object. Objects can inherit permissions from their parent objects or root - object. - - :param request_object_type: str - The type of the request object. Can be one of the following: alerts, alertsv2, authorization, - clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, - instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or - warehouses. - :param request_object_id: str - The id of the request object. - - :returns: :class:`ObjectPermissions` - """ - - headers = { - "Accept": "application/json", - } - - res = self._api.do("GET", f"/api/2.0/permissions/{request_object_type}/{request_object_id}", headers=headers) - return ObjectPermissions.from_dict(res) - - def get_permission_levels(self, request_object_type: str, request_object_id: str) -> GetPermissionLevelsResponse: - """Gets the permission levels that a user can have on an object. - - :param request_object_type: str - The type of the request object. Can be one of the following: alerts, alertsv2, authorization, - clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, - instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or - warehouses. - :param request_object_id: str + :param sort_order: :class:`ListSortOrder` (optional) + The order to sort the results. + :param start_index: int (optional) + Specifies the index of the first result. First item is number 1. - :returns: :class:`GetPermissionLevelsResponse` + :returns: Iterator over :class:`Group` """ + query = {} + if attributes is not None: + query["attributes"] = attributes + if count is not None: + query["count"] = count + if excluded_attributes is not None: + query["excludedAttributes"] = excluded_attributes + if filter is not None: + query["filter"] = filter + if sort_by is not None: + query["sortBy"] = sort_by + if sort_order is not None: + query["sortOrder"] = sort_order.value + if start_index is not None: + query["startIndex"] = start_index headers = { "Accept": "application/json", } - res = self._api.do( - "GET", f"/api/2.0/permissions/{request_object_type}/{request_object_id}/permissionLevels", headers=headers - ) - return GetPermissionLevelsResponse.from_dict(res) + # deduplicate items that may have been added during iteration + seen = set() + query["startIndex"] = 1 + if "count" not in query: + query["count"] = 10000 + while True: + json = self._api.do("GET", "/api/2.0/preview/scim/v2/Groups", query=query, headers=headers) + if "Resources" in json: + for v in json["Resources"]: + i = v["id"] + if i in seen: + continue + seen.add(i) + yield Group.from_dict(v) + if "Resources" not in json or not json["Resources"]: + return + query["startIndex"] += len(json["Resources"]) - def set( - self, - request_object_type: str, - request_object_id: str, - *, - access_control_list: Optional[List[AccessControlRequest]] = None, - ) -> ObjectPermissions: - """Sets permissions on an object, replacing existing permissions if they exist. Deletes all direct - permissions if none are specified. Objects can inherit permissions from their parent objects or root - object. + def patch(self, id: str, *, operations: Optional[List[Patch]] = None, schemas: Optional[List[PatchSchema]] = None): + """Partially updates the details of a group. + + :param id: str + Unique ID in the Databricks workspace. + :param operations: List[:class:`Patch`] (optional) + :param schemas: List[:class:`PatchSchema`] (optional) + The schema of the patch request. Must be ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]. - :param request_object_type: str - The type of the request object. Can be one of the following: alerts, alertsv2, authorization, - clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, - instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or - warehouses. - :param request_object_id: str - The id of the request object. - :param access_control_list: List[:class:`AccessControlRequest`] (optional) - :returns: :class:`ObjectPermissions` """ body = {} - if access_control_list is not None: - body["access_control_list"] = [v.as_dict() for v in access_control_list] + if operations is not None: + body["Operations"] = [v.as_dict() for v in operations] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] headers = { - "Accept": "application/json", "Content-Type": "application/json", } - res = self._api.do( - "PUT", f"/api/2.0/permissions/{request_object_type}/{request_object_id}", body=body, headers=headers - ) - return ObjectPermissions.from_dict(res) + self._api.do("PATCH", f"/api/2.0/preview/scim/v2/Groups/{id}", body=body, headers=headers) def update( self, - request_object_type: str, - request_object_id: str, + id: str, *, - access_control_list: Optional[List[AccessControlRequest]] = None, - ) -> ObjectPermissions: - """Updates the permissions on an object. Objects can inherit permissions from their parent objects or - root object. + display_name: Optional[str] = None, + entitlements: Optional[List[ComplexValue]] = None, + external_id: Optional[str] = None, + groups: Optional[List[ComplexValue]] = None, + members: Optional[List[ComplexValue]] = None, + meta: Optional[ResourceMeta] = None, + roles: Optional[List[ComplexValue]] = None, + schemas: Optional[List[GroupSchema]] = None, + ): + """Updates the details of a group by replacing the entire group entity. + + :param id: str + Databricks group ID + :param display_name: str (optional) + String that represents a human-readable group name + :param entitlements: List[:class:`ComplexValue`] (optional) + Entitlements assigned to the group. See [assigning entitlements] for a full list of supported + values. + + [assigning entitlements]: https://docs.databricks.com/administration-guide/users-groups/index.html#assigning-entitlements + :param external_id: str (optional) + :param groups: List[:class:`ComplexValue`] (optional) + :param members: List[:class:`ComplexValue`] (optional) + :param meta: :class:`ResourceMeta` (optional) + Container for the group identifier. Workspace local versus account. + :param roles: List[:class:`ComplexValue`] (optional) + Corresponds to AWS instance profile/arn role. + :param schemas: List[:class:`GroupSchema`] (optional) + The schema of the group. - :param request_object_type: str - The type of the request object. Can be one of the following: alerts, alertsv2, authorization, - clusters, cluster-policies, dashboards, dbsql-dashboards, directories, experiments, files, - instance-pools, jobs, notebooks, pipelines, queries, registered-models, repos, serving-endpoints, or - warehouses. - :param request_object_id: str - The id of the request object. - :param access_control_list: List[:class:`AccessControlRequest`] (optional) - :returns: :class:`ObjectPermissions` """ body = {} - if access_control_list is not None: - body["access_control_list"] = [v.as_dict() for v in access_control_list] + if display_name is not None: + body["displayName"] = display_name + if entitlements is not None: + body["entitlements"] = [v.as_dict() for v in entitlements] + if external_id is not None: + body["externalId"] = external_id + if groups is not None: + body["groups"] = [v.as_dict() for v in groups] + if members is not None: + body["members"] = [v.as_dict() for v in members] + if meta is not None: + body["meta"] = meta.as_dict() + if roles is not None: + body["roles"] = [v.as_dict() for v in roles] + if schemas is not None: + body["schemas"] = [v.value for v in schemas] headers = { - "Accept": "application/json", "Content-Type": "application/json", } - res = self._api.do( - "PATCH", f"/api/2.0/permissions/{request_object_type}/{request_object_id}", body=body, headers=headers - ) - return ObjectPermissions.from_dict(res) + self._api.do("PUT", f"/api/2.0/preview/scim/v2/Groups/{id}", body=body, headers=headers) class ServicePrincipalsAPI: @@ -4100,109 +6219,3 @@ def update_permissions( res = self._api.do("PATCH", "/api/2.0/permissions/authorization/passwords", body=body, headers=headers) return PasswordPermissions.from_dict(res) - - -class WorkspaceAssignmentAPI: - """The Workspace Permission Assignment API allows you to manage workspace permissions for principals in your - account.""" - - def __init__(self, api_client): - self._api = api_client - - def delete(self, workspace_id: int, principal_id: int): - """Deletes the workspace permissions assignment in a given account and workspace for the specified - principal. - - :param workspace_id: int - The workspace ID for the account. - :param principal_id: int - The ID of the user, service principal, or group. - - - """ - - headers = { - "Accept": "application/json", - } - - self._api.do( - "DELETE", - f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments/principals/{principal_id}", - headers=headers, - ) - - def get(self, workspace_id: int) -> WorkspacePermissions: - """Get an array of workspace permissions for the specified account and workspace. - - :param workspace_id: int - The workspace ID. - - :returns: :class:`WorkspacePermissions` - """ - - headers = { - "Accept": "application/json", - } - - res = self._api.do( - "GET", - f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments/permissions", - headers=headers, - ) - return WorkspacePermissions.from_dict(res) - - def list(self, workspace_id: int) -> Iterator[PermissionAssignment]: - """Get the permission assignments for the specified Databricks account and Databricks workspace. - - :param workspace_id: int - The workspace ID for the account. - - :returns: Iterator over :class:`PermissionAssignment` - """ - - headers = { - "Accept": "application/json", - } - - json = self._api.do( - "GET", - f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments", - headers=headers, - ) - parsed = PermissionAssignments.from_dict(json).permission_assignments - return parsed if parsed is not None else [] - - def update( - self, workspace_id: int, principal_id: int, *, permissions: Optional[List[WorkspacePermission]] = None - ) -> PermissionAssignment: - """Creates or updates the workspace permissions assignment in a given account and workspace for the - specified principal. - - :param workspace_id: int - The workspace ID. - :param principal_id: int - The ID of the user, service principal, or group. - :param permissions: List[:class:`WorkspacePermission`] (optional) - Array of permissions assignments to update on the workspace. Valid values are "USER" and "ADMIN" - (case-sensitive). If both "USER" and "ADMIN" are provided, "ADMIN" takes precedence. Other values - will be ignored. Note that excluding this field, or providing unsupported values, will have the same - effect as providing an empty list, which will result in the deletion of all permissions for the - principal. - - :returns: :class:`PermissionAssignment` - """ - body = {} - if permissions is not None: - body["permissions"] = [v.value for v in permissions] - headers = { - "Accept": "application/json", - "Content-Type": "application/json", - } - - res = self._api.do( - "PUT", - f"/api/2.0/accounts/{self._api.account_id}/workspaces/{workspace_id}/permissionassignments/principals/{principal_id}", - body=body, - headers=headers, - ) - return PermissionAssignment.from_dict(res)