We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[DevEx] Better error messaging when a dbt run is missing 
When the developer doesn't run dbt run but have models that have fal scripts those models dont have any run results.. and we error but not very descriptive, I think if we dont find run results 99% of the time its a wrong `dbt run`.  

--- 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 import json
3 import glob
4 from typing import Dict, Any
5 
6 import dbt.tracking
7 from dbt.contracts.results import RunResultsArtifact
8 
9 import faldbt.lib as lib
10 from faldbt.utils.yaml_helper import load_yaml_text
11 from faldbt.project import DbtProject, DbtManifest, DbtRunResult
12 
13 
14 class FalParseError(Exception):
15     pass
16 
17 
18 def _load_file_contents(path: str, strip: bool = True) -> str:
19     with open(path, "rb") as handle:
20         to_return = handle.read().decode("utf-8")
21 
22     if strip:
23         to_return = to_return.strip()
24 
25     return to_return
26 
27 
28 def _load_yaml(path):
29     contents = _load_file_contents(path)
30     return load_yaml_text(contents)
31 
32 
33 def _read_json(path: str) -> Dict[str, Any]:
34     return json.loads(_load_file_contents(path))
35 
36 
37 def _flatten(t):
38     return [item for sublist in t for item in sublist]
39 
40 
41 def _get_all_model_config(project_root, project_dict):
42     return _flatten(
43         map(
44             ## find one with any kind yml this doesnt need to schema
45             ## look at all of them find the ones that has model in them
46             ## and keep remembering it
47             lambda model_path: glob.glob(
48                 os.path.join(project_root, model_path, "**.yml"), recursive=True
49             ),
50             project_dict["source-paths"],
51         )
52     )
53 
54 
55 def parse_project(project_dir: str, profiles_dir: str, keyword: str):
56     project_dict = _get_project_dict(project_dir)
57     scripts = glob.glob(os.path.join(project_dir, "**.py"), recursive=True)
58     model_config_paths = _get_all_model_config(project_dir, project_dict)
59     target_path = os.path.join(project_dir, project_dict["target-path"])
60     run_result_path = os.path.join(target_path, "run_results.json")
61 
62     config = lib.get_dbt_config(project_dir)
63     lib.register_adapters(config)
64 
65     # Necessary for parse_to_manifest to not fail
66     dbt.tracking.initialize_tracking(profiles_dir)
67 
68     manifest = lib.parse_to_manifest(config)
69     run_result_artifact = RunResultsArtifact(**_read_json(run_result_path))
70     dbtmanifest = DbtManifest(nativeManifest=manifest)
71 
72     models = dbtmanifest.get_models()
73     status_map = dict(
74         map(lambda result: [result["unique_id"], result["status"]], run_result_artifact)
75     )
76     for model in models:
77         model.status = status_map[model.unique_id]
78 
79     return DbtProject(
80         name=project_dict["name"],
81         model_config_paths=list(model_config_paths),
82         models=models,
83         manifest=DbtManifest(nativeManifest=manifest),
84         keyword=keyword,
85         scripts=scripts,
86         run_result=DbtRunResult(run_result_artifact),
87     )
88 
89 
90 def _get_project_dict(project_dir):
91     project_yaml_filepath = os.path.join(project_dir, "dbt_project.yml")
92 
93     if not os.path.lexists(project_yaml_filepath):
94         raise FalParseError(
95             "no dbt_project.yml found at expected path {}".format(project_yaml_filepath)
96         )
97 
98     project_dict = _load_yaml(project_yaml_filepath)
99 
100     if not isinstance(project_dict, dict):
101         raise FalParseError("dbt_project.yml does not parse to a dictionary")
102 
103     return project_dict
104
```

Path: `src/faldbt/project.py`
Content:
```
1 from dataclasses import dataclass, field
2 from typing import Dict, List, List, Any, TypeVar, Sequence
3 from dbt.contracts.graph.parsed import ParsedModelNode
4 from dbt.node_types import NodeType
5 from pathlib import Path
6 from dbt.contracts.graph.manifest import Manifest
7 from dbt.contracts.results import RunResultsArtifact, RunResultOutput
8 
9 
10 class FalGeneralException(Exception):
11     pass
12 
13 
14 @dataclass
15 class DbtModel:
16     node: ParsedModelNode
17     name: str = field(init=False)
18     meta: Dict[str, Any] = field(init=False)
19     status: str = field(init=False)
20     columns: Dict[str, Any] = field(init=False)
21 
22     def __post_init__(self):
23         self.name = self.node.name
24         self.meta = self.node.config.meta
25         self.columns = self.node.columns
26         self.unique_id = self.node.unique_id
27 
28 
29 @dataclass
30 class DbtManifest:
31     nativeManifest: Manifest
32 
33     def get_models(self) -> List[DbtModel]:
34         return list(
35             filter(
36                 lambda model: model.node.resource_type == NodeType.Model,
37                 map(
38                     lambda node: DbtModel(node=node), self.nativeManifest.nodes.values()
39                 ),
40             )
41         )
42 
43 
44 @dataclass
45 class DbtRunResult:
46     nativeRunResult: RunResultsArtifact
47     results: Sequence[RunResultOutput] = field(init=False)
48 
49     def __post_init__(self):
50         self.results = self.nativeRunResult.results
51 
52 
53 T = TypeVar("T", bound="DbtProject")
54 
55 
56 @dataclass
57 class DbtProject:
58     name: str
59     model_config_paths: List[str]
60     models: List[DbtModel]
61     manifest: DbtManifest
62     keyword: str
63     scripts: List[Path]
64     run_result: DbtRunResult
65 
66     def state_has_changed(self, other: DbtManifest) -> bool:
67         return self.manifest != other
68 
69     def find_model_location(self, model: DbtModel) -> List[str]:
70         model_node = self.manifest.nodes[model.unique_id]
71         return model_node.relation_name.replace("`", "")
72 
73     def changed_model_names(self) -> List[str]:
74         return list(
75             map(
76                 lambda result: result["unique_id"].split(".")[-1],
77                 self.run_result.results,
78             )
79         )
80 
81     def get_models_with_keyword(self, keyword) -> List[DbtModel]:
82         return list(filter(lambda model: keyword in model.meta, self.models))
83 
84     def get_filtered_models(self, all):
85         filtered_models: List[DbtModel] = []
86         for node in self.get_models_with_keyword(self.keyword):
87             if all:
88                 filtered_models.append(node)
89             elif node.name in self.changed_model_names():
90                 filtered_models.append(node)
91             else:
92                 continue
93         return filtered_models
94
```
--- 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
@@ -25,7 +25,7 @@
     return to_return
 
 
-def _load_yaml(path):
+def _read_yaml(path):
     contents = _load_file_contents(path)
     return load_yaml_text(contents)
 
@@ -45,7 +45,8 @@
             ## look at all of them find the ones that has model in them
             ## and keep remembering it
             lambda model_path: glob.glob(
-                os.path.join(project_root, model_path, "**.yml"), recursive=True
+                os.path.join(project_root, model_path, "**.yml"),
+                recursive=True,
             ),
             project_dict["source-paths"],
         )
@@ -57,7 +58,12 @@
     scripts = glob.glob(os.path.join(project_dir, "**.py"), recursive=True)
     model_config_paths = _get_all_model_config(project_dir, project_dict)
     target_path = os.path.join(project_dir, project_dict["target-path"])
-    run_result_path = os.path.join(target_path, "run_results.json")
+
+    run_results_path = os.path.join(target_path, "run_results.json")
+    try:
+        run_results = _read_json(run_results_path)
+    except IOError as e:
+        raise FalParseError("Did you forget to run dbt run?") from e
 
     config = lib.get_dbt_config(project_dir)
     lib.register_adapters(config)
@@ -66,7 +72,7 @@
     dbt.tracking.initialize_tracking(profiles_dir)
 
     manifest = lib.parse_to_manifest(config)
-    run_result_artifact = RunResultsArtifact(**_read_json(run_result_path))
+    run_result_artifact = RunResultsArtifact(**run_results)
     dbtmanifest = DbtManifest(nativeManifest=manifest)
 
     models = dbtmanifest.get_models()
@@ -74,7 +80,7 @@
         map(lambda result: [result["unique_id"], result["status"]], run_result_artifact)
     )
     for model in models:
-        model.status = status_map[model.unique_id]
+        model.status = status_map.get(model.unique_id)
 
     return DbtProject(
         name=project_dict["name"],
@@ -90,14 +96,14 @@
 def _get_project_dict(project_dir):
     project_yaml_filepath = os.path.join(project_dir, "dbt_project.yml")
 
-    if not os.path.lexists(project_yaml_filepath):
+    if not os.path.exists(project_yaml_filepath):
         raise FalParseError(
             "no dbt_project.yml found at expected path {}".format(project_yaml_filepath)
         )
 
-    project_dict = _load_yaml(project_yaml_filepath)
+    project_dict = _read_yaml(project_yaml_filepath)
 
     if not isinstance(project_dict, dict):
-        raise FalParseError("dbt_project.yml does not parse to a dictionary")
+        raise FalParseError("dbt_project.yml formatting is wrong")
 
     return project_dict
diff --git a/src/faldbt/project.py b/src/faldbt/project.py
--- a/src/faldbt/project.py
+++ b/src/faldbt/project.py
@@ -70,24 +70,20 @@
         model_node = self.manifest.nodes[model.unique_id]
         return model_node.relation_name.replace("`", "")
 
-    def changed_model_names(self) -> List[str]:
-        return list(
-            map(
-                lambda result: result["unique_id"].split(".")[-1],
-                self.run_result.results,
-            )
-        )
+    def changed_models_ids(self) -> List[str]:
+        return list(map(lambda res: res["unique_id"], self.run_result.results))
 
     def get_models_with_keyword(self, keyword) -> List[DbtModel]:
         return list(filter(lambda model: keyword in model.meta, self.models))
 
     def get_filtered_models(self, all):
+        changed_models_ids = self.changed_models_ids()
         filtered_models: List[DbtModel] = []
+
         for node in self.get_models_with_keyword(self.keyword):
             if all:
                 filtered_models.append(node)
-            elif node.name in self.changed_model_names():
+            elif node.unique_id in changed_models_ids:
                 filtered_models.append(node)
-            else:
-                continue
+
         return filtered_models

{"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@@ -25,7 +25,7 @@\n     return to_return\n \n \n-def _load_yaml(path):\n+def _read_yaml(path):\n     contents = _load_file_contents(path)\n     return load_yaml_text(contents)\n \n@@ -45,7 +45,8 @@\n             ## look at all of them find the ones that has model in them\n             ## and keep remembering it\n             lambda model_path: glob.glob(\n-                os.path.join(project_root, model_path, \"**.yml\"), recursive=True\n+                os.path.join(project_root, model_path, \"**.yml\"),\n+                recursive=True,\n             ),\n             project_dict[\"source-paths\"],\n         )\n@@ -57,7 +58,12 @@\n     scripts = glob.glob(os.path.join(project_dir, \"**.py\"), recursive=True)\n     model_config_paths = _get_all_model_config(project_dir, project_dict)\n     target_path = os.path.join(project_dir, project_dict[\"target-path\"])\n-    run_result_path = os.path.join(target_path, \"run_results.json\")\n+\n+    run_results_path = os.path.join(target_path, \"run_results.json\")\n+    try:\n+        run_results = _read_json(run_results_path)\n+    except IOError as e:\n+        raise FalParseError(\"Did you forget to run dbt run?\") from e\n \n     config = lib.get_dbt_config(project_dir)\n     lib.register_adapters(config)\n@@ -66,7 +72,7 @@\n     dbt.tracking.initialize_tracking(profiles_dir)\n \n     manifest = lib.parse_to_manifest(config)\n-    run_result_artifact = RunResultsArtifact(**_read_json(run_result_path))\n+    run_result_artifact = RunResultsArtifact(**run_results)\n     dbtmanifest = DbtManifest(nativeManifest=manifest)\n \n     models = dbtmanifest.get_models()\n@@ -74,7 +80,7 @@\n         map(lambda result: [result[\"unique_id\"], result[\"status\"]], run_result_artifact)\n     )\n     for model in models:\n-        model.status = status_map[model.unique_id]\n+        model.status = status_map.get(model.unique_id)\n \n     return DbtProject(\n         name=project_dict[\"name\"],\n@@ -90,14 +96,14 @@\n def _get_project_dict(project_dir):\n     project_yaml_filepath = os.path.join(project_dir, \"dbt_project.yml\")\n \n-    if not os.path.lexists(project_yaml_filepath):\n+    if not os.path.exists(project_yaml_filepath):\n         raise FalParseError(\n             \"no dbt_project.yml found at expected path {}\".format(project_yaml_filepath)\n         )\n \n-    project_dict = _load_yaml(project_yaml_filepath)\n+    project_dict = _read_yaml(project_yaml_filepath)\n \n     if not isinstance(project_dict, dict):\n-        raise FalParseError(\"dbt_project.yml does not parse to a dictionary\")\n+        raise FalParseError(\"dbt_project.yml formatting is wrong\")\n \n     return project_dict\ndiff --git a/src/faldbt/project.py b/src/faldbt/project.py\n--- a/src/faldbt/project.py\n+++ b/src/faldbt/project.py\n@@ -70,24 +70,20 @@\n         model_node = self.manifest.nodes[model.unique_id]\n         return model_node.relation_name.replace(\"`\", \"\")\n \n-    def changed_model_names(self) -> List[str]:\n-        return list(\n-            map(\n-                lambda result: result[\"unique_id\"].split(\".\")[-1],\n-                self.run_result.results,\n-            )\n-        )\n+    def changed_models_ids(self) -> List[str]:\n+        return list(map(lambda res: res[\"unique_id\"], self.run_result.results))\n \n     def get_models_with_keyword(self, keyword) -> List[DbtModel]:\n         return list(filter(lambda model: keyword in model.meta, self.models))\n \n     def get_filtered_models(self, all):\n+        changed_models_ids = self.changed_models_ids()\n         filtered_models: List[DbtModel] = []\n+\n         for node in self.get_models_with_keyword(self.keyword):\n             if all:\n                 filtered_models.append(node)\n-            elif node.name in self.changed_model_names():\n+            elif node.unique_id in changed_models_ids:\n                 filtered_models.append(node)\n-            else:\n-                continue\n+\n         return filtered_models\n", "issue": "[DevEx] Better error messaging when a dbt run is missing \nWhen the developer doesn't run dbt run but have models that have fal scripts those models dont have any run results.. and we error but not very descriptive, I think if we dont find run results 99% of the time its a wrong `dbt run`.  \n", "before_files": [{"content": "import os\nimport json\nimport glob\nfrom typing import Dict, Any\n\nimport dbt.tracking\nfrom dbt.contracts.results import RunResultsArtifact\n\nimport faldbt.lib as lib\nfrom faldbt.utils.yaml_helper import load_yaml_text\nfrom faldbt.project import DbtProject, DbtManifest, DbtRunResult\n\n\nclass FalParseError(Exception):\n    pass\n\n\ndef _load_file_contents(path: str, strip: bool = True) -> str:\n    with open(path, \"rb\") as handle:\n        to_return = handle.read().decode(\"utf-8\")\n\n    if strip:\n        to_return = to_return.strip()\n\n    return to_return\n\n\ndef _load_yaml(path):\n    contents = _load_file_contents(path)\n    return load_yaml_text(contents)\n\n\ndef _read_json(path: str) -> Dict[str, Any]:\n    return json.loads(_load_file_contents(path))\n\n\ndef _flatten(t):\n    return [item for sublist in t for item in sublist]\n\n\ndef _get_all_model_config(project_root, project_dict):\n    return _flatten(\n        map(\n            ## find one with any kind yml this doesnt need to schema\n            ## look at all of them find the ones that has model in them\n            ## and keep remembering it\n            lambda model_path: glob.glob(\n                os.path.join(project_root, model_path, \"**.yml\"), recursive=True\n            ),\n            project_dict[\"source-paths\"],\n        )\n    )\n\n\ndef parse_project(project_dir: str, profiles_dir: str, keyword: str):\n    project_dict = _get_project_dict(project_dir)\n    scripts = glob.glob(os.path.join(project_dir, \"**.py\"), recursive=True)\n    model_config_paths = _get_all_model_config(project_dir, project_dict)\n    target_path = os.path.join(project_dir, project_dict[\"target-path\"])\n    run_result_path = os.path.join(target_path, \"run_results.json\")\n\n    config = lib.get_dbt_config(project_dir)\n    lib.register_adapters(config)\n\n    # Necessary for parse_to_manifest to not fail\n    dbt.tracking.initialize_tracking(profiles_dir)\n\n    manifest = lib.parse_to_manifest(config)\n    run_result_artifact = RunResultsArtifact(**_read_json(run_result_path))\n    dbtmanifest = DbtManifest(nativeManifest=manifest)\n\n    models = dbtmanifest.get_models()\n    status_map = dict(\n        map(lambda result: [result[\"unique_id\"], result[\"status\"]], run_result_artifact)\n    )\n    for model in models:\n        model.status = status_map[model.unique_id]\n\n    return DbtProject(\n        name=project_dict[\"name\"],\n        model_config_paths=list(model_config_paths),\n        models=models,\n        manifest=DbtManifest(nativeManifest=manifest),\n        keyword=keyword,\n        scripts=scripts,\n        run_result=DbtRunResult(run_result_artifact),\n    )\n\n\ndef _get_project_dict(project_dir):\n    project_yaml_filepath = os.path.join(project_dir, \"dbt_project.yml\")\n\n    if not os.path.lexists(project_yaml_filepath):\n        raise FalParseError(\n            \"no dbt_project.yml found at expected path {}\".format(project_yaml_filepath)\n        )\n\n    project_dict = _load_yaml(project_yaml_filepath)\n\n    if not isinstance(project_dict, dict):\n        raise FalParseError(\"dbt_project.yml does not parse to a dictionary\")\n\n    return project_dict\n", "path": "src/faldbt/parse.py"}, {"content": "from dataclasses import dataclass, field\nfrom typing import Dict, List, List, Any, TypeVar, Sequence\nfrom dbt.contracts.graph.parsed import ParsedModelNode\nfrom dbt.node_types import NodeType\nfrom pathlib import Path\nfrom dbt.contracts.graph.manifest import Manifest\nfrom dbt.contracts.results import RunResultsArtifact, RunResultOutput\n\n\nclass FalGeneralException(Exception):\n    pass\n\n\n@dataclass\nclass DbtModel:\n    node: ParsedModelNode\n    name: str = field(init=False)\n    meta: Dict[str, Any] = field(init=False)\n    status: str = field(init=False)\n    columns: Dict[str, Any] = field(init=False)\n\n    def __post_init__(self):\n        self.name = self.node.name\n        self.meta = self.node.config.meta\n        self.columns = self.node.columns\n        self.unique_id = self.node.unique_id\n\n\n@dataclass\nclass DbtManifest:\n    nativeManifest: Manifest\n\n    def get_models(self) -> List[DbtModel]:\n        return list(\n            filter(\n                lambda model: model.node.resource_type == NodeType.Model,\n                map(\n                    lambda node: DbtModel(node=node), self.nativeManifest.nodes.values()\n                ),\n            )\n        )\n\n\n@dataclass\nclass DbtRunResult:\n    nativeRunResult: RunResultsArtifact\n    results: Sequence[RunResultOutput] = field(init=False)\n\n    def __post_init__(self):\n        self.results = self.nativeRunResult.results\n\n\nT = TypeVar(\"T\", bound=\"DbtProject\")\n\n\n@dataclass\nclass DbtProject:\n    name: str\n    model_config_paths: List[str]\n    models: List[DbtModel]\n    manifest: DbtManifest\n    keyword: str\n    scripts: List[Path]\n    run_result: DbtRunResult\n\n    def state_has_changed(self, other: DbtManifest) -> bool:\n        return self.manifest != other\n\n    def find_model_location(self, model: DbtModel) -> List[str]:\n        model_node = self.manifest.nodes[model.unique_id]\n        return model_node.relation_name.replace(\"`\", \"\")\n\n    def changed_model_names(self) -> List[str]:\n        return list(\n            map(\n                lambda result: result[\"unique_id\"].split(\".\")[-1],\n                self.run_result.results,\n            )\n        )\n\n    def get_models_with_keyword(self, keyword) -> List[DbtModel]:\n        return list(filter(lambda model: keyword in model.meta, self.models))\n\n    def get_filtered_models(self, all):\n        filtered_models: List[DbtModel] = []\n        for node in self.get_models_with_keyword(self.keyword):\n            if all:\n                filtered_models.append(node)\n            elif node.name in self.changed_model_names():\n                filtered_models.append(node)\n            else:\n                continue\n        return filtered_models\n", "path": "src/faldbt/project.py"}], "after_files": [{"content": "import os\nimport json\nimport glob\nfrom typing import Dict, Any\n\nimport dbt.tracking\nfrom dbt.contracts.results import RunResultsArtifact\n\nimport faldbt.lib as lib\nfrom faldbt.utils.yaml_helper import load_yaml_text\nfrom faldbt.project import DbtProject, DbtManifest, DbtRunResult\n\n\nclass FalParseError(Exception):\n    pass\n\n\ndef _load_file_contents(path: str, strip: bool = True) -> str:\n    with open(path, \"rb\") as handle:\n        to_return = handle.read().decode(\"utf-8\")\n\n    if strip:\n        to_return = to_return.strip()\n\n    return to_return\n\n\ndef _read_yaml(path):\n    contents = _load_file_contents(path)\n    return load_yaml_text(contents)\n\n\ndef _read_json(path: str) -> Dict[str, Any]:\n    return json.loads(_load_file_contents(path))\n\n\ndef _flatten(t):\n    return [item for sublist in t for item in sublist]\n\n\ndef _get_all_model_config(project_root, project_dict):\n    return _flatten(\n        map(\n            ## find one with any kind yml this doesnt need to schema\n            ## look at all of them find the ones that has model in them\n            ## and keep remembering it\n            lambda model_path: glob.glob(\n                os.path.join(project_root, model_path, \"**.yml\"),\n                recursive=True,\n            ),\n            project_dict[\"source-paths\"],\n        )\n    )\n\n\ndef parse_project(project_dir: str, profiles_dir: str, keyword: str):\n    project_dict = _get_project_dict(project_dir)\n    scripts = glob.glob(os.path.join(project_dir, \"**.py\"), recursive=True)\n    model_config_paths = _get_all_model_config(project_dir, project_dict)\n    target_path = os.path.join(project_dir, project_dict[\"target-path\"])\n\n    run_results_path = os.path.join(target_path, \"run_results.json\")\n    try:\n        run_results = _read_json(run_results_path)\n    except IOError as e:\n        raise FalParseError(\"Did you forget to run dbt run?\") from e\n\n    config = lib.get_dbt_config(project_dir)\n    lib.register_adapters(config)\n\n    # Necessary for parse_to_manifest to not fail\n    dbt.tracking.initialize_tracking(profiles_dir)\n\n    manifest = lib.parse_to_manifest(config)\n    run_result_artifact = RunResultsArtifact(**run_results)\n    dbtmanifest = DbtManifest(nativeManifest=manifest)\n\n    models = dbtmanifest.get_models()\n    status_map = dict(\n        map(lambda result: [result[\"unique_id\"], result[\"status\"]], run_result_artifact)\n    )\n    for model in models:\n        model.status = status_map.get(model.unique_id)\n\n    return DbtProject(\n        name=project_dict[\"name\"],\n        model_config_paths=list(model_config_paths),\n        models=models,\n        manifest=DbtManifest(nativeManifest=manifest),\n        keyword=keyword,\n        scripts=scripts,\n        run_result=DbtRunResult(run_result_artifact),\n    )\n\n\ndef _get_project_dict(project_dir):\n    project_yaml_filepath = os.path.join(project_dir, \"dbt_project.yml\")\n\n    if not os.path.exists(project_yaml_filepath):\n        raise FalParseError(\n            \"no dbt_project.yml found at expected path {}\".format(project_yaml_filepath)\n        )\n\n    project_dict = _read_yaml(project_yaml_filepath)\n\n    if not isinstance(project_dict, dict):\n        raise FalParseError(\"dbt_project.yml formatting is wrong\")\n\n    return project_dict\n", "path": "src/faldbt/parse.py"}, {"content": "from dataclasses import dataclass, field\nfrom typing import Dict, List, List, Any, TypeVar, Sequence\nfrom dbt.contracts.graph.parsed import ParsedModelNode\nfrom dbt.node_types import NodeType\nfrom pathlib import Path\nfrom dbt.contracts.graph.manifest import Manifest\nfrom dbt.contracts.results import RunResultsArtifact, RunResultOutput\n\n\nclass FalGeneralException(Exception):\n    pass\n\n\n@dataclass\nclass DbtModel:\n    node: ParsedModelNode\n    name: str = field(init=False)\n    meta: Dict[str, Any] = field(init=False)\n    status: str = field(init=False)\n    columns: Dict[str, Any] = field(init=False)\n\n    def __post_init__(self):\n        self.name = self.node.name\n        self.meta = self.node.config.meta\n        self.columns = self.node.columns\n        self.unique_id = self.node.unique_id\n\n\n@dataclass\nclass DbtManifest:\n    nativeManifest: Manifest\n\n    def get_models(self) -> List[DbtModel]:\n        return list(\n            filter(\n                lambda model: model.node.resource_type == NodeType.Model,\n                map(\n                    lambda node: DbtModel(node=node), self.nativeManifest.nodes.values()\n                ),\n            )\n        )\n\n\n@dataclass\nclass DbtRunResult:\n    nativeRunResult: RunResultsArtifact\n    results: Sequence[RunResultOutput] = field(init=False)\n\n    def __post_init__(self):\n        self.results = self.nativeRunResult.results\n\n\nT = TypeVar(\"T\", bound=\"DbtProject\")\n\n\n@dataclass\nclass DbtProject:\n    name: str\n    model_config_paths: List[str]\n    models: List[DbtModel]\n    manifest: DbtManifest\n    keyword: str\n    scripts: List[Path]\n    run_result: DbtRunResult\n\n    def state_has_changed(self, other: DbtManifest) -> bool:\n        return self.manifest != other\n\n    def find_model_location(self, model: DbtModel) -> List[str]:\n        model_node = self.manifest.nodes[model.unique_id]\n        return model_node.relation_name.replace(\"`\", \"\")\n\n    def changed_models_ids(self) -> List[str]:\n        return list(map(lambda res: res[\"unique_id\"], self.run_result.results))\n\n    def get_models_with_keyword(self, keyword) -> List[DbtModel]:\n        return list(filter(lambda model: keyword in model.meta, self.models))\n\n    def get_filtered_models(self, all):\n        changed_models_ids = self.changed_models_ids()\n        filtered_models: List[DbtModel] = []\n\n        for node in self.get_models_with_keyword(self.keyword):\n            if all:\n                filtered_models.append(node)\n            elif node.unique_id in changed_models_ids:\n                filtered_models.append(node)\n\n        return filtered_models\n", "path": "src/faldbt/project.py"}]}