diff --git a/.codegen/_openapi_sha b/.codegen/_openapi_sha index 153783450..62d390339 100755 --- a/.codegen/_openapi_sha +++ b/.codegen/_openapi_sha @@ -1 +1 @@ -11ae6f9d98f0d0838a5e53c27032f178fecc4ee0 \ No newline at end of file +9e9cd2a1a802f6df10f3a5ffe6aa97b588d5884a \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index cd98507b7..66e361e5b 100755 --- a/.gitattributes +++ b/.gitattributes @@ -34,6 +34,7 @@ databricks/sdk/service/settings.py linguist-generated=true databricks/sdk/service/settingsv2.py linguist-generated=true databricks/sdk/service/sharing.py linguist-generated=true databricks/sdk/service/sql.py linguist-generated=true +databricks/sdk/service/supervisoragents.py linguist-generated=true databricks/sdk/service/tags.py linguist-generated=true databricks/sdk/service/vectorsearch.py linguist-generated=true databricks/sdk/service/workspace.py linguist-generated=true diff --git a/.github/workflows/next-changelog.yml b/.github/workflows/next-changelog.yml index 847aadb8e..475157d1f 100755 --- a/.github/workflows/next-changelog.yml +++ b/.github/workflows/next-changelog.yml @@ -11,8 +11,8 @@ jobs: # Allow Dependabot PRs to pass without a changelog entry if: github.actor != 'dependabot[bot]' runs-on: - group: databricks-deco-testing-runner-group - labels: ubuntu-latest-deco + group: databricks-protected-runner-group + labels: linux-ubuntu-latest steps: - name: Checkout code diff --git a/.github/workflows/tagging.yml b/.github/workflows/tagging.yml index c3c6d2d00..e22f794e0 100755 --- a/.github/workflows/tagging.yml +++ b/.github/workflows/tagging.yml @@ -32,8 +32,8 @@ jobs: github.repository == 'databricks/databricks-sdk-java' environment: "release-is" runs-on: - group: databricks-deco-testing-runner-group - labels: ubuntu-latest-deco + group: databricks-protected-runner-group + labels: linux-ubuntu-latest steps: - name: Generate GitHub App Token id: generate-token @@ -62,3 +62,11 @@ jobs: GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} GITHUB_REPOSITORY: ${{ github.repository }} run: uv run --locked tagging.py + + - name: Upload created tags artifact + if: always() + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 + with: + name: created-tags + path: created_tags.json + if-no-files-found: ignore diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index b558c543f..b70823330 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -15,3 +15,27 @@ ### Internal Changes ### API Changes +* Add `databricks.sdk.service.supervisoragents` package. +* Add [w.secrets_uc](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/catalog/secrets_uc.html) workspace-level service. +* Add [w.supervisor_agents](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/supervisoragents/supervisor_agents.html) workspace-level service. +* Add `update()` method for [w.tokens](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/settings/tokens.html) workspace-level service. +* Add `etag` field for `databricks.sdk.service.dashboards.GenieSpace`. +* Add `etag` field for `databricks.sdk.service.dashboards.GenieUpdateSpaceRequest`. +* Add `branch_id` field for `databricks.sdk.service.postgres.BranchStatus`. +* Add `catalog_id` field for `databricks.sdk.service.postgres.CatalogCatalogStatus`. +* Add `database_id` field for `databricks.sdk.service.postgres.DatabaseDatabaseStatus`. +* Add `endpoint_id` field for `databricks.sdk.service.postgres.EndpointStatus`. +* Add `project_id` field for `databricks.sdk.service.postgres.ProjectStatus`. +* Add `role_id` field for `databricks.sdk.service.postgres.RoleRoleStatus`. +* Add `project` field for `databricks.sdk.service.postgres.SyncedTableSyncedTableStatus`. +* Add `manual` field for `databricks.sdk.service.provisioning.CreateGcpKeyInfo`. +* Add `manual` field for `databricks.sdk.service.provisioning.GcpKeyInfo`. +* Add `apps_runtime` and `lakebase_runtime` fields for `databricks.sdk.service.settings.CustomerFacingIngressNetworkPolicyRequestDestination`. +* Add `blocked_internet_destinations` field for `databricks.sdk.service.settings.EgressNetworkPolicyNetworkAccessPolicy`. +* Add `columns_to_sync` field for `databricks.sdk.service.vectorsearch.DeltaSyncVectorIndexSpecResponse`. +* Add `breaking_change` enum value for `databricks.sdk.service.jobs.TerminationCodeCode`. +* [Breaking] Change `update_catalog_config()` method for [w.data_classification](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/dataclassification/data_classification.html) workspace-level service. Method path has changed. +* [Breaking] Change `update_default_workspace_base_environment()` method for [w.environments](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/environments/environments.html) workspace-level service. Method path has changed. +* [Breaking] Change `update_knowledge_assistant()` method for [w.knowledge_assistants](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/knowledgeassistants/knowledge_assistants.html) workspace-level service. Method path has changed. +* [Breaking] Change `update_branch()`, `update_database()`, `update_endpoint()`, `update_project()` and `update_role()` methods for [w.postgres](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/postgres/postgres.html) workspace-level service. Method path has changed. +* [Breaking] Change `update_default_warehouse_override()` method for [w.warehouses](https://databricks-sdk-py.readthedocs.io/en/latest/workspace/sql/warehouses.html) workspace-level service. Method path has changed. diff --git a/databricks/sdk/__init__.py b/databricks/sdk/__init__.py index dcfa60243..b002b44f6 100755 --- a/databricks/sdk/__init__.py +++ b/databricks/sdk/__init__.py @@ -45,6 +45,7 @@ from databricks.sdk.service import settingsv2 as pkg_settingsv2 from databricks.sdk.service import sharing as pkg_sharing from databricks.sdk.service import sql as pkg_sql +from databricks.sdk.service import supervisoragents as pkg_supervisoragents from databricks.sdk.service import tags as pkg_tags from databricks.sdk.service import vectorsearch as pkg_vectorsearch from databricks.sdk.service import workspace as pkg_workspace @@ -67,7 +68,8 @@ PoliciesAPI, QualityMonitorsAPI, RegisteredModelsAPI, ResourceQuotasAPI, RfaAPI, - SchemasAPI, StorageCredentialsAPI, + SchemasAPI, SecretsUcAPI, + StorageCredentialsAPI, SystemSchemasAPI, TableConstraintsAPI, TablesAPI, TemporaryPathCredentialsAPI, @@ -167,6 +169,7 @@ QueryVisualizationsLegacyAPI, RedashConfigAPI, StatementExecutionAPI, WarehousesAPI) +from databricks.sdk.service.supervisoragents import SupervisorAgentsAPI from databricks.sdk.service.tags import (TagPoliciesAPI, WorkspaceEntityTagAssignmentsAPI) from databricks.sdk.service.vectorsearch import (VectorSearchEndpointsAPI, @@ -377,6 +380,7 @@ def __init__( self._rfa = pkg_catalog.RfaAPI(self._api_client) self._schemas = pkg_catalog.SchemasAPI(self._api_client) self._secrets = pkg_workspace.SecretsAPI(self._api_client) + self._secrets_uc = pkg_catalog.SecretsUcAPI(self._api_client) self._service_principal_secrets_proxy = pkg_oauth2.ServicePrincipalSecretsProxyAPI(self._api_client) self._service_principals_v2 = pkg_iam.ServicePrincipalsV2API(self._api_client) self._serving_endpoints = serving_endpoints @@ -390,6 +394,7 @@ def __init__( self._shares = pkg_sharing.SharesAPI(self._api_client) self._statement_execution = pkg_sql.StatementExecutionAPI(self._api_client) self._storage_credentials = pkg_catalog.StorageCredentialsAPI(self._api_client) + self._supervisor_agents = pkg_supervisoragents.SupervisorAgentsAPI(self._api_client) self._system_schemas = pkg_catalog.SystemSchemasAPI(self._api_client) self._table_constraints = pkg_catalog.TableConstraintsAPI(self._api_client) self._tables = pkg_catalog.TablesAPI(self._api_client) @@ -910,6 +915,11 @@ def secrets(self) -> pkg_workspace.SecretsAPI: """The Secrets API allows you to manage secrets, secret scopes, and access permissions.""" return self._secrets + @property + def secrets_uc(self) -> pkg_catalog.SecretsUcAPI: + """A secret is a Unity Catalog securable object that stores sensitive credential data (such as passwords, tokens, and keys) within a three-level namespace (**catalog_name.schema_name.secret_name**).""" + return self._secrets_uc + @property def service_principal_secrets_proxy(self) -> pkg_oauth2.ServicePrincipalSecretsProxyAPI: """These APIs enable administrators to manage service principal secrets at the workspace level.""" @@ -950,6 +960,11 @@ def storage_credentials(self) -> pkg_catalog.StorageCredentialsAPI: """A storage credential represents an authentication and authorization mechanism for accessing data stored on your cloud tenant.""" return self._storage_credentials + @property + def supervisor_agents(self) -> pkg_supervisoragents.SupervisorAgentsAPI: + """Manage Supervisor Agents and related resources.""" + return self._supervisor_agents + @property def system_schemas(self) -> pkg_catalog.SystemSchemasAPI: """A system schema is a schema that lives within the system catalog.""" diff --git a/databricks/sdk/service/catalog.py b/databricks/sdk/service/catalog.py index 0826b6286..209c85878 100755 --- a/databricks/sdk/service/catalog.py +++ b/databricks/sdk/service/catalog.py @@ -12,6 +12,7 @@ from google.protobuf.timestamp_pb2 import Timestamp +from databricks.sdk.common.types.fieldmask import FieldMask from databricks.sdk.service._internal import (Wait, _enum, _from_dict, _repeated_dict, _repeated_enum, _timestamp) @@ -1670,8 +1671,6 @@ def from_dict(cls, d: Dict[str, Any]) -> ConnectionDependency: @dataclass class ConnectionInfo: - """Next ID: 25""" - comment: Optional[str] = None """User-provided free-form text description.""" @@ -1832,7 +1831,7 @@ def from_dict(cls, d: Dict[str, Any]) -> ConnectionInfo: class ConnectionType(Enum): - """Next Id: 75""" + """Next Id: 77""" BIGQUERY = "BIGQUERY" DATABRICKS = "DATABRICKS" @@ -3001,7 +3000,7 @@ class DeltaSharingScopeEnum(Enum): @dataclass class Dependency: """A dependency of a SQL object. One of the following fields must be defined: __table__, - __function__, __connection__, or __credential__.""" + __function__, __connection__, __credential__, __volume__, or __secret__.""" connection: Optional[ConnectionDependency] = None @@ -6057,6 +6056,41 @@ def from_dict(cls, d: Dict[str, Any]) -> ListSchemasResponse: return cls(next_page_token=d.get("next_page_token", None), schemas=_repeated_dict(d, "schemas", SchemaInfo)) +@dataclass +class ListSecretsResponse: + """Response message for ListSecrets.""" + + next_page_token: Optional[str] = None + """Opaque token to retrieve the next page of results. Absent if there are no more pages. + **page_token** should be set to this value for the next request.""" + + secrets: Optional[List[Secret]] = None + """An array of secret objects.""" + + def as_dict(self) -> dict: + """Serializes the ListSecretsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.secrets: + body["secrets"] = [v.as_dict() for v in self.secrets] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListSecretsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.secrets: + body["secrets"] = self.secrets + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListSecretsResponse: + """Deserializes the ListSecretsResponse from a dictionary.""" + return cls(next_page_token=d.get("next_page_token", None), secrets=_repeated_dict(d, "secrets", Secret)) + + @dataclass class ListStorageCredentialsResponse: next_page_token: Optional[str] = None @@ -8926,6 +8960,174 @@ def from_dict(cls, d: Dict[str, Any]) -> SchemaInfo: ) +@dataclass +class Secret: + """A secret stored in Unity Catalog. Secrets are three-level namespace objects + (catalog.schema.secret) that securely store sensitive credential data such as passwords, tokens, + and keys.""" + + name: str + """The name of the secret, relative to its parent schema.""" + + catalog_name: str + """The name of the catalog where the schema and the secret reside.""" + + schema_name: str + """The name of the schema where the secret resides.""" + + value: str + """The secret value to store. This field is input-only and is not returned in responses — use the + **effective_value** field (via GetSecret with **include_value** set to true) to read the secret + value. The maximum size is 60 KiB (pre-encryption). Accepted content includes passwords, tokens, + keys, and other sensitive credential data.""" + + browse_only: Optional[bool] = None + """Indicates whether the principal is limited to retrieving metadata for the associated object + through the **BROWSE** privilege when **include_browse** is enabled in the request.""" + + comment: Optional[str] = None + """User-provided free-form text description of the secret.""" + + create_time: Optional[Timestamp] = None + """The time at which this secret was created.""" + + created_by: Optional[str] = None + """The principal that created the secret.""" + + effective_owner: Optional[str] = None + """The effective owner of the secret, which may differ from the directly-set **owner** due to + inheritance.""" + + effective_value: Optional[str] = None + """The secret value. Only populated in responses when you have the **READ_SECRET** privilege and + **include_value** is set to true in the request. The maximum size is 60 KiB.""" + + expire_time: Optional[Timestamp] = None + """User-provided expiration time of the secret. This field indicates when the secret should no + longer be used and may be displayed as a warning in the UI. It is purely informational and does + not trigger any automatic actions or affect the secret's lifecycle.""" + + external_secret_id: Optional[str] = None + + full_name: Optional[str] = None + """The three-level (fully qualified) name of the secret, in the form of + **catalog_name.schema_name.secret_name**.""" + + metastore_id: Optional[str] = None + """Unique identifier of the metastore hosting the secret.""" + + owner: Optional[str] = None + """The owner of the secret. Defaults to the creating principal on creation. Can be updated to + transfer ownership of the secret to another principal.""" + + update_time: Optional[Timestamp] = None + """The time at which this secret was last updated.""" + + updated_by: Optional[str] = None + """The principal that last updated the secret.""" + + def as_dict(self) -> dict: + """Serializes the Secret into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.browse_only is not None: + body["browse_only"] = self.browse_only + if self.catalog_name is not None: + body["catalog_name"] = self.catalog_name + if self.comment is not None: + body["comment"] = self.comment + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.created_by is not None: + body["created_by"] = self.created_by + if self.effective_owner is not None: + body["effective_owner"] = self.effective_owner + if self.effective_value is not None: + body["effective_value"] = self.effective_value + if self.expire_time is not None: + body["expire_time"] = self.expire_time.ToJsonString() + if self.external_secret_id is not None: + body["external_secret_id"] = self.external_secret_id + if self.full_name is not None: + body["full_name"] = self.full_name + if self.metastore_id is not None: + body["metastore_id"] = self.metastore_id + if self.name is not None: + body["name"] = self.name + if self.owner is not None: + body["owner"] = self.owner + if self.schema_name is not None: + body["schema_name"] = self.schema_name + if self.update_time is not None: + body["update_time"] = self.update_time.ToJsonString() + if self.updated_by is not None: + body["updated_by"] = self.updated_by + if self.value is not None: + body["value"] = self.value + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Secret into a shallow dictionary of its immediate attributes.""" + body = {} + if self.browse_only is not None: + body["browse_only"] = self.browse_only + if self.catalog_name is not None: + body["catalog_name"] = self.catalog_name + if self.comment is not None: + body["comment"] = self.comment + if self.create_time is not None: + body["create_time"] = self.create_time + if self.created_by is not None: + body["created_by"] = self.created_by + if self.effective_owner is not None: + body["effective_owner"] = self.effective_owner + if self.effective_value is not None: + body["effective_value"] = self.effective_value + if self.expire_time is not None: + body["expire_time"] = self.expire_time + if self.external_secret_id is not None: + body["external_secret_id"] = self.external_secret_id + if self.full_name is not None: + body["full_name"] = self.full_name + if self.metastore_id is not None: + body["metastore_id"] = self.metastore_id + if self.name is not None: + body["name"] = self.name + if self.owner is not None: + body["owner"] = self.owner + if self.schema_name is not None: + body["schema_name"] = self.schema_name + if self.update_time is not None: + body["update_time"] = self.update_time + if self.updated_by is not None: + body["updated_by"] = self.updated_by + if self.value is not None: + body["value"] = self.value + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Secret: + """Deserializes the Secret from a dictionary.""" + return cls( + browse_only=d.get("browse_only", None), + catalog_name=d.get("catalog_name", None), + comment=d.get("comment", None), + create_time=_timestamp(d, "create_time"), + created_by=d.get("created_by", None), + effective_owner=d.get("effective_owner", None), + effective_value=d.get("effective_value", None), + expire_time=_timestamp(d, "expire_time"), + external_secret_id=d.get("external_secret_id", None), + full_name=d.get("full_name", None), + metastore_id=d.get("metastore_id", None), + name=d.get("name", None), + owner=d.get("owner", None), + schema_name=d.get("schema_name", None), + update_time=_timestamp(d, "update_time"), + updated_by=d.get("updated_by", None), + value=d.get("value", None), + ) + + @dataclass class Securable: """Generic definition of a securable, which is uniquely defined in a metastore by its type and full @@ -8974,7 +9176,7 @@ def from_dict(cls, d: Dict[str, Any]) -> Securable: class SecurableKind(Enum): - """Latest kind: CONNECTION_VEEVA_VAULT_OAUTH_M2M = 311; Next id: 312""" + """Latest kind: ENDPOINT_LLM_PROVIDER = 317; Next id: 318""" TABLE_DB_STORAGE = "TABLE_DB_STORAGE" TABLE_DELTA = "TABLE_DELTA" @@ -15663,6 +15865,209 @@ def update( return SchemaInfo.from_dict(res) +class SecretsUcAPI: + """A secret is a Unity Catalog securable object that stores sensitive credential data (such as passwords, + tokens, and keys) within a three-level namespace (**catalog_name.schema_name.secret_name**). + + Secrets can be managed using standard Unity Catalog permissions and are scoped to a schema within a + catalog.""" + + def __init__(self, api_client): + self._api = api_client + + def create_secret(self, secret: Secret) -> Secret: + """Creates a new secret in Unity Catalog. + + You must be the owner of the parent schema or have the **CREATE_SECRET** and **USE SCHEMA** privileges + on the parent schema and **USE CATALOG** on the parent catalog. + + The secret is stored in the specified catalog and schema, and the **value** field contains the + sensitive data to be securely stored. + + :param secret: :class:`Secret` + The secret object to create. The **name**, **catalog_name**, **schema_name**, and **value** fields + are required. + + :returns: :class:`Secret` + """ + + body = secret.as_dict() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("POST", "/api/2.1/unity-catalog/secrets", body=body, headers=headers) + return Secret.from_dict(res) + + def delete_secret(self, full_name: str): + """Deletes a secret by its three-level (fully qualified) name. + + You must be the owner of the secret or a metastore admin. + + :param full_name: str + The three-level (fully qualified) name of the secret (for example, + **catalog_name.schema_name.secret_name**). + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.1/unity-catalog/secrets/{full_name}", headers=headers) + + def get_secret(self, full_name: str, *, include_browse: Optional[bool] = None) -> Secret: + """Gets a secret by its three-level (fully qualified) name. + + You must be a metastore admin, the owner of the secret, or have the **MANAGE** privilege on the + secret. + + The secret value isn't returned by default. To retrieve it, you must also have the **READ_SECRET** + privilege and set **include_value** to true in the request. + + :param full_name: str + The three-level (fully qualified) name of the secret (for example, + **catalog_name.schema_name.secret_name**). + :param include_browse: bool (optional) + Whether to include secrets in the response for which you only have the **BROWSE** privilege, which + limits access to metadata. + + :returns: :class:`Secret` + """ + + query = {} + if include_browse is not None: + query["include_browse"] = include_browse + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.1/unity-catalog/secrets/{full_name}", query=query, headers=headers) + return Secret.from_dict(res) + + def list_secrets( + self, + *, + catalog_name: Optional[str] = None, + include_browse: Optional[bool] = None, + page_size: Optional[int] = None, + page_token: Optional[str] = None, + schema_name: Optional[str] = None, + ) -> Iterator[Secret]: + """Lists secrets in Unity Catalog. + + You must be a metastore admin, the owner of the secret, or have the **MANAGE** privilege on the + secret. + + Both **catalog_name** and **schema_name** must be specified together to filter secrets within a + specific schema. Results are paginated; use the **page_token** field from the response to retrieve + subsequent pages. + + :param catalog_name: str (optional) + The name of the catalog under which to list secrets. Both **catalog_name** and **schema_name** must + be specified together. + :param include_browse: bool (optional) + Whether to include secrets in the response for which you only have the **BROWSE** privilege, which + limits access to metadata. + :param page_size: int (optional) + Maximum number of secrets to return. + + - If not specified, at most 10000 secrets are returned. - If set to a value greater than 0, the page + length is the minimum of this value and 10000. - If set to 0, the page length is set to 10000. - If + set to a value less than 0, an invalid parameter error is returned. + :param page_token: str (optional) + Opaque pagination token to go to the next page based on previous query. The maximum page length is + determined by a server configured value. + :param schema_name: str (optional) + The name of the schema under which to list secrets. Both **catalog_name** and **schema_name** must + be specified together. + + :returns: Iterator over :class:`Secret` + """ + + query = {} + if catalog_name is not None: + query["catalog_name"] = catalog_name + if include_browse is not None: + query["include_browse"] = include_browse + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + if schema_name is not None: + query["schema_name"] = schema_name + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", "/api/2.1/unity-catalog/secrets", query=query, headers=headers) + if "secrets" in json: + for v in json["secrets"]: + yield Secret.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def update_secret(self, full_name: str, secret: Secret, update_mask: FieldMask) -> Secret: + """Updates an existing secret in Unity Catalog. + + You must be the owner of the secret or a metastore admin. If you are a metastore admin, only the + **owner** field can be changed. + + Use the **update_mask** field to specify which fields to update. Supported updatable fields include + **value**, **comment**, **owner**, and **expire_time**. + + :param full_name: str + The three-level (fully qualified) name of the secret (for example, + **catalog_name.schema_name.secret_name**). + :param secret: :class:`Secret` + The secret object containing the fields to update. Only fields specified in **update_mask** will be + updated. + :param update_mask: FieldMask + The field mask specifying which fields of the secret to update. Supported fields: **value**, + **comment**, **owner**, **expire_time**. + + :returns: :class:`Secret` + """ + + body = secret.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do( + "PATCH", f"/api/2.1/unity-catalog/secrets/{full_name}", query=query, body=body, headers=headers + ) + return Secret.from_dict(res) + + class StorageCredentialsAPI: """A storage credential represents an authentication and authorization mechanism for accessing data stored on your cloud tenant. Each storage credential is subject to Unity Catalog access-control policies that @@ -16621,9 +17026,9 @@ class TemporaryPathCredentialsAPI: enable the external_access_enabled flag (off by default) at the metastore level. A user needs to be granted the EXTERNAL USE LOCATION permission by external location owner. For requests on existing external tables, user also needs to be granted the EXTERNAL USE SCHEMA permission at the schema level by catalog - admin. + owner. - Note that EXTERNAL USE SCHEMA is a schema level permission that can only be granted by catalog admin + Note that EXTERNAL USE SCHEMA is a schema level permission that can only be granted by catalog owner explicitly and is not included in schema ownership or ALL PRIVILEGES on the schema for security reasons. Similarly, EXTERNAL USE LOCATION is an external location level permission that can only be granted by external location owner explicitly and is not included in external location ownership or ALL PRIVILEGES on @@ -16690,8 +17095,8 @@ class TemporaryTableCredentialsAPI: Temporary table credentials ensure that data access is limited in scope and duration, reducing the risk of unauthorized access or misuse. To use the temporary table credentials API, a metastore admin needs to enable the external_access_enabled flag (off by default) at the metastore level, and user needs to be - granted the EXTERNAL USE SCHEMA permission at the schema level by catalog admin. Note that EXTERNAL USE - SCHEMA is a schema level permission that can only be granted by catalog admin explicitly and is not + granted the EXTERNAL USE SCHEMA permission at the schema level by catalog owner. Note that EXTERNAL USE + SCHEMA is a schema level permission that can only be granted by catalog owner explicitly and is not included in schema ownership or ALL PRIVILEGES on the schema for security reasons.""" def __init__(self, api_client): diff --git a/databricks/sdk/service/compute.py b/databricks/sdk/service/compute.py index 98e1c7436..4c58de059 100755 --- a/databricks/sdk/service/compute.py +++ b/databricks/sdk/service/compute.py @@ -3165,7 +3165,7 @@ def from_dict(cls, d: Dict[str, Any]) -> EnforceClusterComplianceResponse: @dataclass class Environment: """The environment entity used to preserve serverless environment side panel, jobs' environment for - non-notebook task, and DLT's environment for classic and serverless pipelines. In this minimal + non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip and java dependencies are supported.""" base_environment: Optional[str] = None @@ -10899,7 +10899,7 @@ def enforce_compliance( If a cluster is updated while in a `TERMINATED` state, it will remain `TERMINATED`. The next time the cluster is started, the new attributes will take effect. - Clusters created by the Databricks Jobs, DLT, or Models services cannot be enforced by this API. + Clusters created by the Databricks Jobs, SDP, or Models services cannot be enforced by this API. Instead, use the "Enforce job policy compliance" API to enforce policy compliance on jobs. :param cluster_id: str diff --git a/databricks/sdk/service/dashboards.py b/databricks/sdk/service/dashboards.py index 9ce26afc2..c02084283 100755 --- a/databricks/sdk/service/dashboards.py +++ b/databricks/sdk/service/dashboards.py @@ -1486,6 +1486,10 @@ class GenieSpace: description: Optional[str] = None """Description of the Genie Space""" + etag: Optional[str] = None + """ETag for this space. Pass this value back in the update request to prevent overwriting + concurrent changes.""" + parent_path: Optional[str] = None """Parent folder path of the Genie Space""" @@ -1503,6 +1507,8 @@ def as_dict(self) -> dict: body = {} if self.description is not None: body["description"] = self.description + if self.etag is not None: + body["etag"] = self.etag if self.parent_path is not None: body["parent_path"] = self.parent_path if self.serialized_space is not None: @@ -1520,6 +1526,8 @@ def as_shallow_dict(self) -> dict: body = {} if self.description is not None: body["description"] = self.description + if self.etag is not None: + body["etag"] = self.etag if self.parent_path is not None: body["parent_path"] = self.parent_path if self.serialized_space is not None: @@ -1537,6 +1545,7 @@ def from_dict(cls, d: Dict[str, Any]) -> GenieSpace: """Deserializes the GenieSpace from a dictionary.""" return cls( description=d.get("description", None), + etag=d.get("etag", None), parent_path=d.get("parent_path", None), serialized_space=d.get("serialized_space", None), space_id=d.get("space_id", None), @@ -2401,7 +2410,12 @@ class Thought: """The md formatted content for this thought.""" thought_type: Optional[ThoughtType] = None - """The category of this thought.""" + """The category of this thought. The possible values are: * `THOUGHT_TYPE_DESCRIPTION`: A + high-level description of how the question was interpreted. * `THOUGHT_TYPE_UNDERSTANDING`: How + ambiguous parts of the question were resolved. * `THOUGHT_TYPE_DATA_SOURCING`: Which tables or + datasets were identified as relevant. * `THOUGHT_TYPE_INSTRUCTIONS`: Which author-defined + instructions were referenced. * `THOUGHT_TYPE_STEPS`: The logical steps taken to compute the + answer.""" def as_dict(self) -> dict: """Serializes the Thought into a dictionary suitable for use as a JSON request body.""" @@ -3483,6 +3497,7 @@ def update_space( space_id: str, *, description: Optional[str] = None, + etag: Optional[str] = None, serialized_space: Optional[str] = None, title: Optional[str] = None, warehouse_id: Optional[str] = None, @@ -3493,6 +3508,9 @@ def update_space( Genie space ID :param description: str (optional) Optional description + :param etag: str (optional) + ETag returned by a previous GET or UPDATE. When set, the update will fail if the space has been + modified since. Omit to apply the update unconditionally. :param serialized_space: str (optional) The contents of the Genie Space in serialized string form (full replacement). Use the [Get Genie Space](:method:genie/getspace) API to retrieve an example response, which includes the @@ -3509,6 +3527,8 @@ def update_space( body = {} if description is not None: body["description"] = description + if etag is not None: + body["etag"] = etag if serialized_space is not None: body["serialized_space"] = serialized_space if title is not None: diff --git a/databricks/sdk/service/files.py b/databricks/sdk/service/files.py index 6dc534680..ca65b52c8 100755 --- a/databricks/sdk/service/files.py +++ b/databricks/sdk/service/files.py @@ -783,10 +783,6 @@ class FilesAPI: working with directories (`/fs/directories`). Both endpoints use the standard HTTP methods GET, HEAD, PUT, and DELETE to manage files and directories specified using their URI path. The path is always absolute. - Some Files API client features are currently experimental. To enable them, set - `enable_experimental_files_api_client = True` in your configuration profile or use the environment - variable `DATABRICKS_ENABLE_EXPERIMENTAL_FILES_API_CLIENT=True`. - Use of Files API may incur Databricks data transfer charges. [Unity Catalog volumes]: https://docs.databricks.com/en/connect/unity-catalog/volumes.html""" diff --git a/databricks/sdk/service/iam.py b/databricks/sdk/service/iam.py index 780587698..1d20b457a 100755 --- a/databricks/sdk/service/iam.py +++ b/databricks/sdk/service/iam.py @@ -3585,8 +3585,8 @@ class PermissionsAPI: 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 + can use cluster policies. * **[Spark Declarative Pipelines permissions](:service:pipelines)** — Manage + which users can view, manage, run, cancel, or own a Spark Declarative 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 diff --git a/databricks/sdk/service/jobs.py b/databricks/sdk/service/jobs.py index eda68ce0d..f21c157ec 100755 --- a/databricks/sdk/service/jobs.py +++ b/databricks/sdk/service/jobs.py @@ -8263,6 +8263,7 @@ class TerminationCodeCode(Enum): [Link]: https://kb.databricks.com/en_US/notebooks/too-many-execution-contexts-are-open-right-now""" + BREAKING_CHANGE = "BREAKING_CHANGE" BUDGET_POLICY_LIMIT_EXCEEDED = "BUDGET_POLICY_LIMIT_EXCEEDED" CANCELED = "CANCELED" CLOUD_FAILURE = "CLOUD_FAILURE" diff --git a/databricks/sdk/service/pipelines.py b/databricks/sdk/service/pipelines.py index 4ed07db08..6ff3782e9 100755 --- a/databricks/sdk/service/pipelines.py +++ b/databricks/sdk/service/pipelines.py @@ -2597,7 +2597,7 @@ class PipelineSpec: Catalog.""" channel: Optional[str] = None - """DLT Release Channel that specifies which version to use.""" + """SDP Release Channel that specifies which version to use.""" clusters: Optional[List[PipelineCluster]] = None """Cluster settings for this pipeline deployment.""" @@ -2975,7 +2975,7 @@ def from_dict(cls, d: Dict[str, Any]) -> PipelineTrigger: @dataclass class PipelinesEnvironment: """The environment entity used to preserve serverless environment side panel, jobs' environment for - non-notebook task, and DLT's environment for classic and serverless pipelines. In this minimal + non-notebook task, and SDP's environment for classic and serverless pipelines. In this minimal environment spec, only pip dependencies are supported.""" dependencies: Optional[List[str]] = None @@ -4421,7 +4421,7 @@ def clone( in this pipeline are published to a `target` schema inside `catalog` (for example, `catalog`.`target`.`table`). If `target` is not specified, no data is published to Unity Catalog. :param channel: str (optional) - DLT Release Channel that specifies which version to use. + SDP Release Channel that specifies which version to use. :param clone_mode: :class:`CloneMode` (optional) The type of clone to perform. Currently, only deep copies are supported :param clusters: List[:class:`PipelineCluster`] (optional) @@ -4609,7 +4609,7 @@ def create( in this pipeline are published to a `target` schema inside `catalog` (for example, `catalog`.`target`.`table`). If `target` is not specified, no data is published to Unity Catalog. :param channel: str (optional) - DLT Release Channel that specifies which version to use. + SDP Release Channel that specifies which version to use. :param clusters: List[:class:`PipelineCluster`] (optional) Cluster settings for this pipeline deployment. :param configuration: Dict[str,str] (optional) @@ -5197,7 +5197,7 @@ def update( in this pipeline are published to a `target` schema inside `catalog` (for example, `catalog`.`target`.`table`). If `target` is not specified, no data is published to Unity Catalog. :param channel: str (optional) - DLT Release Channel that specifies which version to use. + SDP Release Channel that specifies which version to use. :param clusters: List[:class:`PipelineCluster`] (optional) Cluster settings for this pipeline deployment. :param configuration: Dict[str,str] (optional) diff --git a/databricks/sdk/service/postgres.py b/databricks/sdk/service/postgres.py index dd5117490..180ca6caf 100755 --- a/databricks/sdk/service/postgres.py +++ b/databricks/sdk/service/postgres.py @@ -201,6 +201,14 @@ def from_dict(cls, d: Dict[str, Any]) -> BranchSpec: @dataclass class BranchStatus: + branch_id: Optional[str] = None + """The short identifier of the branch, suitable for showing to the users. For a branch with name + `projects/my-project/branches/my-branch`, the branch_id is `my-branch`. + + Use this field when building UI components that display branches to users (e.g., a drop-down + selector). Prefer showing `branch_id` instead of the full resource name from `Branch.name`, + which follows the `projects/{project_id}/branches/{branch_id}` format and is not user-friendly.""" + current_state: Optional[BranchStatusState] = None """The branch's state, indicating if it is initializing, ready for use, or archived.""" @@ -235,6 +243,8 @@ class BranchStatus: def as_dict(self) -> dict: """Serializes the BranchStatus into a dictionary suitable for use as a JSON request body.""" body = {} + if self.branch_id is not None: + body["branch_id"] = self.branch_id if self.current_state is not None: body["current_state"] = self.current_state.value if self.default is not None: @@ -260,6 +270,8 @@ def as_dict(self) -> dict: def as_shallow_dict(self) -> dict: """Serializes the BranchStatus into a shallow dictionary of its immediate attributes.""" body = {} + if self.branch_id is not None: + body["branch_id"] = self.branch_id if self.current_state is not None: body["current_state"] = self.current_state if self.default is not None: @@ -286,6 +298,7 @@ def as_shallow_dict(self) -> dict: def from_dict(cls, d: Dict[str, Any]) -> BranchStatus: """Deserializes the BranchStatus from a dictionary.""" return cls( + branch_id=d.get("branch_id", None), current_state=_enum(d, "current_state", BranchStatusState), default=d.get("default", None), expire_time=_timestamp(d, "expire_time"), @@ -452,6 +465,14 @@ class CatalogCatalogStatus: Format: projects/{project_id}/branches/{branch_id}.""" + catalog_id: Optional[str] = None + """The short identifier of the catalog, suitable for showing to the users. For a catalog with name + `catalogs/my-catalog`, the catalog_id is `my-catalog`. + + Use this field when building UI components that display catalogs to users (e.g., a drop-down + selector). Prefer showing `catalog_id` instead of the full resource name from `Catalog.name`, + which follows the `catalogs/{catalog_id}` format and is not user-friendly.""" + postgres_database: Optional[str] = None """The name of the Postgres database associated with the catalog.""" @@ -465,6 +486,8 @@ def as_dict(self) -> dict: body = {} if self.branch is not None: body["branch"] = self.branch + if self.catalog_id is not None: + body["catalog_id"] = self.catalog_id if self.postgres_database is not None: body["postgres_database"] = self.postgres_database if self.project is not None: @@ -476,6 +499,8 @@ def as_shallow_dict(self) -> dict: body = {} if self.branch is not None: body["branch"] = self.branch + if self.catalog_id is not None: + body["catalog_id"] = self.catalog_id if self.postgres_database is not None: body["postgres_database"] = self.postgres_database if self.project is not None: @@ -487,6 +512,7 @@ def from_dict(cls, d: Dict[str, Any]) -> CatalogCatalogStatus: """Deserializes the CatalogCatalogStatus from a dictionary.""" return cls( branch=d.get("branch", None), + catalog_id=d.get("catalog_id", None), postgres_database=d.get("postgres_database", None), project=d.get("project", None), ) @@ -657,6 +683,15 @@ def from_dict(cls, d: Dict[str, Any]) -> DatabaseDatabaseSpec: @dataclass class DatabaseDatabaseStatus: + database_id: Optional[str] = None + """The short identifier of the database, suitable for showing to the users. For a database with + name `projects/my-project/branches/my-branch/databases/my-db`, the database_id is `my-db`. + + Use this field when building UI components that display databases to users (e.g., a drop-down + selector). Prefer showing `database_id` instead of the full resource name from `Database.name`, + which follows the `projects/{project_id}/branches/{branch_id}/databases/{database_id}` format + and is not user-friendly.""" + postgres_database: Optional[str] = None """The name of the Postgres database.""" @@ -667,6 +702,8 @@ class DatabaseDatabaseStatus: def as_dict(self) -> dict: """Serializes the DatabaseDatabaseStatus into a dictionary suitable for use as a JSON request body.""" body = {} + if self.database_id is not None: + body["database_id"] = self.database_id if self.postgres_database is not None: body["postgres_database"] = self.postgres_database if self.role is not None: @@ -676,6 +713,8 @@ def as_dict(self) -> dict: def as_shallow_dict(self) -> dict: """Serializes the DatabaseDatabaseStatus into a shallow dictionary of its immediate attributes.""" body = {} + if self.database_id is not None: + body["database_id"] = self.database_id if self.postgres_database is not None: body["postgres_database"] = self.postgres_database if self.role is not None: @@ -685,7 +724,11 @@ def as_shallow_dict(self) -> dict: @classmethod def from_dict(cls, d: Dict[str, Any]) -> DatabaseDatabaseStatus: """Deserializes the DatabaseDatabaseStatus from a dictionary.""" - return cls(postgres_database=d.get("postgres_database", None), role=d.get("role", None)) + return cls( + database_id=d.get("database_id", None), + postgres_database=d.get("postgres_database", None), + role=d.get("role", None), + ) @dataclass @@ -1150,6 +1193,16 @@ class EndpointStatus: suspend compute operation. A disabled compute endpoint cannot be enabled by a connection or console action.""" + endpoint_id: Optional[str] = None + """The short identifier of the endpoint, suitable for showing to the users. For an endpoint with + name `projects/my-project/branches/my-branch/endpoints/my-endpoint`, the endpoint_id is + `my-endpoint`. + + Use this field when building UI components that display endpoints to users (e.g., a drop-down + selector). Prefer showing `endpoint_id` instead of the full resource name from `Endpoint.name`, + which follows the `projects/{project_id}/branches/{branch_id}/endpoints/{endpoint_id}` format + and is not user-friendly.""" + endpoint_type: Optional[EndpointType] = None """The endpoint type. A branch can only have one READ_WRITE endpoint.""" @@ -1177,6 +1230,8 @@ def as_dict(self) -> dict: body["current_state"] = self.current_state.value if self.disabled is not None: body["disabled"] = self.disabled + if self.endpoint_id is not None: + body["endpoint_id"] = self.endpoint_id if self.endpoint_type is not None: body["endpoint_type"] = self.endpoint_type.value if self.group: @@ -1202,6 +1257,8 @@ def as_shallow_dict(self) -> dict: body["current_state"] = self.current_state if self.disabled is not None: body["disabled"] = self.disabled + if self.endpoint_id is not None: + body["endpoint_id"] = self.endpoint_id if self.endpoint_type is not None: body["endpoint_type"] = self.endpoint_type if self.group: @@ -1224,6 +1281,7 @@ def from_dict(cls, d: Dict[str, Any]) -> EndpointStatus: autoscaling_limit_min_cu=d.get("autoscaling_limit_min_cu", None), current_state=_enum(d, "current_state", EndpointStatusState), disabled=d.get("disabled", None), + endpoint_id=d.get("endpoint_id", None), endpoint_type=_enum(d, "endpoint_type", EndpointType), group=_from_dict(d, "group", EndpointGroupStatus), hosts=_from_dict(d, "hosts", EndpointHosts), @@ -1945,6 +2003,14 @@ class ProjectStatus: pg_version: Optional[int] = None """The effective major Postgres version number.""" + project_id: Optional[str] = None + """The short identifier of the project, suitable for showing to the users. For a project with name + `projects/my-project`, the project_id is `my-project`. + + Use this field when building UI components that display projects to users (e.g., a drop-down + selector). Prefer showing `project_id` instead of the full resource name from `Project.name`, + which follows the `projects/{project_id}` format and is not user-friendly.""" + synthetic_storage_size_bytes: Optional[int] = None """The current space occupied by the project in storage.""" @@ -1971,6 +2037,8 @@ def as_dict(self) -> dict: body["owner"] = self.owner if self.pg_version is not None: body["pg_version"] = self.pg_version + if self.project_id is not None: + body["project_id"] = self.project_id if self.synthetic_storage_size_bytes is not None: body["synthetic_storage_size_bytes"] = self.synthetic_storage_size_bytes return body @@ -1998,6 +2066,8 @@ def as_shallow_dict(self) -> dict: body["owner"] = self.owner if self.pg_version is not None: body["pg_version"] = self.pg_version + if self.project_id is not None: + body["project_id"] = self.project_id if self.synthetic_storage_size_bytes is not None: body["synthetic_storage_size_bytes"] = self.synthetic_storage_size_bytes return body @@ -2016,6 +2086,7 @@ def from_dict(cls, d: Dict[str, Any]) -> ProjectStatus: history_retention_duration=_duration(d, "history_retention_duration"), owner=d.get("owner", None), pg_version=d.get("pg_version", None), + project_id=d.get("project_id", None), synthetic_storage_size_bytes=d.get("synthetic_storage_size_bytes", None), ) @@ -2355,6 +2426,15 @@ class RoleRoleStatus: postgres_role: Optional[str] = None """The name of the Postgres role.""" + role_id: Optional[str] = None + """The short identifier of the role, suitable for showing to the users. For a role with name + `projects/my-project/branches/my-branch/roles/my-role`, the role_id is `my-role`. + + Use this field when building UI components that display roles to users (e.g., a drop-down + selector). Prefer showing `role_id` instead of the full resource name from `Role.name`, which + follows the `projects/{project_id}/branches/{branch_id}/roles/{role_id}` format and is not + user-friendly.""" + def as_dict(self) -> dict: """Serializes the RoleRoleStatus into a dictionary suitable for use as a JSON request body.""" body = {} @@ -2368,6 +2448,8 @@ def as_dict(self) -> dict: body["membership_roles"] = [v.value for v in self.membership_roles] if self.postgres_role is not None: body["postgres_role"] = self.postgres_role + if self.role_id is not None: + body["role_id"] = self.role_id return body def as_shallow_dict(self) -> dict: @@ -2383,6 +2465,8 @@ def as_shallow_dict(self) -> dict: body["membership_roles"] = self.membership_roles if self.postgres_role is not None: body["postgres_role"] = self.postgres_role + if self.role_id is not None: + body["role_id"] = self.role_id return body @classmethod @@ -2394,6 +2478,7 @@ def from_dict(cls, d: Dict[str, Any]) -> RoleRoleStatus: identity_type=_enum(d, "identity_type", RoleIdentityType), membership_roles=_repeated_enum(d, "membership_roles", RoleMembershipRole), postgres_role=d.get("postgres_role", None), + role_id=d.get("role_id", None), ) @@ -2751,6 +2836,11 @@ class SyncedTableSyncedTableStatus: pipeline_id: Optional[str] = None """ID of the associated pipeline.""" + project: Optional[str] = None + """The full resource name of the project associated with the table. + + Format: "projects/{project_id}".""" + provisioning_phase: Optional[ProvisioningPhase] = None """The current phase of the data synchronization pipeline.""" @@ -2774,6 +2864,8 @@ def as_dict(self) -> dict: body["ongoing_sync_progress"] = self.ongoing_sync_progress.as_dict() if self.pipeline_id is not None: body["pipeline_id"] = self.pipeline_id + if self.project is not None: + body["project"] = self.project if self.provisioning_phase is not None: body["provisioning_phase"] = self.provisioning_phase.value if self.unity_catalog_provisioning_state is not None: @@ -2797,6 +2889,8 @@ def as_shallow_dict(self) -> dict: body["ongoing_sync_progress"] = self.ongoing_sync_progress if self.pipeline_id is not None: body["pipeline_id"] = self.pipeline_id + if self.project is not None: + body["project"] = self.project if self.provisioning_phase is not None: body["provisioning_phase"] = self.provisioning_phase if self.unity_catalog_provisioning_state is not None: @@ -2814,6 +2908,7 @@ def from_dict(cls, d: Dict[str, Any]) -> SyncedTableSyncedTableStatus: message=d.get("message", None), ongoing_sync_progress=_from_dict(d, "ongoing_sync_progress", SyncedTablePipelineProgress), pipeline_id=d.get("pipeline_id", None), + project=d.get("project", None), provisioning_phase=_enum(d, "provisioning_phase", ProvisioningPhase), unity_catalog_provisioning_state=_enum(d, "unity_catalog_provisioning_state", ProvisioningInfoState), ) diff --git a/databricks/sdk/service/provisioning.py b/databricks/sdk/service/provisioning.py index a23e59db5..529cc889e 100755 --- a/databricks/sdk/service/provisioning.py +++ b/databricks/sdk/service/provisioning.py @@ -335,6 +335,10 @@ class CreateGcpKeyInfo: """Globally unique service account email that has access to the KMS key. The service account exists within the Databricks CP project.""" + manual: Optional[bool] = None + """When true, Databricks will not use OAuth to grant the service account access to the KMS key. The + customer is responsible for granting access manually.""" + def as_dict(self) -> dict: """Serializes the CreateGcpKeyInfo into a dictionary suitable for use as a JSON request body.""" body = {} @@ -342,6 +346,8 @@ def as_dict(self) -> dict: body["gcp_service_account"] = self.gcp_service_account.as_dict() if self.kms_key_id is not None: body["kms_key_id"] = self.kms_key_id + if self.manual is not None: + body["manual"] = self.manual return body def as_shallow_dict(self) -> dict: @@ -351,6 +357,8 @@ def as_shallow_dict(self) -> dict: body["gcp_service_account"] = self.gcp_service_account if self.kms_key_id is not None: body["kms_key_id"] = self.kms_key_id + if self.manual is not None: + body["manual"] = self.manual return body @classmethod @@ -359,6 +367,7 @@ def from_dict(cls, d: Dict[str, Any]) -> CreateGcpKeyInfo: return cls( gcp_service_account=_from_dict(d, "gcp_service_account", GcpServiceAccount), kms_key_id=d.get("kms_key_id", None), + manual=d.get("manual", None), ) @@ -598,6 +607,10 @@ class GcpKeyInfo: """Globally unique service account email that has access to the KMS key. The service account exists within the Databricks CP project.""" + manual: Optional[bool] = None + """When true, Databricks will not use OAuth to grant the service account access to the KMS key. The + customer is responsible for granting access manually.""" + def as_dict(self) -> dict: """Serializes the GcpKeyInfo into a dictionary suitable for use as a JSON request body.""" body = {} @@ -605,6 +618,8 @@ def as_dict(self) -> dict: body["gcp_service_account"] = self.gcp_service_account.as_dict() if self.kms_key_id is not None: body["kms_key_id"] = self.kms_key_id + if self.manual is not None: + body["manual"] = self.manual return body def as_shallow_dict(self) -> dict: @@ -614,6 +629,8 @@ def as_shallow_dict(self) -> dict: body["gcp_service_account"] = self.gcp_service_account if self.kms_key_id is not None: body["kms_key_id"] = self.kms_key_id + if self.manual is not None: + body["manual"] = self.manual return body @classmethod @@ -622,6 +639,7 @@ def from_dict(cls, d: Dict[str, Any]) -> GcpKeyInfo: return cls( gcp_service_account=_from_dict(d, "gcp_service_account", GcpServiceAccount), kms_key_id=d.get("kms_key_id", None), + manual=d.get("manual", None), ) @@ -1917,6 +1935,14 @@ def create( This operation is available only if your account is on the E2 version of the platform or on a select custom plan that allows multiple workspaces per account. + **GCP only**: To create a customer-managed key on GCP, you must include the + `X-Databricks-GCP-SA-Access-Token` HTTP header in your request. This header must contain a Google + Cloud OAuth access token with the `cloud-platform` scope. The Google identity associated with the + token must also have the `setIamPermissions` and `getIamPermissions` IAM permissions on the key + resource. For details on obtaining this token, see [Authenticate with Google ID tokens]. + + [Authenticate with Google ID tokens]: https://docs.databricks.com/gcp/en/dev-tools/auth/authentication-google-id.html + :param use_cases: List[:class:`KeyUseCase`] The cases that the key can be used for. :param aws_key_info: :class:`CreateAwsKeyInfo` (optional) diff --git a/databricks/sdk/service/settings.py b/databricks/sdk/service/settings.py index dffcf4381..e755fa707 100755 --- a/databricks/sdk/service/settings.py +++ b/databricks/sdk/service/settings.py @@ -7,6 +7,7 @@ from enum import Enum from typing import Any, Dict, Iterator, List, Optional +from databricks.sdk.common.types.fieldmask import FieldMask from databricks.sdk.service._internal import (_enum, _from_dict, _repeated_dict, _repeated_enum) @@ -1083,6 +1084,31 @@ def from_dict(cls, d: Dict[str, Any]) -> CustomerFacingIngressNetworkPolicy: return cls(public_access=_from_dict(d, "public_access", CustomerFacingIngressNetworkPolicyPublicAccess)) +@dataclass +class CustomerFacingIngressNetworkPolicyAppsRuntimeDestination: + all_destinations: Optional[bool] = None + """Must be set to true.""" + + def as_dict(self) -> dict: + """Serializes the CustomerFacingIngressNetworkPolicyAppsRuntimeDestination into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.all_destinations is not None: + body["all_destinations"] = self.all_destinations + return body + + def as_shallow_dict(self) -> dict: + """Serializes the CustomerFacingIngressNetworkPolicyAppsRuntimeDestination into a shallow dictionary of its immediate attributes.""" + body = {} + if self.all_destinations is not None: + body["all_destinations"] = self.all_destinations + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> CustomerFacingIngressNetworkPolicyAppsRuntimeDestination: + """Deserializes the CustomerFacingIngressNetworkPolicyAppsRuntimeDestination from a dictionary.""" + return cls(all_destinations=d.get("all_destinations", None)) + + @dataclass class CustomerFacingIngressNetworkPolicyAuthentication: identities: Optional[List[CustomerFacingIngressNetworkPolicyAuthenticationIdentity]] = None @@ -1190,6 +1216,31 @@ def from_dict(cls, d: Dict[str, Any]) -> CustomerFacingIngressNetworkPolicyIpRan return cls(ip_ranges=d.get("ip_ranges", None)) +@dataclass +class CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination: + all_destinations: Optional[bool] = None + """Must be set to true.""" + + def as_dict(self) -> dict: + """Serializes the CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.all_destinations is not None: + body["all_destinations"] = self.all_destinations + return body + + def as_shallow_dict(self) -> dict: + """Serializes the CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination into a shallow dictionary of its immediate attributes.""" + body = {} + if self.all_destinations is not None: + body["all_destinations"] = self.all_destinations + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination: + """Deserializes the CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination from a dictionary.""" + return cls(all_destinations=d.get("all_destinations", None)) + + @dataclass class CustomerFacingIngressNetworkPolicyPublicAccess: restriction_mode: CustomerFacingIngressNetworkPolicyPublicAccessRestrictionMode @@ -1248,8 +1299,7 @@ class CustomerFacingIngressNetworkPolicyPublicIngressRule: destination: Optional[CustomerFacingIngressNetworkPolicyRequestDestination] = None label: Optional[str] = None - """User-provided name for this ingress rule. Helps identify which rule caused a request to be - denied or dry-run denied.""" + """The label for this ingress rule.""" origin: Optional[CustomerFacingIngressNetworkPolicyPublicRequestOrigin] = None @@ -1339,6 +1389,10 @@ class CustomerFacingIngressNetworkPolicyRequestDestination: """When true, match all destinations, no other destination fields can be set. When not set or false, at least one specific destination must be provided.""" + apps_runtime: Optional[CustomerFacingIngressNetworkPolicyAppsRuntimeDestination] = None + + lakebase_runtime: Optional[CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination] = None + workspace_api: Optional[CustomerFacingIngressNetworkPolicyWorkspaceApiDestination] = None workspace_ui: Optional[CustomerFacingIngressNetworkPolicyWorkspaceUiDestination] = None @@ -1349,6 +1403,10 @@ def as_dict(self) -> dict: body = {} if self.all_destinations is not None: body["all_destinations"] = self.all_destinations + if self.apps_runtime: + body["apps_runtime"] = self.apps_runtime.as_dict() + if self.lakebase_runtime: + body["lakebase_runtime"] = self.lakebase_runtime.as_dict() if self.workspace_api: body["workspace_api"] = self.workspace_api.as_dict() if self.workspace_ui: @@ -1360,6 +1418,10 @@ def as_shallow_dict(self) -> dict: body = {} if self.all_destinations is not None: body["all_destinations"] = self.all_destinations + if self.apps_runtime: + body["apps_runtime"] = self.apps_runtime + if self.lakebase_runtime: + body["lakebase_runtime"] = self.lakebase_runtime if self.workspace_api: body["workspace_api"] = self.workspace_api if self.workspace_ui: @@ -1371,6 +1433,10 @@ def from_dict(cls, d: Dict[str, Any]) -> CustomerFacingIngressNetworkPolicyReque """Deserializes the CustomerFacingIngressNetworkPolicyRequestDestination from a dictionary.""" return cls( all_destinations=d.get("all_destinations", None), + apps_runtime=_from_dict(d, "apps_runtime", CustomerFacingIngressNetworkPolicyAppsRuntimeDestination), + lakebase_runtime=_from_dict( + d, "lakebase_runtime", CustomerFacingIngressNetworkPolicyLakebaseRuntimeDestination + ), workspace_api=_from_dict(d, "workspace_api", CustomerFacingIngressNetworkPolicyWorkspaceApiDestination), workspace_ui=_from_dict(d, "workspace_ui", CustomerFacingIngressNetworkPolicyWorkspaceUiDestination), ) @@ -2633,6 +2699,11 @@ class EgressNetworkPolicyNetworkAccessPolicy: """List of storage destinations that serverless workloads are allowed to access when in RESTRICTED_ACCESS mode.""" + blocked_internet_destinations: Optional[List[EgressNetworkPolicyNetworkAccessPolicyInternetDestination]] = None + """List of internet destinations that serverless workloads are blocked from accessing. These + destinations are enforced when restriction mode is RESTRICTED_ACCESS or DRY_RUN. Currently + supports DNS_NAME type only; IP_RANGE support is planned.""" + policy_enforcement: Optional[EgressNetworkPolicyNetworkAccessPolicyPolicyEnforcement] = None """Optional. When policy_enforcement is not provided, we default to ENFORCE_MODE_ALL_SERVICES""" @@ -2643,6 +2714,8 @@ def as_dict(self) -> dict: body["allowed_internet_destinations"] = [v.as_dict() for v in self.allowed_internet_destinations] if self.allowed_storage_destinations: body["allowed_storage_destinations"] = [v.as_dict() for v in self.allowed_storage_destinations] + if self.blocked_internet_destinations: + body["blocked_internet_destinations"] = [v.as_dict() for v in self.blocked_internet_destinations] if self.policy_enforcement: body["policy_enforcement"] = self.policy_enforcement.as_dict() if self.restriction_mode is not None: @@ -2656,6 +2729,8 @@ def as_shallow_dict(self) -> dict: body["allowed_internet_destinations"] = self.allowed_internet_destinations if self.allowed_storage_destinations: body["allowed_storage_destinations"] = self.allowed_storage_destinations + if self.blocked_internet_destinations: + body["blocked_internet_destinations"] = self.blocked_internet_destinations if self.policy_enforcement: body["policy_enforcement"] = self.policy_enforcement if self.restriction_mode is not None: @@ -2672,6 +2747,9 @@ def from_dict(cls, d: Dict[str, Any]) -> EgressNetworkPolicyNetworkAccessPolicy: allowed_storage_destinations=_repeated_dict( d, "allowed_storage_destinations", EgressNetworkPolicyNetworkAccessPolicyStorageDestination ), + blocked_internet_destinations=_repeated_dict( + d, "blocked_internet_destinations", EgressNetworkPolicyNetworkAccessPolicyInternetDestination + ), policy_enforcement=_from_dict( d, "policy_enforcement", EgressNetworkPolicyNetworkAccessPolicyPolicyEnforcement ), @@ -5657,6 +5735,24 @@ def from_dict(cls, d: Dict[str, Any]) -> UpdatePrivateEndpointRule: ) +@dataclass +class UpdateTokenResponse: + def as_dict(self) -> dict: + """Serializes the UpdateTokenResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + return body + + def as_shallow_dict(self) -> dict: + """Serializes the UpdateTokenResponse into a shallow dictionary of its immediate attributes.""" + body = {} + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> UpdateTokenResponse: + """Deserializes the UpdateTokenResponse from a dictionary.""" + return cls() + + WorkspaceConf = Dict[str, str] @@ -9420,6 +9516,48 @@ def list(self) -> Iterator[PublicTokenInfo]: parsed = ListPublicTokensResponse.from_dict(json).token_infos return parsed if parsed is not None else [] + def update(self, token_id: str, token: PublicTokenInfo, update_mask: FieldMask) -> UpdateTokenResponse: + """Updates the comment or scopes of a token. + + If a token with the specified ID is not valid, this call returns an error **RESOURCE_DOES_NOT_EXIST**. + + :param token_id: str + The SHA-256 hash of the token to be updated. + :param token: :class:`PublicTokenInfo` + :param update_mask: FieldMask + A list of field name under PublicTokenInfo, For example in request use {"update_mask": + "comment,scopes"} + + 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:`UpdateTokenResponse` + """ + + body = {} + if token is not None: + body["token"] = token.as_dict() + if update_mask is not None: + body["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.0/token/{token_id}", body=body, headers=headers) + return UpdateTokenResponse.from_dict(res) + class WorkspaceConfAPI: """This API allows updating known workspace settings for advanced users.""" diff --git a/databricks/sdk/service/supervisoragents.py b/databricks/sdk/service/supervisoragents.py new file mode 100755 index 000000000..b76831d36 --- /dev/null +++ b/databricks/sdk/service/supervisoragents.py @@ -0,0 +1,717 @@ +# Code generated from OpenAPI specs by Databricks SDK Generator. DO NOT EDIT. + +from __future__ import annotations + +import logging +from dataclasses import dataclass +from typing import Any, Dict, Iterator, List, Optional + +from google.protobuf.timestamp_pb2 import Timestamp + +from databricks.sdk.common.types.fieldmask import FieldMask +from databricks.sdk.service._internal import (_from_dict, _repeated_dict, + _timestamp) + +_LOG = logging.getLogger("databricks.sdk") + + +# all definitions in this file are in alphabetical order + + +@dataclass +class App: + """Databricks app. Supported app: custom mcp, custom agent.""" + + name: str + """App name""" + + def as_dict(self) -> dict: + """Serializes the App into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the App into a shallow dictionary of its immediate attributes.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> App: + """Deserializes the App from a dictionary.""" + return cls(name=d.get("name", None)) + + +@dataclass +class Connection: + """Databricks connection. Supported connection: external mcp server.""" + + name: str + + def as_dict(self) -> dict: + """Serializes the Connection into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Connection into a shallow dictionary of its immediate attributes.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Connection: + """Deserializes the Connection from a dictionary.""" + return cls(name=d.get("name", None)) + + +@dataclass +class GenieSpace: + id: str + """The ID of the genie space.""" + + def as_dict(self) -> dict: + """Serializes the GenieSpace into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.id is not None: + body["id"] = self.id + return body + + def as_shallow_dict(self) -> dict: + """Serializes the GenieSpace into a shallow dictionary of its immediate attributes.""" + body = {} + if self.id is not None: + body["id"] = self.id + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> GenieSpace: + """Deserializes the GenieSpace from a dictionary.""" + return cls(id=d.get("id", None)) + + +@dataclass +class KnowledgeAssistant: + knowledge_assistant_id: str + """The ID of the knowledge assistant.""" + + serving_endpoint_name: Optional[str] = None + """Deprecated: use knowledge_assistant_id instead.""" + + def as_dict(self) -> dict: + """Serializes the KnowledgeAssistant into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.knowledge_assistant_id is not None: + body["knowledge_assistant_id"] = self.knowledge_assistant_id + if self.serving_endpoint_name is not None: + body["serving_endpoint_name"] = self.serving_endpoint_name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the KnowledgeAssistant into a shallow dictionary of its immediate attributes.""" + body = {} + if self.knowledge_assistant_id is not None: + body["knowledge_assistant_id"] = self.knowledge_assistant_id + if self.serving_endpoint_name is not None: + body["serving_endpoint_name"] = self.serving_endpoint_name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> KnowledgeAssistant: + """Deserializes the KnowledgeAssistant from a dictionary.""" + return cls( + knowledge_assistant_id=d.get("knowledge_assistant_id", None), + serving_endpoint_name=d.get("serving_endpoint_name", None), + ) + + +@dataclass +class ListSupervisorAgentsResponse: + next_page_token: Optional[str] = None + """A token that can be sent as `page_token` to retrieve the next page. If this field is omitted, + there are no subsequent pages.""" + + supervisor_agents: Optional[List[SupervisorAgent]] = None + + def as_dict(self) -> dict: + """Serializes the ListSupervisorAgentsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.supervisor_agents: + body["supervisor_agents"] = [v.as_dict() for v in self.supervisor_agents] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListSupervisorAgentsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.supervisor_agents: + body["supervisor_agents"] = self.supervisor_agents + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListSupervisorAgentsResponse: + """Deserializes the ListSupervisorAgentsResponse from a dictionary.""" + return cls( + next_page_token=d.get("next_page_token", None), + supervisor_agents=_repeated_dict(d, "supervisor_agents", SupervisorAgent), + ) + + +@dataclass +class ListToolsResponse: + next_page_token: Optional[str] = None + + tools: Optional[List[Tool]] = None + + def as_dict(self) -> dict: + """Serializes the ListToolsResponse into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.tools: + body["tools"] = [v.as_dict() for v in self.tools] + return body + + def as_shallow_dict(self) -> dict: + """Serializes the ListToolsResponse into a shallow dictionary of its immediate attributes.""" + body = {} + if self.next_page_token is not None: + body["next_page_token"] = self.next_page_token + if self.tools: + body["tools"] = self.tools + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> ListToolsResponse: + """Deserializes the ListToolsResponse from a dictionary.""" + return cls(next_page_token=d.get("next_page_token", None), tools=_repeated_dict(d, "tools", Tool)) + + +@dataclass +class SupervisorAgent: + display_name: str + """The display name of the Supervisor Agent, unique at workspace level.""" + + description: str + """Description of what this agent can do (user-facing).""" + + create_time: Optional[Timestamp] = None + """Creation timestamp.""" + + creator: Optional[str] = None + """The creator of the Supervisor Agent.""" + + endpoint_name: Optional[str] = None + """The name of the supervisor agent's serving endpoint.""" + + experiment_id: Optional[str] = None + """The MLflow experiment ID.""" + + id: Optional[str] = None + """Deprecated: Use supervisor_agent_id instead.""" + + instructions: Optional[str] = None + """Optional natural-language instructions for the supervisor agent.""" + + name: Optional[str] = None + """The resource name of the SupervisorAgent. Format: supervisor-agents/{supervisor_agent_id}""" + + supervisor_agent_id: Optional[str] = None + """The universally unique identifier (UUID) of the Supervisor Agent.""" + + def as_dict(self) -> dict: + """Serializes the SupervisorAgent into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time.ToJsonString() + if self.creator is not None: + body["creator"] = self.creator + if self.description is not None: + body["description"] = self.description + if self.display_name is not None: + body["display_name"] = self.display_name + if self.endpoint_name is not None: + body["endpoint_name"] = self.endpoint_name + if self.experiment_id is not None: + body["experiment_id"] = self.experiment_id + if self.id is not None: + body["id"] = self.id + if self.instructions is not None: + body["instructions"] = self.instructions + if self.name is not None: + body["name"] = self.name + if self.supervisor_agent_id is not None: + body["supervisor_agent_id"] = self.supervisor_agent_id + return body + + def as_shallow_dict(self) -> dict: + """Serializes the SupervisorAgent into a shallow dictionary of its immediate attributes.""" + body = {} + if self.create_time is not None: + body["create_time"] = self.create_time + if self.creator is not None: + body["creator"] = self.creator + if self.description is not None: + body["description"] = self.description + if self.display_name is not None: + body["display_name"] = self.display_name + if self.endpoint_name is not None: + body["endpoint_name"] = self.endpoint_name + if self.experiment_id is not None: + body["experiment_id"] = self.experiment_id + if self.id is not None: + body["id"] = self.id + if self.instructions is not None: + body["instructions"] = self.instructions + if self.name is not None: + body["name"] = self.name + if self.supervisor_agent_id is not None: + body["supervisor_agent_id"] = self.supervisor_agent_id + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> SupervisorAgent: + """Deserializes the SupervisorAgent from a dictionary.""" + return cls( + create_time=_timestamp(d, "create_time"), + creator=d.get("creator", None), + description=d.get("description", None), + display_name=d.get("display_name", None), + endpoint_name=d.get("endpoint_name", None), + experiment_id=d.get("experiment_id", None), + id=d.get("id", None), + instructions=d.get("instructions", None), + name=d.get("name", None), + supervisor_agent_id=d.get("supervisor_agent_id", None), + ) + + +@dataclass +class Tool: + tool_type: str + """Tool type. Must be one of: "genie_space", "knowledge_assistant", "uc_function", "connection", + "app", "volume", "lakeview_dashboard", "serving_endpoint".""" + + description: str + """Description of what this tool does (user-facing).""" + + app: Optional[App] = None + + connection: Optional[Connection] = None + + genie_space: Optional[GenieSpace] = None + + id: Optional[str] = None + """Deprecated: Use tool_id instead.""" + + knowledge_assistant: Optional[KnowledgeAssistant] = None + + name: Optional[str] = None + """Full resource name: supervisor-agents/{supervisor_agent_id}/tools/{tool_id}""" + + tool_id: Optional[str] = None + """User specified id of the Tool.""" + + uc_function: Optional[UcFunction] = None + + volume: Optional[Volume] = None + + def as_dict(self) -> dict: + """Serializes the Tool into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.app: + body["app"] = self.app.as_dict() + if self.connection: + body["connection"] = self.connection.as_dict() + if self.description is not None: + body["description"] = self.description + if self.genie_space: + body["genie_space"] = self.genie_space.as_dict() + if self.id is not None: + body["id"] = self.id + if self.knowledge_assistant: + body["knowledge_assistant"] = self.knowledge_assistant.as_dict() + if self.name is not None: + body["name"] = self.name + if self.tool_id is not None: + body["tool_id"] = self.tool_id + if self.tool_type is not None: + body["tool_type"] = self.tool_type + if self.uc_function: + body["uc_function"] = self.uc_function.as_dict() + if self.volume: + body["volume"] = self.volume.as_dict() + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Tool into a shallow dictionary of its immediate attributes.""" + body = {} + if self.app: + body["app"] = self.app + if self.connection: + body["connection"] = self.connection + if self.description is not None: + body["description"] = self.description + if self.genie_space: + body["genie_space"] = self.genie_space + if self.id is not None: + body["id"] = self.id + if self.knowledge_assistant: + body["knowledge_assistant"] = self.knowledge_assistant + if self.name is not None: + body["name"] = self.name + if self.tool_id is not None: + body["tool_id"] = self.tool_id + if self.tool_type is not None: + body["tool_type"] = self.tool_type + if self.uc_function: + body["uc_function"] = self.uc_function + if self.volume: + body["volume"] = self.volume + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Tool: + """Deserializes the Tool from a dictionary.""" + return cls( + app=_from_dict(d, "app", App), + connection=_from_dict(d, "connection", Connection), + description=d.get("description", None), + genie_space=_from_dict(d, "genie_space", GenieSpace), + id=d.get("id", None), + knowledge_assistant=_from_dict(d, "knowledge_assistant", KnowledgeAssistant), + name=d.get("name", None), + tool_id=d.get("tool_id", None), + tool_type=d.get("tool_type", None), + uc_function=_from_dict(d, "uc_function", UcFunction), + volume=_from_dict(d, "volume", Volume), + ) + + +@dataclass +class UcFunction: + name: str + """Full uc function name""" + + def as_dict(self) -> dict: + """Serializes the UcFunction into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the UcFunction into a shallow dictionary of its immediate attributes.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> UcFunction: + """Deserializes the UcFunction from a dictionary.""" + return cls(name=d.get("name", None)) + + +@dataclass +class Volume: + name: str + """Full uc volume name""" + + def as_dict(self) -> dict: + """Serializes the Volume into a dictionary suitable for use as a JSON request body.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + def as_shallow_dict(self) -> dict: + """Serializes the Volume into a shallow dictionary of its immediate attributes.""" + body = {} + if self.name is not None: + body["name"] = self.name + return body + + @classmethod + def from_dict(cls, d: Dict[str, Any]) -> Volume: + """Deserializes the Volume from a dictionary.""" + return cls(name=d.get("name", None)) + + +class SupervisorAgentsAPI: + """Manage Supervisor Agents and related resources.""" + + def __init__(self, api_client): + self._api = api_client + + def create_supervisor_agent(self, supervisor_agent: SupervisorAgent) -> SupervisorAgent: + """Creates a new Supervisor Agent. + + :param supervisor_agent: :class:`SupervisorAgent` + The Supervisor Agent to create. + + :returns: :class:`SupervisorAgent` + """ + + body = supervisor_agent.as_dict() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("POST", "/api/2.1/supervisor-agents", body=body, headers=headers) + return SupervisorAgent.from_dict(res) + + def create_tool(self, parent: str, tool: Tool, tool_id: str) -> Tool: + """Creates a Tool under a Supervisor Agent. Specify one of "genie_space", "knowledge_assistant", + "uc_function", "connection", "app", "volume", "lakeview_dashboard" in the request body. + + :param parent: str + Parent resource where this tool will be created. Format: supervisor-agents/{supervisor_agent_id} + :param tool: :class:`Tool` + :param tool_id: str + The ID to use for the tool, which will become the final component of the tool's resource name. + + :returns: :class:`Tool` + """ + + body = tool.as_dict() + query = {} + if tool_id is not None: + query["tool_id"] = tool_id + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("POST", f"/api/2.1/{parent}/tools", query=query, body=body, headers=headers) + return Tool.from_dict(res) + + def delete_supervisor_agent(self, name: str): + """Deletes a Supervisor Agent. + + :param name: str + The resource name of the Supervisor Agent. Format: supervisor-agents/{supervisor_agent_id} + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.1/{name}", headers=headers) + + def delete_tool(self, name: str): + """Deletes a Tool. + + :param name: str + The resource name of the Tool. Format: supervisor-agents/{supervisor_agent_id}/tools/{tool_id} + + + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + self._api.do("DELETE", f"/api/2.1/{name}", headers=headers) + + def get_supervisor_agent(self, name: str) -> SupervisorAgent: + """Gets a Supervisor Agent. + + :param name: str + The resource name of the Supervisor Agent. Format: supervisor-agents/{supervisor_agent_id} + + :returns: :class:`SupervisorAgent` + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.1/{name}", headers=headers) + return SupervisorAgent.from_dict(res) + + def get_tool(self, name: str) -> Tool: + """Gets a Tool. + + :param name: str + The resource name of the Tool. Format: supervisor-agents/{supervisor_agent_id}/tools/{tool_id} + + :returns: :class:`Tool` + """ + + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("GET", f"/api/2.1/{name}", headers=headers) + return Tool.from_dict(res) + + def list_supervisor_agents( + self, *, page_size: Optional[int] = None, page_token: Optional[str] = None + ) -> Iterator[SupervisorAgent]: + """Lists Supervisor Agents. + + :param page_size: int (optional) + The maximum number of supervisor agents to return. If unspecified, at most 100 supervisor agents + will be returned. The maximum value is 100; values above 100 will be coerced to 100. + :param page_token: str (optional) + A page token, received from a previous `ListSupervisorAgents` call. Provide this to retrieve the + subsequent page. If unspecified, the first page will be returned. + + :returns: Iterator over :class:`SupervisorAgent` + """ + + query = {} + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", "/api/2.1/supervisor-agents", query=query, headers=headers) + if "supervisor_agents" in json: + for v in json["supervisor_agents"]: + yield SupervisorAgent.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def list_tools( + self, parent: str, *, page_size: Optional[int] = None, page_token: Optional[str] = None + ) -> Iterator[Tool]: + """Lists Tools under a Supervisor Agent. + + :param parent: str + Parent resource to list from. Format: supervisor-agents/{supervisor_agent_id} + :param page_size: int (optional) + :param page_token: str (optional) + + :returns: Iterator over :class:`Tool` + """ + + query = {} + if page_size is not None: + query["page_size"] = page_size + if page_token is not None: + query["page_token"] = page_token + headers = { + "Accept": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + while True: + json = self._api.do("GET", f"/api/2.1/{parent}/tools", query=query, headers=headers) + if "tools" in json: + for v in json["tools"]: + yield Tool.from_dict(v) + if "next_page_token" not in json or not json["next_page_token"]: + return + query["page_token"] = json["next_page_token"] + + def update_supervisor_agent( + self, name: str, supervisor_agent: SupervisorAgent, update_mask: FieldMask + ) -> SupervisorAgent: + """Updates a Supervisor Agent. The fields that are required depend on the paths specified in + `update_mask`. Only fields included in the mask will be updated. + + :param name: str + The resource name of the SupervisorAgent. Format: supervisor-agents/{supervisor_agent_id} + :param supervisor_agent: :class:`SupervisorAgent` + The SupervisorAgent to update. + :param update_mask: FieldMask + Field mask for fields to be updated. + + :returns: :class:`SupervisorAgent` + """ + + body = supervisor_agent.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.1/{name}", query=query, body=body, headers=headers) + return SupervisorAgent.from_dict(res) + + def update_tool(self, name: str, tool: Tool, update_mask: FieldMask) -> Tool: + """Updates a Tool. Only the `description` field can be updated. To change immutable fields such as tool + type, spec, or tool ID, delete the tool and recreate it. + + :param name: str + Full resource name: supervisor-agents/{supervisor_agent_id}/tools/{tool_id} + :param tool: :class:`Tool` + The Tool to update. + :param update_mask: FieldMask + Field mask for fields to be updated. + + :returns: :class:`Tool` + """ + + body = tool.as_dict() + query = {} + if update_mask is not None: + query["update_mask"] = update_mask.ToJsonString() + headers = { + "Accept": "application/json", + "Content-Type": "application/json", + } + + cfg = self._api._cfg + if cfg.workspace_id: + headers["X-Databricks-Org-Id"] = cfg.workspace_id + + res = self._api.do("PATCH", f"/api/2.1/{name}", query=query, body=body, headers=headers) + return Tool.from_dict(res) diff --git a/databricks/sdk/service/vectorsearch.py b/databricks/sdk/service/vectorsearch.py index d8e204d7d..aefdb7959 100755 --- a/databricks/sdk/service/vectorsearch.py +++ b/databricks/sdk/service/vectorsearch.py @@ -269,6 +269,11 @@ def from_dict(cls, d: Dict[str, Any]) -> DeltaSyncVectorIndexSpecRequest: @dataclass class DeltaSyncVectorIndexSpecResponse: + columns_to_sync: Optional[List[str]] = None + """[Optional] Select the columns to sync with the vector index. If you leave this field blank, all + columns from the source table are synced with the index. The primary key column and embedding + source column or embedding vector column are always synced.""" + embedding_source_columns: Optional[List[EmbeddingSourceColumn]] = None """The columns that contain the embedding source.""" @@ -294,6 +299,8 @@ class DeltaSyncVectorIndexSpecResponse: def as_dict(self) -> dict: """Serializes the DeltaSyncVectorIndexSpecResponse into a dictionary suitable for use as a JSON request body.""" body = {} + if self.columns_to_sync: + body["columns_to_sync"] = [v for v in self.columns_to_sync] if self.embedding_source_columns: body["embedding_source_columns"] = [v.as_dict() for v in self.embedding_source_columns] if self.embedding_vector_columns: @@ -311,6 +318,8 @@ def as_dict(self) -> dict: def as_shallow_dict(self) -> dict: """Serializes the DeltaSyncVectorIndexSpecResponse into a shallow dictionary of its immediate attributes.""" body = {} + if self.columns_to_sync: + body["columns_to_sync"] = self.columns_to_sync if self.embedding_source_columns: body["embedding_source_columns"] = self.embedding_source_columns if self.embedding_vector_columns: @@ -329,6 +338,7 @@ def as_shallow_dict(self) -> dict: def from_dict(cls, d: Dict[str, Any]) -> DeltaSyncVectorIndexSpecResponse: """Deserializes the DeltaSyncVectorIndexSpecResponse from a dictionary.""" return cls( + columns_to_sync=d.get("columns_to_sync", None), embedding_source_columns=_repeated_dict(d, "embedding_source_columns", EmbeddingSourceColumn), embedding_vector_columns=_repeated_dict(d, "embedding_vector_columns", EmbeddingVectorColumn), embedding_writeback_table=d.get("embedding_writeback_table", None), diff --git a/databricks/sdk/service/workspace.py b/databricks/sdk/service/workspace.py index a2ce77be2..74157ef0e 100755 --- a/databricks/sdk/service/workspace.py +++ b/databricks/sdk/service/workspace.py @@ -2716,10 +2716,10 @@ def __init__(self, api_client): self._api = api_client def delete(self, path: str, *, recursive: Optional[bool] = None): - """Deprecated: use WorkspaceHierarchyService.DeleteTreeNode instead. Deletes an object or a directory - (and optionally recursively deletes all objects in the directory). * If `path` does not exist, this - call returns an error `RESOURCE_DOES_NOT_EXIST`. * If `path` is a non-empty directory and `recursive` - is set to `false`, this call returns an error `DIRECTORY_NOT_EMPTY`. + """Deletes an object or a directory (and optionally recursively deletes all objects in the directory). * + If `path` does not exist, this call returns an error `RESOURCE_DOES_NOT_EXIST`. * If `path` is a + non-empty directory and `recursive` is set to `false`, this call returns an error + `DIRECTORY_NOT_EMPTY`. Object deletion cannot be undone and deleting a directory recursively is not atomic. @@ -2849,8 +2849,8 @@ def get_permissions(self, workspace_object_type: str, workspace_object_id: str) return WorkspaceObjectPermissions.from_dict(res) def get_status(self, path: str) -> ObjectInfo: - """Deprecated: use WorkspaceHierarchyService.GetTreeNode instead. Gets the status of an object or a - directory. If `path` does not exist, this call returns an error `RESOURCE_DOES_NOT_EXIST`. + """Gets the status of an object or a directory. If `path` does not exist, this call returns an error + `RESOURCE_DOES_NOT_EXIST`. :param path: str The absolute path of the notebook or directory. @@ -2938,9 +2938,8 @@ def import_( self._api.do("POST", "/api/2.0/workspace/import", body=body, headers=headers) def list(self, path: str, *, notebooks_modified_after: Optional[int] = None) -> Iterator[ObjectInfo]: - """Deprecated: use WorkspaceHierarchyService.ListTreeNodes instead. Lists the contents of a directory, or - the object if it is not a directory. If the input path does not exist, this call returns an error - `RESOURCE_DOES_NOT_EXIST`. + """Lists the contents of a directory, or the object if it is not a directory. If the input path does not + exist, this call returns an error `RESOURCE_DOES_NOT_EXIST`. :param path: str The absolute path of the notebook or directory. @@ -2968,9 +2967,9 @@ def list(self, path: str, *, notebooks_modified_after: Optional[int] = None) -> return parsed if parsed is not None else [] def mkdirs(self, path: str): - """Deprecated: use WorkspaceHierarchyService.CreateTreeNode instead. Creates the specified directory (and - necessary parent directories if they do not exist). If there is an object (not a directory) at any - prefix of the input path, this call returns an error `RESOURCE_ALREADY_EXISTS`. + """Creates the specified directory (and necessary parent directories if they do not exist). If there is + an object (not a directory) at any prefix of the input path, this call returns an error + `RESOURCE_ALREADY_EXISTS`. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories. diff --git a/docs/packages.py b/docs/packages.py index 9dfb09cd6..aeb245468 100755 --- a/docs/packages.py +++ b/docs/packages.py @@ -15,8 +15,8 @@ class Package: "Manage workspace-level entities that include notebooks, Git checkouts, and secrets"), Package("compute", "Compute", "Use and configure compute for Databricks"), Package("jobs", "Jobs", "Schedule automated jobs on Databricks Workspaces"), - Package("pipelines", "Delta Live Tables", - "Manage pipelines, runs, and other Delta Live Table resources"), + Package("pipelines", "Spark Declarative Pipelines", + "Manage pipelines, runs, and other Spark Declarative Pipeline resources"), Package("files", "File Management", "Manage files on Databricks in a filesystem-like interface"), Package("ml", "Machine Learning", "Create and manage experiments, features, and other machine learning artifacts"), diff --git a/tagging.py b/tagging.py index 56e57781b..79f2894c6 100644 --- a/tagging.py +++ b/tagging.py @@ -18,6 +18,7 @@ CHANGELOG_FILE_NAME = "CHANGELOG.md" PACKAGE_FILE_NAME = ".package.json" CODEGEN_FILE_NAME = ".codegen.json" +CREATED_TAGS_FILE_NAME = "created_tags.json" """ This script tags the release of the SDKs using a combination of the GitHub API and Git commands. It reads the local repository to determine necessary changes, updates changelogs, and creates tags. @@ -467,9 +468,29 @@ def update_changelogs(packages: List[Package]) -> List[TagInfo]: def push_tags(tag_infos: List[TagInfo]) -> None: """ Creates and pushes tags to the repository. + + As a side effect, writes the names of successfully created tags to + ``./created_tags.json`` so that workflows triggering this script can + discover what was produced (the GitHub Actions workflow uploads this + file as the ``created-tags`` artifact). + + Schema: + {"tags": ["service-a/v1.2.3", "service-b/v0.4.0"]} + + The manifest is written even if tag creation fails partway through: + tags that succeeded before the failure are flushed before the + exception is re-raised, so recovery-mode runs still surface their + output. """ - for tag_info in tag_infos: - gh.tag(tag_info.tag_name(), tag_info.content) + created: List[str] = [] + try: + for tag_info in tag_infos: + gh.tag(tag_info.tag_name(), tag_info.content) + created.append(tag_info.tag_name()) + finally: + manifest_path = os.path.join(os.getcwd(), CREATED_TAGS_FILE_NAME) + with open(manifest_path, "w") as f: + json.dump({"tags": created}, f) def run_command(command: List[str]) -> str: