prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Python script should be able to handle relative imports
  I was trying execute a script using `fal`, it works fine when full code is in a single script but breaks down when I write down my script to different modules. Probably this is because fal is internally using python's `exec` builtins function to execute the script after reading the file. Would appreciate it very much if you guys can add this feature to fal as soon as possible. It is a great tool to work with dbt.! :D
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/fal/cli/fal_runner.py`
  Content:
  ```
  1 import argparse
  2 from typing import List
  3 import os
  4 
  5 import dbt.exceptions
  6 import dbt.ui
  7 from dbt.config.profile import DEFAULT_PROFILES_DIR
  8 
  9 from fal.run_scripts import run_global_scripts, run_scripts
  10 from fal.fal_script import FalScript
  11 from faldbt.project import FalDbt, FalGeneralException, FalProject
  12 
  13 
  14 def create_fal_dbt(
  15     args: argparse.Namespace,
  16 ):
  17     real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))
  18     real_profiles_dir = None
  19     if args.profiles_dir is not None:
  20         real_profiles_dir = os.path.realpath(os.path.normpath(args.profiles_dir))
  21     elif os.getenv("DBT_PROFILES_DIR"):
  22         real_profiles_dir = os.path.realpath(
  23             os.path.normpath(os.getenv("DBT_PROFILES_DIR"))
  24         )
  25     else:
  26         real_profiles_dir = DEFAULT_PROFILES_DIR
  27 
  28     return FalDbt(
  29         real_project_dir,
  30         real_profiles_dir,
  31         args.select,
  32         args.exclude,
  33         args.selector,
  34         args.keyword,
  35     )
  36 
  37 
  38 def fal_run(
  39     args: argparse.Namespace,
  40     selects_count=0,  # TODO: remove `action="extend"` to match exactly what dbt does
  41     exclude_count=0,
  42     script_count=0,
  43 ):
  44     "Runs the fal run command in a subprocess"
  45 
  46     args_dict = vars(args)
  47     selector_flags = args.select or args.exclude or args.selector
  48     if args_dict.get("all") and selector_flags:
  49         raise FalGeneralException(
  50             "Cannot pass --all flag alongside selection flags (--select/--models, --exclude, --selector)"
  51         )
  52 
  53     faldbt = create_fal_dbt(args)
  54     project = FalProject(faldbt)
  55     models = project.get_filtered_models(
  56         args_dict.get("all"), selector_flags, args_dict.get("before")
  57     )
  58 
  59     _handle_selector_warnings(selects_count, exclude_count, script_count, args)
  60 
  61     scripts = _select_scripts(args_dict, models, project, args)
  62 
  63     # run model specific scripts first
  64     run_scripts(scripts, project)
  65 
  66     # then run global scripts
  67     if _should_run_global_scripts(args_dict):
  68         _run_global_scripts(
  69             project, faldbt, "before" if args_dict.get("before") else "after"
  70         )
  71 
  72 
  73 def _handle_selector_warnings(selects_count, exclude_count, script_count, args):
  74     # TODO: remove `action="extend"` to match exactly what dbt does
  75     if selects_count > 1:
  76         dbt.exceptions.warn_or_error(
  77             "Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\n"
  78             + f"Please use model selection like dbt. Use: --select {' '.join(args.select)}",
  79             log_fmt=dbt.ui.warning_tag("{}"),
  80         )
  81     if exclude_count > 1:
  82         dbt.exceptions.warn_or_error(
  83             "Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\n"
  84             + f"Please use model exclusion like dbt. Use: --exclude {' '.join(args.exclude)}",
  85             log_fmt=dbt.ui.warning_tag("{}"),
  86         )
  87     if script_count > 1:
  88         dbt.exceptions.warn_or_error(
  89             "Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\n"
  90             + f"Please use: --script {' '.join(args.scripts)}",
  91             log_fmt=dbt.ui.warning_tag("{}"),
  92         )
  93 
  94 
  95 def _should_run_global_scripts(args_dict) -> bool:
  96     return args_dict.get("scripts")
  97 
  98 
  99 def _select_scripts(args_dict, models, project, args) -> List[FalScript]:
  100     scripts = []
  101     # if --script selector is there only run selected scripts
  102     if args_dict.get("scripts"):
  103         scripts = []
  104         for model in models:
  105             model_scripts = model.get_scripts(args.keyword, args_dict.get("before"))
  106             for el in args.scripts:
  107                 if el in model_scripts:
  108                     scripts.append(FalScript(model, el))
  109     else:
  110         real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))
  111         for model in models:
  112             for path in model.get_script_paths(
  113                 args.keyword, real_project_dir, args_dict.get("before")
  114             ):
  115                 scripts.append(FalScript(model, path))
  116 
  117     return scripts
  118 
  119 
  120 def _run_global_scripts(project: FalProject, faldbt: FalDbt, global_key: str):
  121     global_scripts = list(
  122         map(
  123             lambda path: FalScript(None, path),
  124             faldbt._global_script_paths[global_key],
  125         )
  126     )
  127 
  128     run_global_scripts(global_scripts, project)
  129
  ```
  
  Path: `src/fal/fal_script.py`
  Content:
  ```
  1 from dataclasses import dataclass, field
  2 from typing import List, TypeVar, Dict, Union
  3 from faldbt.project import DbtModel, FalDbt
  4 from pathlib import Path
  5 
  6 T = TypeVar("T", bound="FalScript")
  7 
  8 
  9 class FalDagCycle(Exception):
  10     pass
  11 
  12 
  13 @dataclass(frozen=True)
  14 class FalScript:
  15     model: Union[DbtModel, None]
  16     path: Path
  17 
  18     def exec(self, context, faldbt: FalDbt):
  19         """
  20         Executes the script
  21         """
  22         with open(self.path) as file:
  23             a_script = file.read()
  24             exec(
  25                 a_script,
  26                 {
  27                     "context": context,
  28                     "ref": faldbt.ref,
  29                     "source": faldbt.source,
  30                     "write_to_source": faldbt.write_to_source,
  31                     "write_to_firestore": faldbt.write_to_firestore,
  32                     "list_models": faldbt.list_models,
  33                     "list_models_ids": faldbt.list_models_ids,
  34                     "list_sources": faldbt.list_sources,
  35                     "list_features": faldbt.list_features,
  36                 },
  37             )
  38
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/src/fal/cli/fal_runner.py b/src/fal/cli/fal_runner.py
  --- a/src/fal/cli/fal_runner.py
  +++ b/src/fal/cli/fal_runner.py
  @@ -1,5 +1,6 @@
   import argparse
   from typing import List
  +from pathlib import Path
   import os
   
   import dbt.exceptions
  @@ -105,7 +106,7 @@
               model_scripts = model.get_scripts(args.keyword, args_dict.get("before"))
               for el in args.scripts:
                   if el in model_scripts:
  -                    scripts.append(FalScript(model, el))
  +                    scripts.append(FalScript(model, Path(el)))
       else:
           real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))
           for model in models:
  diff --git a/src/fal/fal_script.py b/src/fal/fal_script.py
  --- a/src/fal/fal_script.py
  +++ b/src/fal/fal_script.py
  @@ -2,6 +2,7 @@
   from typing import List, TypeVar, Dict, Union
   from faldbt.project import DbtModel, FalDbt
   from pathlib import Path
  +import sys
   
   T = TypeVar("T", bound="FalScript")
   
  @@ -19,6 +20,11 @@
           """
           Executes the script
           """
  +
  +        # Enable local imports
  +        local_path = str(self.path.parent)
  +        sys.path.append(local_path)
  +
           with open(self.path) as file:
               a_script = file.read()
               exec(
  @@ -35,3 +41,4 @@
                       "list_features": faldbt.list_features,
                   },
               )
  +            sys.path.remove(local_path)
  
verification_info: {"golden_diff": "diff --git a/src/fal/cli/fal_runner.py b/src/fal/cli/fal_runner.py\n--- a/src/fal/cli/fal_runner.py\n+++ b/src/fal/cli/fal_runner.py\n@@ -1,5 +1,6 @@\n import argparse\n from typing import List\n+from pathlib import Path\n import os\n \n import dbt.exceptions\n@@ -105,7 +106,7 @@\n             model_scripts = model.get_scripts(args.keyword, args_dict.get(\"before\"))\n             for el in args.scripts:\n                 if el in model_scripts:\n-                    scripts.append(FalScript(model, el))\n+                    scripts.append(FalScript(model, Path(el)))\n     else:\n         real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))\n         for model in models:\ndiff --git a/src/fal/fal_script.py b/src/fal/fal_script.py\n--- a/src/fal/fal_script.py\n+++ b/src/fal/fal_script.py\n@@ -2,6 +2,7 @@\n from typing import List, TypeVar, Dict, Union\n from faldbt.project import DbtModel, FalDbt\n from pathlib import Path\n+import sys\n \n T = TypeVar(\"T\", bound=\"FalScript\")\n \n@@ -19,6 +20,11 @@\n         \"\"\"\n         Executes the script\n         \"\"\"\n+\n+        # Enable local imports\n+        local_path = str(self.path.parent)\n+        sys.path.append(local_path)\n+\n         with open(self.path) as file:\n             a_script = file.read()\n             exec(\n@@ -35,3 +41,4 @@\n                     \"list_features\": faldbt.list_features,\n                 },\n             )\n+            sys.path.remove(local_path)\n", "issue": "Python script should be able to handle relative imports\nI was trying execute a script using `fal`, it works fine when full code is in a single script but breaks down when I write down my script to different modules. Probably this is because fal is internally using python's `exec` builtins function to execute the script after reading the file. Would appreciate it very much if you guys can add this feature to fal as soon as possible. It is a great tool to work with dbt.! :D\n", "before_files": [{"content": "import argparse\nfrom typing import List\nimport os\n\nimport dbt.exceptions\nimport dbt.ui\nfrom dbt.config.profile import DEFAULT_PROFILES_DIR\n\nfrom fal.run_scripts import run_global_scripts, run_scripts\nfrom fal.fal_script import FalScript\nfrom faldbt.project import FalDbt, FalGeneralException, FalProject\n\n\ndef create_fal_dbt(\n    args: argparse.Namespace,\n):\n    real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))\n    real_profiles_dir = None\n    if args.profiles_dir is not None:\n        real_profiles_dir = os.path.realpath(os.path.normpath(args.profiles_dir))\n    elif os.getenv(\"DBT_PROFILES_DIR\"):\n        real_profiles_dir = os.path.realpath(\n            os.path.normpath(os.getenv(\"DBT_PROFILES_DIR\"))\n        )\n    else:\n        real_profiles_dir = DEFAULT_PROFILES_DIR\n\n    return FalDbt(\n        real_project_dir,\n        real_profiles_dir,\n        args.select,\n        args.exclude,\n        args.selector,\n        args.keyword,\n    )\n\n\ndef fal_run(\n    args: argparse.Namespace,\n    selects_count=0,  # TODO: remove `action=\"extend\"` to match exactly what dbt does\n    exclude_count=0,\n    script_count=0,\n):\n    \"Runs the fal run command in a subprocess\"\n\n    args_dict = vars(args)\n    selector_flags = args.select or args.exclude or args.selector\n    if args_dict.get(\"all\") and selector_flags:\n        raise FalGeneralException(\n            \"Cannot pass --all flag alongside selection flags (--select/--models, --exclude, --selector)\"\n        )\n\n    faldbt = create_fal_dbt(args)\n    project = FalProject(faldbt)\n    models = project.get_filtered_models(\n        args_dict.get(\"all\"), selector_flags, args_dict.get(\"before\")\n    )\n\n    _handle_selector_warnings(selects_count, exclude_count, script_count, args)\n\n    scripts = _select_scripts(args_dict, models, project, args)\n\n    # run model specific scripts first\n    run_scripts(scripts, project)\n\n    # then run global scripts\n    if _should_run_global_scripts(args_dict):\n        _run_global_scripts(\n            project, faldbt, \"before\" if args_dict.get(\"before\") else \"after\"\n        )\n\n\ndef _handle_selector_warnings(selects_count, exclude_count, script_count, args):\n    # TODO: remove `action=\"extend\"` to match exactly what dbt does\n    if selects_count > 1:\n        dbt.exceptions.warn_or_error(\n            \"Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\\n\"\n            + f\"Please use model selection like dbt. Use: --select {' '.join(args.select)}\",\n            log_fmt=dbt.ui.warning_tag(\"{}\"),\n        )\n    if exclude_count > 1:\n        dbt.exceptions.warn_or_error(\n            \"Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\\n\"\n            + f\"Please use model exclusion like dbt. Use: --exclude {' '.join(args.exclude)}\",\n            log_fmt=dbt.ui.warning_tag(\"{}\"),\n        )\n    if script_count > 1:\n        dbt.exceptions.warn_or_error(\n            \"Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\\n\"\n            + f\"Please use: --script {' '.join(args.scripts)}\",\n            log_fmt=dbt.ui.warning_tag(\"{}\"),\n        )\n\n\ndef _should_run_global_scripts(args_dict) -> bool:\n    return args_dict.get(\"scripts\")\n\n\ndef _select_scripts(args_dict, models, project, args) -> List[FalScript]:\n    scripts = []\n    # if --script selector is there only run selected scripts\n    if args_dict.get(\"scripts\"):\n        scripts = []\n        for model in models:\n            model_scripts = model.get_scripts(args.keyword, args_dict.get(\"before\"))\n            for el in args.scripts:\n                if el in model_scripts:\n                    scripts.append(FalScript(model, el))\n    else:\n        real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))\n        for model in models:\n            for path in model.get_script_paths(\n                args.keyword, real_project_dir, args_dict.get(\"before\")\n            ):\n                scripts.append(FalScript(model, path))\n\n    return scripts\n\n\ndef _run_global_scripts(project: FalProject, faldbt: FalDbt, global_key: str):\n    global_scripts = list(\n        map(\n            lambda path: FalScript(None, path),\n            faldbt._global_script_paths[global_key],\n        )\n    )\n\n    run_global_scripts(global_scripts, project)\n", "path": "src/fal/cli/fal_runner.py"}, {"content": "from dataclasses import dataclass, field\nfrom typing import List, TypeVar, Dict, Union\nfrom faldbt.project import DbtModel, FalDbt\nfrom pathlib import Path\n\nT = TypeVar(\"T\", bound=\"FalScript\")\n\n\nclass FalDagCycle(Exception):\n    pass\n\n\n@dataclass(frozen=True)\nclass FalScript:\n    model: Union[DbtModel, None]\n    path: Path\n\n    def exec(self, context, faldbt: FalDbt):\n        \"\"\"\n        Executes the script\n        \"\"\"\n        with open(self.path) as file:\n            a_script = file.read()\n            exec(\n                a_script,\n                {\n                    \"context\": context,\n                    \"ref\": faldbt.ref,\n                    \"source\": faldbt.source,\n                    \"write_to_source\": faldbt.write_to_source,\n                    \"write_to_firestore\": faldbt.write_to_firestore,\n                    \"list_models\": faldbt.list_models,\n                    \"list_models_ids\": faldbt.list_models_ids,\n                    \"list_sources\": faldbt.list_sources,\n                    \"list_features\": faldbt.list_features,\n                },\n            )\n", "path": "src/fal/fal_script.py"}], "after_files": [{"content": "import argparse\nfrom typing import List\nfrom pathlib import Path\nimport os\n\nimport dbt.exceptions\nimport dbt.ui\nfrom dbt.config.profile import DEFAULT_PROFILES_DIR\n\nfrom fal.run_scripts import run_global_scripts, run_scripts\nfrom fal.fal_script import FalScript\nfrom faldbt.project import FalDbt, FalGeneralException, FalProject\n\n\ndef create_fal_dbt(\n    args: argparse.Namespace,\n):\n    real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))\n    real_profiles_dir = None\n    if args.profiles_dir is not None:\n        real_profiles_dir = os.path.realpath(os.path.normpath(args.profiles_dir))\n    elif os.getenv(\"DBT_PROFILES_DIR\"):\n        real_profiles_dir = os.path.realpath(\n            os.path.normpath(os.getenv(\"DBT_PROFILES_DIR\"))\n        )\n    else:\n        real_profiles_dir = DEFAULT_PROFILES_DIR\n\n    return FalDbt(\n        real_project_dir,\n        real_profiles_dir,\n        args.select,\n        args.exclude,\n        args.selector,\n        args.keyword,\n    )\n\n\ndef fal_run(\n    args: argparse.Namespace,\n    selects_count=0,  # TODO: remove `action=\"extend\"` to match exactly what dbt does\n    exclude_count=0,\n    script_count=0,\n):\n    \"Runs the fal run command in a subprocess\"\n\n    args_dict = vars(args)\n    selector_flags = args.select or args.exclude or args.selector\n    if args_dict.get(\"all\") and selector_flags:\n        raise FalGeneralException(\n            \"Cannot pass --all flag alongside selection flags (--select/--models, --exclude, --selector)\"\n        )\n\n    faldbt = create_fal_dbt(args)\n    project = FalProject(faldbt)\n    models = project.get_filtered_models(\n        args_dict.get(\"all\"), selector_flags, args_dict.get(\"before\")\n    )\n\n    _handle_selector_warnings(selects_count, exclude_count, script_count, args)\n\n    scripts = _select_scripts(args_dict, models, project, args)\n\n    # run model specific scripts first\n    run_scripts(scripts, project)\n\n    # then run global scripts\n    if _should_run_global_scripts(args_dict):\n        _run_global_scripts(\n            project, faldbt, \"before\" if args_dict.get(\"before\") else \"after\"\n        )\n\n\ndef _handle_selector_warnings(selects_count, exclude_count, script_count, args):\n    # TODO: remove `action=\"extend\"` to match exactly what dbt does\n    if selects_count > 1:\n        dbt.exceptions.warn_or_error(\n            \"Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\\n\"\n            + f\"Please use model selection like dbt. Use: --select {' '.join(args.select)}\",\n            log_fmt=dbt.ui.warning_tag(\"{}\"),\n        )\n    if exclude_count > 1:\n        dbt.exceptions.warn_or_error(\n            \"Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\\n\"\n            + f\"Please use model exclusion like dbt. Use: --exclude {' '.join(args.exclude)}\",\n            log_fmt=dbt.ui.warning_tag(\"{}\"),\n        )\n    if script_count > 1:\n        dbt.exceptions.warn_or_error(\n            \"Passing multiple --select/--model flags to fal is deprecated and will be removed in fal version 0.4.\\n\"\n            + f\"Please use: --script {' '.join(args.scripts)}\",\n            log_fmt=dbt.ui.warning_tag(\"{}\"),\n        )\n\n\ndef _should_run_global_scripts(args_dict) -> bool:\n    return args_dict.get(\"scripts\")\n\n\ndef _select_scripts(args_dict, models, project, args) -> List[FalScript]:\n    scripts = []\n    # if --script selector is there only run selected scripts\n    if args_dict.get(\"scripts\"):\n        scripts = []\n        for model in models:\n            model_scripts = model.get_scripts(args.keyword, args_dict.get(\"before\"))\n            for el in args.scripts:\n                if el in model_scripts:\n                    scripts.append(FalScript(model, Path(el)))\n    else:\n        real_project_dir = os.path.realpath(os.path.normpath(args.project_dir))\n        for model in models:\n            for path in model.get_script_paths(\n                args.keyword, real_project_dir, args_dict.get(\"before\")\n            ):\n                scripts.append(FalScript(model, path))\n\n    return scripts\n\n\ndef _run_global_scripts(project: FalProject, faldbt: FalDbt, global_key: str):\n    global_scripts = list(\n        map(\n            lambda path: FalScript(None, path),\n            faldbt._global_script_paths[global_key],\n        )\n    )\n\n    run_global_scripts(global_scripts, project)\n", "path": "src/fal/cli/fal_runner.py"}, {"content": "from dataclasses import dataclass, field\nfrom typing import List, TypeVar, Dict, Union\nfrom faldbt.project import DbtModel, FalDbt\nfrom pathlib import Path\nimport sys\n\nT = TypeVar(\"T\", bound=\"FalScript\")\n\n\nclass FalDagCycle(Exception):\n    pass\n\n\n@dataclass(frozen=True)\nclass FalScript:\n    model: Union[DbtModel, None]\n    path: Path\n\n    def exec(self, context, faldbt: FalDbt):\n        \"\"\"\n        Executes the script\n        \"\"\"\n\n        # Enable local imports\n        local_path = str(self.path.parent)\n        sys.path.append(local_path)\n\n        with open(self.path) as file:\n            a_script = file.read()\n            exec(\n                a_script,\n                {\n                    \"context\": context,\n                    \"ref\": faldbt.ref,\n                    \"source\": faldbt.source,\n                    \"write_to_source\": faldbt.write_to_source,\n                    \"write_to_firestore\": faldbt.write_to_firestore,\n                    \"list_models\": faldbt.list_models,\n                    \"list_models_ids\": faldbt.list_models_ids,\n                    \"list_sources\": faldbt.list_sources,\n                    \"list_features\": faldbt.list_features,\n                },\n            )\n            sys.path.remove(local_path)\n", "path": "src/fal/fal_script.py"}]}