From 3c4b4b625da59a259f672bcba758ee6460ede925 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Kristian=20Punsvik?= <31271671+bjornkpu@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:12:21 +0200 Subject: [PATCH 1/2] Skip the apply_mutators phase when no mutators are configured getOpts already receives the phase but only decides `enabled` from whether Python configuration is present, so `apply_mutators` starts a subprocess even when no mutator functions are declared. That subprocess loads every resource through the databricks-bundles dataclasses and returns them unchanged, so the work is wasted for bundles that use `python.resources` without `python.mutators`. It is also not harmless. Any resource the generated dataclasses cannot construct fails the whole phase, and therefore the whole command, even though the resource is defined in YAML and the user asked for nothing to be mutated. A SharePoint file-ingestion pipeline hits this because TableSpec.source_table is required while a file-ingestion object has no source table: TypeError: TableSpec.__init__() missing 1 required keyword-only argument: 'source_table' Configuration errors this phase would report are unaffected, because load_resources runs first and derives its options the same way. --- .../config/mutator/python/python_mutator.go | 7 +++ .../mutator/python/python_mutator_test.go | 50 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/bundle/config/mutator/python/python_mutator.go b/bundle/config/mutator/python/python_mutator.go index 9112aaa808d..269a611a9d2 100644 --- a/bundle/config/mutator/python/python_mutator.go +++ b/bundle/config/mutator/python/python_mutator.go @@ -124,6 +124,13 @@ func getOpts(b *bundle.Bundle, phase phase) (opts, error) { return opts{}, errors.New("experimental/pydabs is deprecated, use python instead (https://docs.databricks.com/dev-tools/bundles/python)") } + // Without mutator functions there is nothing to mutate, so skip the subprocess that would load + // every resource and return it unchanged. load_resources still reports configuration errors. + if phase == PythonMutatorPhaseApplyMutators && + len(b.Config.Python.Mutators) == 0 && len(experimental.Python.Mutators) == 0 { + return opts{}, nil + } + 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'") diff --git a/bundle/config/mutator/python/python_mutator_test.go b/bundle/config/mutator/python/python_mutator_test.go index 9b106f727bf..1600cb1fb0f 100644 --- a/bundle/config/mutator/python/python_mutator_test.go +++ b/bundle/config/mutator/python/python_mutator_test.go @@ -406,6 +406,56 @@ func TestGetOps_empty(t *testing.T) { assert.Equal(t, opts{enabled: false}, actual) } +func TestGetOps_ApplyMutatorsWithoutMutators(t *testing.T) { + actual, err := getOpts(&bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + Resources: []string{ + "resources:load_resources", + }, + }, + }, + }, PythonMutatorPhaseApplyMutators) + + assert.NoError(t, err) + assert.Equal(t, opts{enabled: false}, actual) +} + +func TestGetOps_ApplyMutators(t *testing.T) { + actual, err := getOpts(&bundle.Bundle{ + Config: config.Root{ + Python: config.Python{ + VEnvPath: ".venv", + Mutators: []string{ + "mutators:add_email_notifications", + }, + }, + }, + }, PythonMutatorPhaseApplyMutators) + + assert.NoError(t, err) + assert.Equal(t, opts{venvPath: ".venv", enabled: true, loadLocations: true}, actual) +} + +func TestGetOps_ApplyMutatorsExperimental(t *testing.T) { + actual, err := getOpts(&bundle.Bundle{ + Config: config.Root{ + Experimental: &config.Experimental{ + Python: config.Python{ + VEnvPath: ".venv", + Mutators: []string{ + "mutators:add_email_notifications", + }, + }, + }, + }, + }, PythonMutatorPhaseApplyMutators) + + assert.NoError(t, err) + assert.Equal(t, opts{venvPath: ".venv", enabled: true, loadLocations: true}, actual) +} + func TestApplyBackwardCompatibilityFixes(t *testing.T) { b := &bundle.Bundle{ Config: config.Root{ From 9aad86f1ae62da0a1a1369ee7043b50c8bd337fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Kristian=20Punsvik?= <31271671+bjornkpu@users.noreply.github.com> Date: Mon, 17 Aug 2026 20:12:22 +0200 Subject: [PATCH 2/2] Add changelog fragment --- .nextchanges/bundles/skip-apply-mutators-without-mutators.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .nextchanges/bundles/skip-apply-mutators-without-mutators.md diff --git a/.nextchanges/bundles/skip-apply-mutators-without-mutators.md b/.nextchanges/bundles/skip-apply-mutators-without-mutators.md new file mode 100644 index 00000000000..c0f66ea18d0 --- /dev/null +++ b/.nextchanges/bundles/skip-apply-mutators-without-mutators.md @@ -0,0 +1 @@ +Skip the `apply_mutators` phase when no `python.mutators` are configured, so bundles that only load resources in Python no longer start a subprocess that loads every resource and returns it unchanged.