@@ -156,7 +156,7 @@ def _get_columns(
156156
157157# Type annotation for parent references
158158ParentType = Union ["Model" , "Source" , "Snapshot" , "Seed" ]
159- ChildType = Union ["Model" , "Snapshot" ]
159+ ChildType = Union ["Model" , "Snapshot" , "Exposure" ]
160160
161161
162162@dataclass
@@ -499,6 +499,71 @@ def __hash__(self) -> int:
499499 return hash (self .unique_id )
500500
501501
502+ @dataclass
503+ class Exposure :
504+ """Represents a dbt exposure.
505+
506+ Attributes:
507+ unique_id: The unique id of the exposure (e.g. `exposure.package.exposure1`).
508+ name: The name of the exposure.
509+ description: The description of the exposure.
510+ label: The label of the exposure.
511+ url: The url of the exposure.
512+ maturity: The maturity of the exposure.
513+ original_file_path: The path to the exposure file
514+ (e.g. `models/exposures/exposures.yml`).
515+ type: The type of the exposure, e.g. `application`.
516+ owner: The owner of the exposure,
517+ e.g. `{"name": "owner", "email": "[email protected] "}`. 518+ config: The config of the exposure.
519+ meta: The meta of the exposure.
520+ tags: The list of tags attached to the exposure.
521+ depends_on: The depends_on of the exposure.
522+ parents: The list of models, sources, and snapshot this exposure depends on.
523+ _raw_values: The raw values of the exposure in the manifest.
524+ """
525+
526+ unique_id : str
527+ name : str
528+ description : str
529+ label : str
530+ url : str
531+ maturity : str
532+ original_file_path : str
533+ type : str
534+ owner : dict [str , Any ]
535+ config : dict [str , Any ]
536+ meta : dict [str , Any ]
537+ tags : list [str ]
538+ depends_on : dict [str , list [str ]] = field (default_factory = dict )
539+ parents : list [ParentType ] = field (default_factory = list )
540+ _raw_values : dict [str , Any ] = field (default_factory = dict )
541+
542+ @classmethod
543+ def from_node (cls , node_values : dict [str , Any ]) -> "Exposure" :
544+ """Create an exposure object from a node in the manifest."""
545+ return cls (
546+ unique_id = node_values ["unique_id" ],
547+ name = node_values ["name" ],
548+ description = node_values ["description" ],
549+ label = node_values ["label" ],
550+ url = node_values ["url" ],
551+ maturity = node_values ["maturity" ],
552+ original_file_path = node_values ["original_file_path" ],
553+ type = node_values ["type" ],
554+ owner = node_values ["owner" ],
555+ config = node_values ["config" ],
556+ meta = node_values ["meta" ],
557+ tags = node_values ["tags" ],
558+ depends_on = node_values ["depends_on" ],
559+ _raw_values = node_values ,
560+ )
561+
562+ def __hash__ (self ) -> int :
563+ """Compute a unique hash for an exposure."""
564+ return hash (self .unique_id )
565+
566+
502567@dataclass
503568class Seed (HasColumnsMixin ):
504569 """Represents a dbt seed.
@@ -579,11 +644,11 @@ def __hash__(self) -> int:
579644 return hash (self .unique_id )
580645
581646
582- Evaluable : TypeAlias = Model | Source | Snapshot | Seed
647+ Evaluable : TypeAlias = Model | Source | Snapshot | Seed | Exposure
583648
584649
585650class ManifestLoader :
586- """Load the models, sources, snapshots, seeds and tests from the manifest."""
651+ """Load the evaluables from the manifest."""
587652
588653 def __init__ (self , file_path : Path , select : Iterable [str ] | None = None ):
589654 """Initialize the ManifestLoader.
@@ -604,25 +669,38 @@ def __init__(self, file_path: Path, select: Iterable[str] | None = None):
604669 for source_id , source_values in self .raw_manifest .get ("sources" , {}).items ()
605670 if source_values ["package_name" ] == self .project_name
606671 }
672+ self .raw_exposures = {
673+ exposure_id : exposure_values
674+ for exposure_id , exposure_values in self .raw_manifest .get (
675+ "exposures" , {}
676+ ).items ()
677+ if exposure_values ["package_name" ] == self .project_name
678+ }
607679
608680 self .models : dict [str , Model ] = {}
609681 self .tests : dict [str , list [dict [str , Any ]]] = defaultdict (list )
610682 self .sources : dict [str , Source ] = {}
611683 self .snapshots : dict [str , Snapshot ] = {}
684+ self .exposures : dict [str , Exposure ] = {}
612685 self .seeds : dict [str , Seed ] = {}
613686
614687 self ._reindex_tests ()
615688 self ._load_models ()
616689 self ._load_sources ()
617690 self ._load_snapshots ()
691+ self ._load_exposures ()
618692 self ._load_seeds ()
619693 self ._populate_relatives ()
620694
621695 if select :
622696 self ._filter_evaluables (select )
623697
624698 if (
625- len (self .models ) + len (self .sources ) + len (self .snapshots ) + len (self .seeds )
699+ len (self .models )
700+ + len (self .sources )
701+ + len (self .snapshots )
702+ + len (self .seeds )
703+ + len (self .exposures )
626704 ) == 0 :
627705 logger .warning ("Nothing to evaluate!" )
628706
@@ -647,6 +725,13 @@ def _load_snapshots(self) -> None:
647725 snapshot = Snapshot .from_node (node_values , self .tests .get (node_id , []))
648726 self .snapshots [node_id ] = snapshot
649727
728+ def _load_exposures (self ) -> None :
729+ """Load the exposures from the manifest."""
730+ for node_id , node_values in self .raw_exposures .items ():
731+ if node_values .get ("resource_type" ) == "exposure" :
732+ exposure = Exposure .from_node (node_values )
733+ self .exposures [node_id ] = exposure
734+
650735 def _load_seeds (self ) -> None :
651736 """Load the seeds from the manifest."""
652737 for node_id , node_values in self .raw_nodes .items ():
@@ -672,7 +757,11 @@ def _reindex_tests(self) -> None:
672757
673758 def _populate_relatives (self ) -> None :
674759 """Populate `parents` and `children` for all evaluables."""
675- for node in list (self .models .values ()) + list (self .snapshots .values ()):
760+ for node in (
761+ list (self .models .values ())
762+ + list (self .snapshots .values ())
763+ + list (self .exposures .values ())
764+ ):
676765 for parent_id in node .depends_on .get ("nodes" , []):
677766 if parent_id in self .models :
678767 node .parents .append (self .models [parent_id ])
@@ -704,4 +793,5 @@ def _filter_evaluables(self, select: Iterable[str]) -> None:
704793 k : s for k , s in self .sources .items () if s .selector_name in selected
705794 }
706795 self .snapshots = {k : s for k , s in self .snapshots .items () if s .name in selected }
796+ self .exposures = {k : e for k , e in self .exposures .items () if e .name in selected }
707797 self .seeds = {k : s for k , s in self .seeds .items () if s .name in selected }
0 commit comments