Skip to content

Commit 2a5251c

Browse files
committed
chg: restful_api - use internal serialization features
1 parent 538013d commit 2a5251c

File tree

1 file changed

+72
-112
lines changed

1 file changed

+72
-112
lines changed

glances/outputs/glances_restful_api.py

Lines changed: 72 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
from urllib.parse import urljoin
1616

1717
try:
18-
from typing import Annotated
18+
from typing import Annotated, Any
1919
except ImportError:
2020
# Only for Python 3.8
2121
# To be removed when Python 3.8 support will be dropped
2222
from typing_extensions import Annotated
2323

2424
from glances import __apiversion__, __version__
25+
from glances.globals import json_dumps
2526
from glances.logger import logger
2627
from glances.password import GlancesPassword
2728
from glances.timer import Timer
@@ -31,7 +32,7 @@
3132
from fastapi import APIRouter, Depends, FastAPI, HTTPException, Request, status
3233
from fastapi.middleware.cors import CORSMiddleware
3334
from fastapi.middleware.gzip import GZipMiddleware
34-
from fastapi.responses import HTMLResponse, ORJSONResponse
35+
from fastapi.responses import HTMLResponse, JSONResponse
3536
from fastapi.security import HTTPBasic, HTTPBasicCredentials
3637
from fastapi.staticfiles import StaticFiles
3738
from fastapi.templating import Jinja2Templates
@@ -50,6 +51,18 @@
5051
import threading
5152
import time
5253

54+
55+
class GlancesJSONResponse(JSONResponse):
56+
"""
57+
Glances impl of fastapi's JSONResponse to use internal JSON Serialization features
58+
59+
Ref: https://fastapi.tiangolo.com/advanced/custom-response/
60+
"""
61+
62+
def render(self, content: Any) -> bytes:
63+
return json_dumps(content)
64+
65+
5366
security = HTTPBasic()
5467

5568

@@ -106,11 +119,11 @@ def __init__(self, config=None, args=None):
106119

107120
# FastAPI Init
108121
if self.args.password:
109-
self._app = FastAPI(dependencies=[Depends(self.authentication)])
122+
self._app = FastAPI(default_response_class=GlancesJSONResponse, dependencies=[Depends(self.authentication)])
110123
self._password = GlancesPassword(username=args.username, config=config)
111124

112125
else:
113-
self._app = FastAPI()
126+
self._app = FastAPI(default_response_class=GlancesJSONResponse)
114127
self._password = None
115128

116129
# Set path for WebUI
@@ -178,102 +191,49 @@ def authentication(self, creds: Annotated[HTTPBasicCredentials, Depends(security
178191
headers={"WWW-Authenticate": "Basic"},
179192
)
180193

181-
def _router(self):
194+
def _router(self) -> APIRouter:
182195
"""Define a custom router for Glances path."""
183-
# Create une main router
196+
base_path = f'/api/{self.API_VERSION}'
197+
plugin_path = f"{base_path}/{{plugin}}"
198+
199+
# Create the main router
184200
router = APIRouter(prefix=self.url_prefix)
185201

186202
# REST API
187-
router.add_api_route(
188-
f'/api/{self.API_VERSION}/status',
189-
status_code=status.HTTP_200_OK,
190-
methods=['HEAD', 'GET'],
191-
response_class=ORJSONResponse,
192-
endpoint=self._api_status,
193-
)
194-
195-
router.add_api_route(
196-
f'/api/{self.API_VERSION}/config', response_class=ORJSONResponse, endpoint=self._api_config
197-
)
198-
router.add_api_route(
199-
f'/api/{self.API_VERSION}/config/{{section}}',
200-
response_class=ORJSONResponse,
201-
endpoint=self._api_config_section,
202-
)
203-
router.add_api_route(
204-
f'/api/{self.API_VERSION}/config/{{section}}/{{item}}',
205-
response_class=ORJSONResponse,
206-
endpoint=self._api_config_section_item,
207-
)
208-
209-
router.add_api_route(f'/api/{self.API_VERSION}/args', response_class=ORJSONResponse, endpoint=self._api_args)
210-
router.add_api_route(
211-
f'/api/{self.API_VERSION}/args/{{item}}', response_class=ORJSONResponse, endpoint=self._api_args_item
212-
)
213-
214-
router.add_api_route(
215-
f'/api/{self.API_VERSION}/pluginslist', response_class=ORJSONResponse, endpoint=self._api_plugins
216-
)
217-
router.add_api_route(f'/api/{self.API_VERSION}/all', response_class=ORJSONResponse, endpoint=self._api_all)
218-
router.add_api_route(
219-
f'/api/{self.API_VERSION}/all/limits', response_class=ORJSONResponse, endpoint=self._api_all_limits
220-
)
221-
router.add_api_route(
222-
f'/api/{self.API_VERSION}/all/views', response_class=ORJSONResponse, endpoint=self._api_all_views
223-
)
224-
225-
router.add_api_route(f'/api/{self.API_VERSION}/help', response_class=ORJSONResponse, endpoint=self._api_help)
226-
router.add_api_route(f'/api/{self.API_VERSION}/{{plugin}}', response_class=ORJSONResponse, endpoint=self._api)
227-
router.add_api_route(
228-
f'/api/{self.API_VERSION}/{{plugin}}/history', response_class=ORJSONResponse, endpoint=self._api_history
229-
)
230-
router.add_api_route(
231-
f'/api/{self.API_VERSION}/{{plugin}}/history/{{nb}}',
232-
response_class=ORJSONResponse,
233-
endpoint=self._api_history,
234-
)
235-
router.add_api_route(
236-
f'/api/{self.API_VERSION}/{{plugin}}/top/{{nb}}', response_class=ORJSONResponse, endpoint=self._api_top
237-
)
238-
router.add_api_route(
239-
f'/api/{self.API_VERSION}/{{plugin}}/limits', response_class=ORJSONResponse, endpoint=self._api_limits
240-
)
241-
router.add_api_route(
242-
f'/api/{self.API_VERSION}/{{plugin}}/views', response_class=ORJSONResponse, endpoint=self._api_views
243-
)
244-
router.add_api_route(
245-
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}', response_class=ORJSONResponse, endpoint=self._api_item
246-
)
247-
router.add_api_route(
248-
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/history',
249-
response_class=ORJSONResponse,
250-
endpoint=self._api_item_history,
251-
)
252-
router.add_api_route(
253-
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/history/{{nb}}',
254-
response_class=ORJSONResponse,
255-
endpoint=self._api_item_history,
256-
)
257-
router.add_api_route(
258-
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/description',
259-
response_class=ORJSONResponse,
260-
endpoint=self._api_item_description,
261-
)
262-
router.add_api_route(
263-
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/unit',
264-
response_class=ORJSONResponse,
265-
endpoint=self._api_item_unit,
266-
)
267-
router.add_api_route(
268-
f'/api/{self.API_VERSION}/{{plugin}}/{{item}}/{{value:path}}',
269-
response_class=ORJSONResponse,
270-
endpoint=self._api_value,
271-
)
203+
router.add_api_route(f'{base_path}/status', self._api_status, methods=['HEAD', 'GET'])
204+
205+
route_mapping = {
206+
f'{base_path}/config': self._api_config,
207+
f'{base_path}/config/{{section}}': self._api_config_section,
208+
f'{base_path}/config/{{section}}/{{item}}': self._api_config_section_item,
209+
f'{base_path}/args': self._api_args,
210+
f'{base_path}/args/{{item}}': self._api_args_item,
211+
f'{base_path}/help': self._api_help,
212+
f'{base_path}/all': self._api_all,
213+
f'{base_path}/all/limits': self._api_all_limits,
214+
f'{base_path}/all/views': self._api_all_views,
215+
f'{base_path}/pluginslist': self._api_plugins,
216+
f'{plugin_path}': self._api,
217+
f'{plugin_path}/history': self._api_history,
218+
f'{plugin_path}/history/{{nb}}': self._api_history,
219+
f'{plugin_path}/top/{{nb}}': self._api_top,
220+
f'{plugin_path}/limits': self._api_limits,
221+
f'{plugin_path}/views': self._api_views,
222+
f'{plugin_path}/{{item}}': self._api_item,
223+
f'{plugin_path}/{{item}}/history': self._api_item_history,
224+
f'{plugin_path}/{{item}}/history/{{nb}}': self._api_item_history,
225+
f'{plugin_path}/{{item}}/description': self._api_item_description,
226+
f'{plugin_path}/{{item}}/unit': self._api_item_unit,
227+
f'{plugin_path}/{{item}}/{{value:path}}': self._api_value,
228+
}
229+
230+
for path, endpoint in route_mapping.items():
231+
router.add_api_route(path, endpoint)
272232

273233
# WEB UI
274234
if not self.args.disable_webui:
275235
# Template for the root index.html file
276-
router.add_api_route('/', response_class=HTMLResponse, endpoint=self._index)
236+
router.add_api_route('/', self._index, response_class=HTMLResponse)
277237

278238
# Statics files
279239
self._app.mount(self.url_prefix + '/static', StaticFiles(directory=self.STATIC_PATH), name="static")
@@ -361,7 +321,7 @@ def _api_status(self):
361321
See related issue: Web server health check endpoint #1988
362322
"""
363323

364-
return ORJSONResponse({'version': __version__})
324+
return GlancesJSONResponse({'version': __version__})
365325

366326
def _api_help(self):
367327
"""Glances API RESTful implementation.
@@ -373,7 +333,7 @@ def _api_help(self):
373333
except Exception as e:
374334
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get help view data ({str(e)})")
375335

376-
return ORJSONResponse(plist)
336+
return GlancesJSONResponse(plist)
377337

378338
def _api_plugins(self):
379339
"""Glances API RESTFul implementation.
@@ -409,7 +369,7 @@ def _api_plugins(self):
409369
except Exception as e:
410370
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin list ({str(e)})")
411371

412-
return ORJSONResponse(plist)
372+
return GlancesJSONResponse(plist)
413373

414374
def _api_all(self):
415375
"""Glances API RESTful implementation.
@@ -436,7 +396,7 @@ def _api_all(self):
436396
except Exception as e:
437397
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get stats ({str(e)})")
438398

439-
return ORJSONResponse(statval)
399+
return GlancesJSONResponse(statval)
440400

441401
def _api_all_limits(self):
442402
"""Glances API RESTful implementation.
@@ -452,7 +412,7 @@ def _api_all_limits(self):
452412
except Exception as e:
453413
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits ({str(e)})")
454414

455-
return ORJSONResponse(limits)
415+
return GlancesJSONResponse(limits)
456416

457417
def _api_all_views(self):
458418
"""Glances API RESTful implementation.
@@ -468,7 +428,7 @@ def _api_all_views(self):
468428
except Exception as e:
469429
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views ({str(e)})")
470430

471-
return ORJSONResponse(limits)
431+
return GlancesJSONResponse(limits)
472432

473433
def _api(self, plugin):
474434
"""Glances API RESTful implementation.
@@ -493,7 +453,7 @@ def _api(self, plugin):
493453
except Exception as e:
494454
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get plugin {plugin} ({str(e)})")
495455

496-
return ORJSONResponse(statval)
456+
return GlancesJSONResponse(statval)
497457

498458
def _api_top(self, plugin, nb: int = 0):
499459
"""Glances API RESTful implementation.
@@ -525,7 +485,7 @@ def _api_top(self, plugin, nb: int = 0):
525485
if isinstance(statval, list):
526486
statval = statval[:nb]
527487

528-
return ORJSONResponse(statval)
488+
return GlancesJSONResponse(statval)
529489

530490
def _api_history(self, plugin, nb: int = 0):
531491
"""Glances API RESTful implementation.
@@ -577,7 +537,7 @@ def _api_limits(self, plugin):
577537
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get limits for plugin {plugin} ({str(e)})"
578538
)
579539

580-
return ORJSONResponse(ret)
540+
return GlancesJSONResponse(ret)
581541

582542
def _api_views(self, plugin):
583543
"""Glances API RESTful implementation.
@@ -601,7 +561,7 @@ def _api_views(self, plugin):
601561
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get views for plugin {plugin} ({str(e)})"
602562
)
603563

604-
return ORJSONResponse(ret)
564+
return GlancesJSONResponse(ret)
605565

606566
def _api_item(self, plugin, item):
607567
"""Glances API RESTful implementation.
@@ -629,7 +589,7 @@ def _api_item(self, plugin, item):
629589
detail=f"Cannot get item {item} in plugin {plugin} ({str(e)})",
630590
)
631591

632-
return ORJSONResponse(ret)
592+
return GlancesJSONResponse(ret)
633593

634594
def _api_item_history(self, plugin, item, nb: int = 0):
635595
"""Glances API RESTful implementation.
@@ -657,7 +617,7 @@ def _api_item_history(self, plugin, item, nb: int = 0):
657617
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get history for plugin {plugin} ({str(e)})"
658618
)
659619
else:
660-
return ORJSONResponse(ret)
620+
return GlancesJSONResponse(ret)
661621

662622
def _api_item_description(self, plugin, item):
663623
"""Glances API RESTful implementation.
@@ -682,7 +642,7 @@ def _api_item_description(self, plugin, item):
682642
detail=f"Cannot get {item} description for plugin {plugin} ({str(e)})",
683643
)
684644
else:
685-
return ORJSONResponse(ret)
645+
return GlancesJSONResponse(ret)
686646

687647
def _api_item_unit(self, plugin, item):
688648
"""Glances API RESTful implementation.
@@ -707,7 +667,7 @@ def _api_item_unit(self, plugin, item):
707667
detail=f"Cannot get {item} unit for plugin {plugin} ({str(e)})",
708668
)
709669
else:
710-
return ORJSONResponse(ret)
670+
return GlancesJSONResponse(ret)
711671

712672
def _api_value(self, plugin, item, value):
713673
"""Glances API RESTful implementation.
@@ -735,7 +695,7 @@ def _api_value(self, plugin, item, value):
735695
detail=f"Cannot get {item} = {value} for plugin {plugin} ({str(e)})",
736696
)
737697
else:
738-
return ORJSONResponse(ret)
698+
return GlancesJSONResponse(ret)
739699

740700
def _api_config(self):
741701
"""Glances API RESTful implementation.
@@ -750,7 +710,7 @@ def _api_config(self):
750710
except Exception as e:
751711
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config ({str(e)})")
752712
else:
753-
return ORJSONResponse(args_json)
713+
return GlancesJSONResponse(args_json)
754714

755715
def _api_config_section(self, section):
756716
"""Glances API RESTful implementation.
@@ -772,7 +732,7 @@ def _api_config_section(self, section):
772732
status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get config section {section} ({str(e)})"
773733
)
774734

775-
return ORJSONResponse(ret_section)
735+
return GlancesJSONResponse(ret_section)
776736

777737
def _api_config_section_item(self, section, item):
778738
"""Glances API RESTful implementation.
@@ -803,7 +763,7 @@ def _api_config_section_item(self, section, item):
803763
detail=f"Cannot get item {item} in config section {section} ({str(e)})",
804764
)
805765

806-
return ORJSONResponse(ret_item)
766+
return GlancesJSONResponse(ret_item)
807767

808768
def _api_args(self):
809769
"""Glances API RESTful implementation.
@@ -820,7 +780,7 @@ def _api_args(self):
820780
except Exception as e:
821781
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args ({str(e)})")
822782

823-
return ORJSONResponse(args_json)
783+
return GlancesJSONResponse(args_json)
824784

825785
def _api_args_item(self, item):
826786
"""Glances API RESTful implementation.
@@ -841,4 +801,4 @@ def _api_args_item(self, item):
841801
except Exception as e:
842802
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Cannot get args item ({str(e)})")
843803

844-
return ORJSONResponse(args_json)
804+
return GlancesJSONResponse(args_json)

0 commit comments

Comments
 (0)