Skip to content

Commit cc232cf

Browse files
authored
add support for splitting OpenAPI specification into multiple files. (#30)
* add support for splitting OpenAPI specification into multiple files.
1 parent a6f20f6 commit cc232cf

19 files changed

+373
-120
lines changed

.pre-commit-config.yaml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
repos:
22
- repo: https://github.com/asottile/pyupgrade
3-
rev: v3.15.0
3+
rev: v3.15.1
44
hooks:
55
- id: pyupgrade
66
args: [--py39-plus]
@@ -15,7 +15,7 @@ repos:
1515
- id: check-builtin-literals
1616
- id: check-toml
1717
- repo: https://github.com/psf/black
18-
rev: 23.10.0
18+
rev: 24.2.0
1919
hooks:
2020
- id: black
2121
- repo: https://github.com/asottile/reorder_python_imports
@@ -30,7 +30,7 @@ repos:
3030
- id: text-unicode-replacement-char
3131
exclude: .js$
3232
- repo: https://github.com/pycqa/flake8
33-
rev: 6.1.0
33+
rev: 7.0.0
3434
hooks:
3535
- id: flake8
3636
additional_dependencies:
@@ -42,9 +42,19 @@ repos:
4242
hooks:
4343
- id: checkmake
4444
- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
45-
rev: v2.11.0
45+
rev: v2.12.0
4646
hooks:
4747
- id: pretty-format-yaml
4848
args: [--autofix, --indent, '2']
4949
- id: pretty-format-toml
5050
args: [--autofix, --indent, '2']
51+
- repo: https://codeberg.org/frnmst/md-toc
52+
rev: 8.2.3
53+
hooks:
54+
- id: md-toc
55+
args: [-p, github]
56+
- repo: https://github.com/asottile/pyupgrade
57+
rev: v3.15.1
58+
hooks:
59+
- id: pyupgrade
60+
args: [--py39-plus]

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.11
1+
3.12

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 0.16.0
2+
3+
* Add support for splitting OpenAPI specification into multiple files.
4+
15
## Version 0.15.0
26

37
* Add specification experimental validator for OpenAPI 3.1.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
venv:
55
# Create virtual environment.
6-
python3.11 -m venv venv
6+
python3.12 -m venv venv
77
./venv/bin/pip3 -q install --upgrade pip setuptools wheel
88
./venv/bin/pip3 -q install -e ".[dev]"
99
./venv/bin/pip3 -q install -e .

README.md

Lines changed: 123 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,76 @@
11
# Flask-First
22

3-
Flask extension for using "specification first" principle.
3+
Flask extension for using "specification first" and "API-first" principles.
44

5-
Features:
5+
<!--TOC-->
6+
7+
- [Flask-First](#flask-first)
8+
- [Features](#features)
9+
- [Limitations](#limitations)
10+
- [Installation](#installation)
11+
- [Settings](#settings)
12+
- [Data types](#data-types)
13+
- [Examples](#examples)
14+
- [Simple example](#simple-example)
15+
- [Specification from multiple file](#specification-from-multiple-file)
16+
- [CORS support](#cors-support)
17+
- [Additional documentation](#additional-documentation)
18+
19+
<!--TOC-->
20+
21+
## Features
622

723
* `Application Factory` supported.
8-
* Validating and serializing arguments from `request.headers` to `request.first_headers`.
9-
* Validating and serializing arguments from `request.view_args` to `request.first_view_args`.
10-
* Validating and serializing arguments from `request.args` to `request.first_args`.
11-
* Validating and serializing arguments from `request.cookies` to `request.first_cookies`.
12-
* Validating and serializing arguments from `request.json` to `request.first_json`.
13-
* Validating headers from request.
14-
* Validating cookies from request.
15-
* Validating path parameters from request.
16-
* Validating parameters from request.
17-
* Validating JSON from request.
18-
* Validating JSON from response.
24+
* Validating and serializing headers of request from `request.headers` to `request.first_headers`.
25+
* Validating and serializing path parameters of request from `request.view_args`
26+
to `request.first_view_args`.
27+
* Validating and serializing arguments of request from `request.args` to `request.first_args`.
28+
* Validating and serializing cookies of request from `request.cookies` to `request.first_cookies`.
29+
* Validating and serializing JSON of request from `request.json` to `request.first_json`.
30+
* Validating JSON from response for debugging.
1931
* Provides a Swagger UI.
32+
* Support OpenAPI version 3.1.0.
33+
* Support specification from multiple file.
2034

21-
Limitations
22-
----
35+
### Limitations
2336

2437
Will be added in future releases.
2538

26-
* Full specification in one file.
2739
* Authorization not supported.
2840

29-
## Installing
41+
## Installation
3042

3143
Recommended using the latest version of Python. Flask-First supports Python 3.9 and newer.
3244

3345
Install and update using `pip`:
3446

3547
```shell
36-
$ pip install flask_first
48+
$ pip install -U flask_first
3749
```
3850

39-
Simple example
40-
--------------
51+
## Settings
52+
53+
`FIRST_RESPONSE_VALIDATION` - Default: `False`. Enabling response body validation. Useful when
54+
developing. Must be disabled in a production environment.
55+
56+
## Data types
57+
58+
Supported formats for string type field:
59+
60+
* uuid
61+
* date-time
62+
* date
63+
* time
64+
* email
65+
* ipv4
66+
* ipv6
67+
* uri
68+
* binary
69+
70+
## Examples
71+
72+
### Simple example
73+
4174
OpenAPI 3 specification file `openapi.yaml`:
4275

4376
```yaml
@@ -48,16 +81,16 @@ info:
4881
paths:
4982
/{name}:
5083
parameters:
51-
- name: name
52-
in: path
53-
required: true
54-
schema:
55-
type: string
84+
- name: name
85+
in: path
86+
required: true
87+
schema:
88+
type: string
5689
get:
5790
operationId: index
5891
summary: Returns a list of items
5992
responses:
60-
'200':
93+
200:
6194
description: OK
6295
content:
6396
application/json:
@@ -85,14 +118,13 @@ first = First(path_to_spec, app=app, swagger_ui_path='/docs')
85118
86119
87120
def index(name):
88-
return {'message': name}
121+
return {'message': name}
89122
90123
91124
first.add_view_func(index)
92125
93126
if __name__ == '__main__':
94-
app.run()
95-
127+
app.run()
96128
```
97129

98130
Run application:
@@ -104,12 +136,58 @@ $ python main.py
104136
Check url in browser `http://127.0.0.1:5000/username`. Check SwaggerUI url in
105137
browser `http://127.0.0.1:5000/docs`.
106138

107-
## Settings
139+
### Specification from multiple file
108140

109-
`FIRST_RESPONSE_VALIDATION` - Default: `False`. Enabling response body validation. Useful when
110-
developing. May be disabled in a production environment.
141+
Flask-First supported specification OpenAPI from multiple files. You need create root file for
142+
specification with name `openapi.yaml`.
111143

112-
## CORS support
144+
Root file `openapi.yaml`:
145+
146+
```yaml
147+
openapi: 3.1.0
148+
info:
149+
title: Simple API for Flask-First
150+
version: 1.0.0
151+
paths:
152+
/{name}:
153+
$ref: 'name.openapi.yaml#/name'
154+
components:
155+
schemas:
156+
MessageField:
157+
type: string
158+
description: Field for message.
159+
```
160+
161+
Child file `name.openapi.yaml`:
162+
163+
```yaml
164+
name:
165+
parameters:
166+
- name: name
167+
in: path
168+
required: true
169+
schema:
170+
type: string
171+
get:
172+
operationId: index
173+
summary: Returns a list of items
174+
responses:
175+
'200':
176+
$ref: '#/components/responses/ResponseOK'
177+
components:
178+
responses:
179+
ResponseOK:
180+
description: OK
181+
content:
182+
application/json:
183+
schema:
184+
type: object
185+
properties:
186+
message:
187+
$ref: 'openapi.yaml#/components/schemas/MessageField'
188+
```
189+
190+
### CORS support
113191

114192
Your need enable CORS in Flask and adding `OPTIONS` method in your specification. Example:
115193

@@ -125,34 +203,20 @@ paths:
125203
options:
126204
summary: CORS support
127205
responses:
128-
200:
129-
headers:
130-
Access-Control-Allow-Origin:
131-
schema:
132-
type: string
133-
Access-Control-Allow-Methods:
134-
schema:
135-
type: string
136-
Access-Control-Allow-Headers:
137-
schema:
138-
type: string
139-
content: { }
206+
200:
207+
headers:
208+
Access-Control-Allow-Origin:
209+
schema:
210+
type: string
211+
Access-Control-Allow-Methods:
212+
schema:
213+
type: string
214+
Access-Control-Allow-Headers:
215+
schema:
216+
type: string
217+
content: { }
140218
```
141219

142-
## Data types
143-
144-
Supported formats for string type field:
145-
146-
* uuid
147-
* date-time
148-
* date
149-
* time
150-
* email
151-
* ipv4
152-
* ipv6
153-
* uri
154-
* binary
155-
156220
## Additional documentation
157221

158222
* [OpenAPI Documentation](https://swagger.io/specification/).

pyproject.toml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ version = "0.15.0"
2727

2828
[project.optional-dependencies]
2929
dev = [
30-
"bandit==1.7.5",
31-
"build==1.0.3",
32-
"mypy==1.6.1",
33-
"pre-commit==3.5.0",
34-
"pytest==7.4.2",
30+
"bandit==1.7.7",
31+
"build==1.1.1",
32+
"mypy==1.8.0",
33+
"pre-commit==3.6.2",
34+
"pytest==8.0.2",
3535
"pytest-cov==4.1.0",
36-
"python-dotenv==1.0.0",
37-
"tox==4.11.3",
38-
"twine==4.0.2"
36+
"python-dotenv==1.0.1",
37+
"tox==4.13.0",
38+
"twine==5.0.0"
3939
]
4040

4141
[project.urls]

src/flask_first/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
"""Flask extension for using “specification first” principle."""
21
import re
32
from pathlib import Path
43
from typing import Any
@@ -103,7 +102,8 @@ def _extract_data_from_request(
103102

104103
return method, route, headers, view_args, args, cookies, json
105104

106-
def _resolved_params(self, payload: MultiDict) -> dict:
105+
@staticmethod
106+
def _resolved_params(payload: MultiDict) -> dict:
107107
# payload.to_dict(flat=False) serializing all arguments as list for correct receipt of
108108
# arguments of same name:
109109
# {'first_arg': ['1'], 'second_arg': ['10'], 'args_list': ['1', '2']}
@@ -117,7 +117,8 @@ def _resolved_params(self, payload: MultiDict) -> dict:
117117

118118
return serialized_payload
119119

120-
def _arg_to_list(self, args: dict, schema_fields: dict) -> dict:
120+
@staticmethod
121+
def _arg_to_list(args: dict, schema_fields: dict) -> dict:
121122
for arg in args:
122123
arg_value = schema_fields.get(arg, ...)
123124

src/flask_first/first/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ class FirstException(Exception):
55
"""Common exception."""
66

77

8+
class FirstOpenAPIResolverError(FirstException):
9+
"""Exception for specification resolver error."""
10+
11+
812
class FirstOpenAPIValidation(FirstException):
913
"""Exception for specification validation error."""
1014

0 commit comments

Comments
 (0)