<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Dynamic Workflow Creation Using a Config-Driven Approach in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/dynamic-workflow-creation-using-a-config-driven-approach/ba-p/119839</link>
    <description>&lt;P&gt;&lt;SPAN&gt;In this blog, we'll explore how to dynamically generate &lt;/SPAN&gt;&lt;STRONG&gt;Databricks Workflows.&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_0-1747811525082.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17014iE68F2B44CE6E9512/image-size/medium?v=v2&amp;amp;px=400" role="button" title="de_expert_aj_0-1747811525082.png" alt="de_expert_aj_0-1747811525082.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Imagine needing to create multiple workflows, each consisting of tasks that run different notebooks, with varying cluster configurations and custom parameters such as min/max workers or specific node types. You might want to assign large tasks to dedicated job clusters while grouping smaller tasks to run on shared clusters.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Manually setting up thousands of such workflows—each with potentially hundreds of tasks—would be time-consuming, error-prone, and inefficient.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;To address this, our approach automates the entire setup using a single &lt;/SPAN&gt;&lt;STRONG&gt;configuration table&lt;/STRONG&gt;&lt;SPAN&gt; and the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/dev-tools/sdk-python" target="_blank"&gt;&lt;STRONG&gt;Databricks Python SDK&lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;. This eliminates the need for manual configuration, significantly reduces human error, and improves overall efficiency.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The configuration table captures all essential details, such as cluster settings, notebook paths, and task-specific parameters. By simply adding a new entry to this table, workflows are automatically and consistently generated.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;STRONG&gt;Why Not Terraform or DAB?&lt;/STRONG&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Terraform and Databricks Asset Bundles (DAB) require &lt;/SPAN&gt;&lt;STRONG&gt;predefined&lt;/STRONG&gt;&lt;SPAN&gt; tasks, meaning they lack the ability to generate workflows dynamically based on a config table. Our approach allows us to gain more granular control over task-level configurations, such as worker counts, node types, and execution strategies.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;STRONG&gt;Why Not Just Use a for Each Task in Databricks Workflows?&lt;/STRONG&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;In Databricks Workflows, a for each task applies the same configuration across all iterations, typically executing the same notebook multiple times with different input parameters. While this works well for repetitive tasks with uniform logic, it’s limited when tasks need to diverge in behavior or resources.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;By contrast, the &lt;/SPAN&gt;&lt;STRONG&gt;dynamic workflow generation approach &lt;/STRONG&gt;&lt;SPAN&gt;described above offers several key advantages:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Diverse Notebook Execution&lt;/STRONG&gt;&lt;SPAN&gt;: Each task can invoke a different notebook, enabling varied processing logic within a single pipeline.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Custom Spark Versions&lt;/STRONG&gt;&lt;SPAN&gt;: Tasks can be assigned different Spark versions, optimizing performance based on specific processing needs.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Tailored Cluster Configurations&lt;/STRONG&gt;&lt;SPAN&gt;: Each task can run on customized cluster settings, ensuring the right level of compute and memory for its workload.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;This dynamic design provides greater flexibility and scalability, especially for complex pipelines where each task has unique logic or resource requirements.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Stay tuned as we walk through this powerful &lt;/SPAN&gt;&lt;STRONG&gt;Python SDK-driven&lt;/STRONG&gt;&lt;SPAN&gt; method to automate Databricks Workflow generation!&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color="#999999"&gt;&lt;SPAN&gt;-------------------------------------------------------------------------&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;In this blog, we’ll cover:&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; How to define workflows dynamically using a config table&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Generating job clusters, tasks, and jobs programmatically&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Submitting and managing jobs efficiently&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;High-Level Overview of Steps&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Create the Configuration Table:&lt;/STRONG&gt;&lt;STRONG&gt;&lt;BR /&gt;&lt;/STRONG&gt;&lt;SPAN&gt;Define the structure and parameters that will control task execution, including cluster configurations, notebooks, and job-specific settings.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Leverage the Databricks Python SDK to Generate Workflows:&lt;/STRONG&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt; Use the Databricks SDK to automatically:&lt;/SPAN&gt;&lt;/LI&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Parse the config table&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Create job clusters&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Configure tasks and handle dependencies,&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Create a job/Databricks workflow&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Manage workflow execution&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;/OL&gt;
&lt;H2&gt;&lt;SPAN&gt;Step 1: Configuration Table Design&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;The config table serves as the backbone for dynamically generating workflows. This allows full flexibility in defining:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Different notebooks for each task&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Custom cluster configurations (e.g., worker count, node type, Spark version)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Manage job execution, user access, libraries, compute policies, init scripts, and so on&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":pushpin:"&gt;📌&lt;/span&gt; &lt;/SPAN&gt;&lt;STRONG&gt;Important&lt;/STRONG&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;Each row in the configuration table can define &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;multiple tasks&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; by specifying &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;multiple notebook paths&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt;. The number of tasks created corresponds to the number of notebooks listed in that row.&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;These notebook paths must be provided in the config column as a &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;comma-separated string&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; (e.g., &lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;"path/to/notebook1,path/to/notebook2"&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;).&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;Alternatively, you can structure this column as an &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;array&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; to avoid manual parsing and improve readability.&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;Config Table Schema

sql_query = f"""
CREATE TABLE IF NOT EXISTS {config_catalog}.{config_schema}.job_config (
 config_id BIGINT GENERATED ALWAYS AS IDENTITY COMMENT 'Unique identifier for each configuration entry (Primary Key)',
 batch STRING COMMENT 'Defines job grouping for tasks (Tasks with the same batch belong to the same job)',
 job_cluster_key STRING COMMENT 'Defines which tasks share the same cluster',
 spark_ver STRING COMMENT 'The Spark version used for the job execution',
 driver_node_type STRING COMMENT 'The driver node type (e.g., Standard_DS3_v2, Standard_E32s_v4)',
 driver_nodes INT COMMENT 'Number of cores assigned to the driver node',
 driver_memory STRING COMMENT 'Amount of memory assigned to the driver node (e.g., 4g)',
 executor_node_type STRING COMMENT 'The executor node type (e.g., Standard_DS3_v2, Standard_E32s_v4)',
 min_workers INT COMMENT 'Minimum number of workers required for execution',
 max_workers INT COMMENT 'Maximum number of workers allowed',
 compute_policy STRING COMMENT 'Defines the compute policy applied to the job',
 photon_acceleration BOOLEAN COMMENT 'Indicates whether Photon acceleration is enabled (true/false)',
 tags STRING COMMENT 'Metadata tags (stored as a JSON object) for tracking job details',
 library STRING COMMENT 'Libraries required for job execution',
 init_scripts STRING COMMENT 'Initialization scripts to be run before execution',
 batch_schedule STRING COMMENT 'Job execution schedule (e.g., daily, hourly)',
 job_run_as STRING COMMENT 'User account under which the job runs',
 task_parameters STRING COMMENT 'JSON object containing key-value task-specific parameters',
 user_name STRING COMMENT 'Username associated with job permissions',
 user_permissions STRING COMMENT 'Permission level (e.g., CAN_MANAGE, CAN_RUN)',
 queue BOOLEAN COMMENT 'Indicates if the task should be queued when resources are unavailable',
 notebook_paths STRING COMMENT 'Path to the Databricks notebook for execution',
data_security_mode compute access mode SINGLE_USER or USER_ISOLATION
)
"""
# Execute the SQL query
spark.sql(sql_query)&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Below is a sample dataset that demonstrates how different tasks can be grouped under jobs and clusters. &lt;/SPAN&gt;&lt;STRONG&gt;It will create three pipelines in a workflow, each with two tasks.&lt;/STRONG&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;sql_query = f"""
INSERT INTO users.anjali_jain.job_config (
 batch, job_cluster_key, spark_ver, driver_node_type, driver_nodes, driver_memory,
 executor_node_type, min_workers, max_workers, compute_policy, photon_acceleration,
 tags, library, init_scripts, batch_schedule, job_run_as, task_parameters, user_name,
 user_permissions, queue, notebook_paths
) VALUES (
 'BATCH_1', 'cluster_1', '16.0.x-scala2.12', 'r3.xlarge', 4, '8g',
 'r3.xlarge', 2, 10, 'default', true,
 '{{"tag1": "value1", "tag2": "value2"}}', 'pypi=sample_library',
 'dbfs=/path/to/init_script|abfss=/path/to/init_script', 'daily', 'user.1@xyz.com',
 '{{"param1": "value1", "param2": "value2"}}', 'user.1@xyz.com', 'CAN_MANAGE', true, "path_to_notebook_1,path_to_notebook_2", “SINGLE_USER”
),
(
 'BATCH_1', 'cluster_2', '16.0.x-scala2.12', 'r3.xlarge', 4, '8g',
 'r3.xlarge', 2, 10, 'default', true,
 '{{"tag1": "value1", "tag2": "value2"}}', 'pypi=sample_library',
 'dbfs=/path/to/init_script|abfss=/path/to/init_script', 'daily', 'user.1@xyz.com',
 '{{"param1": "value1", "param2": "value2"}}', 'user.1@xyz.com', 'CAN_MANAGE', true, "path_to_notebook_1,path_to_notebook_2", “SINGLE_USER”
),
(
 'BATCH_1', 'cluster_2', '16.0.x-scala2.12', 'r3.xlarge', 8, '8g',
 'r3.xlarge', 2, 10, 'default', true,
 '{{"tag1": "value1", "tag2": "value2"}}', 'pypi=sample_library',
 'dbfs=/path/to/init_script|abfss=/path/to/init_script', 'daily', 'user.1@xyz.com',
 '{{"param1": "value1", "param2": "value2"}}', 'user.1@xyz.com', 'CAN_MANAGE', true, "path_to_notebook_1,path_to_notebook_2",”SINGLE_USER”
);
"""
# Execute the SQL query
spark.sql(sql_query)&lt;/LI-CODE&gt;
&lt;H4&gt;&lt;SPAN&gt;CONFIG TABLE SNAPSHOT&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_1-1747811635090.png" style="width: 782px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17015iE148835ED4AACC13/image-dimensions/782x127?v=v2" width="782" height="127" role="button" title="de_expert_aj_1-1747811635090.png" alt="de_expert_aj_1-1747811635090.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;H4&gt;&lt;SPAN&gt;JOB SNAPSHOT&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_2-1747811635150.png" style="width: 574px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17016i5E4F428C6E1E2BC8/image-dimensions/574x320?v=v2" width="574" height="320" role="button" title="de_expert_aj_2-1747811635150.png" alt="de_expert_aj_2-1747811635150.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;NOTE:&amp;nbsp;&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;Each row in a config table represents a separate &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;pipeline&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; in a job/workflow&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;UL&gt;
&lt;LI aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;All the tasks sharing the same &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;BATCH&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; belong to the same job.&amp;nbsp;&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;The&lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt; job_cluster_key&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; allows multiple tasks to share the same compute cluster, promoting efficient resource utilization. In the example above, Pipeline 2 and Pipeline 3 are both assigned the same &lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;job_cluster_key&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;—&lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;cluster_2&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt;—which means they run on the same cluster instance rather than provisioning separate clusters for each task.&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;The number of tasks in a &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;pipeline&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; is derived from the number of notebook paths passed in a row.&amp;nbsp;&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;FONT color="#339966"&gt;&lt;STRONG&gt;&lt;I&gt;Example&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt;: Two notebook paths result in two tasks per pipeline.&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;Task dependencies: Task n depends on Task n-1 in the sequence.&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;I&gt;&lt;/I&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;H4&gt;&lt;SPAN&gt;Key Benefits of the Config Table Approach&lt;/SPAN&gt;&lt;/H4&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Dynamic Job Creation&lt;/STRONG&gt;&lt;SPAN&gt;: No need to manually configure a large number of workflows&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Task-Level Control&lt;/STRONG&gt;&lt;SPAN&gt;: Each task can have different worker configurations, compute policies, and notebook paths&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Efficient Job Execution&lt;/STRONG&gt;&lt;SPAN&gt;: Tasks can be grouped under the same job or run independently&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Step 2: Generating Workflows Using Databricks SDK for Python&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Once the config table is set up, we can use the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/dev-tools/sdk-python#notebook" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Databricks SDK for Python &lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;to generate workflows dynamically. The following steps are involved:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;SPAN&gt;Read the config table to retrieve job definitions&amp;nbsp; :&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;SPAN&gt;To dynamically generate workflows, we first need to retrieve the job definitions stored in a &lt;/SPAN&gt;&lt;STRONG&gt;configuration&lt;/STRONG&gt;&lt;SPAN&gt; table.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;We query the table by executing a SQL statement against a Databricks SQL Warehouse using the &lt;/SPAN&gt;&lt;SPAN&gt;execute_statement()&lt;/SPAN&gt;&lt;SPAN&gt; method.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The query results are then mapped into a list of dictionaries (&lt;/SPAN&gt;&lt;SPAN&gt;mapped_results&lt;/SPAN&gt;&lt;SPAN&gt;), which will be used in later steps to generate job clusters and tasks dynamically.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Here’s the code for this step:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;import json
from databricks.sdk import WorkspaceClient
from databricks.sdk.service import jobs, compute, iam
from databricks.sdk.service.jobs import QueueSettings


CONFIG_TABLE_NAME = "catalog.schema.job_config"
SQL_WAREHOUSE_ID = "862f1d7ghf0424f7"  
batch = "BATCH_1" 


try:
   w = WorkspaceClient()
   jobgrp_query = f"SELECT * FROM {CONFIG_TABLE_NAME} WHERE LOWER(TRIM(batch))='{batch.lower().strip()}'"


   print("SQL warehouse starting... ")
   w.warehouses.start_and_wait(SQL_WAREHOUSE_ID)
   wh = w.warehouses.wait_get_warehouse_running(SQL_WAREHOUSE_ID)


   if wh.state == wh.state.RUNNING:
       result = w.statement_execution.execute_statement(
           statement=jobgrp_query, warehouse_id=SQL_WAREHOUSE_ID
       )
       column_names = [col.name for col in result.manifest.schema.columns]
       mapped_results = [
           dict(zip(column_names, row)) for row in result.result.data_array
       ]
except Exception as e:
   print(f"An error occurred: {e}")
   raise e&lt;/LI-CODE&gt;
&lt;P&gt;2.&amp;nbsp;&lt;SPAN&gt;Create job clusters based on the cluster configurations in the table&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;def create_job_clusters(d):
   """
   Create job clusters based on the configuration dictionary.


   Args:
       d (dict): Configuration dictionary containing job details.


   Returns:
       JobCluster: Created job cluster object.
   """
   try:
       job_cluster = jobs.JobCluster(
           job_cluster_key=d["job_cluster_key"],
           new_cluster=compute.ClusterSpec(
               cluster_name="",
               spark_version=d["spark_ver"],
               node_type_id=d['executor_node_type'],
               driver_node_type_id=d['driver_node_type'],
               autoscale=compute.AutoScale(
                   min_workers=d['min_workers'],
                   max_workers=d['max_workers']
               ),
               data_security_mode=compute.DataSecurityMode.SINGLE_USER if d['data_security_mode'] == 'SINGLE_USER' else compute.DataSecurityMode.USER_INSOLATION,
           )
       )
       return job_cluster
   except Exception as e:
       raise e​&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;3.&amp;nbsp;&lt;SPAN&gt;Create tasks and automatically manage task dependencies, where each task &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;n&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; depends on task &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;n-1&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;, as illustrated &lt;/SPAN&gt;&lt;A href="https://docs.google.com/document/d/1lEOuX4D8vEtPvdP615eA6PHTLggkWTZo/edit#heading=h.7ivafnhmftz" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;above&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;While the example below focuses on chaining notebook-based tasks, &lt;/SPAN&gt;&lt;STRONG&gt;there are many more options you can configure via the&amp;nbsp; configuration table&lt;/STRONG&gt;&lt;SPAN&gt;, such as:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Task-level parameters (e.g., base_parameters)&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Timeouts and retry policies&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Libraries to install on the cluster&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Cluster reuse and pool support&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Email alerts or webhook notifications&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Task types beyond notebooks (e.g., Python scripts, JARs)&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI-CODE lang="python"&gt;def create_task(d):
  """
  Create tasks for the job based on the configuration dictionary.
  The number of tasks depends on the notebook_paths

  Args:
      d (dict): Configuration dictionary containing job details.

  Returns:
      list: List of SubmitTask objects.
  """
  try:
      notebook_paths = d['notebook_paths'].split(',')
      tasks = []
      for i, notebook_path in enumerate(notebook_paths):
          task = jobs.Task(
              task_key=f"{i+1}_{d['config_id']}",
              job_cluster_key=d["job_cluster_key"],
              run_if=jobs.RunIf("ALL_SUCCESS"),
              notebook_task=jobs.NotebookTask(notebook_path=notebook_path),
              depends_on=[tasks[-1]] if tasks else []
          )
          tasks.append(task)
      return tasks
  except Exception as e:
      raise e&lt;/LI-CODE&gt;
&lt;P&gt;4. C&lt;SPAN&gt;reate a Job /workflow&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;def create_job(d, tasks, job_clusters, databricks_group_name):
   """
   Create a job in Databricks based on the provided configuration.

   Args:
       d (dict): Configuration dictionary containing job details.
       tasks (list): List of tasks to be included in the job.
       job_clusters (list): List of job clusters to be used.
       databricks_group_name (str): Name of the Databricks group.

   Returns:
       Job: Created job object.
   """
   try:
       job = w.jobs.create(
           name=f"job_{d['batch']}",
           email_notifications=jobs.JobEmailNotifications(no_alert_for_skipped_runs=False),
           timeout_seconds=1000,
           max_concurrent_runs=1,
           tasks=tasks,
           job_clusters=job_clusters,
           queue=QueueSettings(enabled=True),
           access_control_list=[
               iam.AccessControlRequest(
                   user_name=d["user_name"],
                   permission_level=iam.PermissionLevel.CAN_MANAGE if d["user_permissions"] == "CAN_MANAGE" else iam.PermissionLevel.CAN_RUN
               )
           ]
       )
       return job
   except Exception as e:
       print(f"Failed to create job: {e}")
       raise e
&lt;/LI-CODE&gt;
&lt;P&gt;5.&amp;nbsp;&lt;SPAN&gt;Submit the job for execution&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;try:
   tasks = []
   clusters = []
   for d in mapped_results:
       tasks.extend(create_task(d))
       ##create cluster only if it's not already created
       if d["job_cluster_key"] not in [cluster.job_cluster_key for cluster in clusters]:
           clusters.append(create_job_clusters(d))


   if tasks:
       try:
           print("Job is running")
           job = create_job(d, tasks, clusters, "DATABRICKS_GROUP_NAME")
           print(f"JOB_ID : {job.job_id}")
           run = w.jobs.run_now_and_wait(job.job_id)
       except Exception as e:
           print(f"An error occurred in job run: {e}")
           raise e
       print("Job is finished")


       if run.state.result_state != jobs.RunResultState.SUCCESS:
           raise Exception(f"Job failed. Result state: {run.state.result_state}")
       else:
           print("Job finished successfully")
   else:
       print("No tasks to run")
except Exception as e:
   print(f"An error occurred: {e}")
   raise e&lt;/LI-CODE&gt;
&lt;H2&gt;&lt;SPAN&gt;Submitting and Managing Jobs&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;There are multiple ways to submit and manage jobs dynamically:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;1. &lt;A href="https://databricks-sdk-py.readthedocs.io/en/latest/workspace/jobs/jobs.html#:~:text=JobPermissions-,submit,-(%5B%2C%20access_control_list" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;One-Time Runs &lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;(Logical Jobs)&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Submit a one-time run. This endpoint allows you to submit a workload directly without creating a job. You can see the job in Job Runs, but you will not see the physical job under the Jobs tab. This approach is useful for executing workloads on demand without accumulating multiple job definitions in the workspace.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_0-1747813197839.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17017iF7B5B817695879F5/image-size/medium?v=v2&amp;amp;px=400" role="button" title="de_expert_aj_0-1747813197839.png" alt="de_expert_aj_0-1747813197839.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;%python
w.jobs.submit(
   run_name="Onetime_Run",
   tasks=[task],
   job_clusters=[job_cluster])&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;2. Create and Delete Jobs&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Create a job, run it, and delete it afterward to keep the workspace clean.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;created_job = w.jobs.create(name=f'sdk-{time.time_ns()}',
                           tasks=[
                               jobs.Task(description="test",
                                         existing_cluster_id=cluster_id,
                                         notebook_task=jobs.NotebookTask(notebook_path=notebook_path),
                                         task_key="test",
                                         timeout_seconds=0)
                           ])
w.jobs.cancel_all_runs(job_id=created_job.job_id)
# cleanup
w.jobs.delete(job_id=created_job.job_id)&lt;/LI-CODE&gt;
&lt;P&gt;3.&amp;nbsp;&lt;STRONG&gt;Check for Existing Jobs&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;&lt;SPAN&gt;Before creating a new job, check if a similar job already exists based on the job name.&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;existing_jobs = w.jobs.list()
for job in existing_jobs:
   if job.settings.name) == "Dynamic_Job":
       print(f"Job already exists with ID: {job.job_id}")
       break&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;I&gt;Note: This framework can be extended for serverless as well by modifying properties in the config table&lt;/I&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Conclusion&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;By leveraging the Databricks Python SDK and a configuration-driven approach, we can:&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Automate workflow creation dynamically&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Gain fine-grained control over tasks and clusters&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Eliminate the need for manual workflow management&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This method provides greater flexibility than Terraform/DAB (which requires predefined tasks) and workflows ForEach task (which applies the same config to all tasks).&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Now, effortlessly generate a vast number of workflows with just a configuration table! &lt;span class="lia-unicode-emoji" title=":rocket:"&gt;🚀&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;</description>
    <pubDate>Tue, 10 Jun 2025 15:44:50 GMT</pubDate>
    <dc:creator>de_expert_aj</dc:creator>
    <dc:date>2025-06-10T15:44:50Z</dc:date>
    <item>
      <title>Dynamic Workflow Creation Using a Config-Driven Approach</title>
      <link>https://community.databricks.com/t5/technical-blog/dynamic-workflow-creation-using-a-config-driven-approach/ba-p/119839</link>
      <description>&lt;P&gt;&lt;SPAN&gt;In this blog, we'll explore how to dynamically generate &lt;/SPAN&gt;&lt;STRONG&gt;Databricks Workflows.&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_0-1747811525082.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17014iE68F2B44CE6E9512/image-size/medium?v=v2&amp;amp;px=400" role="button" title="de_expert_aj_0-1747811525082.png" alt="de_expert_aj_0-1747811525082.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Imagine needing to create multiple workflows, each consisting of tasks that run different notebooks, with varying cluster configurations and custom parameters such as min/max workers or specific node types. You might want to assign large tasks to dedicated job clusters while grouping smaller tasks to run on shared clusters.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Manually setting up thousands of such workflows—each with potentially hundreds of tasks—would be time-consuming, error-prone, and inefficient.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;To address this, our approach automates the entire setup using a single &lt;/SPAN&gt;&lt;STRONG&gt;configuration table&lt;/STRONG&gt;&lt;SPAN&gt; and the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/dev-tools/sdk-python" target="_blank"&gt;&lt;STRONG&gt;Databricks Python SDK&lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;. This eliminates the need for manual configuration, significantly reduces human error, and improves overall efficiency.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The configuration table captures all essential details, such as cluster settings, notebook paths, and task-specific parameters. By simply adding a new entry to this table, workflows are automatically and consistently generated.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;STRONG&gt;Why Not Terraform or DAB?&lt;/STRONG&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;Terraform and Databricks Asset Bundles (DAB) require &lt;/SPAN&gt;&lt;STRONG&gt;predefined&lt;/STRONG&gt;&lt;SPAN&gt; tasks, meaning they lack the ability to generate workflows dynamically based on a config table. Our approach allows us to gain more granular control over task-level configurations, such as worker counts, node types, and execution strategies.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H3&gt;&lt;STRONG&gt;Why Not Just Use a for Each Task in Databricks Workflows?&lt;/STRONG&gt;&lt;/H3&gt;
&lt;P&gt;&lt;SPAN&gt;In Databricks Workflows, a for each task applies the same configuration across all iterations, typically executing the same notebook multiple times with different input parameters. While this works well for repetitive tasks with uniform logic, it’s limited when tasks need to diverge in behavior or resources.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;By contrast, the &lt;/SPAN&gt;&lt;STRONG&gt;dynamic workflow generation approach &lt;/STRONG&gt;&lt;SPAN&gt;described above offers several key advantages:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Diverse Notebook Execution&lt;/STRONG&gt;&lt;SPAN&gt;: Each task can invoke a different notebook, enabling varied processing logic within a single pipeline.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Custom Spark Versions&lt;/STRONG&gt;&lt;SPAN&gt;: Tasks can be assigned different Spark versions, optimizing performance based on specific processing needs.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Tailored Cluster Configurations&lt;/STRONG&gt;&lt;SPAN&gt;: Each task can run on customized cluster settings, ensuring the right level of compute and memory for its workload.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;This dynamic design provides greater flexibility and scalability, especially for complex pipelines where each task has unique logic or resource requirements.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Stay tuned as we walk through this powerful &lt;/SPAN&gt;&lt;STRONG&gt;Python SDK-driven&lt;/STRONG&gt;&lt;SPAN&gt; method to automate Databricks Workflow generation!&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;FONT color="#999999"&gt;&lt;SPAN&gt;-------------------------------------------------------------------------&lt;/SPAN&gt;&lt;/FONT&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;In this blog, we’ll cover:&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; How to define workflows dynamically using a config table&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Generating job clusters, tasks, and jobs programmatically&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Submitting and managing jobs efficiently&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;High-Level Overview of Steps&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Create the Configuration Table:&lt;/STRONG&gt;&lt;STRONG&gt;&lt;BR /&gt;&lt;/STRONG&gt;&lt;SPAN&gt;Define the structure and parameters that will control task execution, including cluster configurations, notebooks, and job-specific settings.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Leverage the Databricks Python SDK to Generate Workflows:&lt;/STRONG&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt; Use the Databricks SDK to automatically:&lt;/SPAN&gt;&lt;/LI&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Parse the config table&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Create job clusters&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Configure tasks and handle dependencies,&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Create a job/Databricks workflow&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;SPAN&gt;Manage workflow execution&amp;nbsp;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;/OL&gt;
&lt;H2&gt;&lt;SPAN&gt;Step 1: Configuration Table Design&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;The config table serves as the backbone for dynamically generating workflows. This allows full flexibility in defining:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Different notebooks for each task&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Custom cluster configurations (e.g., worker count, node type, Spark version)&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Manage job execution, user access, libraries, compute policies, init scripts, and so on&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":pushpin:"&gt;📌&lt;/span&gt; &lt;/SPAN&gt;&lt;STRONG&gt;Important&lt;/STRONG&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;Each row in the configuration table can define &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;multiple tasks&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; by specifying &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;multiple notebook paths&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt;. The number of tasks created corresponds to the number of notebooks listed in that row.&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;These notebook paths must be provided in the config column as a &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;comma-separated string&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; (e.g., &lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;"path/to/notebook1,path/to/notebook2"&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;).&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;&lt;SPAN&gt;Alternatively, you can structure this column as an &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;array&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; to avoid manual parsing and improve readability.&lt;/SPAN&gt;&lt;/I&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;Config Table Schema

sql_query = f"""
CREATE TABLE IF NOT EXISTS {config_catalog}.{config_schema}.job_config (
 config_id BIGINT GENERATED ALWAYS AS IDENTITY COMMENT 'Unique identifier for each configuration entry (Primary Key)',
 batch STRING COMMENT 'Defines job grouping for tasks (Tasks with the same batch belong to the same job)',
 job_cluster_key STRING COMMENT 'Defines which tasks share the same cluster',
 spark_ver STRING COMMENT 'The Spark version used for the job execution',
 driver_node_type STRING COMMENT 'The driver node type (e.g., Standard_DS3_v2, Standard_E32s_v4)',
 driver_nodes INT COMMENT 'Number of cores assigned to the driver node',
 driver_memory STRING COMMENT 'Amount of memory assigned to the driver node (e.g., 4g)',
 executor_node_type STRING COMMENT 'The executor node type (e.g., Standard_DS3_v2, Standard_E32s_v4)',
 min_workers INT COMMENT 'Minimum number of workers required for execution',
 max_workers INT COMMENT 'Maximum number of workers allowed',
 compute_policy STRING COMMENT 'Defines the compute policy applied to the job',
 photon_acceleration BOOLEAN COMMENT 'Indicates whether Photon acceleration is enabled (true/false)',
 tags STRING COMMENT 'Metadata tags (stored as a JSON object) for tracking job details',
 library STRING COMMENT 'Libraries required for job execution',
 init_scripts STRING COMMENT 'Initialization scripts to be run before execution',
 batch_schedule STRING COMMENT 'Job execution schedule (e.g., daily, hourly)',
 job_run_as STRING COMMENT 'User account under which the job runs',
 task_parameters STRING COMMENT 'JSON object containing key-value task-specific parameters',
 user_name STRING COMMENT 'Username associated with job permissions',
 user_permissions STRING COMMENT 'Permission level (e.g., CAN_MANAGE, CAN_RUN)',
 queue BOOLEAN COMMENT 'Indicates if the task should be queued when resources are unavailable',
 notebook_paths STRING COMMENT 'Path to the Databricks notebook for execution',
data_security_mode compute access mode SINGLE_USER or USER_ISOLATION
)
"""
# Execute the SQL query
spark.sql(sql_query)&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Below is a sample dataset that demonstrates how different tasks can be grouped under jobs and clusters. &lt;/SPAN&gt;&lt;STRONG&gt;It will create three pipelines in a workflow, each with two tasks.&lt;/STRONG&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;sql_query = f"""
INSERT INTO users.anjali_jain.job_config (
 batch, job_cluster_key, spark_ver, driver_node_type, driver_nodes, driver_memory,
 executor_node_type, min_workers, max_workers, compute_policy, photon_acceleration,
 tags, library, init_scripts, batch_schedule, job_run_as, task_parameters, user_name,
 user_permissions, queue, notebook_paths
) VALUES (
 'BATCH_1', 'cluster_1', '16.0.x-scala2.12', 'r3.xlarge', 4, '8g',
 'r3.xlarge', 2, 10, 'default', true,
 '{{"tag1": "value1", "tag2": "value2"}}', 'pypi=sample_library',
 'dbfs=/path/to/init_script|abfss=/path/to/init_script', 'daily', 'user.1@xyz.com',
 '{{"param1": "value1", "param2": "value2"}}', 'user.1@xyz.com', 'CAN_MANAGE', true, "path_to_notebook_1,path_to_notebook_2", “SINGLE_USER”
),
(
 'BATCH_1', 'cluster_2', '16.0.x-scala2.12', 'r3.xlarge', 4, '8g',
 'r3.xlarge', 2, 10, 'default', true,
 '{{"tag1": "value1", "tag2": "value2"}}', 'pypi=sample_library',
 'dbfs=/path/to/init_script|abfss=/path/to/init_script', 'daily', 'user.1@xyz.com',
 '{{"param1": "value1", "param2": "value2"}}', 'user.1@xyz.com', 'CAN_MANAGE', true, "path_to_notebook_1,path_to_notebook_2", “SINGLE_USER”
),
(
 'BATCH_1', 'cluster_2', '16.0.x-scala2.12', 'r3.xlarge', 8, '8g',
 'r3.xlarge', 2, 10, 'default', true,
 '{{"tag1": "value1", "tag2": "value2"}}', 'pypi=sample_library',
 'dbfs=/path/to/init_script|abfss=/path/to/init_script', 'daily', 'user.1@xyz.com',
 '{{"param1": "value1", "param2": "value2"}}', 'user.1@xyz.com', 'CAN_MANAGE', true, "path_to_notebook_1,path_to_notebook_2",”SINGLE_USER”
);
"""
# Execute the SQL query
spark.sql(sql_query)&lt;/LI-CODE&gt;
&lt;H4&gt;&lt;SPAN&gt;CONFIG TABLE SNAPSHOT&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_1-1747811635090.png" style="width: 782px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17015iE148835ED4AACC13/image-dimensions/782x127?v=v2" width="782" height="127" role="button" title="de_expert_aj_1-1747811635090.png" alt="de_expert_aj_1-1747811635090.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;H4&gt;&lt;SPAN&gt;JOB SNAPSHOT&lt;/SPAN&gt;&lt;/H4&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_2-1747811635150.png" style="width: 574px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17016i5E4F428C6E1E2BC8/image-dimensions/574x320?v=v2" width="574" height="320" role="button" title="de_expert_aj_2-1747811635150.png" alt="de_expert_aj_2-1747811635150.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&lt;STRONG&gt;NOTE:&amp;nbsp;&lt;/STRONG&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;Each row in a config table represents a separate &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;pipeline&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; in a job/workflow&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;UL&gt;
&lt;LI aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;All the tasks sharing the same &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;BATCH&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; belong to the same job.&amp;nbsp;&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;The&lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt; job_cluster_key&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; allows multiple tasks to share the same compute cluster, promoting efficient resource utilization. In the example above, Pipeline 2 and Pipeline 3 are both assigned the same &lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;job_cluster_key&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;—&lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;cluster_2&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt;—which means they run on the same cluster instance rather than provisioning separate clusters for each task.&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;FONT color="#339966"&gt;&lt;I&gt;&lt;SPAN&gt;The number of tasks in a &lt;/SPAN&gt;&lt;/I&gt;&lt;STRONG&gt;&lt;I&gt;pipeline&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt; is derived from the number of notebook paths passed in a row.&amp;nbsp;&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="2"&gt;&lt;FONT color="#339966"&gt;&lt;STRONG&gt;&lt;I&gt;Example&lt;/I&gt;&lt;/STRONG&gt;&lt;I&gt;&lt;SPAN&gt;: Two notebook paths result in two tasks per pipeline.&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/I&gt;&lt;I&gt;&lt;SPAN&gt;Task dependencies: Task n depends on Task n-1 in the sequence.&lt;/SPAN&gt;&lt;/I&gt;&lt;/FONT&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;I&gt;&lt;/I&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;H4&gt;&lt;SPAN&gt;Key Benefits of the Config Table Approach&lt;/SPAN&gt;&lt;/H4&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Dynamic Job Creation&lt;/STRONG&gt;&lt;SPAN&gt;: No need to manually configure a large number of workflows&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Task-Level Control&lt;/STRONG&gt;&lt;SPAN&gt;: Each task can have different worker configurations, compute policies, and notebook paths&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Efficient Job Execution&lt;/STRONG&gt;&lt;SPAN&gt;: Tasks can be grouped under the same job or run independently&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H2&gt;&lt;SPAN&gt;Step 2: Generating Workflows Using Databricks SDK for Python&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Once the config table is set up, we can use the &lt;/SPAN&gt;&lt;A href="https://docs.databricks.com/aws/en/dev-tools/sdk-python#notebook" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;Databricks SDK for Python &lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;to generate workflows dynamically. The following steps are involved:&lt;/SPAN&gt;&lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;&lt;SPAN&gt;Read the config table to retrieve job definitions&amp;nbsp; :&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;P&gt;&lt;SPAN&gt;To dynamically generate workflows, we first need to retrieve the job definitions stored in a &lt;/SPAN&gt;&lt;STRONG&gt;configuration&lt;/STRONG&gt;&lt;SPAN&gt; table.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;We query the table by executing a SQL statement against a Databricks SQL Warehouse using the &lt;/SPAN&gt;&lt;SPAN&gt;execute_statement()&lt;/SPAN&gt;&lt;SPAN&gt; method.&amp;nbsp;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;The query results are then mapped into a list of dictionaries (&lt;/SPAN&gt;&lt;SPAN&gt;mapped_results&lt;/SPAN&gt;&lt;SPAN&gt;), which will be used in later steps to generate job clusters and tasks dynamically.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Here’s the code for this step:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;import json
from databricks.sdk import WorkspaceClient
from databricks.sdk.service import jobs, compute, iam
from databricks.sdk.service.jobs import QueueSettings


CONFIG_TABLE_NAME = "catalog.schema.job_config"
SQL_WAREHOUSE_ID = "862f1d7ghf0424f7"  
batch = "BATCH_1" 


try:
   w = WorkspaceClient()
   jobgrp_query = f"SELECT * FROM {CONFIG_TABLE_NAME} WHERE LOWER(TRIM(batch))='{batch.lower().strip()}'"


   print("SQL warehouse starting... ")
   w.warehouses.start_and_wait(SQL_WAREHOUSE_ID)
   wh = w.warehouses.wait_get_warehouse_running(SQL_WAREHOUSE_ID)


   if wh.state == wh.state.RUNNING:
       result = w.statement_execution.execute_statement(
           statement=jobgrp_query, warehouse_id=SQL_WAREHOUSE_ID
       )
       column_names = [col.name for col in result.manifest.schema.columns]
       mapped_results = [
           dict(zip(column_names, row)) for row in result.result.data_array
       ]
except Exception as e:
   print(f"An error occurred: {e}")
   raise e&lt;/LI-CODE&gt;
&lt;P&gt;2.&amp;nbsp;&lt;SPAN&gt;Create job clusters based on the cluster configurations in the table&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;def create_job_clusters(d):
   """
   Create job clusters based on the configuration dictionary.


   Args:
       d (dict): Configuration dictionary containing job details.


   Returns:
       JobCluster: Created job cluster object.
   """
   try:
       job_cluster = jobs.JobCluster(
           job_cluster_key=d["job_cluster_key"],
           new_cluster=compute.ClusterSpec(
               cluster_name="",
               spark_version=d["spark_ver"],
               node_type_id=d['executor_node_type'],
               driver_node_type_id=d['driver_node_type'],
               autoscale=compute.AutoScale(
                   min_workers=d['min_workers'],
                   max_workers=d['max_workers']
               ),
               data_security_mode=compute.DataSecurityMode.SINGLE_USER if d['data_security_mode'] == 'SINGLE_USER' else compute.DataSecurityMode.USER_INSOLATION,
           )
       )
       return job_cluster
   except Exception as e:
       raise e​&lt;/LI-CODE&gt;
&lt;P&gt;&amp;nbsp;3.&amp;nbsp;&lt;SPAN&gt;Create tasks and automatically manage task dependencies, where each task &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;n&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt; depends on task &lt;/SPAN&gt;&lt;I&gt;&lt;SPAN&gt;n-1&lt;/SPAN&gt;&lt;/I&gt;&lt;SPAN&gt;, as illustrated &lt;/SPAN&gt;&lt;A href="https://docs.google.com/document/d/1lEOuX4D8vEtPvdP615eA6PHTLggkWTZo/edit#heading=h.7ivafnhmftz" target="_blank" rel="noopener"&gt;&lt;SPAN&gt;above&lt;/SPAN&gt;&lt;/A&gt;&lt;SPAN&gt;:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;While the example below focuses on chaining notebook-based tasks, &lt;/SPAN&gt;&lt;STRONG&gt;there are many more options you can configure via the&amp;nbsp; configuration table&lt;/STRONG&gt;&lt;SPAN&gt;, such as:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Task-level parameters (e.g., base_parameters)&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Timeouts and retry policies&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Libraries to install on the cluster&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Cluster reuse and pool support&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Email alerts or webhook notifications&lt;BR /&gt;&lt;BR /&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;Task types beyond notebooks (e.g., Python scripts, JARs)&lt;/LI&gt;
&lt;/UL&gt;
&lt;LI-CODE lang="python"&gt;def create_task(d):
  """
  Create tasks for the job based on the configuration dictionary.
  The number of tasks depends on the notebook_paths

  Args:
      d (dict): Configuration dictionary containing job details.

  Returns:
      list: List of SubmitTask objects.
  """
  try:
      notebook_paths = d['notebook_paths'].split(',')
      tasks = []
      for i, notebook_path in enumerate(notebook_paths):
          task = jobs.Task(
              task_key=f"{i+1}_{d['config_id']}",
              job_cluster_key=d["job_cluster_key"],
              run_if=jobs.RunIf("ALL_SUCCESS"),
              notebook_task=jobs.NotebookTask(notebook_path=notebook_path),
              depends_on=[tasks[-1]] if tasks else []
          )
          tasks.append(task)
      return tasks
  except Exception as e:
      raise e&lt;/LI-CODE&gt;
&lt;P&gt;4. C&lt;SPAN&gt;reate a Job /workflow&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;def create_job(d, tasks, job_clusters, databricks_group_name):
   """
   Create a job in Databricks based on the provided configuration.

   Args:
       d (dict): Configuration dictionary containing job details.
       tasks (list): List of tasks to be included in the job.
       job_clusters (list): List of job clusters to be used.
       databricks_group_name (str): Name of the Databricks group.

   Returns:
       Job: Created job object.
   """
   try:
       job = w.jobs.create(
           name=f"job_{d['batch']}",
           email_notifications=jobs.JobEmailNotifications(no_alert_for_skipped_runs=False),
           timeout_seconds=1000,
           max_concurrent_runs=1,
           tasks=tasks,
           job_clusters=job_clusters,
           queue=QueueSettings(enabled=True),
           access_control_list=[
               iam.AccessControlRequest(
                   user_name=d["user_name"],
                   permission_level=iam.PermissionLevel.CAN_MANAGE if d["user_permissions"] == "CAN_MANAGE" else iam.PermissionLevel.CAN_RUN
               )
           ]
       )
       return job
   except Exception as e:
       print(f"Failed to create job: {e}")
       raise e
&lt;/LI-CODE&gt;
&lt;P&gt;5.&amp;nbsp;&lt;SPAN&gt;Submit the job for execution&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;try:
   tasks = []
   clusters = []
   for d in mapped_results:
       tasks.extend(create_task(d))
       ##create cluster only if it's not already created
       if d["job_cluster_key"] not in [cluster.job_cluster_key for cluster in clusters]:
           clusters.append(create_job_clusters(d))


   if tasks:
       try:
           print("Job is running")
           job = create_job(d, tasks, clusters, "DATABRICKS_GROUP_NAME")
           print(f"JOB_ID : {job.job_id}")
           run = w.jobs.run_now_and_wait(job.job_id)
       except Exception as e:
           print(f"An error occurred in job run: {e}")
           raise e
       print("Job is finished")


       if run.state.result_state != jobs.RunResultState.SUCCESS:
           raise Exception(f"Job failed. Result state: {run.state.result_state}")
       else:
           print("Job finished successfully")
   else:
       print("No tasks to run")
except Exception as e:
   print(f"An error occurred: {e}")
   raise e&lt;/LI-CODE&gt;
&lt;H2&gt;&lt;SPAN&gt;Submitting and Managing Jobs&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;There are multiple ways to submit and manage jobs dynamically:&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;1. &lt;A href="https://databricks-sdk-py.readthedocs.io/en/latest/workspace/jobs/jobs.html#:~:text=JobPermissions-,submit,-(%5B%2C%20access_control_list" target="_blank" rel="noopener"&gt;&lt;STRONG&gt;One-Time Runs &lt;/STRONG&gt;&lt;/A&gt;&lt;SPAN&gt;(Logical Jobs)&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Submit a one-time run. This endpoint allows you to submit a workload directly without creating a job. You can see the job in Job Runs, but you will not see the physical job under the Jobs tab. This approach is useful for executing workloads on demand without accumulating multiple job definitions in the workspace.&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;span class="lia-inline-image-display-wrapper lia-image-align-inline" image-alt="de_expert_aj_0-1747813197839.png" style="width: 400px;"&gt;&lt;img src="https://community.databricks.com/t5/image/serverpage/image-id/17017iF7B5B817695879F5/image-size/medium?v=v2&amp;amp;px=400" role="button" title="de_expert_aj_0-1747813197839.png" alt="de_expert_aj_0-1747813197839.png" /&gt;&lt;/span&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;%python
w.jobs.submit(
   run_name="Onetime_Run",
   tasks=[task],
   job_clusters=[job_cluster])&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;2. Create and Delete Jobs&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Create a job, run it, and delete it afterward to keep the workspace clean.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;created_job = w.jobs.create(name=f'sdk-{time.time_ns()}',
                           tasks=[
                               jobs.Task(description="test",
                                         existing_cluster_id=cluster_id,
                                         notebook_task=jobs.NotebookTask(notebook_path=notebook_path),
                                         task_key="test",
                                         timeout_seconds=0)
                           ])
w.jobs.cancel_all_runs(job_id=created_job.job_id)
# cleanup
w.jobs.delete(job_id=created_job.job_id)&lt;/LI-CODE&gt;
&lt;P&gt;3.&amp;nbsp;&lt;STRONG&gt;Check for Existing Jobs&lt;/STRONG&gt;&lt;/P&gt;
&lt;P&gt;&lt;LI-WRAPPER&gt;&lt;/LI-WRAPPER&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;&lt;SPAN&gt;Before creating a new job, check if a similar job already exists based on the job name.&lt;/SPAN&gt;&lt;/SPAN&gt;&lt;LI-CODE lang="python"&gt;existing_jobs = w.jobs.list()
for job in existing_jobs:
   if job.settings.name) == "Dynamic_Job":
       print(f"Job already exists with ID: {job.job_id}")
       break&lt;/LI-CODE&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;STRONG&gt;&lt;I&gt;Note: This framework can be extended for serverless as well by modifying properties in the config table&lt;/I&gt;&lt;/STRONG&gt;&lt;/P&gt;
&lt;H2&gt;&lt;SPAN&gt;Conclusion&lt;/SPAN&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;By leveraging the Databricks Python SDK and a configuration-driven approach, we can:&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Automate workflow creation dynamically&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Gain fine-grained control over tasks and clusters&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;SPAN&gt;&lt;span class="lia-unicode-emoji" title=":white_heavy_check_mark:"&gt;✅&lt;/span&gt; Eliminate the need for manual workflow management&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;This method provides greater flexibility than Terraform/DAB (which requires predefined tasks) and workflows ForEach task (which applies the same config to all tasks).&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&lt;SPAN&gt;Now, effortlessly generate a vast number of workflows with just a configuration table! &lt;span class="lia-unicode-emoji" title=":rocket:"&gt;🚀&lt;/span&gt;&lt;/SPAN&gt;&lt;/P&gt;</description>
      <pubDate>Tue, 10 Jun 2025 15:44:50 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/dynamic-workflow-creation-using-a-config-driven-approach/ba-p/119839</guid>
      <dc:creator>de_expert_aj</dc:creator>
      <dc:date>2025-06-10T15:44:50Z</dc:date>
    </item>
  </channel>
</rss>

