Repository: dagster-io/dagster
Pull request #29123: [dbt] cutover callsites to translator.get_asset_spec
Author: login: alangenfeld
Created: 2025-04-08T22:13:44Z | Merged: 2025-04-11T17:38:45Z

Make `get_asset_spec` authoritative by moving all internal call sites to calling that to get access to properties like the key.

This changes the API signature of `get_asset_spec` from its previous set to a much more manageable `manifest: Mapping[...], unique_id: str, project: Optional[DbtProject]`. 

`project` getting thread through when needed when `enable_code_references` is on is the biggest concern. 

## How I Tested These Changes

existing coverage




Diff:
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py b/python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
index 9592d14b38160..1937619f61060 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
@@ -31,17 +31,12 @@
 from dagster._core.definitions.metadata import TableMetadataSet
 from dagster._core.types.dagster_type import Nothing
 
+from dagster_dbt.dbt_project import DbtProject
 from dagster_dbt.metadata_set import DbtMetadataSet
-from dagster_dbt.utils import (
-    ASSET_RESOURCE_TYPES,
-    dagster_name_fn,
-    get_dbt_resource_props_by_dbt_unique_id_from_manifest,
-    select_unique_ids_from_manifest,
-)
+from dagster_dbt.utils import ASSET_RESOURCE_TYPES, dagster_name_fn, select_unique_ids_from_manifest
 
 if TYPE_CHECKING:
     from dagster_dbt.dagster_dbt_translator import DagsterDbtTranslator, DbtManifestWrapper
-    from dagster_dbt.dbt_project import DbtProject
 
 DAGSTER_DBT_MANIFEST_METADATA_KEY = "dagster_dbt/manifest"
 DAGSTER_DBT_TRANSLATOR_METADATA_KEY = "dagster_dbt/dagster_dbt_translator"
@@ -92,16 +87,20 @@ def cleaned_customers():
 
     manifest, dagster_dbt_translator = get_manifest_and_translator_from_dbt_assets(dbt_assets)
 
-    matching_models = [
-        value
-        for value in manifest["nodes"].values()
+    matching_model_ids = [
+        unique_id
+        for unique_id, value in manifest["nodes"].items()
         if value["name"] == model_name and value["resource_type"] in ASSET_RESOURCE_TYPES
     ]
 
-    if len(matching_models) == 0:
+    if len(matching_model_ids) == 0:
         raise KeyError(f"Could not find a dbt model, seed, or snapshot with name: {model_name}")
 
-    return dagster_dbt_translator.get_asset_key(next(iter(matching_models)))
+    return dagster_dbt_translator.get_asset_spec(
+        manifest,
+        next(iter(matching_model_ids)),
+        None,
+    ).key
 
 
 def get_asset_keys_by_output_name_for_source(
@@ -146,16 +145,18 @@ def upstream_python_asset():
 
     manifest, dagster_dbt_translator = get_manifest_and_translator_from_dbt_assets(dbt_assets)
 
-    matching_nodes = [
-        value for value in manifest["sources"].values() if value["source_name"] == source_name
-    ]
+    matching = {
+        unique_id: value
+        for unique_id, value in manifest["sources"].items()
+        if value["source_name"] == source_name
+    }
 
-    if len(matching_nodes) == 0:
+    if len(matching) == 0:
         raise KeyError(f"Could not find a dbt source with name: {source_name}")
 
     return {
-        dagster_name_fn(value): dagster_dbt_translator.get_asset_key(value)
-        for value in matching_nodes
+        dagster_name_fn(value): dagster_dbt_translator.get_asset_spec(manifest, unique_id, None).key
+        for unique_id, value in matching.items()
     }
 
 
@@ -563,32 +564,32 @@ def default_description_fn(dbt_resource_props: Mapping[str, Any], display_raw_sq
 
 def default_asset_check_fn(
     manifest: Mapping[str, Any],
-    dbt_nodes: Mapping[str, Any],
     dagster_dbt_translator: "DagsterDbtTranslator",
     asset_key: AssetKey,
     test_unique_id: str,
+    project: Optional[DbtProject],
 ) -> Optional[AssetCheckSpec]:
     if not dagster_dbt_translator.settings.enable_asset_checks:
         return None
 
-    test_resource_props = dbt_nodes[test_unique_id]
+    test_resource_props = get_node(manifest, test_unique_id)
     parent_unique_ids: set[str] = set(manifest["parent_map"].get(test_unique_id, []))
 
     asset_check_key = get_asset_check_key_for_test(
         manifest=manifest,
         dagster_dbt_translator=dagster_dbt_translator,
         test_unique_id=test_unique_id,
+        project=project,
     )
 
     if not (asset_check_key and asset_check_key.asset_key == asset_key):
         return None
 
     additional_deps = {
-        dagster_dbt_translator.get_asset_key(dbt_nodes[parent_id])
+        dagster_dbt_translator.get_asset_spec(manifest, parent_id, project).key
         for parent_id in parent_unique_ids
     }
     additional_deps.discard(asset_key)
-
     return AssetCheckSpec(
         name=test_resource_props["name"],
         asset=asset_key,
@@ -632,11 +633,12 @@ def is_valid_upstream_node(dbt_resource_props: Mapping[str, Any]) -> bool:
 
 
 def get_upstream_unique_ids(
-    dbt_nodes: Mapping[str, Any], dbt_resource_props: Mapping[str, Any]
+    manifest: Mapping[str, Any],
+    dbt_resource_props: Mapping[str, Any],
 ) -> AbstractSet[str]:
     upstreams = set()
     for parent_unique_id in dbt_resource_props.get("depends_on", {}).get("nodes", []):
-        parent_node_info = dbt_nodes[parent_unique_id]
+        parent_node_info = get_node(manifest, parent_unique_id)
         # for metrics or ephemeral dbt models, BFS to find valid parents
         if is_non_asset_node(parent_node_info):
             visited = set()
@@ -649,7 +651,7 @@ def get_upstream_unique_ids(
                     continue
                 visited.add(candidate_parent_id)
 
-                candidate_parent_info = dbt_nodes[candidate_parent_id]
+                candidate_parent_info = get_node(manifest, candidate_parent_id)
                 if is_non_asset_node(candidate_parent_info):
                     queue.extend(candidate_parent_info.get("depends_on", {}).get("nodes", []))
                 elif is_valid_upstream_node(candidate_parent_info):
@@ -670,20 +672,19 @@ def build_dbt_specs(
     select: str,
     exclude: str,
     io_manager_key: Optional[str],
-    project: Optional["DbtProject"],
+    project: Optional[DbtProject],
 ) -> tuple[Sequence[AssetSpec], Sequence[AssetCheckSpec]]:
-    dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest)
-    group_props = {group["name"]: group for group in manifest.get("groups", {}).values()}
-
     selected_unique_ids = select_unique_ids_from_manifest(
-        select=select, exclude=exclude, manifest_json=manifest
+        select=select,
+        exclude=exclude,
+        manifest_json=manifest,
     )
 
     specs: list[AssetSpec] = []
     check_specs: list[AssetCheckSpec] = []
     key_by_unique_id: dict[str, AssetKey] = {}
     for unique_id in selected_unique_ids:
-        resource_props = dbt_nodes[unique_id]
+        resource_props = get_node(manifest, unique_id)
         resource_type = resource_props["resource_type"]
 
         # skip non-assets, such as semantic models, metrics, tests, and ephemeral models
@@ -693,10 +694,8 @@ def build_dbt_specs(
         # get the spec for the given node
         spec = translator.get_asset_spec(
             manifest,
-            dbt_nodes,
-            group_props,
+            unique_id,
             project,
-            resource_props,
         )
         key_by_unique_id[unique_id] = spec.key
 
@@ -713,7 +712,11 @@ def build_dbt_specs(
                 continue
 
             check_spec = default_asset_check_fn(
-                manifest, dbt_nodes, translator, spec.key, child_unique_id
+                manifest,
+                translator,
+                spec.key,
+                child_unique_id,
+                project,
             )
             if check_spec:
                 check_specs.append(check_spec)
@@ -721,16 +724,20 @@ def build_dbt_specs(
         # update the keys_by_unqiue_id dictionary to include keys created for upstream
         # assets. note that this step may need to change once the translator is updated
         # to no longer rely on `get_asset_key` as a standalone method
-        for upstream_id in get_upstream_unique_ids(dbt_nodes, resource_props):
-            key_by_unique_id[upstream_id] = translator.get_asset_key(dbt_nodes[upstream_id])
-
-    _validate_asset_keys(translator, dbt_nodes, key_by_unique_id)
+        for upstream_id in get_upstream_unique_ids(manifest, resource_props):
+            key_by_unique_id[upstream_id] = translator.get_asset_spec(
+                manifest,
+                upstream_id,
+                project,
+            ).key
+
+    _validate_asset_keys(translator, manifest, key_by_unique_id)
     return specs, check_specs
 
 
 def _validate_asset_keys(
     translator: "DagsterDbtTranslator",
-    dbt_nodes: Mapping[str, Any],
+    manifest: Mapping[str, Any],
     key_by_unique_id: Mapping[str, AssetKey],
 ) -> None:
     unique_ids_by_key = defaultdict(set)
@@ -742,11 +749,14 @@ def _validate_asset_keys(
         if len(unique_ids) == 1:
             continue
         if translator.settings.enable_duplicate_source_asset_keys:
-            resource_types = {dbt_nodes[unique_id]["resource_type"] for unique_id in unique_ids}
+            resource_types = {
+                get_node(manifest, unique_id)["resource_type"] for unique_id in unique_ids
+            }
             if resource_types == {"source"}:
                 continue
         formatted_ids = [
-            f"  - `{id}` ({dbt_nodes[id]['original_file_path']})" for id in sorted(unique_ids)
+            f"  - `{id}` ({get_node(manifest, id)['original_file_path']})"
+            for id in sorted(unique_ids)
         ]
         error_messages.append(
             "\n".join(
@@ -774,11 +784,12 @@ def get_asset_check_key_for_test(
     manifest: Mapping[str, Any],
     dagster_dbt_translator: "DagsterDbtTranslator",
     test_unique_id: str,
+    project: Optional[DbtProject],
 ) -> Optional[AssetCheckKey]:
     if not test_unique_id.startswith("test"):
         return None
 
-    test_resource_props = manifest["nodes"][test_unique_id]
+    test_resource_props = get_node(manifest, test_unique_id)
     upstream_unique_ids: AbstractSet[str] = set(test_resource_props["depends_on"]["nodes"])
 
     # If the test is generic, it will have an attached node that we can use.
@@ -807,30 +818,30 @@ def get_asset_check_key_for_test(
             attached_node_ref.get("version"),
         )
 
-        project_name = manifest["metadata"]["project_name"]
+        project_name = manifest.get("metadata", {})["project_name"]
         if not ref_package:
             ref_package = project_name
 
-        unique_id_by_ref: Mapping[tuple[str, str, Optional[str]], str] = {
-            (
+        attached_node_unique_id = None
+        for unique_id, dbt_resource_props in manifest["nodes"].items():
+            if (ref_name, ref_package, ref_version) == (
                 dbt_resource_props["name"],
                 dbt_resource_props["package_name"],
                 dbt_resource_props.get("version"),
-            ): unique_id
-            for unique_id, dbt_resource_props in manifest["nodes"].items()
-        }
-
-        attached_node_unique_id = unique_id_by_ref.get((ref_name, ref_package, ref_version))
+            ):
+                attached_node_unique_id = unique_id
+                break
 
     if not attached_node_unique_id:
         return None
 
     return AssetCheckKey(
         name=test_resource_props["name"],
-        asset_key=dagster_dbt_translator.get_asset_key(
-            manifest["nodes"].get(attached_node_unique_id)
-            or manifest["sources"].get(attached_node_unique_id)
-        ),
+        asset_key=dagster_dbt_translator.get_asset_spec(
+            manifest,
+            attached_node_unique_id,
+            project,
+        ).key,
     )
 
 
@@ -897,9 +908,8 @@ def get_subset_selection_for_context(
     # Explicitly select a dbt resource by its path. Selecting a resource by path is more terse
     # than selecting it by its fully qualified name.
     # https://docs.getdbt.com/reference/node-selection/methods#the-path-method
-    dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest)
     selected_asset_resources = get_dbt_resource_names_for_asset_keys(
-        dagster_dbt_translator, dbt_nodes, assets_def, context.selected_asset_keys
+        dagster_dbt_translator, manifest, assets_def, context.selected_asset_keys
     )
 
     # if all asset checks for the subsetted assets are selected, then we can just select the
@@ -932,7 +942,7 @@ def get_subset_selection_for_context(
         selected_dbt_resources = [
             *selected_asset_resources,
             *get_dbt_test_names_for_check_keys(
-                dagster_dbt_translator, dbt_nodes, assets_def, context.selected_asset_check_keys
+                dagster_dbt_translator, manifest, assets_def, context.selected_asset_check_keys
             ),
         ]
         indirect_selection_override = DBT_EMPTY_INDIRECT_SELECTION
@@ -948,7 +958,10 @@ def get_subset_selection_for_context(
         selected_dbt_resources = [
             *selected_asset_resources,
             *get_dbt_test_names_for_check_keys(
-                dagster_dbt_translator, dbt_nodes, assets_def, checks_on_non_selected_assets
+                dagster_dbt_translator,
+                manifest,
+                assets_def,
+                checks_on_non_selected_assets,
             ),
         ]
         indirect_selection_override = None
@@ -970,12 +983,15 @@ def get_subset_selection_for_context(
 
 def get_dbt_resource_names_for_asset_keys(
     translator: "DagsterDbtTranslator",
-    dbt_nodes: Mapping[str, Any],
+    manifest: Mapping[str, Any],
     assets_def: AssetsDefinition,
     asset_keys: Iterable[AssetKey],
 ) -> Sequence[str]:
     dbt_resource_props_gen = (
-        dbt_nodes[assets_def.get_asset_spec(key).metadata[DAGSTER_DBT_UNIQUE_ID_METADATA_KEY]]
+        get_node(
+            manifest,
+            assets_def.get_asset_spec(key).metadata[DAGSTER_DBT_UNIQUE_ID_METADATA_KEY],
+        )
         for key in asset_keys
     )
 
@@ -994,16 +1010,17 @@ def get_dbt_resource_names_for_asset_keys(
 
 def get_dbt_test_names_for_check_keys(
     translator: "DagsterDbtTranslator",
-    dbt_nodes: Mapping[str, Any],
+    manifest: Mapping[str, Any],
     assets_def: AssetsDefinition,
     check_keys: Iterable[AssetCheckKey],
 ) -> Sequence[str]:
     dbt_resource_props_gen = (
-        dbt_nodes[
+        get_node(
+            manifest,
             (assets_def.get_spec_for_check_key(key).metadata or {})[
                 DAGSTER_DBT_UNIQUE_ID_METADATA_KEY
-            ]
-        ]
+            ],
+        )
         for key in check_keys
     )
     # Explicitly select a dbt test by its test name.
@@ -1014,3 +1031,29 @@ def get_dbt_test_names_for_check_keys(
     # Explictly select a dbt test by its fully qualified name (FQN).
     # https://docs.getdbt.com/reference/node-selection/methods#the-file-or-fqn-method
     return [".".join(dbt_resource_props["fqn"]) for dbt_resource_props in dbt_resource_props_gen]
+
+
+def get_node(manifest: Mapping[str, Any], unique_id: str) -> Mapping[str, Any]:
+    """Find a node by unique_id in manifest_json."""
+    if unique_id in manifest["nodes"]:
+        return manifest["nodes"][unique_id]
+
+    if unique_id in manifest["sources"]:
+        return manifest["sources"][unique_id]
+
+    if unique_id in manifest["exposures"]:
+        return manifest["exposures"][unique_id]
+
+    if unique_id in manifest["metrics"]:
+        return manifest["metrics"][unique_id]
+
+    if unique_id in manifest.get("semantic_models", {}):
+        return manifest["semantic_models"][unique_id]
+
+    if unique_id in manifest.get("saved_queries", {}):
+        return manifest["saved_queries"][unique_id]
+
+    if unique_id in manifest.get("unit_tests", {}):
+        return manifest["unit_tests"][unique_id]
+
+    check.failed(f"Could not find {unique_id} in dbt manifest")
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/cloud/asset_defs.py b/python_modules/libraries/dagster-dbt/dagster_dbt/cloud/asset_defs.py
index 63989b2e447ac..5528fa6c22f23 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/cloud/asset_defs.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/cloud/asset_defs.py
@@ -35,12 +35,12 @@
     default_description_fn,
     default_freshness_policy_fn,
     default_group_from_dbt_resource_props,
+    get_node,
 )
 from dagster_dbt.cloud.resources import DbtCloudClient, DbtCloudClientResource, DbtCloudRunStatus
 from dagster_dbt.cloud.utils import result_to_events
 from dagster_dbt.dagster_dbt_translator import DagsterDbtTranslator
 from dagster_dbt.errors import DagsterDbtCloudJobInvariantViolationError
-from dagster_dbt.utils import get_dbt_resource_props_by_dbt_unique_id_from_manifest
 
 DAGSTER_DBT_COMPILE_RUN_ID_ENV_VAR = "DBT_DAGSTER_COMPILE_RUN_ID"
 
@@ -333,12 +333,12 @@ def get_auto_materialize_policy(cls, dbt_resource_props):  # pyright: ignore[rep
                 return self._node_info_to_auto_materialize_policy_fn(dbt_resource_props)
 
         # generate specs for each executed node
-        dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest_json)
         specs = build_dbt_asset_specs(
             manifest=manifest_json,
             dagster_dbt_translator=CustomDagsterDbtTranslator(),
             select=" ".join(
-                f"fqn:{'.'.join(dbt_nodes[unique_id]['fqn'])}" for unique_id in executed_unique_ids
+                f"fqn:{'.'.join(get_node(manifest_json, unique_id)['fqn'])}"
+                for unique_id in executed_unique_ids
             ),
         )
 
@@ -351,7 +351,10 @@ def get_auto_materialize_policy(cls, dbt_resource_props):  # pyright: ignore[rep
             },
             metadata_by_output_name={
                 spec.key.to_python_identifier(): self._build_dbt_cloud_assets_metadata(
-                    dbt_nodes[spec.metadata[DAGSTER_DBT_UNIQUE_ID_METADATA_KEY]]
+                    get_node(
+                        manifest_json,
+                        spec.metadata[DAGSTER_DBT_UNIQUE_ID_METADATA_KEY],
+                    )
                 )
                 for spec in specs
             },
@@ -363,9 +366,10 @@ def get_auto_materialize_policy(cls, dbt_resource_props):  # pyright: ignore[rep
                     spec.key.to_python_identifier(): spec.group_name for spec in specs
                 },
                 "fqns_by_output_name": {
-                    spec.key.to_python_identifier(): dbt_nodes[
-                        spec.metadata[DAGSTER_DBT_UNIQUE_ID_METADATA_KEY]
-                    ]["fqn"]
+                    spec.key.to_python_identifier(): get_node(
+                        manifest_json,
+                        spec.metadata[DAGSTER_DBT_UNIQUE_ID_METADATA_KEY],
+                    )["fqn"]
                     for spec in specs
                 },
             },
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/cloud_v2/run_handler.py b/python_modules/libraries/dagster-dbt/dagster_dbt/cloud_v2/run_handler.py
index 7555490924a08..a23e0dc123534 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/cloud_v2/run_handler.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/cloud_v2/run_handler.py
@@ -196,6 +196,7 @@ def to_default_asset_events(
                     manifest=manifest,
                     dagster_dbt_translator=dagster_dbt_translator,
                     test_unique_id=unique_id,
+                    project=None,
                 )
 
                 if (
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/components/dbt_project/component.py b/python_modules/libraries/dagster-dbt/dagster_dbt/components/dbt_project/component.py
index 7fa89619f3c9a..e527fea184348 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/components/dbt_project/component.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/components/dbt_project/component.py
@@ -7,9 +7,6 @@
 from dagster._core.definitions.asset_key import AssetKey
 from dagster._core.definitions.asset_spec import AssetSpec
 from dagster._core.definitions.assets import AssetsDefinition
-from dagster._core.definitions.declarative_automation.automation_condition import (
-    AutomationCondition,
-)
 from dagster._core.definitions.definitions_class import Definitions
 from dagster._core.execution.context.asset_execution_context import AssetExecutionContext
 from dagster.components import Resolvable, Resolver
@@ -23,92 +20,46 @@
 )
 from dagster.components.scaffold.scaffold import scaffold_with
 from dagster.components.utils import TranslatorResolvingInfo
-from typing_extensions import override
 
 from dagster_dbt.asset_decorator import dbt_assets
-from dagster_dbt.asset_utils import get_asset_key_for_model as get_asset_key_for_model
+from dagster_dbt.asset_utils import get_asset_key_for_model, get_node
 from dagster_dbt.components.dbt_project.scaffolder import DbtProjectComponentScaffolder
 from dagster_dbt.core.resource import DbtCliResource
 from dagster_dbt.dagster_dbt_translator import DagsterDbtTranslator
-from dagster_dbt.dbt_manifest import validate_manifest
 from dagster_dbt.dbt_manifest_asset_selection import DbtManifestAssetSelection
 from dagster_dbt.dbt_project import DbtProject
-from dagster_dbt.utils import get_dbt_resource_props_by_dbt_unique_id_from_manifest
 
 
-def resolve_translator(context: ResolutionContext, model) -> DagsterDbtTranslator:
-    class DagsterDbtTranslatorWithSpecs(DagsterDbtTranslator):
-        def __init__(self, *, resolving_info: TranslatorResolvingInfo):
-            super().__init__()
-            self.resolving_info = resolving_info
-            self.base_translator = DagsterDbtTranslator()
-            self._specs_map: dict[int, AssetSpec] = {}
-
-        @cached_property
-        def project(self) -> DbtProject:
-            return DbtProject(model.dbt.project_dir)
-
-        @cached_property
-        def manifest(self) -> Mapping[str, Any]:
-            return validate_manifest(self.project.manifest_path)
-
-        @cached_property
-        def dbt_nodes(self) -> Mapping[str, Any]:
-            return get_dbt_resource_props_by_dbt_unique_id_from_manifest(self.manifest)
-
-        @cached_property
-        def group_props(self) -> Mapping[str, Any]:
-            return {group["name"]: group for group in self.manifest.get("groups", {}).values()}
-
-        def get_asset_spec_from_props(self, stream_definition: Mapping[str, Any]) -> AssetSpec:
-            if id(stream_definition) not in self._specs_map:
-                base_spec = DagsterDbtTranslator().get_asset_spec(
-                    manifest=self.manifest,
-                    dbt_nodes=self.dbt_nodes,
-                    group_props=self.group_props,
-                    project=self.project,
-                    resource_props=stream_definition,
-                )
-                self._specs_map[id(stream_definition)] = self.resolving_info.get_asset_spec(
-                    base_spec,
-                    {self.resolving_info.obj_name: stream_definition, "spec": base_spec},
-                )
-            return self._specs_map[id(stream_definition)]
-
-        @override
-        def get_asset_key(self, dbt_resource_props: Mapping[str, Any]) -> AssetKey:
-            return self.get_asset_spec_from_props(dbt_resource_props).key
-
-        @override
-        def get_description(self, dbt_resource_props: Mapping[str, Any]) -> Optional[str]:  # pyright: ignore[reportIncompatibleMethodOverride]
-            return self.get_asset_spec_from_props(dbt_resource_props).description
-
-        @override
-        def get_metadata(self, dbt_resource_props: Mapping[str, Any]) -> Mapping[str, Any]:
-            return self.get_asset_spec_from_props(dbt_resource_props).metadata
-
-        @override
-        def get_tags(self, dbt_resource_props: Mapping[str, Any]) -> Mapping[str, str]:
-            return self.get_asset_spec_from_props(dbt_resource_props).tags
-
-        @override
-        def get_group_name(self, dbt_resource_props: Mapping[str, Any]) -> Optional[str]:
-            return self.get_asset_spec_from_props(dbt_resource_props).group_name
-
-        @override
-        def get_code_version(self, dbt_resource_props: Mapping[str, Any]) -> Optional[str]:
-            return self.get_asset_spec_from_props(dbt_resource_props).code_version
-
-        @override
-        def get_owners(self, dbt_resource_props: Mapping[str, Any]) -> Sequence[str]:
-            return self.get_asset_spec_from_props(dbt_resource_props).owners
-
-        @override
-        def get_automation_condition(
-            self, dbt_resource_props: Mapping[str, Any]
-        ) -> Optional[AutomationCondition]:
-            return self.get_asset_spec_from_props(dbt_resource_props).automation_condition
+class ComponentDagsterDbtTranslator(DagsterDbtTranslator):
+    def __init__(self, *, resolving_info: TranslatorResolvingInfo):
+        super().__init__()
+        self.resolving_info = resolving_info
+        self._specs_map: dict[tuple, AssetSpec] = {}
+
+    def get_asset_spec(
+        self,
+        manifest: Mapping[str, Any],
+        unique_id: str,
+        project: Optional[DbtProject],
+    ) -> AssetSpec:
+        memo_id = (id(manifest), unique_id)
+        if memo_id not in self._specs_map:
+            base_spec = super().get_asset_spec(
+                manifest=manifest,
+                unique_id=unique_id,
+                project=project,
+            )
+            self._specs_map[memo_id] = self.resolving_info.get_asset_spec(
+                base_spec,
+                {
+                    self.resolving_info.obj_name: get_node(manifest, unique_id),
+                    "spec": base_spec,
+                },
+            )
+        return self._specs_map[memo_id]
 
+
+def resolve_translator(context: ResolutionContext, model) -> DagsterDbtTranslator:
     if (
         model.asset_attributes
         and isinstance(model.asset_attributes, AssetAttributesModel)
@@ -117,7 +68,7 @@ def get_automation_condition(
         # TODO: Consider supporting alerting deps in the future
         raise ValueError("deps are not supported for dbt_project component")
 
-    return DagsterDbtTranslatorWithSpecs(
+    return ComponentDagsterDbtTranslator(
         resolving_info=TranslatorResolvingInfo(
             "node",
             model.asset_attributes or AssetAttributesModel(),
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/core/dbt_cli_event.py b/python_modules/libraries/dagster-dbt/dagster_dbt/core/dbt_cli_event.py
index d6c11a463d60f..b3ed724fe7b89 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/core/dbt_cli_event.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/core/dbt_cli_event.py
@@ -400,8 +400,7 @@ def to_default_asset_events(
                     exc_info=True,
                 )
 
-            dbt_resource_props = manifest["nodes"][unique_id]
-            asset_key = dagster_dbt_translator.get_asset_key(dbt_resource_props)
+            asset_key = dagster_dbt_translator.get_asset_spec(manifest, unique_id, None).key
             if context and has_asset_def:
                 yield Output(
                     value=None,
@@ -430,7 +429,10 @@ def to_default_asset_events(
                 metadata["dagster_dbt/failed_row_count"] = self.raw_event["data"]["num_failures"]
 
             asset_check_key = get_asset_check_key_for_test(
-                manifest, dagster_dbt_translator, test_unique_id=unique_id
+                manifest,
+                dagster_dbt_translator,
+                test_unique_id=unique_id,
+                project=None,
             )
 
             if (
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py b/python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
index 2e21026478f40..0c7c7dc3ee1f6 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
@@ -36,6 +36,7 @@
     default_group_from_dbt_resource_props,
     default_metadata_from_dbt_resource_props,
     default_owners_from_dbt_resource_props,
+    get_node,
     get_upstream_unique_ids,
     has_self_dependency,
 )
@@ -80,6 +81,7 @@ def __init__(self, settings: Optional[DagsterDbtTranslatorSettings] = None):
             settings (Optional[DagsterDbtTranslatorSettings]): Settings for the translator.
         """
         self._settings = settings or DagsterDbtTranslatorSettings()
+        self._resolved_specs: dict[tuple, AssetSpec] = {}
 
     @property
     def settings(self) -> DagsterDbtTranslatorSettings:
@@ -91,21 +93,26 @@ def settings(self) -> DagsterDbtTranslatorSettings:
     def get_asset_spec(
         self,
         manifest: Mapping[str, Any],
-        dbt_nodes: Mapping[str, Any],
-        group_props: Mapping[str, Any],
+        unique_id: str,
         project: Optional["DbtProject"],
-        resource_props: Mapping[str, Any],
     ) -> AssetSpec:
         """Returns an AssetSpec representing a specific dbt resource."""
-        from dagster_dbt.dagster_dbt_translator import DbtManifestWrapper
+        # memoize resolution for a given manifest & unique_id
+        # since we recursively call get_asset_spec for dependencies
+        memo_id = (id(manifest), unique_id)
+        if memo_id in self._resolved_specs:
+            return self._resolved_specs[memo_id]
+
+        group_props = {group["name"]: group for group in manifest.get("groups", {}).values()}
+        resource_props = get_node(manifest, unique_id)
 
         # calculate the dependencies for the asset
-        upstream_ids = get_upstream_unique_ids(dbt_nodes, resource_props)
+        upstream_ids = get_upstream_unique_ids(manifest, resource_props)
         deps = [
             AssetDep(
-                asset=self.get_asset_key(dbt_nodes[upstream_id]),
+                asset=self.get_asset_spec(manifest, upstream_id, project).key,
                 partition_mapping=self.get_partition_mapping(
-                    resource_props, dbt_nodes[upstream_id]
+                    resource_props, get_node(manifest, upstream_id)
                 ),
             )
             for upstream_id in upstream_ids
@@ -120,6 +127,17 @@ def get_asset_spec(
             )
 
         resource_group_props = group_props.get(resource_props.get("group") or "")
+        if resource_group_props:
+            owners_resource_props = {
+                **resource_props,
+                # this overrides the group key in resource_props, which is bad as
+                # this key is not always empty and this dictionary generally differs
+                # in structure from other inputs, but this is necessary for backcompat
+                **({"group": resource_group_props} if resource_group_props else {}),
+            }
+        else:
+            owners_resource_props = resource_props
+
         spec = AssetSpec(
             key=self.get_asset_key(resource_props),
             deps=deps,
@@ -130,15 +148,7 @@ def get_asset_spec(
             code_version=self.get_code_version(resource_props),
             automation_condition=self.get_automation_condition(resource_props),
             freshness_policy=self.get_freshness_policy(resource_props),
-            owners=self.get_owners(
-                {
-                    **resource_props,
-                    # this overrides the group key in resource_props, which is bad as
-                    # this key is not always empty and this dictionary generally differs
-                    # in structure from other inputs, but this is necessary for backcompat
-                    **({"group": resource_group_props} if resource_group_props else {}),
-                }
-            ),
+            owners=self.get_owners(owners_resource_props),
             tags=self.get_tags(resource_props),
             kinds={"dbt", manifest.get("metadata", {}).get("adapter_type", "dbt")},
             partitions_def=self.get_partitions_def(resource_props),
@@ -166,7 +176,10 @@ def get_asset_spec(
                     project=project,
                 )
             )
-        return spec
+
+        self._resolved_specs[memo_id] = spec
+
+        return self._resolved_specs[memo_id]
 
     @public
     def get_asset_key(self, dbt_resource_props: Mapping[str, Any]) -> AssetKey:
@@ -657,6 +670,8 @@ def get_partitions_def(self, dbt_resource_props: Mapping[str, Any]) -> Optional[
 
 @dataclass
 class DbtManifestWrapper:
+    """Wrapper around parsed DBT manifest json to provide convenient and efficient access."""
+
     manifest: Mapping[str, Any]
 
 
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/dbt_manifest_asset_selection.py b/python_modules/libraries/dagster-dbt/dagster_dbt/dbt_manifest_asset_selection.py
index 1339b63b189c5..f5b6d907bd5b2 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/dbt_manifest_asset_selection.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/dbt_manifest_asset_selection.py
@@ -10,14 +10,10 @@
 from dagster._core.definitions.base_asset_graph import BaseAssetGraph
 from dagster._record import record
 
-from dagster_dbt.asset_utils import get_asset_check_key_for_test, is_non_asset_node
+from dagster_dbt.asset_utils import get_asset_check_key_for_test, get_node, is_non_asset_node
 from dagster_dbt.dagster_dbt_translator import DagsterDbtTranslator
 from dagster_dbt.dbt_manifest import DbtManifestParam, validate_manifest
-from dagster_dbt.utils import (
-    ASSET_RESOURCE_TYPES,
-    get_dbt_resource_props_by_dbt_unique_id_from_manifest,
-    select_unique_ids_from_manifest,
-)
+from dagster_dbt.utils import ASSET_RESOURCE_TYPES, select_unique_ids_from_manifest
 
 
 @record
@@ -91,18 +87,18 @@ def build(
     def resolve_inner(
         self, asset_graph: BaseAssetGraph, allow_missing: bool = False
     ) -> AbstractSet[AssetKey]:
-        dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(self.manifest)
-
         keys = set()
         for unique_id in select_unique_ids_from_manifest(
             select=self.select,
             exclude=self.exclude,
             manifest_json=self.manifest,
         ):
-            dbt_resource_props = dbt_nodes[unique_id]
+            dbt_resource_props = get_node(self.manifest, unique_id)
             is_dbt_asset = dbt_resource_props["resource_type"] in ASSET_RESOURCE_TYPES
             if is_dbt_asset and not is_non_asset_node(dbt_resource_props):
-                asset_key = self.dagster_dbt_translator.get_asset_key(dbt_resource_props)
+                asset_key = self.dagster_dbt_translator.get_asset_spec(
+                    self.manifest, unique_id, None
+                ).key
                 keys.add(asset_key)
 
         return keys
@@ -120,7 +116,10 @@ def resolve_checks_inner(
             manifest_json=self.manifest,
         ):
             asset_check_key = get_asset_check_key_for_test(
-                self.manifest, self.dagster_dbt_translator, test_unique_id=unique_id
+                self.manifest,
+                self.dagster_dbt_translator,
+                test_unique_id=unique_id,
+                project=None,
             )
 
             if asset_check_key:
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt/utils.py b/python_modules/libraries/dagster-dbt/dagster_dbt/utils.py
index a94a3c77fd51d..1e48580c6e656 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt/utils.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt/utils.py
@@ -118,21 +118,6 @@ def __getattr__(self, item):
     return selected
 
 
-def get_dbt_resource_props_by_dbt_unique_id_from_manifest(
-    manifest: Mapping[str, Any],
-) -> Mapping[str, Mapping[str, Any]]:
-    """A mapping of a dbt node's unique id to the node's dictionary representation in the manifest."""
-    return {
-        **manifest["nodes"],
-        **manifest["sources"],
-        **manifest["exposures"],
-        **manifest["metrics"],
-        **manifest.get("semantic_models", {}),
-        **manifest.get("saved_queries", {}),
-        **manifest.get("unit_tests", {}),
-    }
-
-
 def _set_flag_attrs(kvs: dict[str, Any]):
     from dbt.flags import get_flag_dict, set_flags
 
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt_tests/cloud/test_asset_defs.py b/python_modules/libraries/dagster-dbt/dagster_dbt_tests/cloud/test_asset_defs.py
index b25367bb789d3..0715306cd4770 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt_tests/cloud/test_asset_defs.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt_tests/cloud/test_asset_defs.py
@@ -408,7 +408,7 @@ def test_custom_groups(dbt_cloud, dbt_cloud_service):
     dbt_cloud_cacheable_assets = load_assets_from_dbt_cloud_job(
         dbt_cloud=dbt_cloud,
         job_id=DBT_CLOUD_JOB_ID,
-        node_info_to_group_fn=lambda node_info: node_info["tags"][0],
+        node_info_to_group_fn=lambda node_info: next(iter(node_info["tags"]), None),
     )
     dbt_assets_definition_cacheable_data = dbt_cloud_cacheable_assets.compute_cacheable_data()
     dbt_cloud_assets = dbt_cloud_cacheable_assets.build_definitions(
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py b/python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py
index fd414f76276f3..8a48495d20489 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py
@@ -127,15 +127,14 @@ def get_additional_scope(cls) -> Mapping[str, Any]:
         ),
         ({"tags": {"foo": "bar"}}, lambda asset_spec: asset_spec.tags.get("foo") == "bar", False),
         (
-            {"kinds": ["snowflake"]},
-            lambda asset_spec: "snowflake" in asset_spec.kinds
-            and "dbt" in asset_spec.kinds,  # Ensure dbt-specific kind is not overwritten
+            {"kinds": ["snowflake", "dbt"]},
+            lambda asset_spec: "snowflake" in asset_spec.kinds and "dbt" in asset_spec.kinds,
             False,
         ),
         (
-            {"tags": {"foo": "bar"}, "kinds": ["snowflake"]},
+            {"tags": {"foo": "bar"}, "kinds": ["snowflake", "dbt"]},
             lambda asset_spec: "snowflake" in asset_spec.kinds
-            and "dbt" in asset_spec.kinds  # Ensure dbt-specific kind is not overwritten
+            and "dbt" in asset_spec.kinds
             and asset_spec.tags.get("foo") == "bar",
             False,
         ),
diff --git a/python_modules/libraries/dagster-dbt/dagster_dbt_tests/core/test_asset_materialization_execution_duration.py b/python_modules/libraries/dagster-dbt/dagster_dbt_tests/core/test_asset_materialization_execution_duration.py
index c45e8cbcf73a3..d23e36e5a8f63 100644
--- a/python_modules/libraries/dagster-dbt/dagster_dbt_tests/core/test_asset_materialization_execution_duration.py
+++ b/python_modules/libraries/dagster-dbt/dagster_dbt_tests/core/test_asset_materialization_execution_duration.py
@@ -81,6 +81,7 @@ def test_microbatch_log_model_result_to_asset():
                 "alias": "order_history",
                 "path": "mart/public__orders.sql",
                 "config": {"schema": "public"},
+                "description": "",
             }
         },
     }
@@ -171,6 +172,7 @@ def test_incremental_log_model_result_to_asset():
                 "alias": "order_history",
                 "path": "mart/public__orders.sql",
                 "config": {"schema": "public"},
+                "description": "",
             }
         },
     }


Reviews:
user: login: alangenfeld
body: the progression of this PR was roughly: 
* need to move call sites over to get_asset_spec, pretty much all the params are derivable from the `manifest` 
* building up the `dbt_nodes` dict by unique id repeatedly spooked me perf wise knowing that these manifests can get huge so want to kill/cache [1] 
* `DbtManifestWrapper` seemed like a good way to tackle that [2] so try to use that 

instantiating the translator with the manifest is alluring, but is a rough change given we have people pass in already instantiated object instead of just the class 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T22:25:23Z
user: login: OwenKephart
body: requesting changes mostly for explanation on the caching bit. also makes sense for @maximearmstrong to make a final call on this just as he has the most experience with these sorts of "migrations"
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:50:50Z
user: login: alangenfeld
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T14:18:34Z
user: login: OwenKephart
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T17:37:47Z
user: login: alangenfeld
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T17:57:04Z
user: login: OwenKephart
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:09:01Z
user: login: OwenKephart
body: exciting!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T18:10:03Z
user: login: maximearmstrong
body: Overall that looks great!

I'm a bit concerned about changing the signature of `default_asset_key_fn`, see comment below.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:37:13Z
user: login: alangenfeld
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T17:12:42Z
user: login: maximearmstrong
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T17:29:17Z
user: login: maximearmstrong
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T17:29:41Z

Review comments:
diff_hunk: @@ -657,7 +656,37 @@ def get_partitions_def(self, dbt_resource_props: Mapping[str, Any]) -> Optional[
 
 @dataclass
 class DbtManifestWrapper:
+    """Wrapper around parsed DBT manifest json to provide convenient and efficient access."""
+
     manifest: Mapping[str, Any]
+    project: Optional["DbtProject"]
+
+    def get_section(self, key: str) -> Mapping[str, Any]:
+        return self.manifest.get(key, {})
+
+    def get_node(self, unique_id: str) -> Mapping[str, Any]:
+        if unique_id in self.manifest["nodes"]:
+            return self.manifest["nodes"][unique_id]
+
+        if unique_id in self.manifest["sources"]:
+            return self.manifest["sources"][unique_id]
+
+        if unique_id in self.manifest["exposures"]:
+            return self.manifest["exposures"][unique_id]
+
+        if unique_id in self.manifest["metrics"]:
+            return self.manifest["metrics"][unique_id]
+
+        if unique_id in self.manifest.get("semantic_models", {}):
+            return self.manifest["semantic_models"][unique_id]
+
+        if unique_id in self.manifest.get("saved_queries", {}):
+            return self.manifest["saved_queries"][unique_id]
+
+        if unique_id in self.manifest.get("unit_tests", {}):
+            return self.manifest["unit_tests"][unique_id]
+
+        check.failed(f"Could not find {unique_id} in dbt manifest")
path: python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
user: login: alangenfeld
body: [2]
created_at: 2025-04-08T22:17:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -118,21 +118,6 @@ def __getattr__(self, item):
     return selected
 
 
-def get_dbt_resource_props_by_dbt_unique_id_from_manifest(
-    manifest: Mapping[str, Any],
-) -> Mapping[str, Mapping[str, Any]]:
-    """A mapping of a dbt node's unique id to the node's dictionary representation in the manifest."""
-    return {
-        **manifest["nodes"],
-        **manifest["sources"],
-        **manifest["exposures"],
-        **manifest["metrics"],
-        **manifest.get("semantic_models", {}),
-        **manifest.get("saved_queries", {}),
-        **manifest.get("unit_tests", {}),
-    }
path: python_modules/libraries/dagster-dbt/dagster_dbt/utils.py
user: login: alangenfeld
body: [1] 

created_at: 2025-04-08T22:17:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -657,7 +656,37 @@ def get_partitions_def(self, dbt_resource_props: Mapping[str, Any]) -> Optional[
 
 @dataclass
 class DbtManifestWrapper:
+    """Wrapper around parsed DBT manifest json to provide convenient and efficient access."""
+
     manifest: Mapping[str, Any]
path: python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
user: login: alangenfeld
body: probably rename this to `manifest_json` to resolve `manifest.manifest`
created_at: 2025-04-08T22:25:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -666,24 +662,23 @@ def get_upstream_unique_ids(
 def build_dbt_specs(
     *,
     translator: "DagsterDbtTranslator",
-    manifest: Mapping[str, Any],
+    manifest: "DbtManifestWrapper",
     select: str,
     exclude: str,
     io_manager_key: Optional[str],
-    project: Optional["DbtProject"],
 ) -> tuple[Sequence[AssetSpec], Sequence[AssetCheckSpec]]:
-    dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest)
-    group_props = {group["name"]: group for group in manifest.get("groups", {}).values()}
-
+    # dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest)
path: python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
user: login: OwenKephart
body: rm
created_at: 2025-04-08T22:28:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,22 +90,21 @@ def settings(self) -> DagsterDbtTranslatorSettings:
 
     def get_asset_spec(
         self,
-        manifest: Mapping[str, Any],
-        dbt_nodes: Mapping[str, Any],
-        group_props: Mapping[str, Any],
-        project: Optional["DbtProject"],
-        resource_props: Mapping[str, Any],
+        manifest: "DbtManifestWrapper",
+        unique_id: str,
     ) -> AssetSpec:
         """Returns an AssetSpec representing a specific dbt resource."""
-        from dagster_dbt.dagster_dbt_translator import DbtManifestWrapper
+        # dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest)
+        group_props = {group["name"]: group for group in manifest.get_section("groups").values()}
+        resource_props = manifest.get_node(unique_id)
 
         # calculate the dependencies for the asset
-        upstream_ids = get_upstream_unique_ids(dbt_nodes, resource_props)
+        upstream_ids = get_upstream_unique_ids(manifest, resource_props)
         deps = [
             AssetDep(
-                asset=self.get_asset_key(dbt_nodes[upstream_id]),
+                asset=self.get_asset_spec(manifest, upstream_id).key,
path: python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
user: login: OwenKephart
body: what's the quick explanation of why this isn't a perf concern / why caching was not required? specifically, seems like if `get_asset_spec` calls `get_asset_spec` on all of its upstreams, we're gonna end up re-doing work a bunch of times for assets further up in the graph.
created_at: 2025-04-08T23:48:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -118,21 +118,6 @@ def __getattr__(self, item):
     return selected
 
 
-def get_dbt_resource_props_by_dbt_unique_id_from_manifest(
-    manifest: Mapping[str, Any],
-) -> Mapping[str, Mapping[str, Any]]:
-    """A mapping of a dbt node's unique id to the node's dictionary representation in the manifest."""
-    return {
-        **manifest["nodes"],
-        **manifest["sources"],
-        **manifest["exposures"],
-        **manifest["metrics"],
-        **manifest.get("semantic_models", {}),
-        **manifest.get("saved_queries", {}),
-        **manifest.get("unit_tests", {}),
-    }
path: python_modules/libraries/dagster-dbt/dagster_dbt/utils.py
user: login: OwenKephart
body: <3
created_at: 2025-04-08T23:49:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -90,22 +90,21 @@ def settings(self) -> DagsterDbtTranslatorSettings:
 
     def get_asset_spec(
         self,
-        manifest: Mapping[str, Any],
-        dbt_nodes: Mapping[str, Any],
-        group_props: Mapping[str, Any],
-        project: Optional["DbtProject"],
-        resource_props: Mapping[str, Any],
+        manifest: "DbtManifestWrapper",
+        unique_id: str,
     ) -> AssetSpec:
         """Returns an AssetSpec representing a specific dbt resource."""
-        from dagster_dbt.dagster_dbt_translator import DbtManifestWrapper
+        # dbt_nodes = get_dbt_resource_props_by_dbt_unique_id_from_manifest(manifest)
+        group_props = {group["name"]: group for group in manifest.get_section("groups").values()}
+        resource_props = manifest.get_node(unique_id)
 
         # calculate the dependencies for the asset
-        upstream_ids = get_upstream_unique_ids(dbt_nodes, resource_props)
+        upstream_ids = get_upstream_unique_ids(manifest, resource_props)
         deps = [
             AssetDep(
-                asset=self.get_asset_key(dbt_nodes[upstream_id]),
+                asset=self.get_asset_spec(manifest, upstream_id).key,
path: python_modules/libraries/dagster-dbt/dagster_dbt/dagster_dbt_translator.py
user: login: alangenfeld
body: I think you are right that this could be an issue, I only caught this call right at the end of the day. Think i will try memoizing against `(id(manifest_json), unique_id)` 
created_at: 2025-04-09T14:18:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -127,15 +127,14 @@ def get_additional_scope(cls) -> Mapping[str, Any]:
         ),
         ({"tags": {"foo": "bar"}}, lambda asset_spec: asset_spec.tags.get("foo") == "bar", False),
         (
-            {"kinds": ["snowflake"]},
-            lambda asset_spec: "snowflake" in asset_spec.kinds
-            and "dbt" in asset_spec.kinds,  # Ensure dbt-specific kind is not overwritten
+            {"kinds": ["snowflake", "dbt"]},
path: python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py
user: login: OwenKephart
body: why is this necessary? i think i get the picture (we used to get_asset_spec before a separate post-processing step in dagster-dbt) but is there a way to make this change w/ updating this behavior?
created_at: 2025-04-10T17:37:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -127,15 +127,14 @@ def get_additional_scope(cls) -> Mapping[str, Any]:
         ),
         ({"tags": {"foo": "bar"}}, lambda asset_spec: asset_spec.tags.get("foo") == "bar", False),
         (
-            {"kinds": ["snowflake"]},
-            lambda asset_spec: "snowflake" in asset_spec.kinds
-            and "dbt" in asset_spec.kinds,  # Ensure dbt-specific kind is not overwritten
+            {"kinds": ["snowflake", "dbt"]},
path: python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py
user: login: alangenfeld
body: Ya the `dbt` insertion was just a consequence of how things were called before. 

I was trying to stay in line with the existing two behaviors of `merge` vs `replace` and I was less certain about promoting `kinds` to `merge` since its currently `replace`. 

I checked with @benpankow and this seemed reasonable to him. 
created_at: 2025-04-10T17:57:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -127,15 +127,14 @@ def get_additional_scope(cls) -> Mapping[str, Any]:
         ),
         ({"tags": {"foo": "bar"}}, lambda asset_spec: asset_spec.tags.get("foo") == "bar", False),
         (
-            {"kinds": ["snowflake"]},
-            lambda asset_spec: "snowflake" in asset_spec.kinds
-            and "dbt" in asset_spec.kinds,  # Ensure dbt-specific kind is not overwritten
+            {"kinds": ["snowflake", "dbt"]},
path: python_modules/libraries/dagster-dbt/dagster_dbt_tests/components/test_dbt_project_component.py
user: login: OwenKephart
body: > I was trying to stay in line with the existing two behaviors of merge vs replace and I was less certain about promoting kinds to merge since its currently replace.

imo promoting `kinds` to merge makes sense to me but doesn't have to be this PR
created_at: 2025-04-10T18:09:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -563,32 +564,32 @@ def default_description_fn(dbt_resource_props: Mapping[str, Any], display_raw_sq
 
 def default_asset_check_fn(
     manifest: Mapping[str, Any],
-    dbt_nodes: Mapping[str, Any],
     dagster_dbt_translator: "DagsterDbtTranslator",
     asset_key: AssetKey,
     test_unique_id: str,
+    project: Optional[DbtProject],
path: python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
user: login: maximearmstrong
body: I believe changing the signature of this might be a breaking change? I remember that we were recommending using these default functions at some point, when customizing the translator methods. I don't think it's part of our documentation anymore, but wondering if that could break user code?
created_at: 2025-04-11T16:28:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -563,32 +564,32 @@ def default_description_fn(dbt_resource_props: Mapping[str, Any], display_raw_sq
 
 def default_asset_check_fn(
     manifest: Mapping[str, Any],
-    dbt_nodes: Mapping[str, Any],
     dagster_dbt_translator: "DagsterDbtTranslator",
     asset_key: AssetKey,
     test_unique_id: str,
+    project: Optional[DbtProject],
path: python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
user: login: maximearmstrong
body: Maybe we could deprecate the `dbt_nodes` parameter and set the default to None for `project`?

I think these changes are fairly low risk, but just to make sure we don't break user code.
created_at: 2025-04-11T16:36:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -563,32 +564,32 @@ def default_description_fn(dbt_resource_props: Mapping[str, Any], display_raw_sq
 
 def default_asset_check_fn(
     manifest: Mapping[str, Any],
-    dbt_nodes: Mapping[str, Any],
     dagster_dbt_translator: "DagsterDbtTranslator",
     asset_key: AssetKey,
     test_unique_id: str,
+    project: Optional[DbtProject],
path: python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
user: login: alangenfeld
body: hm in what cases do you think a user would use this default fn? it only gets called in `build_dbt_specs` not the translator and its not exported from the top 
created_at: 2025-04-11T17:12:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -563,32 +564,32 @@ def default_description_fn(dbt_resource_props: Mapping[str, Any], display_raw_sq
 
 def default_asset_check_fn(
     manifest: Mapping[str, Any],
-    dbt_nodes: Mapping[str, Any],
     dagster_dbt_translator: "DagsterDbtTranslator",
     asset_key: AssetKey,
     test_unique_id: str,
+    project: Optional[DbtProject],
path: python_modules/libraries/dagster-dbt/dagster_dbt/asset_utils.py
user: login: maximearmstrong
body: Ah nvm - it looks like all other default functions are used in translator methods, but not this one.
created_at: 2025-04-11T17:29:17Z
author_association: CONTRIBUTOR

PR comments:
user: login: alangenfeld
created_at: 2025-04-08T22:14:02Z
body: * **#29171** <a href="https://app.graphite.dev/github/pr/dagster-io/dagster/29171?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* **#29123** <a href="https://app.graphite.dev/github/pr/dagster-io/dagster/29123?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/dagster-io/dagster/29123?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* **#29111** <a href="https://app.graphite.dev/github/pr/dagster-io/dagster/29111?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a>
* `master`


<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
author_association: CONTRIBUTOR
user: login: alangenfeld
created_at: 2025-04-08T22:34:30Z
body: I didn’t end up needing the caching I thought I would so I can probably cut a lot of these changes out and just call [2] where we used to use dbt_nodes
author_association: CONTRIBUTOR