We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Fal read only dbt *.yml files and ignore *.yaml 
<!-- *** Make sure you have searched for an existing bug report for this issue *** -->

**Describe the bug**
In dbt models you can have schems in either .yml or yaml extentions 
also see : https://www.w3schools.io/file/yaml-vs-yml/

**Your environment**
- OS: MacOS
- Paste the following commands output:
```
dbt-fal==1.3.7
fal==0.7.3
dbt-core==1.3.1
```
- Adapter being used: <!-- (if more than one dbt plugin installed) --> 

**How to reproduce**
If you create schema.yaml with fal section the schema will be ignored

**Expected behavior**
schema.yaml should be detected and parsed correctly

**Actual behavior**
Tell us what actually happened.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Additional context**
Add any other context about the problem here.


--- 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/faldbt/parse.py`
Content:
```
1 import os
2 from dataclasses import dataclass
3 import glob
4 from pathlib import Path
5 from typing import Any, List, Dict, Optional, Union, TYPE_CHECKING
6 
7 from dbt.contracts.project import Project as ProjectContract
8 from dbt.config import RuntimeConfig, Project
9 from dbt.config.utils import parse_cli_vars
10 from dbt.contracts.graph.manifest import Manifest
11 from dbt.contracts.results import RunResultsArtifact, FreshnessExecutionResultArtifact
12 from dbt.contracts.project import UserConfig
13 from dbt.config.profile import read_user_config
14 from dbt.exceptions import IncompatibleSchemaException, RuntimeException
15 from fal.utils import cache_static
16 
17 from faldbt.logger import LOGGER
18 from faldbt.utils.yaml_helper import load_yaml
19 from fal.telemetry import telemetry
20 
21 if TYPE_CHECKING:
22     from fal.packages.environments import BaseEnvironment
23 
24 FAL_SCRIPTS_PATH = "fal-scripts-path"
25 FAL_MODELS_PATHS = "fal-models-paths"
26 
27 
28 class FalParseError(Exception):
29     pass
30 
31 
32 def get_dbt_user_config(profiles_dir: str) -> UserConfig:
33     return read_user_config(profiles_dir)
34 
35 
36 @dataclass
37 class RuntimeArgs:
38     project_dir: str
39     profiles_dir: str
40     threads: Optional[int]
41     single_threaded: bool
42     profile: Optional[str]
43     target: Optional[str]
44 
45 
46 def load_dbt_project_contract(project_dir: str) -> ProjectContract:
47     partial_project = Project.partial_load(project_dir)
48     contract = ProjectContract.from_dict(partial_project.project_dict)
49     if not hasattr(contract, "model_paths") or contract.model_paths is None:
50         setattr(contract, "model_paths", contract.source_paths)
51     if not hasattr(contract, "seed_paths") or contract.seed_paths is None:
52         setattr(contract, "seed_paths", contract.data_paths)
53     return contract
54 
55 
56 def get_dbt_config(
57     *,
58     project_dir: str,
59     profiles_dir: str,
60     profile_target: Optional[str] = None,
61     threads: Optional[int] = None,
62     profile: Optional[str] = None,
63 ) -> RuntimeConfig:
64     # Construct a phony config
65     import os
66     args = RuntimeArgs(
67         project_dir=project_dir,
68         profiles_dir=profiles_dir,
69         threads=threads,
70         single_threaded=False,
71         profile=profile,
72         target=profile_target,
73     )
74 
75     if project_dir and not "PYTEST_CURRENT_TEST" in os.environ:
76         # HACK: initializing dbt-fal requires cwd to be project_dir
77         # TODO: this doesn't work in pytest + Github Actions
78         owd = os.getcwd()
79         os.chdir(project_dir)
80         config = RuntimeConfig.from_args(args)
81         os.chdir(owd)
82     else:
83         config = RuntimeConfig.from_args(args)
84     return config
85 
86 
87 def get_el_configs(
88     profiles_dir: str, profile_name: str, target_name: str
89 ) -> Dict[str, Dict]:
90     path = os.path.join(profiles_dir, "profiles.yml")
91     yml = load_yaml(path)
92     sync_configs = (
93         yml.get(profile_name, {}).get("fal_extract_load", {}).get(target_name, {})
94     )
95     return sync_configs
96 
97 
98 def get_vars_dict(project_dir: str, args_vars: str) -> Dict[str, Any]:
99     project_contract = load_dbt_project_contract(project_dir)
100 
101     # NOTE: This happens usually inside unit tests
102     vars = (project_contract is not None and project_contract.vars) or {}
103     cli_vars = parse_cli_vars(args_vars)
104 
105     # cli_vars have higher priority
106     return {**vars, **cli_vars}
107 
108 
109 @cache_static
110 def get_fal_models_dirs(project_dir: str, args_vars: str) -> List[str]:
111     vars = get_vars_dict(project_dir, args_vars)
112     model_paths = vars.get(FAL_MODELS_PATHS) or []
113     if not model_paths:
114         # None or empty list
115         LOGGER.warn(
116             f"Variable '{FAL_MODELS_PATHS}' not defined. Locate fal-format "
117             "Python models in a separate model directory and set it as the variable. "
118             "e.g. {FAL_MODELS_PATHS}: ['fal_models']"
119         )
120 
121         telemetry.log_api(action="fal_models_paths_not_set")
122 
123     if not isinstance(model_paths, list):
124         raise FalParseError(
125             f"Error parsing '{FAL_MODELS_PATHS}'. Expected list of strings and got '{type(model_paths)}'"
126         )
127 
128     return model_paths
129 
130 
131 def get_scripts_dir(project_dir: str, args_vars: str) -> str:
132     vars = get_vars_dict(project_dir, args_vars)
133     scripts_dir = vars.get(FAL_SCRIPTS_PATH, project_dir)
134 
135     if not isinstance(scripts_dir, str):
136         raise FalParseError(
137             f"Error parsing '{FAL_SCRIPTS_PATH}'. Expected string and got '{type(scripts_dir)}'"
138         )
139 
140     return os.path.join(project_dir, scripts_dir)
141 
142 
143 def get_dbt_manifest(config) -> Manifest:
144     from dbt.parser.manifest import ManifestLoader
145 
146     return ManifestLoader.get_full_manifest(config)
147 
148 
149 def get_dbt_sources_artifact(project_dir: str, config: RuntimeConfig):
150     sources_path = os.path.join(project_dir, config.target_path, "sources.json")
151     try:
152         # BACKWARDS: Change intorduced in 1.0.0
153         if hasattr(FreshnessExecutionResultArtifact, "read_and_check_versions"):
154             return FreshnessExecutionResultArtifact.read_and_check_versions(
155                 sources_path
156             )
157         else:
158             return FreshnessExecutionResultArtifact.read(sources_path)
159 
160     except IncompatibleSchemaException as exc:
161         # TODO: add test for this case
162         exc.add_filename(sources_path)
163         raise
164     except RuntimeException as exc:
165         LOGGER.warn("Could not read dbt sources artifact")
166         return None
167 
168 
169 def get_dbt_results(
170     project_dir: str, config: RuntimeConfig
171 ) -> Optional[RunResultsArtifact]:
172     results_path = os.path.join(project_dir, config.target_path, "run_results.json")
173     try:
174         # BACKWARDS: Change intorduced in 1.0.0
175         if hasattr(RunResultsArtifact, "read_and_check_versions"):
176             return RunResultsArtifact.read_and_check_versions(results_path)
177         else:
178             return RunResultsArtifact.read(results_path)
179 
180     except IncompatibleSchemaException as exc:
181         # TODO: add test for this case
182         exc.add_filename(results_path)
183         raise
184     except RuntimeException as exc:
185         LOGGER.warn("Could not read dbt run_results artifact")
186         return None
187 
188 
189 def get_scripts_list(scripts_dir: str) -> List[str]:
190     scripts_path = Path(scripts_dir)
191     return list(map(str, [*scripts_path.rglob("*.py"), *scripts_path.rglob("*.ipynb")]))
192 
193 
194 def get_global_script_configs(source_dirs: List[Path]) -> Dict[str, List[str]]:
195     global_scripts = {"before": [], "after": []}
196     for source_dir in source_dirs:
197         schema_files = glob.glob(os.path.join(source_dir, "**.yml"), recursive=True)
198         for file in schema_files:
199             schema_yml = load_yaml(file)
200             if schema_yml is not None:
201                 fal_config = schema_yml.get("fal", None)
202                 if fal_config is not None:
203                     # sometimes `scripts` can *be* there and still be None
204                     script_paths = fal_config.get("scripts") or []
205                     if isinstance(script_paths, list):
206                         global_scripts["after"] += script_paths
207                     else:
208                         global_scripts["before"] += script_paths.get("before") or []
209                         global_scripts["after"] += script_paths.get("after") or []
210             else:
211                 raise FalParseError("Error parsing the schema file " + file)
212 
213     return global_scripts
214 
215 
216 def _get_required_key(data: Dict[str, Any], name: str) -> Any:
217     if name not in data:
218         raise FalParseError("Missing required key: " + name)
219     return data[name]
220 
221 
222 def load_environments(base_dir: str) -> Dict[str, "BaseEnvironment"]:
223     from fal.packages.environments import create_environment
224     from fal.fal_script import _is_local_environment
225 
226     try:
227         fal_project_path = os.path.join(base_dir, "fal_project.yml")
228         if not os.path.exists(fal_project_path):
229             raise FalParseError(f"{fal_project_path} must exist to define environments")
230 
231         fal_project = load_yaml(fal_project_path)
232 
233         environments = {}
234         for environment in fal_project.get("environments", []):
235             env_name = _get_required_key(environment, "name")
236             if _is_local_environment(env_name):
237                 raise FalParseError(
238                     f"Environment name conflicts with a reserved name: {env_name}."
239                 )
240 
241             env_kind = _get_required_key(environment, "type")
242             environments[env_name] = create_environment(env_name, env_kind, environment)
243         return environments
244     except FalParseError as e:
245         raise RuntimeError("Error loading environments from fal_project.yml") from e
246 
247 def normalize_path(base: str, path: Union[Path, str]):
248     real_base = os.path.realpath(os.path.normpath(base))
249     return Path(os.path.realpath(os.path.join(real_base, path)))
250 
251 
252 def normalize_paths(
253     base: str, paths: Union[List[Path], List[str], List[Union[Path, str]]]
254 ):
255     return list(map(lambda path: normalize_path(base, path), paths))
256
```
--- 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()

```

diff --git a/src/faldbt/parse.py b/src/faldbt/parse.py
--- a/src/faldbt/parse.py
+++ b/src/faldbt/parse.py
@@ -194,7 +194,10 @@
 def get_global_script_configs(source_dirs: List[Path]) -> Dict[str, List[str]]:
     global_scripts = {"before": [], "after": []}
     for source_dir in source_dirs:
+        # Scan directories for .yml files
         schema_files = glob.glob(os.path.join(source_dir, "**.yml"), recursive=True)
+        # Scan directories for .yaml files
+        schema_files += glob.glob(os.path.join(source_dir, "**.yaml"), recursive=True)
         for file in schema_files:
             schema_yml = load_yaml(file)
             if schema_yml is not None:

{"golden_diff": "diff --git a/src/faldbt/parse.py b/src/faldbt/parse.py\n--- a/src/faldbt/parse.py\n+++ b/src/faldbt/parse.py\n@@ -194,7 +194,10 @@\n def get_global_script_configs(source_dirs: List[Path]) -> Dict[str, List[str]]:\n     global_scripts = {\"before\": [], \"after\": []}\n     for source_dir in source_dirs:\n+        # Scan directories for .yml files\n         schema_files = glob.glob(os.path.join(source_dir, \"**.yml\"), recursive=True)\n+        # Scan directories for .yaml files\n+        schema_files += glob.glob(os.path.join(source_dir, \"**.yaml\"), recursive=True)\n         for file in schema_files:\n             schema_yml = load_yaml(file)\n             if schema_yml is not None:\n", "issue": "Fal read only dbt *.yml files and ignore *.yaml \n<!-- *** Make sure you have searched for an existing bug report for this issue *** -->\r\n\r\n**Describe the bug**\r\nIn dbt models you can have schems in either .yml or yaml extentions \r\nalso see : https://www.w3schools.io/file/yaml-vs-yml/\r\n\r\n**Your environment**\r\n- OS: MacOS\r\n- Paste the following commands output:\r\n```\r\ndbt-fal==1.3.7\r\nfal==0.7.3\r\ndbt-core==1.3.1\r\n```\r\n- Adapter being used: <!-- (if more than one dbt plugin installed) --> \r\n\r\n**How to reproduce**\r\nIf you create schema.yaml with fal section the schema will be ignored\r\n\r\n**Expected behavior**\r\nschema.yaml should be detected and parsed correctly\r\n\r\n**Actual behavior**\r\nTell us what actually happened.\r\n\r\n**Screenshots**\r\nIf applicable, add screenshots to help explain your problem.\r\n\r\n**Additional context**\r\nAdd any other context about the problem here.\r\n\n", "before_files": [{"content": "import os\nfrom dataclasses import dataclass\nimport glob\nfrom pathlib import Path\nfrom typing import Any, List, Dict, Optional, Union, TYPE_CHECKING\n\nfrom dbt.contracts.project import Project as ProjectContract\nfrom dbt.config import RuntimeConfig, Project\nfrom dbt.config.utils import parse_cli_vars\nfrom dbt.contracts.graph.manifest import Manifest\nfrom dbt.contracts.results import RunResultsArtifact, FreshnessExecutionResultArtifact\nfrom dbt.contracts.project import UserConfig\nfrom dbt.config.profile import read_user_config\nfrom dbt.exceptions import IncompatibleSchemaException, RuntimeException\nfrom fal.utils import cache_static\n\nfrom faldbt.logger import LOGGER\nfrom faldbt.utils.yaml_helper import load_yaml\nfrom fal.telemetry import telemetry\n\nif TYPE_CHECKING:\n    from fal.packages.environments import BaseEnvironment\n\nFAL_SCRIPTS_PATH = \"fal-scripts-path\"\nFAL_MODELS_PATHS = \"fal-models-paths\"\n\n\nclass FalParseError(Exception):\n    pass\n\n\ndef get_dbt_user_config(profiles_dir: str) -> UserConfig:\n    return read_user_config(profiles_dir)\n\n\n@dataclass\nclass RuntimeArgs:\n    project_dir: str\n    profiles_dir: str\n    threads: Optional[int]\n    single_threaded: bool\n    profile: Optional[str]\n    target: Optional[str]\n\n\ndef load_dbt_project_contract(project_dir: str) -> ProjectContract:\n    partial_project = Project.partial_load(project_dir)\n    contract = ProjectContract.from_dict(partial_project.project_dict)\n    if not hasattr(contract, \"model_paths\") or contract.model_paths is None:\n        setattr(contract, \"model_paths\", contract.source_paths)\n    if not hasattr(contract, \"seed_paths\") or contract.seed_paths is None:\n        setattr(contract, \"seed_paths\", contract.data_paths)\n    return contract\n\n\ndef get_dbt_config(\n    *,\n    project_dir: str,\n    profiles_dir: str,\n    profile_target: Optional[str] = None,\n    threads: Optional[int] = None,\n    profile: Optional[str] = None,\n) -> RuntimeConfig:\n    # Construct a phony config\n    import os\n    args = RuntimeArgs(\n        project_dir=project_dir,\n        profiles_dir=profiles_dir,\n        threads=threads,\n        single_threaded=False,\n        profile=profile,\n        target=profile_target,\n    )\n\n    if project_dir and not \"PYTEST_CURRENT_TEST\" in os.environ:\n        # HACK: initializing dbt-fal requires cwd to be project_dir\n        # TODO: this doesn't work in pytest + Github Actions\n        owd = os.getcwd()\n        os.chdir(project_dir)\n        config = RuntimeConfig.from_args(args)\n        os.chdir(owd)\n    else:\n        config = RuntimeConfig.from_args(args)\n    return config\n\n\ndef get_el_configs(\n    profiles_dir: str, profile_name: str, target_name: str\n) -> Dict[str, Dict]:\n    path = os.path.join(profiles_dir, \"profiles.yml\")\n    yml = load_yaml(path)\n    sync_configs = (\n        yml.get(profile_name, {}).get(\"fal_extract_load\", {}).get(target_name, {})\n    )\n    return sync_configs\n\n\ndef get_vars_dict(project_dir: str, args_vars: str) -> Dict[str, Any]:\n    project_contract = load_dbt_project_contract(project_dir)\n\n    # NOTE: This happens usually inside unit tests\n    vars = (project_contract is not None and project_contract.vars) or {}\n    cli_vars = parse_cli_vars(args_vars)\n\n    # cli_vars have higher priority\n    return {**vars, **cli_vars}\n\n\n@cache_static\ndef get_fal_models_dirs(project_dir: str, args_vars: str) -> List[str]:\n    vars = get_vars_dict(project_dir, args_vars)\n    model_paths = vars.get(FAL_MODELS_PATHS) or []\n    if not model_paths:\n        # None or empty list\n        LOGGER.warn(\n            f\"Variable '{FAL_MODELS_PATHS}' not defined. Locate fal-format \"\n            \"Python models in a separate model directory and set it as the variable. \"\n            \"e.g. {FAL_MODELS_PATHS}: ['fal_models']\"\n        )\n\n        telemetry.log_api(action=\"fal_models_paths_not_set\")\n\n    if not isinstance(model_paths, list):\n        raise FalParseError(\n            f\"Error parsing '{FAL_MODELS_PATHS}'. Expected list of strings and got '{type(model_paths)}'\"\n        )\n\n    return model_paths\n\n\ndef get_scripts_dir(project_dir: str, args_vars: str) -> str:\n    vars = get_vars_dict(project_dir, args_vars)\n    scripts_dir = vars.get(FAL_SCRIPTS_PATH, project_dir)\n\n    if not isinstance(scripts_dir, str):\n        raise FalParseError(\n            f\"Error parsing '{FAL_SCRIPTS_PATH}'. Expected string and got '{type(scripts_dir)}'\"\n        )\n\n    return os.path.join(project_dir, scripts_dir)\n\n\ndef get_dbt_manifest(config) -> Manifest:\n    from dbt.parser.manifest import ManifestLoader\n\n    return ManifestLoader.get_full_manifest(config)\n\n\ndef get_dbt_sources_artifact(project_dir: str, config: RuntimeConfig):\n    sources_path = os.path.join(project_dir, config.target_path, \"sources.json\")\n    try:\n        # BACKWARDS: Change intorduced in 1.0.0\n        if hasattr(FreshnessExecutionResultArtifact, \"read_and_check_versions\"):\n            return FreshnessExecutionResultArtifact.read_and_check_versions(\n                sources_path\n            )\n        else:\n            return FreshnessExecutionResultArtifact.read(sources_path)\n\n    except IncompatibleSchemaException as exc:\n        # TODO: add test for this case\n        exc.add_filename(sources_path)\n        raise\n    except RuntimeException as exc:\n        LOGGER.warn(\"Could not read dbt sources artifact\")\n        return None\n\n\ndef get_dbt_results(\n    project_dir: str, config: RuntimeConfig\n) -> Optional[RunResultsArtifact]:\n    results_path = os.path.join(project_dir, config.target_path, \"run_results.json\")\n    try:\n        # BACKWARDS: Change intorduced in 1.0.0\n        if hasattr(RunResultsArtifact, \"read_and_check_versions\"):\n            return RunResultsArtifact.read_and_check_versions(results_path)\n        else:\n            return RunResultsArtifact.read(results_path)\n\n    except IncompatibleSchemaException as exc:\n        # TODO: add test for this case\n        exc.add_filename(results_path)\n        raise\n    except RuntimeException as exc:\n        LOGGER.warn(\"Could not read dbt run_results artifact\")\n        return None\n\n\ndef get_scripts_list(scripts_dir: str) -> List[str]:\n    scripts_path = Path(scripts_dir)\n    return list(map(str, [*scripts_path.rglob(\"*.py\"), *scripts_path.rglob(\"*.ipynb\")]))\n\n\ndef get_global_script_configs(source_dirs: List[Path]) -> Dict[str, List[str]]:\n    global_scripts = {\"before\": [], \"after\": []}\n    for source_dir in source_dirs:\n        schema_files = glob.glob(os.path.join(source_dir, \"**.yml\"), recursive=True)\n        for file in schema_files:\n            schema_yml = load_yaml(file)\n            if schema_yml is not None:\n                fal_config = schema_yml.get(\"fal\", None)\n                if fal_config is not None:\n                    # sometimes `scripts` can *be* there and still be None\n                    script_paths = fal_config.get(\"scripts\") or []\n                    if isinstance(script_paths, list):\n                        global_scripts[\"after\"] += script_paths\n                    else:\n                        global_scripts[\"before\"] += script_paths.get(\"before\") or []\n                        global_scripts[\"after\"] += script_paths.get(\"after\") or []\n            else:\n                raise FalParseError(\"Error parsing the schema file \" + file)\n\n    return global_scripts\n\n\ndef _get_required_key(data: Dict[str, Any], name: str) -> Any:\n    if name not in data:\n        raise FalParseError(\"Missing required key: \" + name)\n    return data[name]\n\n\ndef load_environments(base_dir: str) -> Dict[str, \"BaseEnvironment\"]:\n    from fal.packages.environments import create_environment\n    from fal.fal_script import _is_local_environment\n\n    try:\n        fal_project_path = os.path.join(base_dir, \"fal_project.yml\")\n        if not os.path.exists(fal_project_path):\n            raise FalParseError(f\"{fal_project_path} must exist to define environments\")\n\n        fal_project = load_yaml(fal_project_path)\n\n        environments = {}\n        for environment in fal_project.get(\"environments\", []):\n            env_name = _get_required_key(environment, \"name\")\n            if _is_local_environment(env_name):\n                raise FalParseError(\n                    f\"Environment name conflicts with a reserved name: {env_name}.\"\n                )\n\n            env_kind = _get_required_key(environment, \"type\")\n            environments[env_name] = create_environment(env_name, env_kind, environment)\n        return environments\n    except FalParseError as e:\n        raise RuntimeError(\"Error loading environments from fal_project.yml\") from e\n\ndef normalize_path(base: str, path: Union[Path, str]):\n    real_base = os.path.realpath(os.path.normpath(base))\n    return Path(os.path.realpath(os.path.join(real_base, path)))\n\n\ndef normalize_paths(\n    base: str, paths: Union[List[Path], List[str], List[Union[Path, str]]]\n):\n    return list(map(lambda path: normalize_path(base, path), paths))\n", "path": "src/faldbt/parse.py"}], "after_files": [{"content": "import os\nfrom dataclasses import dataclass\nimport glob\nfrom pathlib import Path\nfrom typing import Any, List, Dict, Optional, Union, TYPE_CHECKING\n\nfrom dbt.contracts.project import Project as ProjectContract\nfrom dbt.config import RuntimeConfig, Project\nfrom dbt.config.utils import parse_cli_vars\nfrom dbt.contracts.graph.manifest import Manifest\nfrom dbt.contracts.results import RunResultsArtifact, FreshnessExecutionResultArtifact\nfrom dbt.contracts.project import UserConfig\nfrom dbt.config.profile import read_user_config\nfrom dbt.exceptions import IncompatibleSchemaException, RuntimeException\nfrom fal.utils import cache_static\n\nfrom faldbt.logger import LOGGER\nfrom faldbt.utils.yaml_helper import load_yaml\nfrom fal.telemetry import telemetry\n\nif TYPE_CHECKING:\n    from fal.packages.environments import BaseEnvironment\n\nFAL_SCRIPTS_PATH = \"fal-scripts-path\"\nFAL_MODELS_PATHS = \"fal-models-paths\"\n\n\nclass FalParseError(Exception):\n    pass\n\n\ndef get_dbt_user_config(profiles_dir: str) -> UserConfig:\n    return read_user_config(profiles_dir)\n\n\n@dataclass\nclass RuntimeArgs:\n    project_dir: str\n    profiles_dir: str\n    threads: Optional[int]\n    single_threaded: bool\n    profile: Optional[str]\n    target: Optional[str]\n\n\ndef load_dbt_project_contract(project_dir: str) -> ProjectContract:\n    partial_project = Project.partial_load(project_dir)\n    contract = ProjectContract.from_dict(partial_project.project_dict)\n    if not hasattr(contract, \"model_paths\") or contract.model_paths is None:\n        setattr(contract, \"model_paths\", contract.source_paths)\n    if not hasattr(contract, \"seed_paths\") or contract.seed_paths is None:\n        setattr(contract, \"seed_paths\", contract.data_paths)\n    return contract\n\n\ndef get_dbt_config(\n    *,\n    project_dir: str,\n    profiles_dir: str,\n    profile_target: Optional[str] = None,\n    threads: Optional[int] = None,\n    profile: Optional[str] = None,\n) -> RuntimeConfig:\n    # Construct a phony config\n    import os\n    args = RuntimeArgs(\n        project_dir=project_dir,\n        profiles_dir=profiles_dir,\n        threads=threads,\n        single_threaded=False,\n        profile=profile,\n        target=profile_target,\n    )\n\n    if project_dir and not \"PYTEST_CURRENT_TEST\" in os.environ:\n        # HACK: initializing dbt-fal requires cwd to be project_dir\n        # TODO: this doesn't work in pytest + Github Actions\n        owd = os.getcwd()\n        os.chdir(project_dir)\n        config = RuntimeConfig.from_args(args)\n        os.chdir(owd)\n    else:\n        config = RuntimeConfig.from_args(args)\n    return config\n\n\ndef get_el_configs(\n    profiles_dir: str, profile_name: str, target_name: str\n) -> Dict[str, Dict]:\n    path = os.path.join(profiles_dir, \"profiles.yml\")\n    yml = load_yaml(path)\n    sync_configs = (\n        yml.get(profile_name, {}).get(\"fal_extract_load\", {}).get(target_name, {})\n    )\n    return sync_configs\n\n\ndef get_vars_dict(project_dir: str, args_vars: str) -> Dict[str, Any]:\n    project_contract = load_dbt_project_contract(project_dir)\n\n    # NOTE: This happens usually inside unit tests\n    vars = (project_contract is not None and project_contract.vars) or {}\n    cli_vars = parse_cli_vars(args_vars)\n\n    # cli_vars have higher priority\n    return {**vars, **cli_vars}\n\n\n@cache_static\ndef get_fal_models_dirs(project_dir: str, args_vars: str) -> List[str]:\n    vars = get_vars_dict(project_dir, args_vars)\n    model_paths = vars.get(FAL_MODELS_PATHS) or []\n    if not model_paths:\n        # None or empty list\n        LOGGER.warn(\n            f\"Variable '{FAL_MODELS_PATHS}' not defined. Locate fal-format \"\n            \"Python models in a separate model directory and set it as the variable. \"\n            \"e.g. {FAL_MODELS_PATHS}: ['fal_models']\"\n        )\n\n        telemetry.log_api(action=\"fal_models_paths_not_set\")\n\n    if not isinstance(model_paths, list):\n        raise FalParseError(\n            f\"Error parsing '{FAL_MODELS_PATHS}'. Expected list of strings and got '{type(model_paths)}'\"\n        )\n\n    return model_paths\n\n\ndef get_scripts_dir(project_dir: str, args_vars: str) -> str:\n    vars = get_vars_dict(project_dir, args_vars)\n    scripts_dir = vars.get(FAL_SCRIPTS_PATH, project_dir)\n\n    if not isinstance(scripts_dir, str):\n        raise FalParseError(\n            f\"Error parsing '{FAL_SCRIPTS_PATH}'. Expected string and got '{type(scripts_dir)}'\"\n        )\n\n    return os.path.join(project_dir, scripts_dir)\n\n\ndef get_dbt_manifest(config) -> Manifest:\n    from dbt.parser.manifest import ManifestLoader\n\n    return ManifestLoader.get_full_manifest(config)\n\n\ndef get_dbt_sources_artifact(project_dir: str, config: RuntimeConfig):\n    sources_path = os.path.join(project_dir, config.target_path, \"sources.json\")\n    try:\n        # BACKWARDS: Change intorduced in 1.0.0\n        if hasattr(FreshnessExecutionResultArtifact, \"read_and_check_versions\"):\n            return FreshnessExecutionResultArtifact.read_and_check_versions(\n                sources_path\n            )\n        else:\n            return FreshnessExecutionResultArtifact.read(sources_path)\n\n    except IncompatibleSchemaException as exc:\n        # TODO: add test for this case\n        exc.add_filename(sources_path)\n        raise\n    except RuntimeException as exc:\n        LOGGER.warn(\"Could not read dbt sources artifact\")\n        return None\n\n\ndef get_dbt_results(\n    project_dir: str, config: RuntimeConfig\n) -> Optional[RunResultsArtifact]:\n    results_path = os.path.join(project_dir, config.target_path, \"run_results.json\")\n    try:\n        # BACKWARDS: Change intorduced in 1.0.0\n        if hasattr(RunResultsArtifact, \"read_and_check_versions\"):\n            return RunResultsArtifact.read_and_check_versions(results_path)\n        else:\n            return RunResultsArtifact.read(results_path)\n\n    except IncompatibleSchemaException as exc:\n        # TODO: add test for this case\n        exc.add_filename(results_path)\n        raise\n    except RuntimeException as exc:\n        LOGGER.warn(\"Could not read dbt run_results artifact\")\n        return None\n\n\ndef get_scripts_list(scripts_dir: str) -> List[str]:\n    scripts_path = Path(scripts_dir)\n    return list(map(str, [*scripts_path.rglob(\"*.py\"), *scripts_path.rglob(\"*.ipynb\")]))\n\n\ndef get_global_script_configs(source_dirs: List[Path]) -> Dict[str, List[str]]:\n    global_scripts = {\"before\": [], \"after\": []}\n    for source_dir in source_dirs:\n        # Scan directories for .yml files\n        schema_files = glob.glob(os.path.join(source_dir, \"**.yml\"), recursive=True)\n        # Scan directories for .yaml files\n        schema_files += glob.glob(os.path.join(source_dir, \"**.yaml\"), recursive=True)\n        for file in schema_files:\n            schema_yml = load_yaml(file)\n            if schema_yml is not None:\n                fal_config = schema_yml.get(\"fal\", None)\n                if fal_config is not None:\n                    # sometimes `scripts` can *be* there and still be None\n                    script_paths = fal_config.get(\"scripts\") or []\n                    if isinstance(script_paths, list):\n                        global_scripts[\"after\"] += script_paths\n                    else:\n                        global_scripts[\"before\"] += script_paths.get(\"before\") or []\n                        global_scripts[\"after\"] += script_paths.get(\"after\") or []\n            else:\n                raise FalParseError(\"Error parsing the schema file \" + file)\n\n    return global_scripts\n\n\ndef _get_required_key(data: Dict[str, Any], name: str) -> Any:\n    if name not in data:\n        raise FalParseError(\"Missing required key: \" + name)\n    return data[name]\n\n\ndef load_environments(base_dir: str) -> Dict[str, \"BaseEnvironment\"]:\n    from fal.packages.environments import create_environment\n    from fal.fal_script import _is_local_environment\n\n    try:\n        fal_project_path = os.path.join(base_dir, \"fal_project.yml\")\n        if not os.path.exists(fal_project_path):\n            raise FalParseError(f\"{fal_project_path} must exist to define environments\")\n\n        fal_project = load_yaml(fal_project_path)\n\n        environments = {}\n        for environment in fal_project.get(\"environments\", []):\n            env_name = _get_required_key(environment, \"name\")\n            if _is_local_environment(env_name):\n                raise FalParseError(\n                    f\"Environment name conflicts with a reserved name: {env_name}.\"\n                )\n\n            env_kind = _get_required_key(environment, \"type\")\n            environments[env_name] = create_environment(env_name, env_kind, environment)\n        return environments\n    except FalParseError as e:\n        raise RuntimeError(\"Error loading environments from fal_project.yml\") from e\n\ndef normalize_path(base: str, path: Union[Path, str]):\n    real_base = os.path.realpath(os.path.normpath(base))\n    return Path(os.path.realpath(os.path.join(real_base, path)))\n\n\ndef normalize_paths(\n    base: str, paths: Union[List[Path], List[str], List[Union[Path, str]]]\n):\n    return list(map(lambda path: normalize_path(base, path), paths))\n", "path": "src/faldbt/parse.py"}]}