Skip to content

Commit 7049ca0

Browse files
authored
fix spec viewing in SwaggerUI. (#31)
* fix spec viewing in SwaggerUI.
1 parent b68643d commit 7049ca0

File tree

10 files changed

+103
-38
lines changed

10 files changed

+103
-38
lines changed

.github/workflows/testing.yaml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Flask-First testing
2+
run-name: ${{ github.actor }} is testing Flask-First
3+
on: [push]
4+
jobs:
5+
linters:
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v4
9+
- uses: actions/setup-python@v5
10+
with:
11+
python-version: '3.12'
12+
- run: make format
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
- uses: actions/setup-python@v5
18+
with:
19+
python-version: '3.12'
20+
- run: make test
21+
tox:
22+
runs-on: ubuntu-latest
23+
steps:
24+
- uses: actions/checkout@v4
25+
- uses: actions/setup-python@v5
26+
with:
27+
python-version: |
28+
3.8
29+
3.9
30+
3.10
31+
3.11
32+
3.12
33+
- run: make tox
34+
build:
35+
runs-on: ubuntu-latest
36+
steps:
37+
- uses: actions/checkout@v4
38+
- uses: actions/setup-python@v5
39+
with:
40+
python-version: '3.12'
41+
- run: make build

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 0.16.1
2+
3+
* Fix the display of the specification described in several files in Swagger UI.
4+
15
## Version 0.16.0
26

37
* Add support for splitting OpenAPI specification into multiple files.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ venv:
88
./venv/bin/pip3 -q install -e ".[dev]"
99
./venv/bin/pip3 -q install -e .
1010

11-
format:
11+
format: venv
1212
# Run checking and formatting sources.
1313
./venv/bin/pre-commit run -a
1414

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ dependencies = [
1818
'openapi-spec-validator>=0.5.0',
1919
'marshmallow>=3.14.1'
2020
]
21-
description = "Flask extension for using 'specification first' principle via OpenAPI specification"
21+
description = "Flask extension for using 'specification first' or 'API-first' principle via OpenAPI specification."
2222
license = {file = "LICENSE"}
2323
name = "Flask-First"
2424
readme = "README.md"
2525
requires-python = ">=3.9"
26-
version = "0.16.0"
26+
version = "0.16.1"
2727

2828
[project.optional-dependencies]
2929
dev = [

src/flask_first/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ def init_app(self, app: Flask) -> None:
255255
)
256256

257257
if self.swagger_ui_path:
258-
add_swagger_ui_blueprint(self.app, self.path_to_spec, self.swagger_ui_path)
258+
add_swagger_ui_blueprint(self.app, self.spec, self.swagger_ui_path)
259259

260260
self._register_request_validation()
261261

src/flask_first/first/specification.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ class Resolver:
2323
Specification from several files are supported.
2424
"""
2525

26-
def __init__(self, abs_path: Path):
27-
self.abs_path = abs_path
26+
def __init__(self, abs_path: Path or str):
27+
self.abs_path = Path(abs_path)
2828
self.root_dir = self.abs_path.resolve().parent
2929

3030
@staticmethod
@@ -53,7 +53,11 @@ def _get_schema_from_file_ref(self, root_dir: Path, relative_path: Path, keys: d
5353
def _resolving(self, schema: dict, relative_path_to_file_schema: str) -> dict or list[dict]:
5454
if isinstance(schema, dict):
5555
if '$ref' in schema:
56-
relative_file_path_from_ref, local_path = schema['$ref'].split('#/')
56+
try:
57+
relative_file_path_from_ref, local_path = schema['$ref'].split('#/')
58+
except AttributeError:
59+
raise FirstOpenAPIResolverError(f'"$ref" <{schema["$ref"]}> must be string.')
60+
5761
local_path_parts = local_path.split('/')
5862

5963
if relative_file_path_from_ref:

src/flask_first/swagger_ui.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from flask import Blueprint
44
from flask import Flask
55
from flask import render_template
6-
from flask import send_file
76
from flask import url_for
87

8+
from .first.specification import Specification
99

10-
def add_swagger_ui_blueprint(app: Flask, path_to_spec: str, swagger_ui_path: str or Path) -> None:
10+
11+
def add_swagger_ui_blueprint(app: Flask, spec: Specification, swagger_ui_path: str or Path) -> None:
1112
swagger_ui = Blueprint(
1213
'swagger_ui',
1314
__name__,
@@ -22,10 +23,10 @@ def swagger_ui_static(filename):
2223

2324
@swagger_ui.route('/')
2425
def swagger_ui_page():
25-
return render_template('swagger_ui/index.html', path_to_spec=path_to_spec)
26+
return render_template('swagger_ui/index.html')
2627

27-
@swagger_ui.route('/openapi.yaml')
28+
@swagger_ui.route('/openapi.json')
2829
def get_file_spec():
29-
return send_file(path_to_spec)
30+
return spec.raw_spec
3031

3132
app.register_blueprint(swagger_ui)

tests/conftest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ def fx_client(fx_app) -> FlaskClient:
4545
def fx_create_app():
4646
def _create_app(path_to_spec: str, routes_functions: Iterable):
4747
app = Flask('testing_app')
48-
app.debug = 1
48+
app.debug = True
4949
app.config['FIRST_RESPONSE_VALIDATION'] = True
5050

51-
first = First(path_to_spec, app)
51+
first = First(path_to_spec, app, swagger_ui_path='/docs')
5252
for func in routes_functions:
5353
first.add_view_func(func)
5454

tests/test_flask_first.py

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,44 @@
1818
}
1919

2020

21+
def test_specification__factory_app():
22+
def mini_endpoint() -> dict:
23+
return {'message': 'test_factory_app'}
24+
25+
first = First(Path(BASEDIR, 'specs/v3.1.0/mini.openapi.yaml'))
26+
27+
def create_app():
28+
flask_app = Flask('factory_app')
29+
first.init_app(flask_app)
30+
first.add_view_func(mini_endpoint)
31+
return flask_app
32+
33+
app = create_app()
34+
35+
with app.test_client() as test_client:
36+
r = test_client.get('/mini_endpoint')
37+
assert r.status_code == 200
38+
assert r.json['message'] == 'test_factory_app'
39+
40+
41+
def test_flask_first__swagger_ui(fx_create_app):
42+
def mini_endpoint() -> dict:
43+
return {'message': 'test_flask_first__swagger_ui'}
44+
45+
test_client = fx_create_app(Path(BASEDIR, 'specs/v3.1.0/mini.openapi.yaml'), [mini_endpoint])
46+
47+
r = test_client.get('/docs/openapi.json')
48+
assert r.status_code == 200
49+
assert r.text
50+
51+
r = test_client.get('/docs', follow_redirects=True)
52+
assert r.status_code == 200
53+
assert '<title>Swagger UI</title>' in r.text
54+
55+
56+
# Old test
57+
58+
2159
def test_specification__create_item(fx_app, fx_client):
2260
def create_item() -> tuple:
2361
obj = {**request.json, 'uuid': ITEM['uuid']}
@@ -218,29 +256,6 @@ def endpoint_with_header() -> dict:
218256
assert r.json['message'] == 'test_header'
219257

220258

221-
def test_specification__factory_app():
222-
def mini_endpoint() -> dict:
223-
return {'message': 'test_factory_app'}
224-
225-
first = First(Path(BASEDIR, 'specs/v3.1.0/mini.openapi.yaml'))
226-
227-
def create_app():
228-
app = Flask('factory_app')
229-
app.debug = 1
230-
app.testing = 1
231-
app.config['FIRST_RESPONSE_VALIDATION'] = True
232-
first.init_app(app)
233-
first.add_view_func(mini_endpoint)
234-
return app
235-
236-
app = create_app()
237-
238-
with app.test_client() as test_client:
239-
r = test_client.get('/mini_endpoint')
240-
assert r.status_code == 200
241-
assert r.json['message'] == 'test_factory_app'
242-
243-
244259
def test_specification__registration_function():
245260
def mini_endpoint() -> dict:
246261
return {'message': 'test_factory_app'}

tests/test_specification.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def test_specification__check_v30_specs(spec):
4949

5050
r = app.test_client().get('/docs', follow_redirects=True)
5151
assert r.status_code == 200
52-
assert '/docs/openapi.yaml' in r.data.decode()
52+
assert '/docs/openapi.json' in r.data.decode()
5353

5454

5555
def test_specification__nullable_parameter():

0 commit comments

Comments
 (0)