flask-ast-openapi 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/PKG-INFO +24 -5
- flask_ast_openapi-0.2.0/src/flask_ast_openapi.egg-info/PKG-INFO → flask_ast_openapi-0.2.2/README.md +37 -30
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/pyproject.toml +1 -1
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi/generator.py +145 -46
- flask_ast_openapi-0.2.0/README.md → flask_ast_openapi-0.2.2/src/flask_ast_openapi.egg-info/PKG-INFO +46 -15
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/tests/test_generator.py +129 -6
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/LICENSE +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/setup.cfg +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi/__init__.py +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi/cli.py +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi/swagger.py +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/SOURCES.txt +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/dependency_links.txt +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/entry_points.txt +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/requires.txt +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/top_level.txt +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/tests/test_cli.py +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/tests/test_package.py +0 -0
- {flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/tests/test_swagger.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flask-ast-openapi
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Generate OpenAPI specifications from Flask source code using AST
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -55,6 +55,8 @@ It also provides optional Swagger UI integration for Flask applications.
|
|
|
55
55
|
* Marshmallow request schema support
|
|
56
56
|
* Marshmallow response schema support
|
|
57
57
|
* Cross-file Marshmallow schema resolution
|
|
58
|
+
* Automatic request content-type detection for JSON and multipart forms
|
|
59
|
+
* Request and response content-type docstring overrides
|
|
58
60
|
* Nested Marshmallow schema support
|
|
59
61
|
* OpenAPI component schema generation
|
|
60
62
|
* Configurable authentication decorators
|
|
@@ -431,6 +433,23 @@ def create_user():
|
|
|
431
433
|
...
|
|
432
434
|
```
|
|
433
435
|
|
|
436
|
+
JSON bodies are detected from `request.get_json()` and `request.json`.
|
|
437
|
+
Multipart bodies are detected from `request.form` and `request.files`.
|
|
438
|
+
For custom media types, use docstring directives:
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
@app.post("/documents")
|
|
442
|
+
def upload_document():
|
|
443
|
+
"""
|
|
444
|
+
:request: DocumentUploadSchema
|
|
445
|
+
:response: DocumentResponseSchema
|
|
446
|
+
:request-content-type: multipart/form-data
|
|
447
|
+
:response-content-type: application/json
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
...
|
|
451
|
+
```
|
|
452
|
+
|
|
434
453
|
The generator detects `UserRequestSchema` and creates an OpenAPI request-body schema.
|
|
435
454
|
|
|
436
455
|
## Response Schemas
|
|
@@ -785,12 +804,12 @@ Future development may include:
|
|
|
785
804
|
Current version:
|
|
786
805
|
|
|
787
806
|
```text
|
|
788
|
-
0.2.
|
|
807
|
+
0.2.1
|
|
789
808
|
```
|
|
790
809
|
|
|
791
|
-
Version `0.2.
|
|
792
|
-
|
|
793
|
-
|
|
810
|
+
Version `0.2.1` adds automatic JSON and multipart request content-type
|
|
811
|
+
detection, explicit request/response media-type overrides, and avoids creating
|
|
812
|
+
request bodies for header-only and path-only inputs.
|
|
794
813
|
|
|
795
814
|
## License
|
|
796
815
|
|
flask_ast_openapi-0.2.0/src/flask_ast_openapi.egg-info/PKG-INFO → flask_ast_openapi-0.2.2/README.md
RENAMED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: flask-ast-openapi
|
|
3
|
-
Version: 0.2.0
|
|
4
|
-
Summary: Generate OpenAPI specifications from Flask source code using AST
|
|
5
|
-
Requires-Python: >=3.10
|
|
6
|
-
Description-Content-Type: text/markdown
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Dist: Flask>=3.0
|
|
9
|
-
Provides-Extra: dev
|
|
10
|
-
Requires-Dist: pytest>=8; extra == "dev"
|
|
11
|
-
Dynamic: license-file
|
|
12
|
-
|
|
13
1
|
# flask-ast-openapi
|
|
14
2
|
|
|
15
3
|
Generate OpenAPI 3 specifications from Flask source code using Python's Abstract Syntax Tree (AST).
|
|
@@ -31,10 +19,10 @@ It also provides optional Swagger UI integration for Flask applications.
|
|
|
31
19
|
* `@app.put(...)`
|
|
32
20
|
* `@app.patch(...)`
|
|
33
21
|
* `@app.delete(...)`
|
|
34
|
-
* Blueprint route support
|
|
35
|
-
* Blueprint `url_prefix` support
|
|
36
|
-
* Nested and cross-file Blueprint registration support
|
|
37
|
-
* Automatic Blueprint tags for Swagger UI grouping
|
|
22
|
+
* Blueprint route support
|
|
23
|
+
* Blueprint `url_prefix` support
|
|
24
|
+
* Nested and cross-file Blueprint registration support
|
|
25
|
+
* Automatic Blueprint tags for Swagger UI grouping
|
|
38
26
|
* Flask path parameter conversion to OpenAPI format
|
|
39
27
|
* Flask converter support:
|
|
40
28
|
|
|
@@ -53,8 +41,10 @@ It also provides optional Swagger UI integration for Flask applications.
|
|
|
53
41
|
* Function docstrings as OpenAPI descriptions
|
|
54
42
|
* Synchronous and asynchronous Flask route support
|
|
55
43
|
* Marshmallow request schema support
|
|
56
|
-
* Marshmallow response schema support
|
|
57
|
-
* Cross-file Marshmallow schema resolution
|
|
44
|
+
* Marshmallow response schema support
|
|
45
|
+
* Cross-file Marshmallow schema resolution
|
|
46
|
+
* Automatic request content-type detection for JSON and multipart forms
|
|
47
|
+
* Request and response content-type docstring overrides
|
|
58
48
|
* Nested Marshmallow schema support
|
|
59
49
|
* OpenAPI component schema generation
|
|
60
50
|
* Configurable authentication decorators
|
|
@@ -417,7 +407,7 @@ class UserRequestSchema(Schema):
|
|
|
417
407
|
active = fields.Boolean()
|
|
418
408
|
```
|
|
419
409
|
|
|
420
|
-
A route can reference the schema from its docstring:
|
|
410
|
+
A route can reference the schema from its docstring:
|
|
421
411
|
|
|
422
412
|
```python
|
|
423
413
|
@app.post("/users")
|
|
@@ -428,8 +418,25 @@ def create_user():
|
|
|
428
418
|
:request: UserRequestSchema
|
|
429
419
|
"""
|
|
430
420
|
|
|
431
|
-
...
|
|
432
|
-
```
|
|
421
|
+
...
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
JSON bodies are detected from `request.get_json()` and `request.json`.
|
|
425
|
+
Multipart bodies are detected from `request.form` and `request.files`.
|
|
426
|
+
For custom media types, use docstring directives:
|
|
427
|
+
|
|
428
|
+
```python
|
|
429
|
+
@app.post("/documents")
|
|
430
|
+
def upload_document():
|
|
431
|
+
"""
|
|
432
|
+
:request: DocumentUploadSchema
|
|
433
|
+
:response: DocumentResponseSchema
|
|
434
|
+
:request-content-type: multipart/form-data
|
|
435
|
+
:response-content-type: application/json
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
...
|
|
439
|
+
```
|
|
433
440
|
|
|
434
441
|
The generator detects `UserRequestSchema` and creates an OpenAPI request-body schema.
|
|
435
442
|
|
|
@@ -782,15 +789,15 @@ Future development may include:
|
|
|
782
789
|
|
|
783
790
|
## Version
|
|
784
791
|
|
|
785
|
-
Current version:
|
|
786
|
-
|
|
787
|
-
```text
|
|
788
|
-
0.2.
|
|
789
|
-
```
|
|
790
|
-
|
|
791
|
-
Version `0.2.
|
|
792
|
-
|
|
793
|
-
|
|
792
|
+
Current version:
|
|
793
|
+
|
|
794
|
+
```text
|
|
795
|
+
0.2.1
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
Version `0.2.1` adds automatic JSON and multipart request content-type
|
|
799
|
+
detection, explicit request/response media-type overrides, and avoids creating
|
|
800
|
+
request bodies for header-only and path-only inputs.
|
|
794
801
|
|
|
795
802
|
## License
|
|
796
803
|
|
|
@@ -32,8 +32,10 @@ class RouteDefinition:
|
|
|
32
32
|
auth_schemes: list[str] = field(default_factory=list)
|
|
33
33
|
auth_scheme_mode: str = "and"
|
|
34
34
|
description: str | None = None
|
|
35
|
-
request_schema: dict[str, Any] | None = None
|
|
35
|
+
request_schema: dict[str, Any] | None = None
|
|
36
36
|
response_schema: dict[str, Any] | None = None
|
|
37
|
+
request_content_type: str | None = "application/json"
|
|
38
|
+
response_content_type: str = "application/json"
|
|
37
39
|
tag: str | None = None
|
|
38
40
|
@dataclass
|
|
39
41
|
class PathParameter:
|
|
@@ -283,10 +285,16 @@ class FlaskASTOpenAPI:
|
|
|
283
285
|
function
|
|
284
286
|
)
|
|
285
287
|
),
|
|
286
|
-
request_schema=self.build_request_schema_from_docstring(
|
|
287
|
-
tree,
|
|
288
|
-
function,
|
|
289
|
-
),
|
|
288
|
+
request_schema=self.build_request_schema_from_docstring(
|
|
289
|
+
tree,
|
|
290
|
+
function,
|
|
291
|
+
),
|
|
292
|
+
request_content_type=(
|
|
293
|
+
self.extract_request_content_type(function)
|
|
294
|
+
),
|
|
295
|
+
response_content_type=(
|
|
296
|
+
self.extract_response_content_type(function)
|
|
297
|
+
),
|
|
290
298
|
uses_json_body=(
|
|
291
299
|
self.function_uses_json_body(
|
|
292
300
|
function
|
|
@@ -422,11 +430,12 @@ class FlaskASTOpenAPI:
|
|
|
422
430
|
route.path
|
|
423
431
|
)
|
|
424
432
|
|
|
425
|
-
parameters.extend(
|
|
426
|
-
self.build_query_parameters_from_names(
|
|
427
|
-
route.query_parameter_names
|
|
428
|
-
|
|
429
|
-
|
|
433
|
+
parameters.extend(
|
|
434
|
+
self.build_query_parameters_from_names(
|
|
435
|
+
route.query_parameter_names,
|
|
436
|
+
route.request_schema,
|
|
437
|
+
)
|
|
438
|
+
)
|
|
430
439
|
response_status_codes = list(
|
|
431
440
|
route.response_status_codes
|
|
432
441
|
)
|
|
@@ -451,16 +460,20 @@ class FlaskASTOpenAPI:
|
|
|
451
460
|
operation: dict[str, Any] = {
|
|
452
461
|
"operationId": route.function_name,
|
|
453
462
|
"parameters": parameters,
|
|
454
|
-
"responses": self.build_openapi_responses(
|
|
455
|
-
response_status_codes,
|
|
456
|
-
response_schemas,
|
|
457
|
-
|
|
463
|
+
"responses": self.build_openapi_responses(
|
|
464
|
+
response_status_codes,
|
|
465
|
+
response_schemas,
|
|
466
|
+
route.response_content_type,
|
|
467
|
+
),
|
|
458
468
|
}
|
|
459
469
|
|
|
460
470
|
if route.tag:
|
|
461
471
|
operation["tags"] = [route.tag]
|
|
462
472
|
|
|
463
|
-
if
|
|
473
|
+
if (
|
|
474
|
+
route.request_content_type is not None
|
|
475
|
+
and (route.uses_json_body or route.request_schema is not None)
|
|
476
|
+
):
|
|
464
477
|
request_schema = route.request_schema
|
|
465
478
|
|
|
466
479
|
if request_schema is None:
|
|
@@ -471,12 +484,12 @@ class FlaskASTOpenAPI:
|
|
|
471
484
|
)
|
|
472
485
|
|
|
473
486
|
operation["requestBody"] = {
|
|
474
|
-
"required": True,
|
|
475
|
-
"content": {
|
|
476
|
-
|
|
477
|
-
"schema": request_schema,
|
|
478
|
-
}
|
|
479
|
-
},
|
|
487
|
+
"required": True,
|
|
488
|
+
"content": {
|
|
489
|
+
route.request_content_type: {
|
|
490
|
+
"schema": request_schema,
|
|
491
|
+
}
|
|
492
|
+
},
|
|
480
493
|
}
|
|
481
494
|
|
|
482
495
|
if route.description:
|
|
@@ -781,21 +794,34 @@ class FlaskASTOpenAPI:
|
|
|
781
794
|
)
|
|
782
795
|
|
|
783
796
|
return parameters
|
|
784
|
-
def build_query_parameters_from_names(
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
797
|
+
def build_query_parameters_from_names(
|
|
798
|
+
self,
|
|
799
|
+
names: list[str],
|
|
800
|
+
request_schema: dict[str, Any] | None = None,
|
|
801
|
+
) -> list[dict[str, Any]]:
|
|
802
|
+
"""Build OpenAPI query parameters from their names."""
|
|
803
|
+
|
|
804
|
+
schema_properties = (
|
|
805
|
+
request_schema.get("properties", {})
|
|
806
|
+
if request_schema
|
|
807
|
+
else {}
|
|
808
|
+
)
|
|
809
|
+
required_names = set(
|
|
810
|
+
request_schema.get("required", [])
|
|
811
|
+
if request_schema
|
|
812
|
+
else []
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
return [
|
|
816
|
+
{
|
|
817
|
+
"name": name,
|
|
818
|
+
"in": "query",
|
|
819
|
+
"required": name in required_names,
|
|
820
|
+
"schema": schema_properties.get(name, {"type": "string"}),
|
|
821
|
+
}
|
|
822
|
+
for name in names
|
|
823
|
+
]
|
|
824
|
+
def function_uses_json_body(self,function: ast.FunctionDef | ast.AsyncFunctionDef,) -> bool:
|
|
799
825
|
"""Check whether a route function reads a JSON request body."""
|
|
800
826
|
|
|
801
827
|
for node in ast.walk(function):
|
|
@@ -816,7 +842,79 @@ class FlaskASTOpenAPI:
|
|
|
816
842
|
):
|
|
817
843
|
return True
|
|
818
844
|
|
|
819
|
-
return False
|
|
845
|
+
return False
|
|
846
|
+
|
|
847
|
+
def function_uses_multipart_body(
|
|
848
|
+
self,
|
|
849
|
+
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
850
|
+
) -> bool:
|
|
851
|
+
"""Check whether a route reads form fields or uploaded files."""
|
|
852
|
+
|
|
853
|
+
for node in ast.walk(function):
|
|
854
|
+
if not isinstance(node, ast.Attribute):
|
|
855
|
+
continue
|
|
856
|
+
|
|
857
|
+
if (
|
|
858
|
+
isinstance(node.value, ast.Name)
|
|
859
|
+
and node.value.id == "request"
|
|
860
|
+
and node.attr in {"form", "files"}
|
|
861
|
+
):
|
|
862
|
+
return True
|
|
863
|
+
|
|
864
|
+
return False
|
|
865
|
+
|
|
866
|
+
def extract_docstring_directive(
|
|
867
|
+
self,
|
|
868
|
+
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
869
|
+
directive: str,
|
|
870
|
+
) -> str | None:
|
|
871
|
+
"""Extract a named ``:directive: value`` from a docstring."""
|
|
872
|
+
|
|
873
|
+
docstring = ast.get_docstring(function)
|
|
874
|
+
if not docstring:
|
|
875
|
+
return None
|
|
876
|
+
|
|
877
|
+
match = re.search(
|
|
878
|
+
rf"^\s*:{re.escape(directive)}:\s*([^\s]+)",
|
|
879
|
+
docstring,
|
|
880
|
+
re.MULTILINE,
|
|
881
|
+
)
|
|
882
|
+
return match.group(1) if match else None
|
|
883
|
+
|
|
884
|
+
def extract_request_content_type(
|
|
885
|
+
self,
|
|
886
|
+
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
887
|
+
) -> str | None:
|
|
888
|
+
"""Infer request body media type, allowing a docstring override."""
|
|
889
|
+
|
|
890
|
+
explicit = self.extract_docstring_directive(
|
|
891
|
+
function,
|
|
892
|
+
"request-content-type",
|
|
893
|
+
)
|
|
894
|
+
if explicit:
|
|
895
|
+
return explicit
|
|
896
|
+
|
|
897
|
+
if self.function_uses_multipart_body(function):
|
|
898
|
+
return "multipart/form-data"
|
|
899
|
+
|
|
900
|
+
if self.function_uses_json_body(function):
|
|
901
|
+
return "application/json"
|
|
902
|
+
|
|
903
|
+
return None
|
|
904
|
+
|
|
905
|
+
def extract_response_content_type(
|
|
906
|
+
self,
|
|
907
|
+
function: ast.FunctionDef | ast.AsyncFunctionDef,
|
|
908
|
+
) -> str:
|
|
909
|
+
"""Return the declared response media type or JSON by default."""
|
|
910
|
+
|
|
911
|
+
return (
|
|
912
|
+
self.extract_docstring_directive(
|
|
913
|
+
function,
|
|
914
|
+
"response-content-type",
|
|
915
|
+
)
|
|
916
|
+
or "application/json"
|
|
917
|
+
)
|
|
820
918
|
def extract_json_body_field_names(self,function: ast.FunctionDef | ast.AsyncFunctionDef,) -> list[str]:
|
|
821
919
|
"""Extract JSON body field names used inside a route function."""
|
|
822
920
|
|
|
@@ -1151,10 +1249,11 @@ class FlaskASTOpenAPI:
|
|
|
1151
1249
|
status_codes.append(status_code)
|
|
1152
1250
|
|
|
1153
1251
|
return status_codes
|
|
1154
|
-
def build_openapi_responses(
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1252
|
+
def build_openapi_responses(
|
|
1253
|
+
self,
|
|
1254
|
+
status_codes: list[int],
|
|
1255
|
+
response_schemas: dict[int, dict[str, Any]] | None = None,
|
|
1256
|
+
content_type: str = "application/json",
|
|
1158
1257
|
) -> dict[str, Any]:
|
|
1159
1258
|
"""Build OpenAPI responses from status codes and schemas."""
|
|
1160
1259
|
|
|
@@ -1176,11 +1275,11 @@ class FlaskASTOpenAPI:
|
|
|
1176
1275
|
schema = response_schemas.get(status_code)
|
|
1177
1276
|
|
|
1178
1277
|
if schema:
|
|
1179
|
-
response["content"] = {
|
|
1180
|
-
|
|
1181
|
-
"schema": schema,
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1278
|
+
response["content"] = {
|
|
1279
|
+
content_type: {
|
|
1280
|
+
"schema": schema,
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1184
1283
|
|
|
1185
1284
|
responses[str(status_code)] = response
|
|
1186
1285
|
|
flask_ast_openapi-0.2.0/README.md → flask_ast_openapi-0.2.2/src/flask_ast_openapi.egg-info/PKG-INFO
RENAMED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flask-ast-openapi
|
|
3
|
+
Version: 0.2.2
|
|
4
|
+
Summary: Generate OpenAPI specifications from Flask source code using AST
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: Flask>=3.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
11
|
+
Dynamic: license-file
|
|
12
|
+
|
|
1
13
|
# flask-ast-openapi
|
|
2
14
|
|
|
3
15
|
Generate OpenAPI 3 specifications from Flask source code using Python's Abstract Syntax Tree (AST).
|
|
@@ -19,10 +31,10 @@ It also provides optional Swagger UI integration for Flask applications.
|
|
|
19
31
|
* `@app.put(...)`
|
|
20
32
|
* `@app.patch(...)`
|
|
21
33
|
* `@app.delete(...)`
|
|
22
|
-
* Blueprint route support
|
|
23
|
-
* Blueprint `url_prefix` support
|
|
24
|
-
* Nested and cross-file Blueprint registration support
|
|
25
|
-
* Automatic Blueprint tags for Swagger UI grouping
|
|
34
|
+
* Blueprint route support
|
|
35
|
+
* Blueprint `url_prefix` support
|
|
36
|
+
* Nested and cross-file Blueprint registration support
|
|
37
|
+
* Automatic Blueprint tags for Swagger UI grouping
|
|
26
38
|
* Flask path parameter conversion to OpenAPI format
|
|
27
39
|
* Flask converter support:
|
|
28
40
|
|
|
@@ -41,8 +53,10 @@ It also provides optional Swagger UI integration for Flask applications.
|
|
|
41
53
|
* Function docstrings as OpenAPI descriptions
|
|
42
54
|
* Synchronous and asynchronous Flask route support
|
|
43
55
|
* Marshmallow request schema support
|
|
44
|
-
* Marshmallow response schema support
|
|
45
|
-
* Cross-file Marshmallow schema resolution
|
|
56
|
+
* Marshmallow response schema support
|
|
57
|
+
* Cross-file Marshmallow schema resolution
|
|
58
|
+
* Automatic request content-type detection for JSON and multipart forms
|
|
59
|
+
* Request and response content-type docstring overrides
|
|
46
60
|
* Nested Marshmallow schema support
|
|
47
61
|
* OpenAPI component schema generation
|
|
48
62
|
* Configurable authentication decorators
|
|
@@ -419,6 +433,23 @@ def create_user():
|
|
|
419
433
|
...
|
|
420
434
|
```
|
|
421
435
|
|
|
436
|
+
JSON bodies are detected from `request.get_json()` and `request.json`.
|
|
437
|
+
Multipart bodies are detected from `request.form` and `request.files`.
|
|
438
|
+
For custom media types, use docstring directives:
|
|
439
|
+
|
|
440
|
+
```python
|
|
441
|
+
@app.post("/documents")
|
|
442
|
+
def upload_document():
|
|
443
|
+
"""
|
|
444
|
+
:request: DocumentUploadSchema
|
|
445
|
+
:response: DocumentResponseSchema
|
|
446
|
+
:request-content-type: multipart/form-data
|
|
447
|
+
:response-content-type: application/json
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
...
|
|
451
|
+
```
|
|
452
|
+
|
|
422
453
|
The generator detects `UserRequestSchema` and creates an OpenAPI request-body schema.
|
|
423
454
|
|
|
424
455
|
## Response Schemas
|
|
@@ -770,15 +801,15 @@ Future development may include:
|
|
|
770
801
|
|
|
771
802
|
## Version
|
|
772
803
|
|
|
773
|
-
Current version:
|
|
774
|
-
|
|
775
|
-
```text
|
|
776
|
-
0.2.
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
Version `0.2.
|
|
780
|
-
|
|
781
|
-
|
|
804
|
+
Current version:
|
|
805
|
+
|
|
806
|
+
```text
|
|
807
|
+
0.2.1
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Version `0.2.1` adds automatic JSON and multipart request content-type
|
|
811
|
+
detection, explicit request/response media-type overrides, and avoids creating
|
|
812
|
+
request bodies for header-only and path-only inputs.
|
|
782
813
|
|
|
783
814
|
## License
|
|
784
815
|
|
|
@@ -908,7 +908,7 @@ def get_users():
|
|
|
908
908
|
]
|
|
909
909
|
|
|
910
910
|
|
|
911
|
-
def test_build_openapi_operation_includes_query_parameters(tmp_path):
|
|
911
|
+
def test_build_openapi_operation_includes_query_parameters(tmp_path):
|
|
912
912
|
generator = FlaskASTOpenAPI(tmp_path)
|
|
913
913
|
|
|
914
914
|
route = RouteDefinition(
|
|
@@ -920,7 +920,7 @@ def test_build_openapi_operation_includes_query_parameters(tmp_path):
|
|
|
920
920
|
|
|
921
921
|
operation = generator.build_openapi_operation(route)
|
|
922
922
|
|
|
923
|
-
assert operation["parameters"] == [
|
|
923
|
+
assert operation["parameters"] == [
|
|
924
924
|
{
|
|
925
925
|
"name": "page",
|
|
926
926
|
"in": "query",
|
|
@@ -937,10 +937,47 @@ def test_build_openapi_operation_includes_query_parameters(tmp_path):
|
|
|
937
937
|
"type": "string",
|
|
938
938
|
},
|
|
939
939
|
},
|
|
940
|
-
]
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
def
|
|
940
|
+
]
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
def test_build_openapi_operation_uses_request_schema_for_query_parameters(
|
|
944
|
+
tmp_path,
|
|
945
|
+
):
|
|
946
|
+
generator = FlaskASTOpenAPI(tmp_path)
|
|
947
|
+
|
|
948
|
+
route = RouteDefinition(
|
|
949
|
+
function_name="get_users",
|
|
950
|
+
path="/users",
|
|
951
|
+
methods=["GET"],
|
|
952
|
+
query_parameter_names=["page", "page_size"],
|
|
953
|
+
request_schema={
|
|
954
|
+
"type": "object",
|
|
955
|
+
"properties": {
|
|
956
|
+
"page": {"type": "integer"},
|
|
957
|
+
"page_size": {"type": "integer"},
|
|
958
|
+
},
|
|
959
|
+
},
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
operation = generator.build_openapi_operation(route)
|
|
963
|
+
|
|
964
|
+
assert operation["parameters"] == [
|
|
965
|
+
{
|
|
966
|
+
"name": "page",
|
|
967
|
+
"in": "query",
|
|
968
|
+
"required": False,
|
|
969
|
+
"schema": {"type": "integer"},
|
|
970
|
+
},
|
|
971
|
+
{
|
|
972
|
+
"name": "page_size",
|
|
973
|
+
"in": "query",
|
|
974
|
+
"required": False,
|
|
975
|
+
"schema": {"type": "integer"},
|
|
976
|
+
},
|
|
977
|
+
]
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def test_build_openapi_operation_combines_path_and_query_parameters(
|
|
944
981
|
tmp_path,
|
|
945
982
|
):
|
|
946
983
|
generator = FlaskASTOpenAPI(tmp_path)
|
|
@@ -6405,3 +6442,89 @@ def create_user():
|
|
|
6405
6442
|
response_schema = operation["responses"]["200"]["content"]["application/json"]["schema"]
|
|
6406
6443
|
assert request_schema["properties"]["name"] == {"type": "string"}
|
|
6407
6444
|
assert response_schema["properties"]["id"] == {"type": "integer"}
|
|
6445
|
+
|
|
6446
|
+
|
|
6447
|
+
def test_generate_infers_multipart_form_data(tmp_path):
|
|
6448
|
+
(tmp_path / "routes.py").write_text(
|
|
6449
|
+
"""
|
|
6450
|
+
from flask import Blueprint, request
|
|
6451
|
+
from marshmallow import Schema, fields
|
|
6452
|
+
|
|
6453
|
+
class UploadSchema(Schema):
|
|
6454
|
+
title = fields.String(required=True)
|
|
6455
|
+
file = fields.Raw(required=True)
|
|
6456
|
+
|
|
6457
|
+
upload_bp = Blueprint("upload", __name__)
|
|
6458
|
+
|
|
6459
|
+
@upload_bp.route("/upload", methods=["POST"])
|
|
6460
|
+
def upload():
|
|
6461
|
+
\"\"\":request: UploadSchema\"\"\"
|
|
6462
|
+
title = request.form.get("title")
|
|
6463
|
+
file = request.files.get("file")
|
|
6464
|
+
return {}, 200
|
|
6465
|
+
""",
|
|
6466
|
+
encoding="utf-8",
|
|
6467
|
+
)
|
|
6468
|
+
|
|
6469
|
+
spec = FlaskASTOpenAPI(tmp_path).generate()
|
|
6470
|
+
content = spec["paths"]["/upload"]["post"]["requestBody"]["content"]
|
|
6471
|
+
|
|
6472
|
+
assert "multipart/form-data" in content
|
|
6473
|
+
assert "application/json" not in content
|
|
6474
|
+
|
|
6475
|
+
|
|
6476
|
+
def test_generate_does_not_create_body_for_header_only_schema(tmp_path):
|
|
6477
|
+
(tmp_path / "routes.py").write_text(
|
|
6478
|
+
"""
|
|
6479
|
+
from flask import Blueprint, request
|
|
6480
|
+
from marshmallow import Schema, fields
|
|
6481
|
+
|
|
6482
|
+
class TokenSchema(Schema):
|
|
6483
|
+
token = fields.String(required=True)
|
|
6484
|
+
|
|
6485
|
+
auth_bp = Blueprint("auth", __name__)
|
|
6486
|
+
|
|
6487
|
+
@auth_bp.route("/validate", methods=["POST"])
|
|
6488
|
+
def validate():
|
|
6489
|
+
\"\"\":request: TokenSchema\"\"\"
|
|
6490
|
+
token = request.headers.get("Authorization")
|
|
6491
|
+
return {}, 200
|
|
6492
|
+
""",
|
|
6493
|
+
encoding="utf-8",
|
|
6494
|
+
)
|
|
6495
|
+
|
|
6496
|
+
spec = FlaskASTOpenAPI(tmp_path).generate()
|
|
6497
|
+
|
|
6498
|
+
assert "requestBody" not in spec["paths"]["/validate"]["post"]
|
|
6499
|
+
|
|
6500
|
+
|
|
6501
|
+
def test_docstring_can_override_request_and_response_content_types(tmp_path):
|
|
6502
|
+
(tmp_path / "routes.py").write_text(
|
|
6503
|
+
"""
|
|
6504
|
+
from flask import Blueprint, request
|
|
6505
|
+
from marshmallow import Schema, fields
|
|
6506
|
+
|
|
6507
|
+
class PayloadSchema(Schema):
|
|
6508
|
+
value = fields.String(required=True)
|
|
6509
|
+
|
|
6510
|
+
bp = Blueprint("custom", __name__)
|
|
6511
|
+
|
|
6512
|
+
@bp.route("/custom", methods=["POST"])
|
|
6513
|
+
def custom():
|
|
6514
|
+
\"\"\"
|
|
6515
|
+
:request: PayloadSchema
|
|
6516
|
+
:response: PayloadSchema
|
|
6517
|
+
:request-content-type: application/x-www-form-urlencoded
|
|
6518
|
+
:response-content-type: application/problem+json
|
|
6519
|
+
\"\"\"
|
|
6520
|
+
value = request.form.get("value")
|
|
6521
|
+
return {"value": value}, 200
|
|
6522
|
+
""",
|
|
6523
|
+
encoding="utf-8",
|
|
6524
|
+
)
|
|
6525
|
+
|
|
6526
|
+
spec = FlaskASTOpenAPI(tmp_path).generate()
|
|
6527
|
+
operation = spec["paths"]["/custom"]["post"]
|
|
6528
|
+
|
|
6529
|
+
assert "application/x-www-form-urlencoded" in operation["requestBody"]["content"]
|
|
6530
|
+
assert "application/problem+json" in operation["responses"]["200"]["content"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/requires.txt
RENAMED
|
File without changes
|
{flask_ast_openapi-0.2.0 → flask_ast_openapi-0.2.2}/src/flask_ast_openapi.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|