Repository: fal-ai/dbt-fal

Issue:
support `--full-refresh` flag with `fal flow run`
**Context**
I usually to `dbt run --full-refresh` to completely recreate incremental models. Recently I switched to using `fal flow run --experimental-models` instead of `dbt run` to work with python models in the dbt dag. But fal flow run doesn't support the `--full-refresh` flag

**Describe the solution you'd like**
Pass flags that are not supported by `fal flow run` to `dbt run` whenever `fal` executes `dbt run`. At the very least, please pass on the `--full-refresh` flag

**Is there an existing feature request for this? No**
 * [x]  I have searched the existing issues


Discussion:

Resolving patch:
diff --git a/src/fal/cli/args.py b/src/fal/cli/args.py
--- a/src/fal/cli/args.py
+++ b/src/fal/cli/args.py
@@ -94,7 +94,16 @@ def _add_target_option(parser: argparse.ArgumentParser):
         "--target",
         type=str,
         default=None,
-        help="Specify a custom target from profiles.yml",
+        help="Specify a custom target from profiles.yml.",
+    )
+
+
+def _add_full_refresh_option(parser: argparse.ArgumentParser):
+    parser.add_argument(
+        "--full-refresh",
+        action="store_true",
+        default=False,
+        help="If specified, fal will pass dbt calls the --full-refresh flag, which will drop incremental models and fully-recalculate the incremental table from the model definition.",
     )
 
 
@@ -221,6 +230,7 @@ def _build_flow_parser(sub: argparse.ArgumentParser):
     _add_experimental_threading(flow_run_parser)
     _add_vars_option(flow_run_parser)
     _add_target_option(flow_run_parser)
+    _add_full_refresh_option(flow_run_parser)
 
 
 def _build_cli_parser():
diff --git a/src/fal/cli/dbt_runner.py b/src/fal/cli/dbt_runner.py
--- a/src/fal/cli/dbt_runner.py
+++ b/src/fal/cli/dbt_runner.py
@@ -71,6 +71,9 @@ def get_dbt_command_list(args: argparse.Namespace, models_list: List[str]) -> Li
     if args.state:
         command_list += ["--state", args.state]
 
+    if args.full_refresh:
+        command_list += ["--full-refresh"]
+
     if args.target:
         command_list += ["--target", args.target]
 
diff --git a/src/fal/telemetry/telemetry.py b/src/fal/telemetry/telemetry.py
--- a/src/fal/telemetry/telemetry.py
+++ b/src/fal/telemetry/telemetry.py
@@ -374,6 +374,7 @@ def _clean_args_list(args: List[str]) -> List[str]:
         "--threads",
         "--thread",
         "--state",
+        "--full-refresh",
         "-s",
         "--select",
         "-m",


Test patch:
diff --git a/integration_tests/projects/008_pure_python_models/models/fal/staging/model_c.sql b/integration_tests/projects/008_pure_python_models/models/fal/staging/model_c.sql
--- a/integration_tests/projects/008_pure_python_models/models/fal/staging/model_c.sql
+++ b/integration_tests/projects/008_pure_python_models/models/fal/staging/model_c.sql
@@ -1,12 +1,12 @@
 
 {{ config(materialized='ephemeral') }}
 /*
-FAL_GENERATED 5703f4afba785d3cf956a51bcb4dc564
+FAL_GENERATED 2d1b785862f633481ecaa4b410a5a8a6
 
 Script dependencies:
 
-{{ ref("model_b") }}
 {{ ref('model_a') }}
+{{ ref('model_b') }}
 
 */
 
diff --git a/integration_tests/projects/008_pure_python_models/models/model_d.sql b/integration_tests/projects/008_pure_python_models/models/model_d.sql
--- a/integration_tests/projects/008_pure_python_models/models/model_d.sql
+++ b/integration_tests/projects/008_pure_python_models/models/model_d.sql
@@ -1,3 +1,6 @@
+-- To test full-refresh
+{{ config(materialized='incremental') }}
+
 WITH data AS (
     SELECT 
         my_int,
diff --git a/integration_tests/projects/008_pure_python_models/models/model_e.ipynb b/integration_tests/projects/008_pure_python_models/models/model_e.ipynb
--- a/integration_tests/projects/008_pure_python_models/models/model_e.ipynb
+++ b/integration_tests/projects/008_pure_python_models/models/model_e.ipynb
@@ -9,6 +9,7 @@
     "import os\n",
     "from functools import reduce\n",
     "from faldbt.magics import init_fal\n",
+    "from fal.typing import *\n",
     "%init_fal project_dir=../.. profiles_dir=../../.. default_model_name=model_e_notebook"
    ]
   },
diff --git a/integration_tests/projects/008_pure_python_models/models/staging/model_c.py b/integration_tests/projects/008_pure_python_models/models/staging/model_c.py
--- a/integration_tests/projects/008_pure_python_models/models/staging/model_c.py
+++ b/integration_tests/projects/008_pure_python_models/models/staging/model_c.py
@@ -1,29 +1,15 @@
 """
-DEPENDENCY: ref("model_b")
+DEPENDENCY:
+# not really used, but we put it to make sure it is picked up
 - ref('model_a')
 """
-import os
-from functools import reduce
-from datetime import datetime
+from _fal_testing.utils import create_model_artifact
+from fal.typing import *
 
-# does not get picked up, so we put it in the docstring
-df = ref(*["model_b"])
+df = ref("model_b")
 
 df["my_bool"] = True
 
-# BigQuery only:
-# df["my_ts"] = datetime(2022, 6, 27, 17, 12, 25)
-
 write_to_model(df)
 
-model_name = context.current_model.name
-
-output = f"Model name: {model_name}"
-output = output + f"\nStatus: {context.current_model.status}"
-
-output = output + "\nModel dataframe name: {model_name}"
-temp_dir = os.environ["temp_dir"]
-
-write_dir = open(reduce(os.path.join, [temp_dir, model_name + ".txt"]), "w")
-write_dir.write(output)
-write_dir.close()
+create_model_artifact(context)
diff --git a/tests/test_cli.py b/tests/test_cli.py
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -126,6 +126,28 @@ def test_flow_run_with_defer(capfd):
         assert len(found) == 7
 
 
+def test_flow_run_with_full_refresh(capfd):
+    with ProjectTemporaryDirectory() as tmp_dir:
+        captured = _run_fal(
+            [
+                # fmt: off
+                "flow", "run",
+                "--full-refresh",
+                "--project-dir", tmp_dir,
+                "--profiles-dir", profiles_dir,
+                # fmt: on
+            ],
+            capfd,
+        )
+
+        executing_re = re.compile(
+            r": dbt run --threads 1 --project-dir [\w\d\/\-\_]+ --profiles-dir [\w\d\/\-\_]+tests/mock/mockProfile --full-refresh"
+        )
+        found = executing_re.findall(captured.out)
+        # We run each model separately
+        assert len(found) == 7
+
+
 def test_flow_run_with_vars(capfd):
     with ProjectTemporaryDirectory() as tmp_dir:
         captured = _run_fal(