@@ -65,83 +65,155 @@ async def resource_counts(
6565 ),
6666 allow_cache : bool = Query (True ),
6767 ):
68- def count_resources_for_project (project_name : str ):
68+ """
69+ Resource counts and feature store inventory metadata.
70+
71+ Returns counts per resource type, plus enriched summaries:
72+ feature services (names), feature views (with per-view feature
73+ count and materialization info), project list, and the registry
74+ last-updated timestamp.
75+ """
76+
77+ def get_registry_last_updated () -> Optional [str ]:
6978 try :
70- entities = grpc_call (
79+ from google .protobuf .empty_pb2 import Empty as EmptyProto
80+
81+ registry_proto = grpc_call (grpc_handler .Proto , EmptyProto ())
82+ return registry_proto .get ("lastUpdated" , None )
83+ except Exception :
84+ return None
85+
86+ def _extract_fv_summary (any_fv : dict , project_name : str ) -> Optional [Dict ]:
87+ fv = _extract_feature_view_from_any (any_fv )
88+ if not fv :
89+ return None
90+ spec = fv .get ("spec" , {})
91+ features = spec .get ("features" , [])
92+ return {
93+ "name" : spec .get ("name" , "" ),
94+ "project" : project_name ,
95+ "type" : fv .get ("type" , "featureView" ),
96+ "featureCount" : len (features ) if isinstance (features , list ) else 0 ,
97+ }
98+
99+ def collect_resources_for_project (project_name : str ) -> dict :
100+ entities_list : list = []
101+ try :
102+ entities_resp = grpc_call (
71103 grpc_handler .ListEntities ,
72104 RegistryServer_pb2 .ListEntitiesRequest (
73105 project = project_name , allow_cache = allow_cache
74106 ),
75107 )
108+ entities_list = entities_resp .get ("entities" , [])
76109 except Exception :
77- entities = {"entities" : []}
110+ pass
111+
112+ data_sources_list : list = []
78113 try :
79- data_sources = grpc_call (
114+ ds_resp = grpc_call (
80115 grpc_handler .ListDataSources ,
81116 RegistryServer_pb2 .ListDataSourcesRequest (
82117 project = project_name , allow_cache = allow_cache
83118 ),
84119 )
120+ data_sources_list = ds_resp .get ("dataSources" , [])
85121 except Exception :
86- data_sources = {"dataSources" : []}
122+ pass
123+
124+ saved_datasets_list : list = []
87125 try :
88- saved_datasets = grpc_call (
126+ sd_resp = grpc_call (
89127 grpc_handler .ListSavedDatasets ,
90128 RegistryServer_pb2 .ListSavedDatasetsRequest (
91129 project = project_name , allow_cache = allow_cache
92130 ),
93131 )
132+ saved_datasets_list = sd_resp .get ("savedDatasets" , [])
94133 except Exception :
95- saved_datasets = {"savedDatasets" : []}
134+ pass
135+
136+ features_list : list = []
96137 try :
97- features = grpc_call (
138+ feat_resp = grpc_call (
98139 grpc_handler .ListFeatures ,
99140 RegistryServer_pb2 .ListFeaturesRequest (
100141 project = project_name , allow_cache = allow_cache
101142 ),
102143 )
144+ features_list = feat_resp .get ("features" , [])
103145 except Exception :
104- features = {"features" : []}
146+ pass
147+
148+ raw_fv_list : list = []
149+ fv_summaries : List [Dict ] = []
105150 try :
106- feature_views = grpc_call (
151+ fv_resp = grpc_call (
107152 grpc_handler .ListAllFeatureViews ,
108153 RegistryServer_pb2 .ListAllFeatureViewsRequest (
109154 project = project_name , allow_cache = allow_cache
110155 ),
111156 )
157+ raw_fv_list = fv_resp .get ("featureViews" , [])
158+ for any_fv in raw_fv_list :
159+ summary = _extract_fv_summary (any_fv , project_name )
160+ if summary :
161+ fv_summaries .append (summary )
112162 except Exception :
113- feature_views = {"featureViews" : []}
163+ pass
164+
165+ fs_summaries : List [Dict ] = []
166+ raw_fs_list : list = []
114167 try :
115- feature_services = grpc_call (
168+ fs_resp = grpc_call (
116169 grpc_handler .ListFeatureServices ,
117170 RegistryServer_pb2 .ListFeatureServicesRequest (
118171 project = project_name , allow_cache = allow_cache
119172 ),
120173 )
174+ raw_fs_list = fs_resp .get ("featureServices" , [])
175+ for fs in raw_fs_list :
176+ spec = fs .get ("spec" , {})
177+ fs_summaries .append (
178+ {"name" : spec .get ("name" , "" ), "project" : project_name }
179+ )
121180 except Exception :
122- feature_services = {"featureServices" : []}
181+ pass
182+
123183 return {
124- "entities" : len (entities .get ("entities" , [])),
125- "dataSources" : len (data_sources .get ("dataSources" , [])),
126- "savedDatasets" : len (saved_datasets .get ("savedDatasets" , [])),
127- "features" : len (features .get ("features" , [])),
128- "featureViews" : len (feature_views .get ("featureViews" , [])),
129- "featureServices" : len (feature_services .get ("featureServices" , [])),
184+ "counts" : {
185+ "entities" : len (entities_list ),
186+ "dataSources" : len (data_sources_list ),
187+ "savedDatasets" : len (saved_datasets_list ),
188+ "features" : len (features_list ),
189+ "featureViews" : len (fv_summaries ),
190+ "featureServices" : len (fs_summaries ),
191+ },
192+ "featureServices" : fs_summaries ,
193+ "featureViews" : fv_summaries ,
130194 }
131195
196+ registry_last_updated = get_registry_last_updated ()
197+
132198 if project :
133- counts = count_resources_for_project (project )
134- return {"project" : project , "counts" : counts }
199+ resources = collect_resources_for_project (project )
200+ return {
201+ "project" : project ,
202+ "counts" : resources ["counts" ],
203+ "featureServices" : resources ["featureServices" ],
204+ "featureViews" : resources ["featureViews" ],
205+ "projects" : [{"name" : project }],
206+ "registryLastUpdated" : registry_last_updated ,
207+ }
135208 else :
136- # List all projects via gRPC
137209 projects_resp = grpc_call (
138210 grpc_handler .ListProjects ,
139211 RegistryServer_pb2 .ListProjectsRequest (allow_cache = allow_cache ),
140212 )
141- all_projects = [
142- p ["spec" ]["name" ] for p in projects_resp . get ( "projects" , [])
143- ]
144- all_counts = {}
213+ all_projects = projects_resp . get ( "projects" , [])
214+ project_names = [ p ["spec" ]["name" ] for p in all_projects if "spec" in p ]
215+
216+ all_counts : Dict [ str , dict ] = {}
145217 total_counts = {
146218 "entities" : 0 ,
147219 "dataSources" : 0 ,
@@ -150,12 +222,37 @@ def count_resources_for_project(project_name: str):
150222 "featureViews" : 0 ,
151223 "featureServices" : 0 ,
152224 }
153- for project_name in all_projects :
154- counts = count_resources_for_project (project_name )
155- all_counts [project_name ] = counts
225+ all_fs : List [Dict ] = []
226+ all_fv : List [Dict ] = []
227+ project_summaries : List [Dict ] = []
228+
229+ for pname in project_names :
230+ resources = collect_resources_for_project (pname )
231+ counts = resources ["counts" ]
232+ all_counts [pname ] = counts
156233 for k in total_counts :
157234 total_counts [k ] += counts [k ]
158- return {"total" : total_counts , "perProject" : all_counts }
235+ all_fs .extend (resources ["featureServices" ])
236+ all_fv .extend (resources ["featureViews" ])
237+ proj_info : Dict = next (
238+ (p for p in all_projects if p .get ("spec" , {}).get ("name" ) == pname ),
239+ {},
240+ )
241+ project_summaries .append (
242+ {
243+ "name" : pname ,
244+ "description" : proj_info .get ("spec" , {}).get ("description" , "" ),
245+ }
246+ )
247+
248+ return {
249+ "total" : total_counts ,
250+ "perProject" : all_counts ,
251+ "featureServices" : all_fs ,
252+ "featureViews" : all_fv ,
253+ "projects" : project_summaries ,
254+ "registryLastUpdated" : registry_last_updated ,
255+ }
159256
160257 @router .get (
161258 "/metrics/popular_tags" , tags = ["Metrics" ], response_model = PopularTagsResponse
0 commit comments