Skip to content

Commit 46603ee

Browse files
authored
Merge pull request #30 from modern-python/fix-swagger
refactor swagger instrument
2 parents fb922c1 + d9aca2b commit 46603ee

19 files changed

+33
-45
lines changed

lite_bootstrap/bootstrappers/fastapi_bootstrapper.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from lite_bootstrap import import_checker
66
from lite_bootstrap.bootstrappers.base import BaseBootstrapper
7-
from lite_bootstrap.fastapi_offline_docs.main import enable_offline_docs
7+
from lite_bootstrap.helpers.fastapi_helpers import enable_offline_docs
88
from lite_bootstrap.instruments.cors_instrument import CorsConfig, CorsInstrument
99
from lite_bootstrap.instruments.healthchecks_instrument import (
1010
HealthChecksConfig,
@@ -139,10 +139,9 @@ class FastApiSwaggerInstrument(SwaggerInstrument):
139139
bootstrap_config: FastAPIConfig
140140

141141
def bootstrap(self) -> None:
142-
self.bootstrap_config.application.docs_url = self.bootstrap_config.swagger_path
143142
if self.bootstrap_config.swagger_offline_docs:
144143
enable_offline_docs(
145-
self.bootstrap_config.application, static_files_handler=self.bootstrap_config.service_static_path
144+
self.bootstrap_config.application, static_path=self.bootstrap_config.swagger_static_path
146145
)
147146

148147

lite_bootstrap/bootstrappers/litestar_bootstrapper.py

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import dataclasses
2-
import pathlib
32
import typing
43

54
from lite_bootstrap import import_checker
65
from lite_bootstrap.bootstrappers.base import BaseBootstrapper
6+
from lite_bootstrap.helpers.path import is_valid_path
77
from lite_bootstrap.instruments.cors_instrument import CorsConfig, CorsInstrument
88
from lite_bootstrap.instruments.healthchecks_instrument import (
99
HealthChecksConfig,
@@ -49,6 +49,7 @@ class LitestarConfig(
4949
application_config: "AppConfig" = dataclasses.field(default_factory=lambda: AppConfig())
5050
opentelemetry_excluded_urls: list[str] = dataclasses.field(default_factory=list)
5151
prometheus_additional_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
52+
swagger_path: str = "/docs"
5253

5354

5455
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
@@ -145,15 +146,19 @@ class LitestarPrometheusController(PrometheusController):
145146
@dataclasses.dataclass(kw_only=True, frozen=True)
146147
class LitestarSwaggerInstrument(SwaggerInstrument):
147148
bootstrap_config: LitestarConfig
149+
not_ready_message = "swagger_path is empty or not valid"
150+
151+
def is_ready(self) -> bool:
152+
return bool(self.bootstrap_config.swagger_path) and is_valid_path(self.bootstrap_config.swagger_path)
148153

149154
def bootstrap(self) -> None:
150155
render_plugins: typing.Final = (
151156
(
152157
SwaggerRenderPlugin(
153-
js_url=f"{self.bootstrap_config.service_static_path}/swagger-ui-bundle.js",
154-
css_url=f"{self.bootstrap_config.service_static_path}/swagger-ui.css",
158+
js_url=f"{self.bootstrap_config.swagger_static_path}/swagger-ui-bundle.js",
159+
css_url=f"{self.bootstrap_config.swagger_static_path}/swagger-ui.css",
155160
standalone_preset_js_url=(
156-
f"{self.bootstrap_config.service_static_path}/swagger-ui-standalone-preset.js"
161+
f"{self.bootstrap_config.swagger_static_path}/swagger-ui-standalone-preset.js"
157162
),
158163
),
159164
)
@@ -169,10 +174,9 @@ def bootstrap(self) -> None:
169174
**self.bootstrap_config.swagger_extra_params,
170175
)
171176
if self.bootstrap_config.swagger_offline_docs:
172-
static_dir_path = pathlib.Path(__file__).parent.parent / "litestar_swagger_static"
173177
self.bootstrap_config.application_config.route_handlers.append(
174178
create_static_files_router(
175-
path=self.bootstrap_config.service_static_path, directories=[static_dir_path]
179+
path=self.bootstrap_config.swagger_static_path, directories=["lite_bootstrap/static/litestar_docs"]
176180
)
177181
)
178182

Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
import pathlib
31
import typing
42

53
from lite_bootstrap import import_checker
@@ -15,9 +13,7 @@
1513

1614
def enable_offline_docs(
1715
app: "FastAPI",
18-
static_files_handler: str = "/static",
19-
static_dir_path: os.PathLike[str] = pathlib.Path(__file__).parent / "static",
20-
include_docs_in_schema: bool = False,
16+
static_path: str,
2117
) -> None:
2218
if not (app_openapi_url := app.openapi_url):
2319
msg = "No app.openapi_url specified"
@@ -33,29 +29,27 @@ def enable_offline_docs(
3329
if typing.cast(Route, route).path not in (docs_url, redoc_url, swagger_ui_oauth2_redirect_url)
3430
]
3531

36-
app.mount(static_files_handler, StaticFiles(directory=static_dir_path), name=static_files_handler)
32+
app.mount(static_path, StaticFiles(directory="lite_bootstrap/static/fastapi_docs"), name="static")
3733

38-
@app.get(docs_url, include_in_schema=include_docs_in_schema)
34+
@app.get(docs_url, include_in_schema=False)
3935
async def custom_swagger_ui_html(request: Request) -> HTMLResponse:
40-
root_path = typing.cast(str, request.scope.get("root_path", "").rstrip("/"))
41-
swagger_js_url = f"{root_path}{static_files_handler}/swagger-ui-bundle.js"
42-
swagger_css_url = f"{root_path}{static_files_handler}/swagger-ui.css"
36+
root_path = request.scope.get("root_path", "").rstrip("/")
4337
return get_swagger_ui_html(
44-
openapi_url=root_path + app_openapi_url,
38+
openapi_url=f"{root_path}{app_openapi_url}",
4539
title=f"{app.title} - Swagger UI",
4640
oauth2_redirect_url=app.swagger_ui_oauth2_redirect_url,
47-
swagger_js_url=swagger_js_url,
48-
swagger_css_url=swagger_css_url,
41+
swagger_js_url=f"{root_path}{static_path}/swagger-ui-bundle.js",
42+
swagger_css_url=f"{root_path}{static_path}/swagger-ui.css",
4943
)
5044

51-
@app.get(swagger_ui_oauth2_redirect_url, include_in_schema=include_docs_in_schema)
45+
@app.get(swagger_ui_oauth2_redirect_url, include_in_schema=False)
5246
async def swagger_ui_redirect() -> HTMLResponse:
5347
return get_swagger_ui_oauth2_redirect_html()
5448

55-
@app.get(redoc_url, include_in_schema=include_docs_in_schema)
49+
@app.get(redoc_url, include_in_schema=False)
5650
async def redoc_html() -> HTMLResponse:
5751
return get_redoc_html(
5852
openapi_url=app_openapi_url,
5953
title=f"{app.title} - ReDoc",
60-
redoc_js_url=f"{static_files_handler}/redoc.standalone.js",
54+
redoc_js_url=f"{static_path}/redoc.standalone.js",
6155
)
File renamed without changes.

lite_bootstrap/instruments/base.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@ class BaseConfig:
1414
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
1515
class BaseInstrument(abc.ABC):
1616
bootstrap_config: BaseConfig
17+
not_ready_message = ""
1718
missing_dependency_message = ""
1819

19-
@property
20-
@abc.abstractmethod
21-
def not_ready_message(self) -> str: ...
22-
2320
def bootstrap(self) -> None: ... # noqa: B027
2421

2522
def teardown(self) -> None: ... # noqa: B027
2623

27-
@abc.abstractmethod
28-
def is_ready(self) -> bool: ...
24+
def is_ready(self) -> bool:
25+
return True
2926

3027
@staticmethod
3128
def check_dependencies() -> bool:

lite_bootstrap/instruments/prometheus_instrument.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import dataclasses
22

3-
from lite_bootstrap.helpers import is_valid_path
3+
from lite_bootstrap.helpers.path import is_valid_path
44
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
55

66

Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
import dataclasses
22
import typing
33

4-
from lite_bootstrap.helpers import is_valid_path
54
from lite_bootstrap.instruments.base import BaseConfig, BaseInstrument
65

76

87
@dataclasses.dataclass(kw_only=True, frozen=True)
98
class SwaggerConfig(BaseConfig):
10-
service_static_path: str = "/static"
11-
swagger_path: str = "/docs"
9+
swagger_static_path: str = "/static"
1210
swagger_offline_docs: bool = False
1311
swagger_extra_params: dict[str, typing.Any] = dataclasses.field(default_factory=dict)
1412

1513

1614
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
1715
class SwaggerInstrument(BaseInstrument):
1816
bootstrap_config: SwaggerConfig
19-
not_ready_message = "swagger_path is empty or not valid"
20-
21-
def is_ready(self) -> bool:
22-
return bool(self.bootstrap_config.swagger_path) and is_valid_path(self.bootstrap_config.swagger_path)

tests/test_fastapi_bootstrap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def test_fastapi_bootstrap(fastapi_config: FastAPIConfig) -> None:
4545
assert response.status_code == status.HTTP_200_OK
4646
assert response.text
4747

48-
response = test_client.get(fastapi_config.swagger_path)
48+
response = test_client.get(str(application.docs_url))
4949
assert response.status_code == status.HTTP_200_OK
5050
assert response.text
5151

tests/test_fastapi_offline_docs.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi import FastAPI
55
from fastapi.testclient import TestClient
66

7-
from lite_bootstrap.fastapi_offline_docs.main import enable_offline_docs
7+
from lite_bootstrap.helpers.fastapi_helpers import enable_offline_docs
88

99

1010
def test_fastapi_offline_docs() -> None:
@@ -13,7 +13,7 @@ def test_fastapi_offline_docs() -> None:
1313
static_files_handler = "/static2"
1414

1515
app = FastAPI(title="Tests", docs_url=docs_url, redoc_url=redoc_url)
16-
enable_offline_docs(app, static_files_handler=static_files_handler)
16+
enable_offline_docs(app, static_path=static_files_handler)
1717

1818
with TestClient(app) as client:
1919
resp = client.get(docs_url)
@@ -31,7 +31,7 @@ def test_fastapi_offline_docs() -> None:
3131

3232
def test_fastapi_offline_docs_root_path() -> None:
3333
app: FastAPI = FastAPI(title="Tests", root_path="/some-root-path", docs_url="/custom_docs")
34-
enable_offline_docs(app)
34+
enable_offline_docs(app, static_path="/static")
3535

3636
with TestClient(app, root_path="/some-root-path") as client:
3737
response = client.get("/custom_docs")
@@ -47,4 +47,4 @@ def test_fastapi_offline_docs_raises_without_openapi_url() -> None:
4747
app = FastAPI(openapi_url=None)
4848

4949
with pytest.raises(RuntimeError, match="No app.openapi_url specified"):
50-
enable_offline_docs(app)
50+
enable_offline_docs(app, static_path="/static")

tests/test_litestar_bootstrap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def test_litestar_bootstrap(litestar_config: LitestarConfig) -> None:
5353

5454
response = test_client.get(litestar_config.swagger_path)
5555
assert response.status_code == status_codes.HTTP_200_OK
56-
response = test_client.get(f"{litestar_config.service_static_path}/swagger-ui.css")
56+
response = test_client.get(f"{litestar_config.swagger_static_path}/swagger-ui.css")
5757
assert response.status_code == status_codes.HTTP_200_OK
5858

5959
assert not bootstrapper.is_bootstrapped

0 commit comments

Comments
 (0)