66from collections import defaultdict
77from dataclasses import dataclass , field
88from pathlib import Path
9- from typing import Any , Iterable , Literal , TypeAlias , Union
9+ from typing import Any , Iterable , List , Literal , TypeAlias , Union
1010
1111from dbt_score .dbt_utils import dbt_ls
1212
@@ -154,6 +154,10 @@ def _get_columns(
154154 ]
155155
156156
157+ # Type annotation for parent references
158+ ParentType = Union ["Model" , "Source" , "Snapshot" , "Seed" ]
159+
160+
157161@dataclass
158162class Model (HasColumnsMixin ):
159163 """Represents a dbt model.
@@ -205,7 +209,7 @@ class Model(HasColumnsMixin):
205209 tests : list [Test ] = field (default_factory = list )
206210 depends_on : dict [str , list [str ]] = field (default_factory = dict )
207211 constraints : list [Constraint ] = field (default_factory = list )
208- parents : list [ Union [ "Model" , "Source" , "Snapshot" ] ] = field (default_factory = list )
212+ parents : List [ ParentType ] = field (default_factory = list )
209213 _raw_values : dict [str , Any ] = field (default_factory = dict )
210214 _raw_test_values : list [dict [str , Any ]] = field (default_factory = list )
211215
@@ -245,6 +249,7 @@ def from_node(
245249 Constraint .from_raw_values (constraint )
246250 for constraint in node_values ["constraints" ]
247251 ],
252+ parents = [], # Will be populated later
248253 _raw_values = node_values ,
249254 _raw_test_values = test_values ,
250255 )
@@ -443,7 +448,7 @@ class Snapshot(HasColumnsMixin):
443448 depends_on : dict [str , list [str ]] = field (default_factory = dict )
444449 strategy : str | None = None
445450 unique_key : list [str ] | None = None
446- parents : list [ Union [ "Model" , "Source" , "Snapshot" ] ] = field (default_factory = list )
451+ parents : List [ ParentType ] = field (default_factory = list )
447452 _raw_values : dict [str , Any ] = field (default_factory = dict )
448453 _raw_test_values : list [dict [str , Any ]] = field (default_factory = list )
449454
@@ -477,6 +482,7 @@ def from_node(
477482 .get ("column_name" )
478483 ],
479484 depends_on = node_values ["depends_on" ],
485+ parents = [], # Will be populated later
480486 _raw_values = node_values ,
481487 _raw_test_values = test_values ,
482488 )
@@ -486,11 +492,89 @@ def __hash__(self) -> int:
486492 return hash (self .unique_id )
487493
488494
489- Evaluable : TypeAlias = Model | Source | Snapshot
495+ @dataclass
496+ class Seed (HasColumnsMixin ):
497+ """Represents a dbt seed.
498+
499+ Attributes:
500+ unique_id: The id of the seed, e.g. `seed.package.seed_name`.
501+ name: The name of the seed.
502+ relation_name: The relation name of the seed, e.g. `db.schema.seed_name`.
503+ description: The full description of the seed.
504+ original_file_path: The seed path, e.g. `data/seed_name.csv`.
505+ config: The config of the seed.
506+ meta: The meta of the seed.
507+ columns: The list of columns of the seed.
508+ package_name: The package name of the seed.
509+ database: The database name of the seed.
510+ schema: The schema name of the seed.
511+ alias: The alias of the seed.
512+ patch_path: The yml path of the seed, e.g. `seeds.yml`.
513+ tags: The list of tags attached to the seed.
514+ tests: The list of tests attached to the seed.
515+ _raw_values: The raw values of the seed (node) in the manifest.
516+ _raw_test_values: The raw test values of the seed (node) in the manifest.
517+ """
518+
519+ unique_id : str
520+ name : str
521+ relation_name : str
522+ description : str
523+ original_file_path : str
524+ config : dict [str , Any ]
525+ meta : dict [str , Any ]
526+ columns : list [Column ]
527+ package_name : str
528+ database : str
529+ schema : str
530+ alias : str | None = None
531+ patch_path : str | None = None
532+ tags : list [str ] = field (default_factory = list )
533+ tests : list [Test ] = field (default_factory = list )
534+ _raw_values : dict [str , Any ] = field (default_factory = dict )
535+ _raw_test_values : list [dict [str , Any ]] = field (default_factory = list )
536+
537+ @classmethod
538+ def from_node (
539+ cls , node_values : dict [str , Any ], test_values : list [dict [str , Any ]]
540+ ) -> "Seed" :
541+ """Create a seed object from a node and its tests in the manifest."""
542+ return cls (
543+ unique_id = node_values ["unique_id" ],
544+ name = node_values ["name" ],
545+ relation_name = node_values ["relation_name" ],
546+ description = node_values ["description" ],
547+ original_file_path = node_values ["original_file_path" ],
548+ config = node_values ["config" ],
549+ meta = node_values ["meta" ],
550+ columns = cls ._get_columns (node_values , test_values ),
551+ package_name = node_values ["package_name" ],
552+ database = node_values ["database" ],
553+ schema = node_values ["schema" ],
554+ alias = node_values ["alias" ],
555+ patch_path = node_values ["patch_path" ],
556+ tags = node_values ["tags" ],
557+ tests = [
558+ Test .from_node (test )
559+ for test in test_values
560+ if not test .get ("test_metadata" , {})
561+ .get ("kwargs" , {})
562+ .get ("column_name" )
563+ ],
564+ _raw_values = node_values ,
565+ _raw_test_values = test_values ,
566+ )
567+
568+ def __hash__ (self ) -> int :
569+ """Compute a unique hash for a seed."""
570+ return hash (self .unique_id )
571+
572+
573+ Evaluable : TypeAlias = Model | Source | Snapshot | Seed
490574
491575
492576class ManifestLoader :
493- """Load the models, sources, snapshots and tests from the manifest."""
577+ """Load the models, sources, snapshots, seeds and tests from the manifest."""
494578
495579 def __init__ (self , file_path : Path , select : Iterable [str ] | None = None ):
496580 """Initialize the ManifestLoader.
@@ -516,17 +600,21 @@ def __init__(self, file_path: Path, select: Iterable[str] | None = None):
516600 self .tests : dict [str , list [dict [str , Any ]]] = defaultdict (list )
517601 self .sources : dict [str , Source ] = {}
518602 self .snapshots : dict [str , Snapshot ] = {}
603+ self .seeds : dict [str , Seed ] = {}
519604
520605 self ._reindex_tests ()
521606 self ._load_models ()
522607 self ._load_sources ()
523608 self ._load_snapshots ()
609+ self ._load_seeds ()
524610 self ._populate_parents ()
525611
526612 if select :
527613 self ._filter_evaluables (select )
528614
529- if (len (self .models ) + len (self .sources ) + len (self .snapshots )) == 0 :
615+ if (
616+ len (self .models ) + len (self .sources ) + len (self .snapshots ) + len (self .seeds )
617+ ) == 0 :
530618 logger .warning ("Nothing to evaluate!" )
531619
532620 def _load_models (self ) -> None :
@@ -550,6 +638,13 @@ def _load_snapshots(self) -> None:
550638 snapshot = Snapshot .from_node (node_values , self .tests .get (node_id , []))
551639 self .snapshots [node_id ] = snapshot
552640
641+ def _load_seeds (self ) -> None :
642+ """Load the seeds from the manifest."""
643+ for node_id , node_values in self .raw_nodes .items ():
644+ if node_values .get ("resource_type" ) == "seed" :
645+ seed = Seed .from_node (node_values , self .tests .get (node_id , []))
646+ self .seeds [node_id ] = seed
647+
553648 def _reindex_tests (self ) -> None :
554649 """Index tests based on their associated evaluable."""
555650 for node_values in self .raw_nodes .values ():
@@ -576,6 +671,8 @@ def _populate_parents(self) -> None:
576671 node .parents .append (self .snapshots [parent_id ])
577672 elif parent_id in self .sources :
578673 node .parents .append (self .sources [parent_id ])
674+ elif parent_id in self .seeds :
675+ node .parents .append (self .seeds [parent_id ])
579676
580677 def _filter_evaluables (self , select : Iterable [str ]) -> None :
581678 """Filter evaluables like dbt's --select."""
@@ -594,3 +691,4 @@ def _filter_evaluables(self, select: Iterable[str]) -> None:
594691 k : s for k , s in self .sources .items () if s .selector_name in selected
595692 }
596693 self .snapshots = {k : s for k , s in self .snapshots .items () if s .name in selected }
694+ self .seeds = {k : s for k , s in self .seeds .items () if s .name in selected }
0 commit comments