Global Job Parameters, Thanks To DABs Mutators

Aren't you tired of defining the same parameters in every Databricks job? The same catalog variable, the same schema, the same environment config, all copy-pasted across every project, every deploy. It's the kind of boilerplate that seems harmless until you rename a schema and realize you're touching a dozen job definitions.

Mutators fix this. Here's how.

The Problem

When you build jobs with Declarative Automation Bundles, every job that shares the same platform tends to share the same environment values: which catalog, which schemas, which S3 buckets. Right now, most teams define these manually in each job or pass them as widget parameters in every notebook.

What if you could define all of that once, centrally, and have it injected into every job automatically at deploy time?

The Solution: Mutators

Mutators are Python functions that run during bundle loading and deployment. They can read your bundle structure (including variables and the current target) and modify any resource defined in YAML or Python before it hits Databricks. You can have more than one applied in the order specified in python.mutators.

Python support for DABs is now GA and requires Databricks CLI 0.275.0

python:
venv_path: .venv
mutators:
-"mutators:inject_standard_job_parameters"

Think of mutators like YAML injection at deploy time. You take a value from your bundle (the target environment, a variable like business_domain ) and use it to inject configuration into your resources automatically.

For example, we can have a job without the parameters specified and write a mutator to set their values from our JSON. With this pattern, your job definition only needs to declare its tasks. No parameters:

resources:
  jobs:
    example_job:
      name: "example-mutator-params-${bundle.target}"
      # no parameters here
      tasks:
        - task_key: "print_params"
          notebook_task:
            notebook_path: ../src/example_job.ipynb

The mutator reads parameter values from a central config JSON and injects them at deploy time:

{
  "dev": {
    "catalog": "uc_dev",
    "areas": {
      "marketing": {
        "bronze_schema": "mkt_bronze",
        "silver_schema": "mkt_silver",
        "gold_schema": "mkt_gold",
        "ingestion_bucket": "s3://company-dev-marketing-ingestion"
      }
    }
  },
  "prod": {
    "catalog": "uc_prod",
    "areas": {
      "marketing": {
        "bronze_schema": "mkt_bronze",
        "silver_schema": "mkt_silver",
        "gold_schema": "mkt_gold",
        "ingestion_bucket": "s3://company-prod-marketing-ingestion"
      }
    }
  }
}

Here are the key pieces of the mutator:

  • The decorator: @job_mutator

  • Read the central config: config = json.loads((Path(file).parent / “containers.json”).read_text())

  • Get the current target (Yes, mutators see our bundle structure): env_cfg = config[bundle.target]

  • Resolve a bundle variable: area_cfg = env_cfg[“areas”][bundle.resolve_variable(Variables.business_domain)]

  • Inject values as job parameters: JobParameterDefinition.from_dict()

And here is a minimal working code for our global job parameters:

# mutators.py
import json
from dataclasses import replace
from pathlib import Path

from databricks.bundles.core import Bundle, Variable, job_mutator, variables
from databricks.bundles.jobs import Job, JobParameterDefinition

@variables
class Variables:
    business_domain: Variable[str]

@job_mutator
def inject_standard_job_parameters(bundle: Bundle, job: Job) -> Job:
    config = json.loads((Path(__file__).parent / "containers.json").read_text())

    domain = bundle.resolve_variable(Variables.business_domain)
    env_cfg = config[bundle.target]
    domain_cfg = env_cfg["areas"][domain]

    # Pull all values from JSON + add target and business_domain
    params = {"target": bundle.target, "business_domain": domain}
    params.update({k: v for k, v in env_cfg.items() if k != "areas"})
    params.update({k: v for k, v in domain_cfg.items()})

    # Existing explicit job parameters win over injected defaults
    existing = {p.name: p.default for p in (job.parameters or [])}
    merged = {**params, **existing}

    return replace(
        job,
        parameters=[
            JobParameterDefinition.from_dict({"name": k, "default": str(v)})
            for k, v in merged.items()
        ],
    )

When you run databricks bundle deploy , you'll see Python invoked and databricks-bundles installed.

Your jobs in the UI will show the full parameter list, populated automatically from the config.

How Python and Virtual Environments Work

If venv_path is not set, DABs fall back to the Python interpreter in the current shell. The selected environment must contain the databricks-bundles package. The pydabs template uses uv, but standard venv works too. The mutator venv is local and runs only at deploy time (it’s not the Python runtime for your Databricks job tasks.)

Write your first mutator

The example above is about global job parameters. This is useful, but it is not the only usage.

  • Add standard tags, like env, owner, domain, and cost_center.

  • Add default email notifications.

  • Add or check compute settings, for example, serverless rules.

  • Use mutators as validators in CI/CD, not only as YAML changers.

So mutators are not only about editing the config. They are also a good place for central rules. My simple mental model is this: YAML defines what the project wants, and mutators add or check what the platform requires.

Hubert Dudek

Databricks MVP | Advisor to Databricks Product Board and Technical advisor to SunnyData

https://www.linkedin.com/in/hubertdudek/
Next
Next

Speaking the Language of Finance: Why Our Databricks BrickBuilder Specialization Matters