@@ -31,6 +31,24 @@ class DataStatus(BaseModel):
3131 status : MaterializationStatus
3232 dependencies : list [str ]
3333
34+ def __str__ (self ):
35+ status = self .status
36+ status_parts = []
37+
38+ if not status .latest_materialization and not status .partition_status :
39+ status_parts .append ("No analytics data" )
40+ if status .latest_materialization :
41+ status_parts .append (
42+ f"Last: { status .latest_materialization .strftime ('%Y-%m-%d %H:%M:%S' )} "
43+ )
44+ if status .partition_status :
45+ ps = status .partition_status
46+ status_parts .append (
47+ f"Partitions: { ps .num_materialized } /{ ps .num_partitions } "
48+ )
49+ status_text = f" ({ ', ' .join (status_parts )} )" if status_parts else ""
50+ return f"{ self .key } { status_text } "
51+
3452
3553class DataAnalytics :
3654 """Container for analytics data with tree-structured display methods."""
@@ -57,7 +75,7 @@ def _calculate_root_keys(self) -> list[str]:
5775
5876 def __iter__ (self ):
5977 """Iterate over the analytics data keys."""
60- return iter (self ._analytics_data .keys ())
78+ return iter (self ._analytics_data .items ())
6179
6280 def __contains__ (self , key : str ) -> bool :
6381 """Check if a key exists in the analytics data."""
@@ -72,6 +90,15 @@ def root_keys(self) -> list[str]:
7290 """Get the root keys (top-level nodes in the dependency tree)."""
7391 return self ._root_keys .copy ()
7492
93+ @property
94+ def sources (self ) -> list [DataStatus ]:
95+ """Get all sources (keys with no dependencies)."""
96+ return [
97+ status
98+ for status in self ._analytics_data .values ()
99+ if len (status .dependencies ) == 0
100+ ]
101+
75102 def get (self , key : str ) -> DataStatus | None :
76103 """Get analytics data for a specific key."""
77104 return self ._analytics_data .get (key )
@@ -104,27 +131,9 @@ def _print_analytics_tree(
104131 visited .add (key )
105132 data_status = self ._analytics_data [key ]
106133
107- # Print the current node with inline status information
108- status = data_status .status
109- status_parts = []
110-
111- if not status .latest_materialization and not status .partition_status :
112- status_parts .append ("No analytics data" )
113- if status .latest_materialization :
114- status_parts .append (
115- f"Last: { status .latest_materialization .strftime ('%Y-%m-%d %H:%M:%S' )} "
116- )
117- if status .partition_status :
118- ps = status .partition_status
119- status_parts .append (
120- f"Partitions: { ps .num_materialized } /{ ps .num_partitions } "
121- )
122-
123- status_text = f" ({ ', ' .join (status_parts )} )" if status_parts else ""
124-
125134 # Determine the prefix for the current node
126135 prefix = "└── " if is_last else "├── "
127- print (f"{ indent } { prefix } { key } { status_text } " )
136+ print (f"{ indent } { prefix } { data_status } " )
128137
129138 # Print dependencies
130139 if data_status .dependencies :
0 commit comments