55from typing import Callable , Optional
66
77import uvicorn
8- from fastapi import FastAPI , Request , Response , status
8+ from fastapi import FastAPI , Response , status
99from fastapi .middleware .cors import CORSMiddleware
1010from fastapi .staticfiles import StaticFiles
1111
@@ -18,16 +18,12 @@ def _build_projects_list(
1818 store : "feast.FeatureStore" ,
1919 project_id : str ,
2020 root_path : str ,
21- mode : str ,
2221):
23- """Build the projects list for the UI, with mode-aware registry paths ."""
22+ """Build the projects list for the UI."""
2423 discovered_projects = []
2524 registry = store .registry .proto ()
2625
27- if mode == "proto" :
28- registry_path_template = f"{ root_path } /registry"
29- else :
30- registry_path_template = f"{ root_path } /api/v1"
26+ registry_path_template = f"{ root_path } /api/v1"
3127
3228 if registry and registry .projects and len (registry .projects ) > 0 :
3329 for proj in registry .projects :
@@ -60,52 +56,7 @@ def _build_projects_list(
6056 }
6157 discovered_projects .insert (0 , all_projects_entry )
6258
63- return {"projects" : discovered_projects , "mode" : mode }
64-
65-
66- def _setup_proto_mode (
67- app : FastAPI , store : "feast.FeatureStore" , registry_ttl_secs : int
68- ):
69- """Set up the legacy proto-blob serving mode (GET /registry)."""
70- registry_proto = None
71- shutting_down = False
72- active_timer : Optional [threading .Timer ] = None
73-
74- def async_refresh ():
75- store .refresh_registry ()
76- nonlocal registry_proto
77- registry_proto = store .registry .proto ()
78- if shutting_down :
79- return
80- nonlocal active_timer
81- active_timer = threading .Timer (registry_ttl_secs , async_refresh )
82- active_timer .start ()
83-
84- @app .on_event ("shutdown" )
85- def shutdown_event ():
86- nonlocal shutting_down
87- shutting_down = True
88- if active_timer :
89- active_timer .cancel ()
90-
91- async_refresh ()
92-
93- @app .get ("/registry" )
94- def read_registry ():
95- if registry_proto is None :
96- return Response (status_code = status .HTTP_503_SERVICE_UNAVAILABLE )
97- return Response (
98- content = registry_proto .SerializeToString (),
99- media_type = "application/octet-stream" ,
100- )
101-
102- @app .get ("/health" )
103- def health ():
104- return (
105- Response (status_code = status .HTTP_200_OK )
106- if registry_proto
107- else Response (status_code = status .HTTP_503_SERVICE_UNAVAILABLE )
108- )
59+ return {"projects" : discovered_projects }
10960
11061
11162def _setup_rest_mode (app : FastAPI , store : "feast.FeatureStore" , registry_ttl_secs : int ):
@@ -142,15 +93,6 @@ def shutdown_event():
14293 register_all_routes (rest_app , grpc_handler )
14394 app .mount ("/api/v1" , rest_app )
14495
145- @app .get ("/registry" )
146- def read_registry ():
147- if registry_proto is None :
148- return Response (status_code = status .HTTP_503_SERVICE_UNAVAILABLE )
149- return Response (
150- content = registry_proto .SerializeToString (),
151- media_type = "application/octet-stream" ,
152- )
153-
15496 @app .get ("/health" )
15597 def health ():
15698 return (
@@ -162,106 +104,11 @@ def health():
162104 logger .info ("REST registry API mounted at /api/v1" )
163105
164106
165- def _setup_rest_external_mode (
166- app : FastAPI ,
167- store : "feast.FeatureStore" ,
168- rest_api_url : str ,
169- registry_ttl_secs : int ,
170- ):
171- """Reverse-proxy REST API calls to an external registry server."""
172- import httpx
173-
174- rest_api_url = rest_api_url .rstrip ("/" )
175- client = httpx .AsyncClient (timeout = 60.0 )
176-
177- registry_proto = None
178- shutting_down = False
179- active_timer : Optional [threading .Timer ] = None
180-
181- def async_refresh ():
182- store .refresh_registry ()
183- nonlocal registry_proto
184- registry_proto = store .registry .proto ()
185- if shutting_down :
186- return
187- nonlocal active_timer
188- active_timer = threading .Timer (registry_ttl_secs , async_refresh )
189- active_timer .start ()
190-
191- @app .on_event ("shutdown" )
192- async def shutdown_event ():
193- nonlocal shutting_down
194- shutting_down = True
195- if active_timer :
196- active_timer .cancel ()
197- await client .aclose ()
198-
199- async_refresh ()
200-
201- @app .api_route ("/api/v1/{path:path}" , methods = ["GET" , "POST" , "PUT" , "DELETE" ])
202- async def proxy_to_external (request : Request , path : str ):
203- target_url = f"{ rest_api_url } /{ path } "
204- query_string = str (request .url .query )
205- if query_string :
206- target_url = f"{ target_url } ?{ query_string } "
207-
208- headers = {
209- k : v
210- for k , v in request .headers .items ()
211- if k .lower () not in ("host" , "content-length" , "transfer-encoding" )
212- }
213-
214- body = await request .body ()
215-
216- try :
217- resp = await client .request (
218- method = request .method ,
219- url = target_url ,
220- headers = headers ,
221- content = body if body else None ,
222- )
223- return Response (
224- content = resp .content ,
225- status_code = resp .status_code ,
226- media_type = resp .headers .get ("content-type" , "application/json" ),
227- )
228- except httpx .RequestError as e :
229- logger .error (f"Error proxying to { target_url } : { e } " )
230- return Response (
231- content = json .dumps (
232- {"detail" : "Failed to reach the upstream registry API" }
233- ),
234- status_code = status .HTTP_502_BAD_GATEWAY ,
235- media_type = "application/json" ,
236- )
237-
238- @app .get ("/registry" )
239- def read_registry ():
240- if registry_proto is None :
241- return Response (status_code = status .HTTP_503_SERVICE_UNAVAILABLE )
242- return Response (
243- content = registry_proto .SerializeToString (),
244- media_type = "application/octet-stream" ,
245- )
246-
247- @app .get ("/health" )
248- def health ():
249- return (
250- Response (status_code = status .HTTP_200_OK )
251- if registry_proto
252- else Response (status_code = status .HTTP_503_SERVICE_UNAVAILABLE )
253- )
254-
255- logger .info (f"REST external proxy configured → { rest_api_url } " )
256-
257-
258107def get_app (
259108 store : "feast.FeatureStore" ,
260109 project_id : str ,
261110 registry_ttl_secs : int ,
262111 root_path : str = "" ,
263- mode : str = "proto" ,
264- rest_api_url : str = "" ,
265112):
266113 app = FastAPI ()
267114
@@ -273,16 +120,11 @@ def get_app(
273120 allow_headers = ["*" ],
274121 )
275122
276- if mode == "rest" :
277- _setup_rest_mode (app , store , registry_ttl_secs )
278- elif mode == "rest-external" :
279- _setup_rest_external_mode (app , store , rest_api_url , registry_ttl_secs )
280- else :
281- _setup_proto_mode (app , store , registry_ttl_secs )
123+ _setup_rest_mode (app , store , registry_ttl_secs )
282124
283125 ui_dir_ref = importlib_resources .files (__spec__ .parent ) / "ui/build/" # type: ignore[name-defined, arg-type]
284126 with importlib_resources .as_file (ui_dir_ref ) as ui_dir :
285- projects_dict = _build_projects_list (store , project_id , root_path , mode )
127+ projects_dict = _build_projects_list (store , project_id , root_path )
286128 with ui_dir .joinpath ("projects-list.json" ).open (mode = "w" ) as f :
287129 f .write (json .dumps (projects_dict ))
288130
@@ -312,19 +154,15 @@ def start_server(
312154 root_path : str = "" ,
313155 tls_key_path : str = "" ,
314156 tls_cert_path : str = "" ,
315- mode : str = "proto" ,
316- rest_api_url : str = "" ,
317157):
318158 app = get_app (
319159 store ,
320160 project_id ,
321161 registry_ttl_sec ,
322162 root_path ,
323- mode = mode ,
324- rest_api_url = rest_api_url ,
325163 )
326164
327- logger .info (f"Starting Feast UI server in ' { mode } ' mode on { host } :{ port } " )
165+ logger .info (f"Starting Feast UI server on { host } :{ port } " )
328166
329167 if tls_key_path and tls_cert_path :
330168 uvicorn .run (
0 commit comments