diff --git a/acceptance/bundle/python/dashboards-support/dashboard.lvdash.json b/acceptance/bundle/python/dashboards-support/dashboard.lvdash.json new file mode 100644 index 00000000000..c47c13304ee --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/dashboard.lvdash.json @@ -0,0 +1,3 @@ +{ + "pages": [] +} diff --git a/acceptance/bundle/python/dashboards-support/databricks.yml b/acceptance/bundle/python/dashboards-support/databricks.yml new file mode 100644 index 00000000000..09a34b1dfbd --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/databricks.yml @@ -0,0 +1,17 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +python: + resources: + - "resources:load_resources" + mutators: + - "mutators:update_dashboard" + +resources: + dashboards: + my_dashboard_1: + display_name: "My Dashboard" + file_path: dashboard.lvdash.json + warehouse_id: my_warehouse diff --git a/acceptance/bundle/python/dashboards-support/mutators.py b/acceptance/bundle/python/dashboards-support/mutators.py new file mode 100644 index 00000000000..ec5c2562016 --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/mutators.py @@ -0,0 +1,11 @@ +from dataclasses import replace + +from databricks.bundles.core import dashboard_mutator +from databricks.bundles.dashboards import Dashboard + + +@dashboard_mutator +def update_dashboard(dashboard: Dashboard) -> Dashboard: + assert isinstance(dashboard.display_name, str) + + return replace(dashboard, display_name=f"{dashboard.display_name} (updated)") diff --git a/acceptance/bundle/python/dashboards-support/out.test.toml b/acceptance/bundle/python/dashboards-support/out.test.toml new file mode 100644 index 00000000000..02da5baab89 --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/out.test.toml @@ -0,0 +1,4 @@ +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] +EnvMatrix.DMS = ["", "true"] +EnvMatrix.PYDAB_VERSION = ["current"] diff --git a/acceptance/bundle/python/dashboards-support/output.txt b/acceptance/bundle/python/dashboards-support/output.txt new file mode 100644 index 00000000000..622ae31776c --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/output.txt @@ -0,0 +1,39 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json +{ + "experimental": { + "python": { + "mutators": [ + "mutators:update_dashboard" + ], + "resources": [ + "resources:load_resources" + ] + } + }, + "resources": { + "dashboards": { + "my_dashboard_1": { + "display_name": "My Dashboard (updated)", + "embed_credentials": false, + "file_path": "dashboard.lvdash.json", + "parent_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/resources", + "serialized_dashboard": "{\n \"pages\": []\n}\n", + "warehouse_id": "my_warehouse" + }, + "my_dashboard_2": { + "display_name": "My Dashboard (2) (updated)", + "embed_credentials": false, + "parent_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/resources", + "serialized_dashboard": { + "pages": [ + { + "name": "python_page" + } + ] + }, + "warehouse_id": "my_warehouse_2" + } + } + } +} diff --git a/acceptance/bundle/python/dashboards-support/resources.py b/acceptance/bundle/python/dashboards-support/resources.py new file mode 100644 index 00000000000..28450057932 --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/resources.py @@ -0,0 +1,16 @@ +from databricks.bundles.core import Resources + + +def load_resources() -> Resources: + resources = Resources() + + resources.add_dashboard( + "my_dashboard_2", + { + "display_name": "My Dashboard (2)", + "serialized_dashboard": {"pages": [{"name": "python_page"}]}, + "warehouse_id": "my_warehouse_2", + }, + ) + + return resources diff --git a/acceptance/bundle/python/dashboards-support/script b/acceptance/bundle/python/dashboards-support/script new file mode 100644 index 00000000000..e273fb45a53 --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/script @@ -0,0 +1,5 @@ + +trace uv run $UV_ARGS -q $CLI bundle validate --output json | \ + jq "pick(.experimental.python, .resources)" + +rm -fr .databricks __pycache__ diff --git a/acceptance/bundle/python/dashboards-support/test.toml b/acceptance/bundle/python/dashboards-support/test.toml new file mode 100644 index 00000000000..3b72786e9dd --- /dev/null +++ b/acceptance/bundle/python/dashboards-support/test.toml @@ -0,0 +1,4 @@ +Cloud = false # tests don't interact with APIs + +# dashboards are only supported in the current version of the wheel +EnvMatrix.PYDAB_VERSION = ["current"] diff --git a/python/codegen/codegen/generated_imports.py b/python/codegen/codegen/generated_imports.py index c7f7af63342..9bf1ff5aa2c 100644 --- a/python/codegen/codegen/generated_imports.py +++ b/python/codegen/codegen/generated_imports.py @@ -44,7 +44,7 @@ def get_code( b = CodeBuilder() b.append( - "from typing import Literal, Optional, TypedDict, ClassVar, TYPE_CHECKING\n" + "from typing import Any, Literal, Optional, TypedDict, ClassVar, TYPE_CHECKING\n" ) b.append("from enum import Enum\n") b.append("from dataclasses import dataclass, replace, field\n") diff --git a/python/codegen/codegen/packages.py b/python/codegen/codegen/packages.py index f5c2a53fb39..8359ced9c3e 100644 --- a/python/codegen/codegen/packages.py +++ b/python/codegen/codegen/packages.py @@ -9,6 +9,7 @@ "resources.Schema": "schemas", "resources.Volume": "volumes", "resources.Alert": "alerts", + "resources.Dashboard": "dashboards", } RESOURCE_TYPES = list(RESOURCE_NAMESPACE.keys()) @@ -20,6 +21,7 @@ "number": "float", "int64": "int", "float64": "float", + "interface": "Any", } PRIMITIVES = [ @@ -31,6 +33,7 @@ "int", "int64", "float64", + "interface", ] diff --git a/python/codegen/codegen_tests/test_generated_dataclass.py b/python/codegen/codegen_tests/test_generated_dataclass.py index 504d05bfa11..6695ba92d14 100644 --- a/python/codegen/codegen_tests/test_generated_dataclass.py +++ b/python/codegen/codegen_tests/test_generated_dataclass.py @@ -26,6 +26,20 @@ def test_generate_type_string(): ) +def test_generate_type_interface(): + generated_type = generate_type( + namespace="dashboards", + ref="#/$defs/interface", + is_param=False, + ) + + assert generated_type == GeneratedType( + name="Any", + package=None, + parameters=[], + ) + + def test_generate_type_dict(): generated_type = generate_type( namespace="jobs", diff --git a/python/databricks/bundles/core/__init__.py b/python/databricks/bundles/core/__init__.py index cbf4661aed8..b53d64ac480 100644 --- a/python/databricks/bundles/core/__init__.py +++ b/python/databricks/bundles/core/__init__.py @@ -16,6 +16,7 @@ "VariableOrOptional", "alert_mutator", "catalog_mutator", + "dashboard_mutator", "job_mutator", "load_resources_from_current_package_module", "load_resources_from_module", @@ -36,6 +37,7 @@ from databricks.bundles.core._generated import ( alert_mutator, catalog_mutator, + dashboard_mutator, job_mutator, pipeline_mutator, schema_mutator, diff --git a/python/databricks/bundles/core/_generated/__init__.py b/python/databricks/bundles/core/_generated/__init__.py index ade9313238c..aa77633a9a6 100644 --- a/python/databricks/bundles/core/_generated/__init__.py +++ b/python/databricks/bundles/core/_generated/__init__.py @@ -7,6 +7,10 @@ _CatalogResources, catalog_mutator, ) +from databricks.bundles.core._generated.dashboards import ( + _DashboardResources, + dashboard_mutator, +) from databricks.bundles.core._generated.jobs import _JobResources, job_mutator from databricks.bundles.core._generated.pipelines import ( _PipelineResources, @@ -23,6 +27,7 @@ "_all_resource_types", "alert_mutator", "catalog_mutator", + "dashboard_mutator", "job_mutator", "pipeline_mutator", "schema_mutator", @@ -33,6 +38,7 @@ class _GeneratedResources( _AlertResources, _CatalogResources, + _DashboardResources, _JobResources, _PipelineResources, _SchemaResources, @@ -45,6 +51,7 @@ def _all_resource_types() -> "tuple[_ResourceType, ...]": from databricks.bundles.core._generated import ( alerts, catalogs, + dashboards, jobs, pipelines, schemas, @@ -54,6 +61,7 @@ def _all_resource_types() -> "tuple[_ResourceType, ...]": return ( alerts._resource_type(), catalogs._resource_type(), + dashboards._resource_type(), jobs._resource_type(), pipelines._resource_type(), schemas._resource_type(), diff --git a/python/databricks/bundles/core/_generated/dashboards.py b/python/databricks/bundles/core/_generated/dashboards.py new file mode 100644 index 00000000000..75cbb368354 --- /dev/null +++ b/python/databricks/bundles/core/_generated/dashboards.py @@ -0,0 +1,118 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from collections.abc import Callable +from typing import TYPE_CHECKING, Optional, overload + +from databricks.bundles.core._bundle import Bundle +from databricks.bundles.core._location import Location +from databricks.bundles.core._resource_mutator import ResourceMutator +from databricks.bundles.core._transform import _transform + +if TYPE_CHECKING: + from databricks.bundles.core._resource_type import _ResourceType + from databricks.bundles.dashboards._models.dashboard import ( + Dashboard, + DashboardParam, + ) + + +def _resource_type() -> "_ResourceType": + from databricks.bundles.core._resource_type import _ResourceType + from databricks.bundles.dashboards._models.dashboard import Dashboard + + return _ResourceType( + resource_type=Dashboard, + singular_name="dashboard", + plural_name="dashboards", + ) + + +class _DashboardResources: + """ + Generated dashboard accessors, mixed into Resources. + """ + + # Provided by the Resources subclass; declared here so the generated methods + # below type-check. + _resources: dict[str, dict] + + if TYPE_CHECKING: + + def add_location(self, path: tuple[str, ...], location: Location) -> None: ... + + def add_diagnostic_error( + self, + msg: str, + *, + detail: Optional[str] = None, + path: Optional[tuple[str, ...]] = None, + location: Optional[Location] = None, + ) -> None: ... + + @property + def dashboards(self) -> dict[str, "Dashboard"]: + return self._resources["dashboards"] + + def add_dashboard( + self, + resource_name: str, + dashboard: "DashboardParam", + *, + location: Optional[Location] = None, + ) -> None: + """ + Adds the resource dashboard to the collection of resources. Resource name must be unique across all dashboards. + + :param resource_name: unique identifier for the dashboard + :param dashboard: the dashboard to add, can be Dashboard or dict + :param location: optional location of the dashboard in the source code + """ + from databricks.bundles.dashboards._models.dashboard import Dashboard + + dashboard = _transform(Dashboard, dashboard) + path = ("resources", "dashboards", resource_name) + location = location or Location.from_stack_frame(depth=1) + + if self._resources["dashboards"].get(resource_name): + self.add_diagnostic_error( + msg=f"Duplicate resource name '{resource_name}' for resource 'dashboard'. Resource names must be unique.", + location=location, + path=path, + ) + else: + if location: + self.add_location(path, location) + + self._resources["dashboards"][resource_name] = dashboard + + +@overload +def dashboard_mutator( + function: Callable[[Bundle, "Dashboard"], "Dashboard"], +) -> ResourceMutator["Dashboard"]: ... + + +@overload +def dashboard_mutator( + function: Callable[["Dashboard"], "Dashboard"], +) -> ResourceMutator["Dashboard"]: ... + + +def dashboard_mutator(function: Callable) -> ResourceMutator["Dashboard"]: + """ + Decorator for defining mutator for dashboards. Function should return a new instance of the dashboard + with the desired changes, instead of mutating the input dashboard. + + Example: + + .. code-block:: python + + @dashboard_mutator + def my_dashboard_mutator(bundle: Bundle, dashboard: Dashboard) -> Dashboard: + return replace(dashboard, ...) + + :param function: Function that mutates dashboards. + """ + from databricks.bundles.dashboards._models.dashboard import Dashboard + + return ResourceMutator(resource_type=Dashboard, function=function) diff --git a/python/databricks/bundles/core/_transform.py b/python/databricks/bundles/core/_transform.py index 9ccc24441c3..6fca9d3a03e 100644 --- a/python/databricks/bundles/core/_transform.py +++ b/python/databricks/bundles/core/_transform.py @@ -137,6 +137,9 @@ def _transform_field(cls: Type[_T], field: Field, value: Any) -> _T: def _transform(cls: Type[_T], value: Any) -> _T: origin = get_origin(cls) + if cls is Any: + return value + if is_dataclass(cls) and isinstance(value, cls): # type:ignore return value diff --git a/python/databricks/bundles/dashboards/__init__.py b/python/databricks/bundles/dashboards/__init__.py new file mode 100644 index 00000000000..126427fcca6 --- /dev/null +++ b/python/databricks/bundles/dashboards/__init__.py @@ -0,0 +1,42 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +__all__ = [ + "Dashboard", + "DashboardDict", + "DashboardParam", + "Lifecycle", + "LifecycleDict", + "LifecycleParam", + "LifecycleState", + "LifecycleStateParam", + "Permission", + "PermissionDict", + "PermissionLevel", + "PermissionLevelParam", + "PermissionParam", +] + + +from databricks.bundles.dashboards._models.dashboard import ( + Dashboard, + DashboardDict, + DashboardParam, +) +from databricks.bundles.dashboards._models.lifecycle import ( + Lifecycle, + LifecycleDict, + LifecycleParam, +) +from databricks.bundles.dashboards._models.lifecycle_state import ( + LifecycleState, + LifecycleStateParam, +) +from databricks.bundles.dashboards._models.permission import ( + Permission, + PermissionDict, + PermissionParam, +) +from databricks.bundles.dashboards._models.permission_level import ( + PermissionLevel, + PermissionLevelParam, +) diff --git a/python/databricks/bundles/dashboards/_models/dashboard.py b/python/databricks/bundles/dashboards/_models/dashboard.py new file mode 100644 index 00000000000..2a3e5f03364 --- /dev/null +++ b/python/databricks/bundles/dashboards/_models/dashboard.py @@ -0,0 +1,209 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, TypedDict + +from databricks.bundles.core._resource import Resource +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOrList, VariableOrOptional +from databricks.bundles.dashboards._models.lifecycle import Lifecycle, LifecycleParam +from databricks.bundles.dashboards._models.lifecycle_state import ( + LifecycleState, + LifecycleStateParam, +) +from databricks.bundles.dashboards._models.permission import Permission, PermissionParam + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Dashboard(Resource): + """""" + + create_time: VariableOrOptional[str] = None + """ + The timestamp of when the dashboard was created. + """ + + dashboard_id: VariableOrOptional[str] = None + """ + UUID identifying the dashboard. + """ + + dataset_catalog: VariableOrOptional[str] = None + """ + Sets the default catalog for all datasets in this dashboard. When set, this overrides the catalog specified in individual dataset definitions. + """ + + dataset_schema: VariableOrOptional[str] = None + """ + Sets the default schema for all datasets in this dashboard. When set, this overrides the schema specified in individual dataset definitions. + """ + + display_name: VariableOrOptional[str] = None + """ + The display name of the dashboard. + """ + + embed_credentials: VariableOrOptional[bool] = None + + etag: VariableOrOptional[str] = None + """ + The etag for the dashboard. Can be optionally provided on updates to ensure that the dashboard + has not been modified since the last read. + This field is excluded in List Dashboards responses. + """ + + file_path: VariableOrOptional[str] = None + + lifecycle: VariableOrOptional[Lifecycle] = None + """ + Settings that control the deployment lifecycle of the resource, such as preventing it from being destroyed. + """ + + lifecycle_state: VariableOrOptional[LifecycleState] = None + """ + The state of the dashboard resource. Used for tracking trashed status. + """ + + parent_path: VariableOrOptional[str] = None + """ + The workspace path of the folder containing the dashboard. Includes leading slash and no + trailing slash. + This field is excluded in List Dashboards responses. + """ + + path: VariableOrOptional[str] = None + """ + The workspace path of the dashboard asset, including the file name. + Exported dashboards always have the file extension `.lvdash.json`. + This field is excluded in List Dashboards responses. + """ + + permissions: VariableOrList[Permission] = field(default_factory=list) + """ + The permissions to apply to this resource. + """ + + serialized_dashboard: VariableOrOptional[Any] = None + """ + The contents of the dashboard in serialized string form. + This field is excluded in List Dashboards responses. + Use the [get dashboard API](https://docs.databricks.com/api/workspace/lakeview/get) + to retrieve an example response, which includes the `serialized_dashboard` field. + This field provides the structure of the JSON string that represents the dashboard's + layout and components. + """ + + update_time: VariableOrOptional[str] = None + """ + The timestamp of when the dashboard was last updated by the user. + This field is excluded in List Dashboards responses. + """ + + warehouse_id: VariableOrOptional[str] = None + """ + The warehouse ID used to run the dashboard. + """ + + @classmethod + def from_dict(cls, value: "DashboardDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "DashboardDict": + return _transform_to_json_value(self) # type:ignore + + +class DashboardDict(TypedDict, total=False): + """""" + + create_time: VariableOrOptional[str] + """ + The timestamp of when the dashboard was created. + """ + + dashboard_id: VariableOrOptional[str] + """ + UUID identifying the dashboard. + """ + + dataset_catalog: VariableOrOptional[str] + """ + Sets the default catalog for all datasets in this dashboard. When set, this overrides the catalog specified in individual dataset definitions. + """ + + dataset_schema: VariableOrOptional[str] + """ + Sets the default schema for all datasets in this dashboard. When set, this overrides the schema specified in individual dataset definitions. + """ + + display_name: VariableOrOptional[str] + """ + The display name of the dashboard. + """ + + embed_credentials: VariableOrOptional[bool] + + etag: VariableOrOptional[str] + """ + The etag for the dashboard. Can be optionally provided on updates to ensure that the dashboard + has not been modified since the last read. + This field is excluded in List Dashboards responses. + """ + + file_path: VariableOrOptional[str] + + lifecycle: VariableOrOptional[LifecycleParam] + """ + Settings that control the deployment lifecycle of the resource, such as preventing it from being destroyed. + """ + + lifecycle_state: VariableOrOptional[LifecycleStateParam] + """ + The state of the dashboard resource. Used for tracking trashed status. + """ + + parent_path: VariableOrOptional[str] + """ + The workspace path of the folder containing the dashboard. Includes leading slash and no + trailing slash. + This field is excluded in List Dashboards responses. + """ + + path: VariableOrOptional[str] + """ + The workspace path of the dashboard asset, including the file name. + Exported dashboards always have the file extension `.lvdash.json`. + This field is excluded in List Dashboards responses. + """ + + permissions: VariableOrList[PermissionParam] + """ + The permissions to apply to this resource. + """ + + serialized_dashboard: VariableOrOptional[Any] + """ + The contents of the dashboard in serialized string form. + This field is excluded in List Dashboards responses. + Use the [get dashboard API](https://docs.databricks.com/api/workspace/lakeview/get) + to retrieve an example response, which includes the `serialized_dashboard` field. + This field provides the structure of the JSON string that represents the dashboard's + layout and components. + """ + + update_time: VariableOrOptional[str] + """ + The timestamp of when the dashboard was last updated by the user. + This field is excluded in List Dashboards responses. + """ + + warehouse_id: VariableOrOptional[str] + """ + The warehouse ID used to run the dashboard. + """ + + +DashboardParam = DashboardDict | Dashboard diff --git a/python/databricks/bundles/dashboards/_models/lifecycle.py b/python/databricks/bundles/dashboards/_models/lifecycle.py new file mode 100644 index 00000000000..697776a198e --- /dev/null +++ b/python/databricks/bundles/dashboards/_models/lifecycle.py @@ -0,0 +1,40 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOrOptional + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Lifecycle: + """""" + + prevent_destroy: VariableOrOptional[bool] = None + """ + Lifecycle setting to prevent the resource from being destroyed. + """ + + @classmethod + def from_dict(cls, value: "LifecycleDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "LifecycleDict": + return _transform_to_json_value(self) # type:ignore + + +class LifecycleDict(TypedDict, total=False): + """""" + + prevent_destroy: VariableOrOptional[bool] + """ + Lifecycle setting to prevent the resource from being destroyed. + """ + + +LifecycleParam = LifecycleDict | Lifecycle diff --git a/python/databricks/bundles/dashboards/_models/lifecycle_state.py b/python/databricks/bundles/dashboards/_models/lifecycle_state.py new file mode 100644 index 00000000000..15654e77a1f --- /dev/null +++ b/python/databricks/bundles/dashboards/_models/lifecycle_state.py @@ -0,0 +1,12 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from enum import Enum +from typing import Literal + + +class LifecycleState(Enum): + ACTIVE = "ACTIVE" + TRASHED = "TRASHED" + + +LifecycleStateParam = Literal["ACTIVE", "TRASHED"] | LifecycleState diff --git a/python/databricks/bundles/dashboards/_models/permission.py b/python/databricks/bundles/dashboards/_models/permission.py new file mode 100644 index 00000000000..fb4ea994f6d --- /dev/null +++ b/python/databricks/bundles/dashboards/_models/permission.py @@ -0,0 +1,74 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from dataclasses import dataclass +from typing import TYPE_CHECKING, TypedDict + +from databricks.bundles.core._transform import _transform +from databricks.bundles.core._transform_to_json import _transform_to_json_value +from databricks.bundles.core._variable import VariableOr, VariableOrOptional +from databricks.bundles.dashboards._models.permission_level import ( + PermissionLevel, + PermissionLevelParam, +) + +if TYPE_CHECKING: + from typing_extensions import Self + + +@dataclass(kw_only=True) +class Permission: + """""" + + level: VariableOr[PermissionLevel] + """ + The permission level to apply. The allowed levels depend on the resource type. + """ + + group_name: VariableOrOptional[str] = None + """ + The name of the group granted the permission level. + """ + + service_principal_name: VariableOrOptional[str] = None + """ + The name of the service principal granted the permission level. + """ + + user_name: VariableOrOptional[str] = None + """ + The name of the user granted the permission level. + """ + + @classmethod + def from_dict(cls, value: "PermissionDict") -> "Self": + return _transform(cls, value) + + def as_dict(self) -> "PermissionDict": + return _transform_to_json_value(self) # type:ignore + + +class PermissionDict(TypedDict, total=False): + """""" + + level: VariableOr[PermissionLevelParam] + """ + The permission level to apply. The allowed levels depend on the resource type. + """ + + group_name: VariableOrOptional[str] + """ + The name of the group granted the permission level. + """ + + service_principal_name: VariableOrOptional[str] + """ + The name of the service principal granted the permission level. + """ + + user_name: VariableOrOptional[str] + """ + The name of the user granted the permission level. + """ + + +PermissionParam = PermissionDict | Permission diff --git a/python/databricks/bundles/dashboards/_models/permission_level.py b/python/databricks/bundles/dashboards/_models/permission_level.py new file mode 100644 index 00000000000..c6111911b29 --- /dev/null +++ b/python/databricks/bundles/dashboards/_models/permission_level.py @@ -0,0 +1,58 @@ +# Code generated by pydabs-codegen. DO NOT EDIT. + +from enum import Enum +from typing import Literal + + +class PermissionLevel(Enum): + """ + Permission level + """ + + CAN_MANAGE = "CAN_MANAGE" + CAN_RESTART = "CAN_RESTART" + CAN_ATTACH_TO = "CAN_ATTACH_TO" + IS_OWNER = "IS_OWNER" + CAN_MANAGE_RUN = "CAN_MANAGE_RUN" + CAN_VIEW = "CAN_VIEW" + CAN_READ = "CAN_READ" + CAN_RUN = "CAN_RUN" + CAN_EDIT = "CAN_EDIT" + CAN_USE = "CAN_USE" + CAN_MANAGE_STAGING_VERSIONS = "CAN_MANAGE_STAGING_VERSIONS" + CAN_MANAGE_PRODUCTION_VERSIONS = "CAN_MANAGE_PRODUCTION_VERSIONS" + CAN_EDIT_METADATA = "CAN_EDIT_METADATA" + CAN_VIEW_METADATA = "CAN_VIEW_METADATA" + CAN_BIND = "CAN_BIND" + CAN_QUERY = "CAN_QUERY" + CAN_MONITOR = "CAN_MONITOR" + CAN_CREATE = "CAN_CREATE" + CAN_MONITOR_ONLY = "CAN_MONITOR_ONLY" + CAN_CREATE_APP = "CAN_CREATE_APP" + + +PermissionLevelParam = ( + Literal[ + "CAN_MANAGE", + "CAN_RESTART", + "CAN_ATTACH_TO", + "IS_OWNER", + "CAN_MANAGE_RUN", + "CAN_VIEW", + "CAN_READ", + "CAN_RUN", + "CAN_EDIT", + "CAN_USE", + "CAN_MANAGE_STAGING_VERSIONS", + "CAN_MANAGE_PRODUCTION_VERSIONS", + "CAN_EDIT_METADATA", + "CAN_VIEW_METADATA", + "CAN_BIND", + "CAN_QUERY", + "CAN_MONITOR", + "CAN_CREATE", + "CAN_MONITOR_ONLY", + "CAN_CREATE_APP", + ] + | PermissionLevel +) diff --git a/python/databricks_tests/core/public_api.txt b/python/databricks_tests/core/public_api.txt index 222665796df..5463954e978 100644 --- a/python/databricks_tests/core/public_api.txt +++ b/python/databricks_tests/core/public_api.txt @@ -15,6 +15,7 @@ __all__ = [ VariableOrOptional, alert_mutator, catalog_mutator, + dashboard_mutator, job_mutator, load_resources_from_current_package_module, load_resources_from_module, @@ -67,6 +68,7 @@ class ResourceMutator(Generic): class Resources: def add_alert(self, resource_name: str, alert: AlertParam, *, location: Union[Location, None] = None) -> None def add_catalog(self, resource_name: str, catalog: CatalogParam, *, location: Union[Location, None] = None) -> None + def add_dashboard(self, resource_name: str, dashboard: DashboardParam, *, location: Union[Location, None] = None) -> None def add_diagnostic_error(self, msg: str, *, detail: Union[str, None] = None, path: Union[tuple[str, ...], None] = None, location: Union[Location, None] = None) -> None def add_diagnostic_warning(self, msg: str, *, detail: Union[str, None] = None, path: Union[tuple[str, ...], None] = None, location: Union[Location, None] = None) -> None def add_diagnostics(self, other: Diagnostics) -> None @@ -79,6 +81,7 @@ class Resources: def add_volume(self, resource_name: str, volume: VolumeParam, *, location: Union[Location, None] = None) -> None @property alerts -> dict[str, Alert] @property catalogs -> dict[str, Catalog] + @property dashboards -> dict[str, Dashboard] @property diagnostics -> Diagnostics @property jobs -> dict[str, Job] @property pipelines -> dict[str, Pipeline] @@ -110,6 +113,10 @@ def alert_mutator(function: Callable) -> ResourceMutator[Alert] @overload def catalog_mutator(function: Callable[[Catalog], Catalog]) -> ResourceMutator[Catalog] def catalog_mutator(function: Callable) -> ResourceMutator[Catalog] +@overload def dashboard_mutator(function: Callable[[Bundle, Dashboard], Dashboard]) -> ResourceMutator[Dashboard] +@overload def dashboard_mutator(function: Callable[[Dashboard], Dashboard]) -> ResourceMutator[Dashboard] +def dashboard_mutator(function: Callable) -> ResourceMutator[Dashboard] + @overload def job_mutator(function: Callable[[Bundle, Job], Job]) -> ResourceMutator[Job] @overload def job_mutator(function: Callable[[Job], Job]) -> ResourceMutator[Job] def job_mutator(function: Callable) -> ResourceMutator[Job] @@ -139,6 +146,7 @@ def volume_mutator(function: Callable) -> ResourceMutator[Volume] == _ResourceType.all() registry == singular_name=alert plural_name=alerts resource_type=Alert singular_name=catalog plural_name=catalogs resource_type=Catalog +singular_name=dashboard plural_name=dashboards resource_type=Dashboard singular_name=job plural_name=jobs resource_type=Job singular_name=pipeline plural_name=pipelines resource_type=Pipeline singular_name=schema plural_name=schemas resource_type=Schema diff --git a/python/databricks_tests/core/test_resources.py b/python/databricks_tests/core/test_resources.py index ee2ab7ec405..2b4a17ed9b2 100644 --- a/python/databricks_tests/core/test_resources.py +++ b/python/databricks_tests/core/test_resources.py @@ -17,6 +17,7 @@ Severity, alert_mutator, catalog_mutator, + dashboard_mutator, job_mutator, pipeline_mutator, schema_mutator, @@ -26,6 +27,7 @@ from databricks.bundles.core._resource import Resource from databricks.bundles.core._resource_mutator import ResourceMutator from databricks.bundles.core._resource_type import _ResourceType +from databricks.bundles.dashboards._models.dashboard import Dashboard from databricks.bundles.jobs._models.job import Job from databricks.bundles.pipelines._models.pipeline import Pipeline from databricks.bundles.schemas._models.schema import Schema @@ -128,6 +130,21 @@ class TestCase: ), resource_types[Catalog], ), + ( + TestCase( + add_resource=Resources.add_dashboard, + dict_example={ + "display_name": "My Dashboard", + "serialized_dashboard": {"pages": []}, + }, + dataclass_example=Dashboard( + display_name="My Dashboard", + serialized_dashboard={"pages": []}, + ), + mutator=dashboard_mutator, + ), + resource_types[Dashboard], + ), ] test_case_ids = [tpe.plural_name for _, tpe in test_cases] diff --git a/python/databricks_tests/core/test_transform.py b/python/databricks_tests/core/test_transform.py index b437d010c3a..ad5aa4a01e2 100644 --- a/python/databricks_tests/core/test_transform.py +++ b/python/databricks_tests/core/test_transform.py @@ -1,7 +1,7 @@ import math from dataclasses import dataclass from enum import Enum -from typing import Optional +from typing import Any, Optional import pytest @@ -34,6 +34,15 @@ class MyDataclass: color: Optional[Color] +@pytest.mark.parametrize("value", ["{}", {"pages": []}, [1], 1, True]) +def test_transform_any(value): + @dataclass + class Fake: + field: Any + + assert _transform(Fake, {"field": value}) == Fake(field=value) + + def test_transform_int(): @dataclass class Fake: