Skip to content

Commit 2caf705

Browse files
committed
feat: separate a function for adding external Swagger to an openapi.py
Signed-off-by: ImMin5 <mino@megazone.com>
1 parent 6622ad3 commit 2caf705

File tree

2 files changed

+125
-102
lines changed

2 files changed

+125
-102
lines changed
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import logging
2+
import json
3+
import os
4+
5+
from fastapi import FastAPI
6+
from fastapi.openapi.docs import get_swagger_ui_html
7+
from fastapi.responses import HTMLResponse
8+
9+
from spaceone.core import config
10+
11+
_LOGGER = logging.getLogger(__name__)
12+
13+
14+
def _add_external_api_route(
15+
app: FastAPI,
16+
prefix: str,
17+
service_name: str,
18+
external_swagger_path: str
19+
) -> None:
20+
async def get_openapi():
21+
openapi_json_file = os.path.join(external_swagger_path, f"{service_name}_openapi.json")
22+
with open(openapi_json_file, 'r') as f:
23+
custom_openapi_schema = json.loads(f.read())
24+
return custom_openapi_schema
25+
26+
get_openapi.__name__ = get_openapi.__name__ + prefix
27+
app.add_api_route(prefix + "/openapi.json", get_openapi, include_in_schema=False)
28+
29+
async def swagger_ui_html() -> HTMLResponse:
30+
return get_swagger_ui_html(
31+
openapi_url=prefix + "/openapi.json",
32+
title=f'{service_name.title().replace("-", " ")} API' + ' - Swagger UI',
33+
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
34+
init_oauth=app.swagger_ui_init_oauth,
35+
)
36+
37+
swagger_ui_html.__name__ = swagger_ui_html.__name__ + prefix
38+
app.add_api_route(prefix + "/docs", swagger_ui_html, include_in_schema=False)
39+
40+
41+
def _services_from_openapi_json_files(openapi_json_path):
42+
"""
43+
openapi json file must follow {service}_openapi.json file name format.
44+
if '-' exist in {service} converted to '_'.
45+
"""
46+
services = []
47+
openapi_json_files = os.listdir(openapi_json_path)
48+
for openapi_json_file in openapi_json_files:
49+
services.append('_'.join(openapi_json_file.split('_')[:-1]).lower())
50+
return services
51+
52+
53+
def _sort_services(services):
54+
external_swagger_priority_services = config.get_global('EXTERNAL_SWAGGER_PRIORITY_SERVICES', [])
55+
external_swagger_priority_services = [external_service.replace('-', '_').lower() for external_service in
56+
external_swagger_priority_services]
57+
58+
sorted_services = list(dict.fromkeys(external_swagger_priority_services + sorted(services)))
59+
return sorted_services
60+
61+
62+
def _create_openapi_json(app, service_name, external_swagger_path, service_description):
63+
openapi_json_file = os.path.join(external_swagger_path, f"{service_name}_openapi.json")
64+
service_name_with_dash = service_name.replace('_', '-')
65+
try:
66+
with open(openapi_json_file, 'r') as f:
67+
custom_openapi_schema = json.loads(f.read())
68+
custom_openapi_schema['openapi'] = app.openapi().get('openapi')
69+
description = custom_openapi_schema['info']['summary']
70+
service_description[service_name] = f"| **{service_name.replace('_', ' ').title()}** | {description} | [/{service_name_with_dash}/docs](/{service_name_with_dash}/docs) |\n"
71+
72+
with open(openapi_json_file, 'w') as f:
73+
json.dump(custom_openapi_schema, f, indent=2)
74+
except Exception as e:
75+
_LOGGER.error(f'[_create_openapi_json] {openapi_json_file} : {e}', exc_info=True)
76+
77+
78+
def _check_external_swagger_path(external_swagger_path):
79+
if not external_swagger_path:
80+
_LOGGER.info('[_check_external_swagger_path] EXTERNAL_SWAGGER_PATH is not set')
81+
return False
82+
83+
if not os.path.exists(external_swagger_path):
84+
_LOGGER.error(f'[_check_external_swagger_path] "{external_swagger_path}" : Not Found')
85+
return False
86+
87+
return True
88+
89+
90+
def _create_external_apis_description(app, service_description):
91+
app.openapi()['info']['description'] += "\n<br><br>\n"
92+
app.openapi()['info']['description'] += "\n## List of External APIs\n"
93+
app.openapi()['info']['description'] += "\n[Home](/docs)\n"
94+
app.openapi()['info']['description'] += "| **Name** | **Description** | **URL** |\n"
95+
app.openapi()['info']['description'] += "|:---|:--- |:---|\n"
96+
app.openapi()['info']['description'] += ''.join(service_description.values())
97+
98+
99+
def add_external_swagger(app):
100+
try:
101+
external_swagger_path = config.get_global('EXTERNAL_SWAGGER_PATH')
102+
103+
if not _check_external_swagger_path(external_swagger_path):
104+
return app
105+
106+
services = _services_from_openapi_json_files(external_swagger_path)
107+
sorted_services = _sort_services(services)
108+
109+
service_description = {}
110+
for service in sorted_services:
111+
_create_openapi_json(app=app, service_name=service, external_swagger_path=external_swagger_path,
112+
service_description=service_description)
113+
_add_external_api_route(app=app, prefix=f"/{service.replace('_', '-')}", service_name=service,
114+
external_swagger_path=external_swagger_path)
115+
116+
if len(services) > 0:
117+
_create_external_apis_description(app, service_description)
118+
119+
except Exception as e:
120+
_LOGGER.error(f'[add_external_swagger] {e}', exc_info=True)
121+
finally:
122+
return app

src/spaceone/core/fastapi/server.py

Lines changed: 3 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
import logging
22
import uvicorn
3-
import json
4-
import os
53

6-
from fastapi import FastAPI, Request
4+
from fastapi import FastAPI
75
from fastapi.middleware.cors import CORSMiddleware
8-
from fastapi.openapi.docs import get_swagger_ui_html
9-
from fastapi.responses import HTMLResponse
106

117
from spaceone.core import config
128
from spaceone.core.logger import set_logger
139
from spaceone.core.opentelemetry import set_tracer, set_metric
1410
from spaceone.core.extension.server_info import ServerInfoManager
11+
from spaceone.core.fastapi.openapi import add_external_swagger
1512

1613
_LOGGER = logging.getLogger(__name__)
1714

@@ -134,107 +131,11 @@ def _init_fast_api():
134131
)
135132

136133

137-
def _get_all_services_from_openapi_json_files(openapi_json_path):
138-
services = []
139-
openapi_json_files = os.listdir(openapi_json_path)
140-
for openapi_json_file in openapi_json_files:
141-
services.append('_'.join(openapi_json_file.split('_')[:-1]).lower())
142-
return services
143-
144-
145-
def _sort_services(services):
146-
return sorted(services, key=lambda x: ('identity' not in x, 'inventory' not in x, 'cost-analysis' not in x,
147-
'monitoring' not in x, 'notification' not in x, 'repository' not in x, x))
148-
149-
150-
def _create_openapi_json(app, service_name):
151-
swagger_path = config.get_global('EXTENSION_SWAGGER_PATH')
152-
swagger_path = os.path.join(swagger_path, f"{service_name.replace('-', '_')}_openapi.json")
153-
try:
154-
with open(swagger_path, 'r') as f:
155-
custom_openapi_schema = json.loads(f.read())
156-
custom_openapi_schema['openapi'] = app.openapi().get('openapi')
157-
description = custom_openapi_schema['info']['summary']
158-
app.openapi()['info']['description'] += f"| **{service_name.replace('-', ' ').title()}** | {description} | [/{service_name}/docs](/{service_name}/docs) |\n"
159-
160-
with open(swagger_path, 'w') as f:
161-
json.dump(custom_openapi_schema, f, indent=2)
162-
except Exception as e:
163-
_LOGGER.error(f'[_create_openapi_json] {swagger_path} : {e}', exc_info=True)
164-
165-
166-
def _override_openapi(app):
167-
extension_swagger_path = config.get_global('EXTENSION_SWAGGER_PATH')
168-
if not os.path.exists(extension_swagger_path):
169-
_LOGGER.info(f'[_override_openapi] Extension Swagger Path is not exists. (path = {extension_swagger_path})')
170-
return app
171-
172-
services = _get_all_services_from_openapi_json_files(extension_swagger_path)
173-
services = _sort_services(services)
174-
_openapi_info = app.openapi().get('info')
175-
_openapi_version = app.openapi().get('openapi')
176-
177-
app.openapi()['info']['description'] += "\n<br><br>\n"
178-
app.openapi()['info']['description'] += "\n## List of Services\n"
179-
app.openapi()['info']['description'] += "\n[Home](/docs)\n"
180-
app.openapi()['info']['description'] += "| **Service** | **Description** | **URL** |\n"
181-
app.openapi()['info']['description'] += "|:---|:--- |:---|\n"
182-
183-
for service in services:
184-
service = service.replace('_', '-')
185-
_create_openapi_json(app, service_name=service)
186-
build_docs(app, prefix=f"/{service}", service_name=service)
187-
188-
app.openapi()['info'][
189-
'description'] += "| **Console API** | Service that offers features exclusive to the Console API. | [/docs](/docs#console-api%20%3E%20api) |\n"
190-
191-
return app
192-
193-
194-
def build_docs(
195-
app: FastAPI,
196-
prefix: str,
197-
service_name: str
198-
) -> None:
199-
async def get_openapi():
200-
swagger_path = config.get_global('EXTENSION_SWAGGER_PATH')
201-
swagger_path = os.path.join(swagger_path, f"{service_name.replace('-','_')}_openapi.json")
202-
with open(swagger_path, 'r') as f:
203-
custom_openapi_schema = json.loads(f.read())
204-
return custom_openapi_schema
205-
206-
get_openapi.__name__ = get_openapi.__name__ + prefix
207-
app.add_api_route(prefix + "/openapi.json", get_openapi, include_in_schema=False)
208-
209-
async def swagger_ui_html() -> HTMLResponse:
210-
return get_swagger_ui_html(
211-
openapi_url=prefix + "/openapi.json",
212-
title=f'{service_name.title().replace("-"," ")} API' + ' - Swagger UI',
213-
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
214-
init_oauth=app.swagger_ui_init_oauth,
215-
)
216-
217-
swagger_ui_html.__name__ = swagger_ui_html.__name__ + prefix
218-
app.add_api_route(prefix + "/docs", swagger_ui_html, include_in_schema=False)
219-
220-
221-
def _get_all_services_list(app):
222-
services = []
223-
for route in app.routes:
224-
path = route.path.split('/')
225-
if len(path) == 4:
226-
services.append(path[1].replace('-', '_'))
227-
228-
services = list(set(services))
229-
sorted_services = sorted(services, key=lambda x: ('identity' not in x, 'inventory' not in x, 'cost_analysis' not in x, x))
230-
return sorted_services
231-
232-
233134
def fast_api_app():
234135
app = _init_fast_api()
235136
app = _add_middlewares(app)
236137
app = _include_routers(app)
237-
app = _override_openapi(app)
138+
app = add_external_swagger(app)
238139
return app
239140

240141

0 commit comments

Comments
 (0)