@@ -153,6 +153,101 @@ def milliseconds(self) -> int:
153153 return self .seconds * 1000
154154
155155
156+ class DbtNodeInfo (PydanticModel ):
157+ """
158+ Represents dbt-specific model information set by the dbt loader and intended to be made available at the Snapshot level
159+ (as opposed to hidden within the individual model jinja macro registries).
160+
161+ This allows for things like injecting implementations of variables / functions into the Jinja context that are compatible with
162+ their dbt equivalents but are backed by the sqlmesh snapshots in any given plan / environment
163+ """
164+
165+ unique_id : str
166+ """This is the node/resource name/unique_id that's used as the node key in the dbt manifest.
167+ It's prefixed by the resource type and is exposed in context variables like {{ selected_resources }}.
168+
169+ Examples:
170+ - test.jaffle_shop.unique_stg_orders_order_id.e3b841c71a
171+ - seed.jaffle_shop.raw_payments
172+ - model.jaffle_shop.stg_orders
173+ """
174+
175+ name : str
176+ """Name of this object in the dbt global namespace, used by things like {{ ref() }} calls.
177+
178+ Examples:
179+ - unique_stg_orders_order_id
180+ - raw_payments
181+ - stg_orders
182+ """
183+
184+ fqn : str
185+ """Used for selectors in --select/--exclude.
186+ Takes the filesystem into account so may be structured differently to :unique_id.
187+
188+ Examples:
189+ - jaffle_shop.staging.unique_stg_orders_order_id
190+ - jaffle_shop.raw_payments
191+ - jaffle_shop.staging.stg_orders
192+ """
193+
194+ alias : t .Optional [str ] = None
195+ """This is dbt's way of overriding the _physical table_ a model is written to.
196+
197+ It's used in the following situation:
198+ - Say you have two models, "stg_customers" and "customers"
199+ - You want "stg_customers" to be written to the "staging" schema as eg "staging.customers" - NOT "staging.stg_customers"
200+ - But you cant rename the file to "customers" because it will conflict with your other model file "customers"
201+ - Even if you put it in a different folder, eg "staging/customers.sql" - dbt still has a global namespace so it will conflict
202+ when you try to do something like "{{ ref('customers') }}"
203+ - So dbt's solution to this problem is to keep calling it "stg_customers" at the dbt project/model level,
204+ but allow overriding the physical table to "customers" via something like "{{ config(alias='customers', schema='staging') }}"
205+
206+ Note that if :alias is set, it does *not* replace :name at the model level and cannot be used interchangably with :name.
207+ It also does not affect the :fqn or :unique_id. It's just used to override :name when it comes time to generate the physical table name.
208+ """
209+
210+ @model_validator (mode = "after" )
211+ def post_init (self ) -> Self :
212+ # by default, dbt sets alias to the same as :name
213+ # however, we only want to include :alias if it is actually different / actually providing an override
214+ if self .alias == self .name :
215+ self .alias = None
216+ return self
217+
218+ def to_expression (self ) -> exp .Expression :
219+ """Produce a SQLGlot expression representing this object, for use in things like the model/audit definition renderers"""
220+ return exp .tuple_ (
221+ * (
222+ exp .PropertyEQ (this = exp .var (k ), expression = exp .Literal .string (v ))
223+ for k , v in sorted (self .model_dump (exclude_none = True ).items ())
224+ )
225+ )
226+
227+
228+ class DbtInfoMixin :
229+ """This mixin encapsulates properties that only exist for dbt compatibility and are otherwise not required
230+ for native projects"""
231+
232+ @property
233+ def dbt_node_info (self ) -> t .Optional [DbtNodeInfo ]:
234+ raise NotImplementedError ()
235+
236+ @property
237+ def dbt_unique_id (self ) -> t .Optional [str ]:
238+ """Used for compatibility with jinja context variables such as {{ selected_resources }}"""
239+ if self .dbt_node_info :
240+ return self .dbt_node_info .unique_id
241+ return None
242+
243+ @property
244+ def dbt_fqn (self ) -> t .Optional [str ]:
245+ """Used in the selector engine for compatibility with selectors that select models by dbt fqn"""
246+ if self .dbt_node_info :
247+ return self .dbt_node_info .fqn
248+ return None
249+
250+
156251# this must be sorted in descending order
157252INTERVAL_SECONDS = {
158253 IntervalUnit .YEAR : 60 * 60 * 24 * 365 ,
@@ -165,7 +260,7 @@ def milliseconds(self) -> int:
165260}
166261
167262
168- class _Node (PydanticModel ):
263+ class _Node (DbtInfoMixin , PydanticModel ):
169264 """
170265 Node is the core abstraction for entity that can be executed within the scheduler.
171266
@@ -199,7 +294,7 @@ class _Node(PydanticModel):
199294 interval_unit_ : t .Optional [IntervalUnit ] = Field (alias = "interval_unit" , default = None )
200295 tags : t .List [str ] = []
201296 stamp : t .Optional [str ] = None
202- dbt_name : t .Optional [str ] = None # dbt node name
297+ dbt_node_info_ : t .Optional [DbtNodeInfo ] = Field ( alias = "dbt_node_info" , default = None )
203298 _path : t .Optional [Path ] = None
204299 _data_hash : t .Optional [str ] = None
205300 _metadata_hash : t .Optional [str ] = None
@@ -446,6 +541,10 @@ def is_audit(self) -> bool:
446541 """Return True if this is an audit node"""
447542 return False
448543
544+ @property
545+ def dbt_node_info (self ) -> t .Optional [DbtNodeInfo ]:
546+ return self .dbt_node_info_
547+
449548
450549class NodeType (str , Enum ):
451550 MODEL = "model"
0 commit comments