From 7612d1b9c775d99a79622085937e1aa814137781 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Tue, 2 Sep 2025 15:41:23 +0200 Subject: [PATCH 01/13] [Python] Move 'python' outside of experimental --- .../python/mutator-ordering/databricks.yml | 9 +- .../python/mutator-ordering/out.test.toml | 2 +- .../bundle/python/mutator-ordering/output.txt | 10 +- .../python/pipelines-support/databricks.yml | 11 +-- .../python/pipelines-support/out.test.toml | 2 +- .../python/pipelines-support/output.txt | 2 +- .../bundle/python/pipelines-support/test.toml | 6 -- .../python/resolve-variable/databricks.yml | 7 +- .../python/resolve-variable/out.test.toml | 2 +- .../python/resource-loading/databricks.yml | 9 +- .../python/resource-loading/out.test.toml | 2 +- .../bundle/python/resource-loading/output.txt | 8 +- .../restricted-execution/databricks.yml | 7 +- .../python/restricted-execution/out.test.toml | 2 +- acceptance/bundle/python/test.toml | 2 +- .../python/unicode-support/databricks.yml | 7 +- .../python/unicode-support/out.test.toml | 2 +- .../bundle/python/unicode-support/output.txt | 2 +- .../bundle/python/unicode-support/test.toml | 6 -- .../python/volumes-support/databricks.yml | 11 +-- .../python/volumes-support/out.test.toml | 2 +- .../bundle/python/volumes-support/output.txt | 2 +- .../bundle/python/volumes-support/test.toml | 6 -- .../output/my_jobs_as_code/databricks.yml | 15 ++- .../config/mutator/python/python_mutator.go | 80 +++++++++++++-- .../mutator/python/python_mutator_test.go | 97 +++++++++++++++++++ bundle/config/root.go | 3 + bundle/internal/schema/annotations.yml | 3 + bundle/schema/jsonschema.json | 3 + .../{{.project_name}}/databricks.yml.tmpl | 15 ++- 30 files changed, 239 insertions(+), 96 deletions(-) diff --git a/acceptance/bundle/python/mutator-ordering/databricks.yml b/acceptance/bundle/python/mutator-ordering/databricks.yml index 79ca24c848..8bdc14133d 100644 --- a/acceptance/bundle/python/mutator-ordering/databricks.yml +++ b/acceptance/bundle/python/mutator-ordering/databricks.yml @@ -3,11 +3,10 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - mutators: - - "mutators:add_task_1" - - "mutators:add_task_2" +python: + mutators: + - "mutators:add_task_1" + - "mutators:add_task_2" resources: jobs: diff --git a/acceptance/bundle/python/mutator-ordering/out.test.toml b/acceptance/bundle/python/mutator-ordering/out.test.toml index b3771f289d..5dceae8b60 100644 --- a/acceptance/bundle/python/mutator-ordering/out.test.toml +++ b/acceptance/bundle/python/mutator-ordering/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with databricks-bundles==0.7.3", "--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/mutator-ordering/output.txt b/acceptance/bundle/python/mutator-ordering/output.txt index 4381849f63..e2af260f3c 100644 --- a/acceptance/bundle/python/mutator-ordering/output.txt +++ b/acceptance/bundle/python/mutator-ordering/output.txt @@ -47,7 +47,7 @@ 3 ] ], - "experimental": [ + "python": [ [ 0, 7, @@ -57,28 +57,28 @@ "resources": [ [ 0, - 13, + 12, 3 ] ], "resources.jobs": [ [ 0, - 14, + 13, 5 ] ], "resources.jobs.my_job": [ [ 0, - 15, + 14, 7 ] ], "resources.jobs.my_job.tasks": [ [ 0, - 15, + 14, 14 ] ], diff --git a/acceptance/bundle/python/pipelines-support/databricks.yml b/acceptance/bundle/python/pipelines-support/databricks.yml index 2075b171f8..a6398686e9 100644 --- a/acceptance/bundle/python/pipelines-support/databricks.yml +++ b/acceptance/bundle/python/pipelines-support/databricks.yml @@ -3,12 +3,11 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - resources: - - "resources:load_resources" - mutators: - - "mutators:update_pipeline" +python: + resources: + - "resources:load_resources" + mutators: + - "mutators:update_pipeline" resources: pipelines: diff --git a/acceptance/bundle/python/pipelines-support/out.test.toml b/acceptance/bundle/python/pipelines-support/out.test.toml index 346faf6401..5dceae8b60 100644 --- a/acceptance/bundle/python/pipelines-support/out.test.toml +++ b/acceptance/bundle/python/pipelines-support/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/pipelines-support/output.txt b/acceptance/bundle/python/pipelines-support/output.txt index deeef33a12..cae9c1807f 100644 --- a/acceptance/bundle/python/pipelines-support/output.txt +++ b/acceptance/bundle/python/pipelines-support/output.txt @@ -1,5 +1,5 @@ ->>> uv run --with-requirements requirements-latest.txt --no-cache -q [CLI] bundle validate --output json +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json { "experimental": { "python": { diff --git a/acceptance/bundle/python/pipelines-support/test.toml b/acceptance/bundle/python/pipelines-support/test.toml index b43fe72b07..95d0b9d9f9 100644 --- a/acceptance/bundle/python/pipelines-support/test.toml +++ b/acceptance/bundle/python/pipelines-support/test.toml @@ -1,8 +1,2 @@ Local = true Cloud = false # tests don't interact with APIs - -[EnvMatrix] -UV_ARGS = [ - # pipelines are only supported in the latest version of the wheel - "--with-requirements requirements-latest.txt --no-cache", -] diff --git a/acceptance/bundle/python/resolve-variable/databricks.yml b/acceptance/bundle/python/resolve-variable/databricks.yml index 1775202701..56ca480a8f 100644 --- a/acceptance/bundle/python/resolve-variable/databricks.yml +++ b/acceptance/bundle/python/resolve-variable/databricks.yml @@ -3,10 +3,9 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - resources: - - "resources:load_resources" +python: + resources: + - "resources:load_resources" variables: string_variable: diff --git a/acceptance/bundle/python/resolve-variable/out.test.toml b/acceptance/bundle/python/resolve-variable/out.test.toml index b3771f289d..5dceae8b60 100644 --- a/acceptance/bundle/python/resolve-variable/out.test.toml +++ b/acceptance/bundle/python/resolve-variable/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with databricks-bundles==0.7.3", "--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/resource-loading/databricks.yml b/acceptance/bundle/python/resource-loading/databricks.yml index b35128260a..4ab7c8d5dd 100644 --- a/acceptance/bundle/python/resource-loading/databricks.yml +++ b/acceptance/bundle/python/resource-loading/databricks.yml @@ -3,11 +3,10 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - resources: - - "resources:load_resources_1" - - "resources:load_resources_2" +python: + resources: + - "resources:load_resources_1" + - "resources:load_resources_2" resources: jobs: diff --git a/acceptance/bundle/python/resource-loading/out.test.toml b/acceptance/bundle/python/resource-loading/out.test.toml index b3771f289d..5dceae8b60 100644 --- a/acceptance/bundle/python/resource-loading/out.test.toml +++ b/acceptance/bundle/python/resource-loading/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with databricks-bundles==0.7.3", "--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/resource-loading/output.txt b/acceptance/bundle/python/resource-loading/output.txt index 72b19078a7..c51c21151f 100644 --- a/acceptance/bundle/python/resource-loading/output.txt +++ b/acceptance/bundle/python/resource-loading/output.txt @@ -65,7 +65,7 @@ 3 ] ], - "experimental": [ + "python": [ [ 0, 7, @@ -75,14 +75,14 @@ "resources": [ [ 0, - 13, + 12, 3 ] ], "resources.jobs": [ [ 0, - 14, + 13, 5 ] ], @@ -103,7 +103,7 @@ "resources.jobs.my_job_3": [ [ 0, - 15, + 14, 7 ] ], diff --git a/acceptance/bundle/python/restricted-execution/databricks.yml b/acceptance/bundle/python/restricted-execution/databricks.yml index cedcbf8e96..abd39a4027 100644 --- a/acceptance/bundle/python/restricted-execution/databricks.yml +++ b/acceptance/bundle/python/restricted-execution/databricks.yml @@ -3,10 +3,9 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - mutators: - - "mutators:read_envs" +python: + mutators: + - "mutators:read_envs" resources: jobs: diff --git a/acceptance/bundle/python/restricted-execution/out.test.toml b/acceptance/bundle/python/restricted-execution/out.test.toml index b3771f289d..5dceae8b60 100644 --- a/acceptance/bundle/python/restricted-execution/out.test.toml +++ b/acceptance/bundle/python/restricted-execution/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with databricks-bundles==0.7.3", "--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/test.toml b/acceptance/bundle/python/test.toml index 08b731953d..811e486970 100644 --- a/acceptance/bundle/python/test.toml +++ b/acceptance/bundle/python/test.toml @@ -7,6 +7,6 @@ Ignore = [ [EnvMatrix] UV_ARGS = [ - "--with databricks-bundles==0.7.3", + "--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache", ] diff --git a/acceptance/bundle/python/unicode-support/databricks.yml b/acceptance/bundle/python/unicode-support/databricks.yml index e50f29bab7..6e36a21a4e 100644 --- a/acceptance/bundle/python/unicode-support/databricks.yml +++ b/acceptance/bundle/python/unicode-support/databricks.yml @@ -3,10 +3,9 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - resources: - - "resources:load_resources" +python: + resources: + - "resources:load_resources" resources: jobs: diff --git a/acceptance/bundle/python/unicode-support/out.test.toml b/acceptance/bundle/python/unicode-support/out.test.toml index 346faf6401..5dceae8b60 100644 --- a/acceptance/bundle/python/unicode-support/out.test.toml +++ b/acceptance/bundle/python/unicode-support/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/unicode-support/output.txt b/acceptance/bundle/python/unicode-support/output.txt index 34d2bc6baa..c4f59ef7d4 100644 --- a/acceptance/bundle/python/unicode-support/output.txt +++ b/acceptance/bundle/python/unicode-support/output.txt @@ -1,5 +1,5 @@ ->>> uv run --with-requirements requirements-latest.txt --no-cache -q [CLI] bundle validate --output json +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json Warning: This is a warning message with unicode characters: 🔥🔥🔥 { diff --git a/acceptance/bundle/python/unicode-support/test.toml b/acceptance/bundle/python/unicode-support/test.toml index d0ad877fb5..95d0b9d9f9 100644 --- a/acceptance/bundle/python/unicode-support/test.toml +++ b/acceptance/bundle/python/unicode-support/test.toml @@ -1,8 +1,2 @@ Local = true Cloud = false # tests don't interact with APIs - -[EnvMatrix] -UV_ARGS = [ - # only fixed in the latest version - "--with-requirements requirements-latest.txt --no-cache", -] diff --git a/acceptance/bundle/python/volumes-support/databricks.yml b/acceptance/bundle/python/volumes-support/databricks.yml index 93fb3c698e..a93a535342 100644 --- a/acceptance/bundle/python/volumes-support/databricks.yml +++ b/acceptance/bundle/python/volumes-support/databricks.yml @@ -3,12 +3,11 @@ bundle: sync: {paths: []} # don't need to copy files -experimental: - python: - resources: - - "resources:load_resources" - mutators: - - "mutators:update_volume" +python: + resources: + - "resources:load_resources" + mutators: + - "mutators:update_volume" resources: volumes: diff --git a/acceptance/bundle/python/volumes-support/out.test.toml b/acceptance/bundle/python/volumes-support/out.test.toml index 346faf6401..5dceae8b60 100644 --- a/acceptance/bundle/python/volumes-support/out.test.toml +++ b/acceptance/bundle/python/volumes-support/out.test.toml @@ -3,4 +3,4 @@ Cloud = false [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] - UV_ARGS = ["--with-requirements requirements-latest.txt --no-cache"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/volumes-support/output.txt b/acceptance/bundle/python/volumes-support/output.txt index 9320e9b78c..e2b6d8771e 100644 --- a/acceptance/bundle/python/volumes-support/output.txt +++ b/acceptance/bundle/python/volumes-support/output.txt @@ -1,5 +1,5 @@ ->>> uv run --with-requirements requirements-latest.txt --no-cache -q [CLI] bundle validate --output json +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json { "experimental": { "python": { diff --git a/acceptance/bundle/python/volumes-support/test.toml b/acceptance/bundle/python/volumes-support/test.toml index b43fe72b07..95d0b9d9f9 100644 --- a/acceptance/bundle/python/volumes-support/test.toml +++ b/acceptance/bundle/python/volumes-support/test.toml @@ -1,8 +1,2 @@ Local = true Cloud = false # tests don't interact with APIs - -[EnvMatrix] -UV_ARGS = [ - # pipelines are only supported in the latest version of the wheel - "--with-requirements requirements-latest.txt --no-cache", -] diff --git a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml index b09d99917e..c981da5ad7 100644 --- a/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml +++ b/acceptance/bundle/templates/experimental-jobs-as-code/output/my_jobs_as_code/databricks.yml @@ -5,14 +5,13 @@ bundle: uuid: [UUID] databricks_cli_version: ">= 0.248.0" -experimental: - python: - # Activate virtual environment before loading resources defined in Python. - # If disabled, defaults to using the Python interpreter available in the current shell. - venv_path: .venv - # Functions called to load resources defined in Python. See resources/__init__.py - resources: - - "resources:load_resources" +python: + # Activate virtual environment before loading resources defined in Python. + # If disabled, defaults to using the Python interpreter available in the current shell. + venv_path: .venv + # Functions called to load resources defined in Python. See resources/__init__.py + resources: + - "resources:load_resources" artifacts: default: diff --git a/bundle/config/mutator/python/python_mutator.go b/bundle/config/mutator/python/python_mutator.go index 3d9e8db2bf..b26033264c 100644 --- a/bundle/config/mutator/python/python_mutator.go +++ b/bundle/config/mutator/python/python_mutator.go @@ -111,34 +111,98 @@ type runPythonMutatorOpts struct { func getOpts(b *bundle.Bundle, phase phase) (opts, error) { experimental := b.Config.Experimental if experimental == nil { - return opts{}, nil + experimental = &config.Experimental{} } // using reflect.DeepEquals in case we add more fields pydabsEnabled := !reflect.DeepEqual(experimental.PyDABs, config.PyDABs{}) - pythonEnabled := !reflect.DeepEqual(experimental.Python, config.Python{}) + experimentalPythonEnabled := !reflect.DeepEqual(experimental.Python, config.Python{}) + pythonEnabled := !reflect.DeepEqual(b.Config.Python, config.Python{}) if pydabsEnabled { return opts{}, errors.New("experimental/pydabs is deprecated, use experimental/python instead (https://docs.databricks.com/dev-tools/bundles/python)") } - if pythonEnabled { - // don't execute for phases for 'pydabs' section - if phase == PythonMutatorPhaseLoadResources || phase == PythonMutatorPhaseApplyMutators { + if experimentalPythonEnabled && pythonEnabled { + if !reflect.DeepEqual(experimental.Python, b.Config.Python) { + return opts{}, errors.New("'experimental/python' and 'python' configuration properties are mutually exclusive, use only 'python'") + } else { return opts{ enabled: true, - venvPath: experimental.Python.VEnvPath, + venvPath: b.Config.Python.VEnvPath, loadLocations: true, }, nil - } else { - return opts{}, nil } + } else if pythonEnabled { + return opts{ + enabled: true, + venvPath: b.Config.Python.VEnvPath, + loadLocations: true, + }, nil + } else if experimentalPythonEnabled { + return opts{ + enabled: true, + venvPath: experimental.Python.VEnvPath, + loadLocations: true, + }, nil } else { return opts{}, nil } } +// applyBackwardsCompatibilityFixes applies fixes to bundle configuration +// so that older version of databricks-bundles Python library continue to see +// 'experimental.python' section even if bundle configuration uses 'python' section. +// +// This way upgrades are less risky if CLI version doesn't match Python package version. +// +// Later version of Python package should reject 'experimental.python' unless 'python' section +// is set to equivalent value, and after that reject 'experimental.python' altogether. +func applyBackwardsCompatibilityFixes(b *bundle.Bundle) error { + return b.Config.Mutate(func(value dyn.Value) (dyn.Value, error) { + outValue := value + + pythonValue, _ := dyn.Get(outValue, "python") + if !pythonValue.IsValid() { + // if 'python' section doesn't exist, nothing to do + return value, nil + } + + experimentalPythonValue, _ := dyn.Get(outValue, "experimental.python") + + if experimentalPythonValue.IsValid() { + // if 'experimental.python' section exists, nothing to do + return value, nil + } + + experimentalValue, _ := dyn.Get(outValue, "experimental") + if !experimentalValue.IsValid() { + updated, err := dyn.Set(outValue, "experimental", dyn.NewValue(map[string]dyn.Value{}, nil)) + if err != nil { + return dyn.InvalidValue, fmt.Errorf("failed to create 'experimental' section: %w", err) + } else { + outValue = updated + } + } + + // move 'python' section to 'experimental.python' + updated, err := dyn.Set(outValue, "experimental.python", pythonValue) + if err != nil { + return dyn.InvalidValue, fmt.Errorf("failed to set 'experimental.python' section: %w", err) + } else { + outValue = updated + } + + return outValue, nil + }) +} + func (m *pythonMutator) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + err := applyBackwardsCompatibilityFixes(b) + if err != nil { + return diag.FromErr(fmt.Errorf("failed to apply backwards compatibility fixes: %w", err)) + } + opts, err := getOpts(b, m.phase) if err != nil { return diag.Errorf("failed to apply python mutator: %s", err) diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index df74c69985..db52ace807 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -304,6 +304,22 @@ experimental: } func TestGetOps_Python(t *testing.T) { + actual, err := getOpts(&bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + Resources: []string{ + "resources:load_resources", + }, + }, + }, + }, PythonMutatorPhaseLoadResources) + + assert.NoError(t, err) + assert.Equal(t, opts{venvPath: ".venv", enabled: true, loadLocations: true}, actual) +} + +func TestGetOps_ExperimentalPython(t *testing.T) { actual, err := getOpts(&bundle.Bundle{ Config: config.Root{ Experimental: &config.Experimental{ @@ -321,6 +337,52 @@ func TestGetOps_Python(t *testing.T) { assert.Equal(t, opts{venvPath: ".venv", enabled: true, loadLocations: true}, actual) } +func TestGetOps_BothPythonEqual(t *testing.T) { + actual, err := getOpts(&bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + Resources: []string{ + "resources:load_resources", + }, + }, + Experimental: &config.Experimental{ + Python: config.Python{ + VEnvPath: ".venv", + Resources: []string{ + "resources:load_resources", + }, + }, + }, + }, + }, PythonMutatorPhaseLoadResources) + + assert.NoError(t, err) + assert.Equal(t, opts{venvPath: ".venv", enabled: true, loadLocations: true}, actual) +} + +func TestGetOps_BothPythonNotEqual(t *testing.T) { + _, err := getOpts(&bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + Resources: []string{ + "resources:load_resources", + }, + }, + Experimental: &config.Experimental{ + Python: config.Python{ + Resources: []string{ + "resources:load_resources", + }, + }, + }, + }, + }, PythonMutatorPhaseLoadResources) + + assert.Error(t, err) +} + func TestGetOps_PyDABs(t *testing.T) { _, err := getOpts(&bundle.Bundle{ Config: config.Root{ @@ -342,6 +404,41 @@ func TestGetOps_empty(t *testing.T) { assert.Equal(t, opts{enabled: false}, actual) } +func TestApplyBackwardCompatibilityFixes(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + }, + }, + } + + err := applyBackwardsCompatibilityFixes(b) + + assert.NoError(t, err) + assert.Equal(t, config.Python{VEnvPath: ".venv"}, b.Config.Experimental.Python) +} + +func TestApplyBackwardCompatibilityFixes_unchanged(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + }, + Experimental: &config.Experimental{ + Python: config.Python{ + VEnvPath: "should_remain_unchanged", + }, + }, + }, + } + + err := applyBackwardsCompatibilityFixes(b) + + assert.NoError(t, err) + assert.Equal(t, config.Python{VEnvPath: "should_remain_unchanged"}, b.Config.Experimental.Python) +} + func TestLoadDiagnosticsFile_nonExistent(t *testing.T) { // this is an important behaviour, see loadDiagnosticsFile docstring _, err := loadDiagnosticsFile("non_existent_file.json") diff --git a/bundle/config/root.go b/bundle/config/root.go index c468cdf3df..57f2d92be5 100644 --- a/bundle/config/root.go +++ b/bundle/config/root.go @@ -82,6 +82,9 @@ type Root struct { Locations *dynloc.Locations `json:"__locations,omitempty" bundle:"internal"` Scripts map[string]Script `json:"scripts,omitempty"` + + // Python configures loading of Python code defined with 'databricks-bundles' package. + Python Python `json:"python,omitempty"` } // Load loads the bundle configuration file at the specified path. diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index 09b28d1586..85ab74e11d 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -289,6 +289,9 @@ github.com/databricks/cli/bundle/config.Root: Defines bundle deployment presets. "markdown_description": |- Defines bundle deployment presets. See [\_](/dev-tools/bundles/deployment-modes.md#presets). + "python": + "description": |- + PLACEHOLDER "resources": "description": |- A Map that defines the resources for the bundle, where each key is the name of the resource, and the value is a Map that defines the resource. diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index 681398872c..4d6fa20fec 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -10595,6 +10595,9 @@ "$ref": "#/$defs/github.com/databricks/cli/bundle/config.Presets", "markdownDescription": "Defines bundle deployment presets. See [presets](https://docs.databricks.com/dev-tools/bundles/deployment-modes.html#presets)." }, + "python": { + "$ref": "#/$defs/github.com/databricks/cli/bundle/config.Python" + }, "resources": { "description": "A Map that defines the resources for the bundle, where each key is the name of the resource, and the value is a Map that defines the resource.", "$ref": "#/$defs/github.com/databricks/cli/bundle/config.Resources", diff --git a/libs/template/templates/experimental-jobs-as-code/template/{{.project_name}}/databricks.yml.tmpl b/libs/template/templates/experimental-jobs-as-code/template/{{.project_name}}/databricks.yml.tmpl index ec91f6d964..7b3dec97a0 100644 --- a/libs/template/templates/experimental-jobs-as-code/template/{{.project_name}}/databricks.yml.tmpl +++ b/libs/template/templates/experimental-jobs-as-code/template/{{.project_name}}/databricks.yml.tmpl @@ -5,14 +5,13 @@ bundle: uuid: {{bundle_uuid}} databricks_cli_version: ">= 0.248.0" -experimental: - python: - # Activate virtual environment before loading resources defined in Python. - # If disabled, defaults to using the Python interpreter available in the current shell. - venv_path: .venv - # Functions called to load resources defined in Python. See resources/__init__.py - resources: - - "resources:load_resources" +python: + # Activate virtual environment before loading resources defined in Python. + # If disabled, defaults to using the Python interpreter available in the current shell. + venv_path: .venv + # Functions called to load resources defined in Python. See resources/__init__.py + resources: + - "resources:load_resources" {{ if .include_python -}} artifacts: From cf22af479ba91a74c58671d7eb951e1fb1e27ab0 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Mon, 8 Sep 2025 10:43:04 +0200 Subject: [PATCH 02/13] Add more acceptenace tests --- .../databricks.yml | 15 ++++ .../out.test.toml | 6 ++ .../output.txt | 87 +++++++++++++++++++ .../resources.py | 11 +++ .../script | 8 ++ .../databricks.yml | 15 ++++ .../out.test.toml | 6 ++ .../output.txt | 20 +++++ .../script | 8 ++ .../experimental-compatibility/databricks.yml | 13 +++ .../experimental-compatibility/out.test.toml | 6 ++ .../experimental-compatibility/output.txt | 80 +++++++++++++++++ .../experimental-compatibility/resources.py | 11 +++ .../python/experimental-compatibility/script | 8 ++ 14 files changed, 294 insertions(+) create mode 100644 acceptance/bundle/python/experimental-compatibility-both-equal/databricks.yml create mode 100644 acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml create mode 100644 acceptance/bundle/python/experimental-compatibility-both-equal/output.txt create mode 100644 acceptance/bundle/python/experimental-compatibility-both-equal/resources.py create mode 100644 acceptance/bundle/python/experimental-compatibility-both-equal/script create mode 100644 acceptance/bundle/python/experimental-compatibility-both-error/databricks.yml create mode 100644 acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml create mode 100644 acceptance/bundle/python/experimental-compatibility-both-error/output.txt create mode 100644 acceptance/bundle/python/experimental-compatibility-both-error/script create mode 100644 acceptance/bundle/python/experimental-compatibility/databricks.yml create mode 100644 acceptance/bundle/python/experimental-compatibility/out.test.toml create mode 100644 acceptance/bundle/python/experimental-compatibility/output.txt create mode 100644 acceptance/bundle/python/experimental-compatibility/resources.py create mode 100644 acceptance/bundle/python/experimental-compatibility/script diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/databricks.yml b/acceptance/bundle/python/experimental-compatibility-both-equal/databricks.yml new file mode 100644 index 0000000000..3619351bf7 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/databricks.yml @@ -0,0 +1,15 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +# We DO NOT allow specifying both 'python' and 'experimental/python' if they are not equal. + +experimental: + python: + resources: + - "resources:load_resources" + +python: + resources: + - "resources:load_resources" diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml b/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml new file mode 100644 index 0000000000..4ba6c15f3a --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt b/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt new file mode 100644 index 0000000000..9001bededd --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt @@ -0,0 +1,87 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json --include-locations +{ + "experimental": { + "python": { + "resources": [ + "resources:load_resources" + ] + } + }, + "resources": { + "jobs": { + "my_job": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + }, + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "My Job", + "permissions": [], + "queue": { + "enabled": true + } + } + } + }, + "__locations": { + "files": [ + "__generated_by_python__.yml", + "databricks.yml", + "resources.py" + ], + "locations": { + "bundle": [ + [ + 1, + 2, + 3 + ] + ], + "experimental": [ + [ + 1, + 9, + 3 + ] + ], + "python": [ + [ + 1, + 14, + 3 + ] + ], + "resources": [ + [ + 0, + 1, + 846 + ] + ], + "resources.jobs": [ + [ + 0, + 1, + 855 + ] + ], + "resources.jobs.my_job": [ + [ + 2, + 6, + 1 + ] + ], + "sync": [ + [ + 1, + 4, + 7 + ] + ] + } + } +} diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/resources.py b/acceptance/bundle/python/experimental-compatibility-both-equal/resources.py new file mode 100644 index 0000000000..b633ad17be --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/resources.py @@ -0,0 +1,11 @@ +from databricks.bundles.core import Resources + + +def load_resources() -> Resources: + resources = Resources() + resources.add_job( + resource_name="my_job", + job={"name": "My Job"}, + ) + + return resources diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/script b/acceptance/bundle/python/experimental-compatibility-both-equal/script new file mode 100644 index 0000000000..1ac6863729 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/script @@ -0,0 +1,8 @@ +echo "$DATABRICKS_BUNDLES_WHEEL" > "requirements-latest.txt" + +# each job should record location where add_job function was called + +trace uv run $UV_ARGS -q $CLI bundle validate --output json --include-locations | \ + jq "pick(.experimental.python, .resources, .__locations.files, .__locations.locations)" + +rm -fr .databricks __pycache__ diff --git a/acceptance/bundle/python/experimental-compatibility-both-error/databricks.yml b/acceptance/bundle/python/experimental-compatibility-both-error/databricks.yml new file mode 100644 index 0000000000..f322a31be8 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-error/databricks.yml @@ -0,0 +1,15 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +# We allow specifying both 'python' and 'experimental/python' if they are equal. + +experimental: + python: + resources: + - "resources:load_resources_1" + +python: + resources: + - "resources:load_resources_2" diff --git a/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml b/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml new file mode 100644 index 0000000000..4ba6c15f3a --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/experimental-compatibility-both-error/output.txt b/acceptance/bundle/python/experimental-compatibility-both-error/output.txt new file mode 100644 index 0000000000..76074acf65 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-error/output.txt @@ -0,0 +1,20 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json --include-locations +Error: failed to apply python mutator: 'experimental/python' and 'python' configuration properties are mutually exclusive, use only 'python' + +{ + "experimental": { + "python": { + "resources": [ + "resources:load_resources_1" + ] + } + }, + "resources": null, + "__locations": { + "files": null, + "locations": null + } +} + +Exit code: 1 diff --git a/acceptance/bundle/python/experimental-compatibility-both-error/script b/acceptance/bundle/python/experimental-compatibility-both-error/script new file mode 100644 index 0000000000..1ac6863729 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility-both-error/script @@ -0,0 +1,8 @@ +echo "$DATABRICKS_BUNDLES_WHEEL" > "requirements-latest.txt" + +# each job should record location where add_job function was called + +trace uv run $UV_ARGS -q $CLI bundle validate --output json --include-locations | \ + jq "pick(.experimental.python, .resources, .__locations.files, .__locations.locations)" + +rm -fr .databricks __pycache__ diff --git a/acceptance/bundle/python/experimental-compatibility/databricks.yml b/acceptance/bundle/python/experimental-compatibility/databricks.yml new file mode 100644 index 0000000000..598b81ef50 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility/databricks.yml @@ -0,0 +1,13 @@ +bundle: + name: my_project + +sync: {paths: []} # don't need to copy files + +# We keep support for experimental section for backward-compatibility until it's fully deprecated. +# +# The next step until deletion is returning an error when 'experimental/python' is used. + +experimental: + python: + resources: + - "resources:load_resources" \ No newline at end of file diff --git a/acceptance/bundle/python/experimental-compatibility/out.test.toml b/acceptance/bundle/python/experimental-compatibility/out.test.toml new file mode 100644 index 0000000000..4ba6c15f3a --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility/out.test.toml @@ -0,0 +1,6 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"] + UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/experimental-compatibility/output.txt b/acceptance/bundle/python/experimental-compatibility/output.txt new file mode 100644 index 0000000000..3f6f5b3da1 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility/output.txt @@ -0,0 +1,80 @@ + +>>> uv run [UV_ARGS] -q [CLI] bundle validate --output json --include-locations +{ + "experimental": { + "python": { + "resources": [ + "resources:load_resources" + ] + } + }, + "resources": { + "jobs": { + "my_job": { + "deployment": { + "kind": "BUNDLE", + "metadata_file_path": "/Workspace/Users/[USERNAME]/.bundle/my_project/default/state/metadata.json" + }, + "edit_mode": "UI_LOCKED", + "format": "MULTI_TASK", + "max_concurrent_runs": 1, + "name": "My Job", + "permissions": [], + "queue": { + "enabled": true + } + } + } + }, + "__locations": { + "files": [ + "__generated_by_python__.yml", + "databricks.yml", + "resources.py" + ], + "locations": { + "bundle": [ + [ + 1, + 2, + 3 + ] + ], + "experimental": [ + [ + 1, + 11, + 3 + ] + ], + "resources": [ + [ + 0, + 1, + 791 + ] + ], + "resources.jobs": [ + [ + 0, + 1, + 800 + ] + ], + "resources.jobs.my_job": [ + [ + 2, + 6, + 1 + ] + ], + "sync": [ + [ + 1, + 4, + 7 + ] + ] + } + } +} diff --git a/acceptance/bundle/python/experimental-compatibility/resources.py b/acceptance/bundle/python/experimental-compatibility/resources.py new file mode 100644 index 0000000000..b633ad17be --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility/resources.py @@ -0,0 +1,11 @@ +from databricks.bundles.core import Resources + + +def load_resources() -> Resources: + resources = Resources() + resources.add_job( + resource_name="my_job", + job={"name": "My Job"}, + ) + + return resources diff --git a/acceptance/bundle/python/experimental-compatibility/script b/acceptance/bundle/python/experimental-compatibility/script new file mode 100644 index 0000000000..1ac6863729 --- /dev/null +++ b/acceptance/bundle/python/experimental-compatibility/script @@ -0,0 +1,8 @@ +echo "$DATABRICKS_BUNDLES_WHEEL" > "requirements-latest.txt" + +# each job should record location where add_job function was called + +trace uv run $UV_ARGS -q $CLI bundle validate --output json --include-locations | \ + jq "pick(.experimental.python, .resources, .__locations.files, .__locations.locations)" + +rm -fr .databricks __pycache__ From e881b5f74c0df098a8f3be2653fd748d279c6985 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Mon, 8 Sep 2025 10:48:41 +0200 Subject: [PATCH 03/13] Clarify UV_ARGS comments --- acceptance/bundle/python/pipelines-support/test.toml | 2 -- acceptance/bundle/python/test.toml | 7 ++++--- acceptance/bundle/python/unicode-support/test.toml | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 acceptance/bundle/python/pipelines-support/test.toml delete mode 100644 acceptance/bundle/python/unicode-support/test.toml diff --git a/acceptance/bundle/python/pipelines-support/test.toml b/acceptance/bundle/python/pipelines-support/test.toml deleted file mode 100644 index 95d0b9d9f9..0000000000 --- a/acceptance/bundle/python/pipelines-support/test.toml +++ /dev/null @@ -1,2 +0,0 @@ -Local = true -Cloud = false # tests don't interact with APIs diff --git a/acceptance/bundle/python/test.toml b/acceptance/bundle/python/test.toml index 811e486970..2b3186a5ef 100644 --- a/acceptance/bundle/python/test.toml +++ b/acceptance/bundle/python/test.toml @@ -1,12 +1,13 @@ -# Tests don't interact with APIs - Ignore = [ - # created by scripts to install the latest wheel with UV_ARGS + # requirements-latest.txt is created by scripts to install databricks-bundles from the same commit "requirements-latest.txt", ] [EnvMatrix] UV_ARGS = [ + # pin to a specific version from the past to avoid issues with unintended breaking changes + # we can't expect CLI and databricks-bundles versions to be always in sync "--with databricks-bundles==0.266.0", + # requirements-latest.txt is created by scripts install databricks-bundles from the same commit "--with-requirements requirements-latest.txt --no-cache", ] diff --git a/acceptance/bundle/python/unicode-support/test.toml b/acceptance/bundle/python/unicode-support/test.toml deleted file mode 100644 index 95d0b9d9f9..0000000000 --- a/acceptance/bundle/python/unicode-support/test.toml +++ /dev/null @@ -1,2 +0,0 @@ -Local = true -Cloud = false # tests don't interact with APIs From 15a20d68f4abc801241079d0903d18f4c22891f7 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Mon, 8 Sep 2025 15:46:17 +0200 Subject: [PATCH 04/13] Fix lint --- .../bundle/python/experimental-compatibility/databricks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/bundle/python/experimental-compatibility/databricks.yml b/acceptance/bundle/python/experimental-compatibility/databricks.yml index 598b81ef50..ce6b257376 100644 --- a/acceptance/bundle/python/experimental-compatibility/databricks.yml +++ b/acceptance/bundle/python/experimental-compatibility/databricks.yml @@ -10,4 +10,4 @@ sync: {paths: []} # don't need to copy files experimental: python: resources: - - "resources:load_resources" \ No newline at end of file + - "resources:load_resources" From 68e1b7fd08d0473f0e8e74a9a0f71b74bc24bf8b Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 14:40:17 +0200 Subject: [PATCH 05/13] Few more fixes --- experimental/python/databricks/bundles/core/_resources.py | 7 +++---- libs/template/template.go | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/experimental/python/databricks/bundles/core/_resources.py b/experimental/python/databricks/bundles/core/_resources.py index 698f2a5ca4..9be121718e 100644 --- a/experimental/python/databricks/bundles/core/_resources.py +++ b/experimental/python/databricks/bundles/core/_resources.py @@ -27,10 +27,9 @@ class Resources: .. code-block:: yaml - experimental: - python: - resources: - - "resources:load_resources" + python: + resources: + - "resources:load_resources" `load_resources` function can be implemented using built-in functions: diff --git a/libs/template/template.go b/libs/template/template.go index 7f392bb217..a5c985f531 100644 --- a/libs/template/template.go +++ b/libs/template/template.go @@ -86,7 +86,7 @@ var databricksTemplates = []Template{ }, { name: Pydabs, - hidden: true, + hidden: false, description: "A variant of 'default-python' template that defines resources in Python instead of YAML", Reader: &overridingReader{ underlying: &builtinReader{name: string(DefaultPython)}, @@ -101,6 +101,7 @@ var databricksTemplates = []Template{ }, { name: ExperimentalJobsAsCode, + hidden: true, description: "Jobs as code template (experimental)", Reader: &builtinReader{name: string(ExperimentalJobsAsCode)}, Writer: &writerWithFullTelemetry{defaultWriter: defaultWriter{name: ExperimentalJobsAsCode}}, From a0b0f66193730467e76fdf1055c4b8139d7af611 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 14:40:45 +0200 Subject: [PATCH 06/13] Update template --- .../{{.project_name}}/databricks.yml.tmpl | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl b/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl index 30ee774ee2..e9b5fdaea8 100644 --- a/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl +++ b/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl @@ -7,14 +7,13 @@ bundle: name: {{.project_name}} uuid: {{bundle_uuid}} {{- if $with_pydabs }} -experimental: - python: - # Activate virtual environment before loading resources defined in Python. - # If disabled, defaults to using the Python interpreter available in the current shell. - venv_path: .venv - # Functions called to load resources defined in Python. See resources/__init__.py - resources: - - "resources:load_resources" +python: + # Activate virtual environment before loading resources defined in Python. + # If disabled, defaults to using the Python interpreter available in the current shell. + venv_path: .venv + # Functions called to load resources defined in Python. See resources/__init__.py + resources: + - "resources:load_resources" {{- end }} {{ if $with_python }} From 251596d81069d4c2862d303292230f01db37df87 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 14:54:17 +0200 Subject: [PATCH 07/13] Update tests --- .../templates/pydabs/init-classic/output.txt | 2 +- .../output/my_pydabs/databricks.yml | 50 +++++++++++++++++++ .../templates/pydabs/init-classic/script | 2 +- .../{{.project_name}}/databricks.yml.tmpl | 1 + 4 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 acceptance/bundle/templates/pydabs/init-classic/output/my_pydabs/databricks.yml diff --git a/acceptance/bundle/templates/pydabs/init-classic/output.txt b/acceptance/bundle/templates/pydabs/init-classic/output.txt index d924c24631..34cf6ea363 100644 --- a/acceptance/bundle/templates/pydabs/init-classic/output.txt +++ b/acceptance/bundle/templates/pydabs/init-classic/output.txt @@ -9,4 +9,4 @@ Workspace to use (auto-detected, edit in 'my_pydabs/databricks.yml'): [DATABRICK Please refer to the README.md file for "getting started" instructions. See also the documentation at https://docs.databricks.com/dev-tools/bundles/index.html. ->>> find output/my_pydabs -mindepth 1 ! -name pyproject.toml ! -regex .*/resources.* -delete +>>> find output/my_pydabs -mindepth 1 ! -name databricks.yml ! -name pyproject.toml ! -regex .*/resources.* -delete diff --git a/acceptance/bundle/templates/pydabs/init-classic/output/my_pydabs/databricks.yml b/acceptance/bundle/templates/pydabs/init-classic/output/my_pydabs/databricks.yml new file mode 100644 index 0000000000..b637d0ab67 --- /dev/null +++ b/acceptance/bundle/templates/pydabs/init-classic/output/my_pydabs/databricks.yml @@ -0,0 +1,50 @@ +# This is a Databricks asset bundle definition for my_pydabs. +# See https://docs.databricks.com/dev-tools/bundles/index.html for documentation. +bundle: + name: my_pydabs + uuid: [UUID] + +python: + # Activate virtual environment before loading resources defined in Python. + # If disabled, defaults to using the Python interpreter available in the current shell. + venv_path: .venv + # Functions called to load resources defined in Python. See resources/__init__.py + resources: + - "resources:load_resources" + +artifacts: + python_artifact: + type: whl + build: uv build --wheel + +include: + - resources/*.yml + - resources/*/*.yml + +targets: + dev: + # The default target uses 'mode: development' to create a development copy. + # - Deployed resources get prefixed with '[dev my_user_name]' + # - Any job schedules and triggers are paused by default. + # See also https://docs.databricks.com/dev-tools/bundles/deployment-modes.html. + mode: development + default: true + workspace: + host: [DATABRICKS_URL] + + presets: + # Set dynamic_version: true on all artifacts of type "whl". + # This makes "bundle deploy" add a timestamp to wheel's version before uploading, + # new wheel takes over the previous installation even if actual wheel version is unchanged. + # See https://docs.databricks.com/aws/en/dev-tools/bundles/settings + artifacts_dynamic_version: true + + prod: + mode: production + workspace: + host: [DATABRICKS_URL] + # We explicitly deploy to /Workspace/Users/[USERNAME] to make sure we only have a single copy. + root_path: /Workspace/Users/[USERNAME]/.bundle/${bundle.name}/${bundle.target} + permissions: + - user_name: [USERNAME] + level: CAN_MANAGE diff --git a/acceptance/bundle/templates/pydabs/init-classic/script b/acceptance/bundle/templates/pydabs/init-classic/script index 1d4076927a..3cb73cc727 100644 --- a/acceptance/bundle/templates/pydabs/init-classic/script +++ b/acceptance/bundle/templates/pydabs/init-classic/script @@ -1,4 +1,4 @@ trace $CLI bundle init pydabs --config-file ./input.json --output-dir output # only keep relevant files for snapshots -trace find output/my_pydabs -mindepth 1 ! -name 'pyproject.toml' ! -regex '.*/resources.*' -delete +trace find output/my_pydabs -mindepth 1 ! -name 'databricks.yml' ! -name 'pyproject.toml' ! -regex '.*/resources.*' -delete diff --git a/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl b/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl index e9b5fdaea8..27d0e471b8 100644 --- a/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl +++ b/libs/template/templates/default-python/template/{{.project_name}}/databricks.yml.tmpl @@ -7,6 +7,7 @@ bundle: name: {{.project_name}} uuid: {{bundle_uuid}} {{- if $with_pydabs }} + python: # Activate virtual environment before loading resources defined in Python. # If disabled, defaults to using the Python interpreter available in the current shell. From c0db9349523f36918c23e807e9f149b71ce12b86 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 15:01:06 +0200 Subject: [PATCH 08/13] Add CHANGELOG --- NEXT_CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 8a1908225e..2eea690ecc 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -3,13 +3,14 @@ ## Release v0.275.0 ### Notable Changes - -### CLI +* Python support for Databricks Asset Bundles is now generally available### CLI * Remove `inplace` mode for the `--progress-format` flag. ([#3811](https://github.com/databricks/cli/pull/3811)) ### Dependency updates ### Bundles +* Add `pydabs` template replacing `experimental-jobs-as-code` template ([#3806](https://github.com/databricks/cli/pull/3806)) +* You can now use `python` section instead of `experimental/python` ([#3540](https://github.com/databricks/cli/pull/3540)) ### API Changes From 97cb2e750e2bdf5d361ed8acefe244fce13cc8f7 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 15:01:11 +0200 Subject: [PATCH 09/13] Update acc --- acceptance/bundle/help/bundle-init/output.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acceptance/bundle/help/bundle-init/output.txt b/acceptance/bundle/help/bundle-init/output.txt index b66253e08a..066d2988f7 100644 --- a/acceptance/bundle/help/bundle-init/output.txt +++ b/acceptance/bundle/help/bundle-init/output.txt @@ -7,7 +7,7 @@ TEMPLATE_PATH optionally specifies which template to use. It can be one of the f - default-sql: The default SQL template for .sql files that run with Databricks SQL - dbt-sql: The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks) - mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks) -- experimental-jobs-as-code: Jobs as code template (experimental) +- pydabs: A variant of 'default-python' template that defines resources in Python instead of YAML - a local file system path with a template directory - a Git repository URL, e.g. https://github.com/my/repository From 32adbe75f3de7a9b215a815d9d1ae23198407333 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 15:01:14 +0200 Subject: [PATCH 10/13] Fix tests --- libs/template/template_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/template/template_test.go b/libs/template/template_test.go index 48772e0f45..546473cb24 100644 --- a/libs/template/template_test.go +++ b/libs/template/template_test.go @@ -12,7 +12,7 @@ func TestTemplateHelpDescriptions(t *testing.T) { - default-sql: The default SQL template for .sql files that run with Databricks SQL - dbt-sql: The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks) - mlops-stacks: The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks) -- experimental-jobs-as-code: Jobs as code template (experimental)` +- pydabs: A variant of 'default-python' template that defines resources in Python instead of YAML` assert.Equal(t, expected, HelpDescriptions()) } From 5848f321c4ff41053c149a4da851db682b51fd7b Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 15:02:25 +0200 Subject: [PATCH 11/13] Fix changelog --- NEXT_CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 2eea690ecc..c9cbeb995f 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -3,8 +3,9 @@ ## Release v0.275.0 ### Notable Changes -* Python support for Databricks Asset Bundles is now generally available### CLI +* Python support for Databricks Asset Bundles is now generally available +### CLI * Remove `inplace` mode for the `--progress-format` flag. ([#3811](https://github.com/databricks/cli/pull/3811)) ### Dependency updates From ba1d8c284e93872cd7710350cbf0258e03db56bf Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 15:08:53 +0200 Subject: [PATCH 12/13] Fix tests --- libs/template/template_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/template/template_test.go b/libs/template/template_test.go index 546473cb24..8c21dd266f 100644 --- a/libs/template/template_test.go +++ b/libs/template/template_test.go @@ -22,7 +22,7 @@ func TestTemplateOptions(t *testing.T) { {Name: "default-sql", Id: "The default SQL template for .sql files that run with Databricks SQL"}, {Name: "dbt-sql", Id: "The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks)"}, {Name: "mlops-stacks", Id: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)"}, - {Name: "experimental-jobs-as-code", Id: "Jobs as code template (experimental)"}, + {Name: "pydabs", Id: "A variant of 'default-python' template that defines resources in Python instead of YAML"}, {Name: "custom...", Id: "Bring your own template"}, } assert.Equal(t, expected, options()) From 084dabf8767caee7552a47b839b39343755a0547 Mon Sep 17 00:00:00 2001 From: Gleb Kanterov Date: Fri, 24 Oct 2025 15:18:06 +0200 Subject: [PATCH 13/13] Fix minor diffs --- .../experimental-compatibility-both-equal/out.test.toml | 2 +- .../python/experimental-compatibility-both-equal/output.txt | 5 ++--- .../experimental-compatibility-both-error/out.test.toml | 2 +- .../bundle/python/experimental-compatibility/out.test.toml | 2 +- .../bundle/python/experimental-compatibility/output.txt | 5 ++--- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml b/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml index 4ba6c15f3a..5dceae8b60 100644 --- a/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/out.test.toml @@ -2,5 +2,5 @@ Local = true Cloud = false [EnvMatrix] - DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt b/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt index 9001bededd..a649ac03b5 100644 --- a/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt +++ b/acceptance/bundle/python/experimental-compatibility-both-equal/output.txt @@ -19,7 +19,6 @@ "format": "MULTI_TASK", "max_concurrent_runs": 1, "name": "My Job", - "permissions": [], "queue": { "enabled": true } @@ -58,14 +57,14 @@ [ 0, 1, - 846 + 880 ] ], "resources.jobs": [ [ 0, 1, - 855 + 889 ] ], "resources.jobs.my_job": [ diff --git a/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml b/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml index 4ba6c15f3a..5dceae8b60 100644 --- a/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml +++ b/acceptance/bundle/python/experimental-compatibility-both-error/out.test.toml @@ -2,5 +2,5 @@ Local = true Cloud = false [EnvMatrix] - DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/experimental-compatibility/out.test.toml b/acceptance/bundle/python/experimental-compatibility/out.test.toml index 4ba6c15f3a..5dceae8b60 100644 --- a/acceptance/bundle/python/experimental-compatibility/out.test.toml +++ b/acceptance/bundle/python/experimental-compatibility/out.test.toml @@ -2,5 +2,5 @@ Local = true Cloud = false [EnvMatrix] - DATABRICKS_CLI_DEPLOYMENT = ["terraform", "direct-exp"] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct-exp"] UV_ARGS = ["--with databricks-bundles==0.266.0", "--with-requirements requirements-latest.txt --no-cache"] diff --git a/acceptance/bundle/python/experimental-compatibility/output.txt b/acceptance/bundle/python/experimental-compatibility/output.txt index 3f6f5b3da1..40a5754e11 100644 --- a/acceptance/bundle/python/experimental-compatibility/output.txt +++ b/acceptance/bundle/python/experimental-compatibility/output.txt @@ -19,7 +19,6 @@ "format": "MULTI_TASK", "max_concurrent_runs": 1, "name": "My Job", - "permissions": [], "queue": { "enabled": true } @@ -51,14 +50,14 @@ [ 0, 1, - 791 + 825 ] ], "resources.jobs": [ [ 0, 1, - 800 + 834 ] ], "resources.jobs.my_job": [