@@ -283,13 +283,25 @@ def build_env(
283283 # We don't rely on `env` to keep track of visited objects, because it's populated in post-order
284284 visited : t .Set [str ] = set ()
285285
286- def walk (obj : t .Any , name : str ) -> None :
286+ def walk (obj : t .Any , name : str , is_metadata : t . Optional [ bool ] = None ) -> None :
287287 obj_module = inspect .getmodule (obj )
288288 if name in visited or (obj_module and obj_module .__name__ == "builtins" ):
289289 return
290290
291291 visited .add (name )
292- if name not in env :
292+ name_missing_from_env = name not in env
293+
294+ if name_missing_from_env or (
295+ not is_metadata and env [name ] == obj and getattr (env [name ], c .SQLMESH_METADATA , None )
296+ ):
297+ if not name_missing_from_env :
298+ # The existing object in the env is "metadata only" but we're walking it again as a
299+ # non-"metadata only" dependency, so we update this flag to ensure all transitive
300+ # dependencies are also not marked as "metadata only"
301+ is_metadata = False
302+ if hasattr (obj , c .SQLMESH_METADATA ):
303+ delattr (obj , c .SQLMESH_METADATA )
304+
293305 if hasattr (obj , c .SQLMESH_MACRO ):
294306 # We only need to add the undecorated code of @macro() functions in env, which
295307 # is accessible through the `__wrapped__` attribute added by functools.wraps
@@ -308,6 +320,11 @@ def walk(obj: t.Any, name: str) -> None:
308320 or not hasattr (obj_module , "__file__" )
309321 or not _is_relative_to (obj_module .__file__ , path )
310322 ):
323+ if is_metadata :
324+ setattr (obj , c .SQLMESH_METADATA , True )
325+ elif hasattr (obj , c .SQLMESH_METADATA ):
326+ delattr (obj , c .SQLMESH_METADATA )
327+
311328 env [name ] = obj
312329 return
313330 elif env [name ] != obj :
@@ -318,10 +335,10 @@ def walk(obj: t.Any, name: str) -> None:
318335 if inspect .isclass (obj ):
319336 for var in decorator_vars (obj ):
320337 if obj_module and var in obj_module .__dict__ :
321- walk (obj_module .__dict__ [var ], var )
338+ walk (obj_module .__dict__ [var ], var , is_metadata )
322339
323340 for base in obj .__bases__ :
324- walk (base , base .__qualname__ )
341+ walk (base , base .__qualname__ , is_metadata )
325342
326343 for k , v in obj .__dict__ .items ():
327344 if k .startswith ("__" ):
@@ -335,19 +352,23 @@ def walk(obj: t.Any, name: str) -> None:
335352 # Walk the method if it's part of the object, else it's a global function and we just store it
336353 if v .__qualname__ .startswith (obj .__qualname__ ):
337354 for k , v in func_globals (v ).items ():
338- walk (v , k )
355+ walk (v , k , is_metadata )
339356 else :
340- walk (v , v .__name__ )
357+ walk (v , v .__name__ , is_metadata )
341358 elif callable (obj ):
342359 for k , v in func_globals (obj ).items ():
343- walk (v , k )
360+ walk (v , k , is_metadata )
361+
362+ if is_metadata :
363+ setattr (obj , c .SQLMESH_METADATA , True )
344364
345365 # We store the object in the environment after its dependencies, because otherwise we
346366 # could crash at environment hydration time, since dicts are ordered and the top-level
347367 # objects would be loaded before their dependencies.
348368 env [name ] = obj
349369
350- walk (obj , name )
370+ # The "metadata only" annotation of the object is transitive
371+ walk (obj , name , getattr (obj , c .SQLMESH_METADATA , None ))
351372
352373
353374@dataclass
@@ -411,6 +432,8 @@ def serialize_env(env: t.Dict[str, t.Any], path: Path) -> t.Dict[str, Executable
411432 serialized = {}
412433
413434 for k , v in env .items ():
435+ is_metadata = getattr (v , c .SQLMESH_METADATA , None )
436+
414437 if isinstance (v , LITERALS ) or v is None :
415438 serialized [k ] = Executable .value (v )
416439 elif inspect .ismodule (v ):
@@ -423,6 +446,7 @@ def serialize_env(env: t.Dict[str, t.Any], path: Path) -> t.Dict[str, Executable
423446 serialized [k ] = Executable (
424447 payload = f"import { name } { postfix } " ,
425448 kind = ExecutableKind .IMPORT ,
449+ is_metadata = is_metadata ,
426450 )
427451 elif callable (v ):
428452 name = v .__name__
@@ -447,6 +471,7 @@ def serialize_env(env: t.Dict[str, t.Any], path: Path) -> t.Dict[str, Executable
447471 v = wrapped
448472 file_path = Path (inspect .getfile (wrapped ))
449473 relative_obj_file_path = _is_relative_to (file_path , path )
474+ is_metadata = is_metadata or getattr (v , c .SQLMESH_METADATA , None )
450475 except TypeError :
451476 file_path = None
452477 relative_obj_file_path = False
@@ -459,12 +484,13 @@ def serialize_env(env: t.Dict[str, t.Any], path: Path) -> t.Dict[str, Executable
459484 # Do `as_posix` to serialize windows path back to POSIX
460485 path = t .cast (Path , file_path ).relative_to (path .absolute ()).as_posix (),
461486 alias = k if name != k else None ,
462- is_metadata = getattr ( v , c . SQLMESH_METADATA , None ) ,
487+ is_metadata = is_metadata ,
463488 )
464489 else :
465490 serialized [k ] = Executable (
466491 payload = f"from { v .__module__ } import { name } " ,
467492 kind = ExecutableKind .IMPORT ,
493+ is_metadata = is_metadata ,
468494 )
469495 else :
470496 raise SQLMeshError (
0 commit comments