APIFlask 2.3.0__tar.gz → 2.3.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.
Files changed (92) hide show
  1. {apiflask-2.3.0 → apiflask-2.3.2}/CHANGES.md +21 -1
  2. {apiflask-2.3.0 → apiflask-2.3.2}/PKG-INFO +2 -1
  3. {apiflask-2.3.0 → apiflask-2.3.2}/pyproject.toml +2 -1
  4. {apiflask-2.3.0 → apiflask-2.3.2}/src/APIFlask.egg-info/PKG-INFO +2 -1
  5. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/app.py +6 -4
  6. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/scaffold.py +21 -55
  7. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_decorator_input.py +46 -113
  8. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_headers.py +17 -12
  9. {apiflask-2.3.0 → apiflask-2.3.2}/LICENSE +0 -0
  10. {apiflask-2.3.0 → apiflask-2.3.2}/MANIFEST.in +0 -0
  11. {apiflask-2.3.0 → apiflask-2.3.2}/NOTICE +0 -0
  12. {apiflask-2.3.0 → apiflask-2.3.2}/README.md +0 -0
  13. {apiflask-2.3.0 → apiflask-2.3.2}/examples/README.md +0 -0
  14. {apiflask-2.3.0 → apiflask-2.3.2}/examples/auth/basic_auth/app.py +0 -0
  15. {apiflask-2.3.0 → apiflask-2.3.2}/examples/auth/token_auth/app.py +0 -0
  16. {apiflask-2.3.0 → apiflask-2.3.2}/examples/base_response/app.py +0 -0
  17. {apiflask-2.3.0 → apiflask-2.3.2}/examples/basic/app.py +0 -0
  18. {apiflask-2.3.0 → apiflask-2.3.2}/examples/blueprint_tags/app.py +0 -0
  19. {apiflask-2.3.0 → apiflask-2.3.2}/examples/cbv/app.py +0 -0
  20. {apiflask-2.3.0 → apiflask-2.3.2}/examples/dataclass/app.py +0 -0
  21. {apiflask-2.3.0 → apiflask-2.3.2}/examples/file_upload/app.py +0 -0
  22. {apiflask-2.3.0 → apiflask-2.3.2}/examples/file_upload/upload/.gitkeep +0 -0
  23. {apiflask-2.3.0 → apiflask-2.3.2}/examples/openapi/basic/app.py +0 -0
  24. {apiflask-2.3.0 → apiflask-2.3.2}/examples/openapi/custom_decorators/app.py +0 -0
  25. {apiflask-2.3.0 → apiflask-2.3.2}/examples/openapi/static_docs/app.py +0 -0
  26. {apiflask-2.3.0 → apiflask-2.3.2}/examples/openapi/static_docs/generate_docs.sh +0 -0
  27. {apiflask-2.3.0 → apiflask-2.3.2}/examples/openapi/static_docs/index.html +0 -0
  28. {apiflask-2.3.0 → apiflask-2.3.2}/examples/openapi/static_docs/openapi.json +0 -0
  29. {apiflask-2.3.0 → apiflask-2.3.2}/examples/orm/app.py +0 -0
  30. {apiflask-2.3.0 → apiflask-2.3.2}/examples/pagination/app.py +0 -0
  31. {apiflask-2.3.0 → apiflask-2.3.2}/examples/requirements.txt +0 -0
  32. {apiflask-2.3.0 → apiflask-2.3.2}/examples/test_examples.py +0 -0
  33. {apiflask-2.3.0 → apiflask-2.3.2}/requirements/dev.txt +0 -0
  34. {apiflask-2.3.0 → apiflask-2.3.2}/requirements/docs.txt +0 -0
  35. {apiflask-2.3.0 → apiflask-2.3.2}/requirements/examples.txt +0 -0
  36. {apiflask-2.3.0 → apiflask-2.3.2}/requirements/min-versions.txt +0 -0
  37. {apiflask-2.3.0 → apiflask-2.3.2}/requirements/tests.txt +0 -0
  38. {apiflask-2.3.0 → apiflask-2.3.2}/requirements/typing.txt +0 -0
  39. {apiflask-2.3.0 → apiflask-2.3.2}/setup.cfg +0 -0
  40. {apiflask-2.3.0 → apiflask-2.3.2}/src/APIFlask.egg-info/SOURCES.txt +0 -0
  41. {apiflask-2.3.0 → apiflask-2.3.2}/src/APIFlask.egg-info/dependency_links.txt +0 -0
  42. {apiflask-2.3.0 → apiflask-2.3.2}/src/APIFlask.egg-info/entry_points.txt +0 -0
  43. {apiflask-2.3.0 → apiflask-2.3.2}/src/APIFlask.egg-info/requires.txt +0 -0
  44. {apiflask-2.3.0 → apiflask-2.3.2}/src/APIFlask.egg-info/top_level.txt +0 -0
  45. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/__init__.py +0 -0
  46. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/blueprint.py +0 -0
  47. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/commands.py +0 -0
  48. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/exceptions.py +0 -0
  49. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/fields.py +0 -0
  50. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/helpers.py +0 -0
  51. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/openapi.py +0 -0
  52. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/py.typed +0 -0
  53. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/route.py +0 -0
  54. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/schemas.py +0 -0
  55. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/security.py +0 -0
  56. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/settings.py +0 -0
  57. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/types.py +0 -0
  58. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/ui_templates.py +0 -0
  59. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/validators.py +0 -0
  60. {apiflask-2.3.0 → apiflask-2.3.2}/src/apiflask/views.py +0 -0
  61. {apiflask-2.3.0 → apiflask-2.3.2}/tests/__init__.py +0 -0
  62. {apiflask-2.3.0 → apiflask-2.3.2}/tests/conftest.py +0 -0
  63. {apiflask-2.3.0 → apiflask-2.3.2}/tests/schemas.py +0 -0
  64. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_app.py +0 -0
  65. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_apps/__init__.py +0 -0
  66. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_async.py +0 -0
  67. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_base_response.py +0 -0
  68. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_blueprint.py +0 -0
  69. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_commands.py +0 -0
  70. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_decorator_auth_required.py +0 -0
  71. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_decorator_doc.py +0 -0
  72. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_decorator_output.py +0 -0
  73. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_decorators.py +0 -0
  74. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_exceptions.py +0 -0
  75. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_fields.py +0 -0
  76. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_helpers.py +0 -0
  77. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_basic.py +0 -0
  78. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_blueprint.py +0 -0
  79. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_extensions.py +0 -0
  80. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_info.py +0 -0
  81. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_paths.py +0 -0
  82. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_security.py +0 -0
  83. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_openapi_tags.py +0 -0
  84. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_route.py +0 -0
  85. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_schemas.py +0 -0
  86. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_security.py +0 -0
  87. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_settings_api_docs.py +0 -0
  88. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_settings_auto_behaviour.py +0 -0
  89. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_settings_openapi_fields.py +0 -0
  90. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_settings_openapi_spec.py +0 -0
  91. {apiflask-2.3.0 → apiflask-2.3.2}/tests/test_settings_response_customization.py +0 -0
  92. {apiflask-2.3.0 → apiflask-2.3.2}/tox.ini +0 -0
@@ -1,3 +1,23 @@
1
+ ## Version 2.3.2
2
+
3
+ Released: 2024/12/15
4
+
5
+ - Fix response headers to be compliant with the OpenAPI specification for versions 3.0.0+ ([issue #631][issue_631]).
6
+ - Fix input data loading implementation when input validation is skipped ([issue #629][issue_629]).
7
+
8
+ [issue_629]: https://github.com/apiflask/apiflask/issues/629
9
+ [issue_631]: https://github.com/apiflask/apiflask/issues/631
10
+
11
+
12
+ ## Version 2.3.1
13
+
14
+ Released: 2024/12/7
15
+
16
+ - Include input documentation in API spec when specifying `validation=False` on `@input` decorator ([issue #626][issue_626]).
17
+
18
+ [issue_626]: https://github.com/apiflask/apiflask/issues/626
19
+
20
+
1
21
  ## Version 2.3.0
2
22
 
3
23
  Released: 2024/11/25
@@ -70,7 +90,7 @@ Released: 2023/12/16
70
90
  - Allow adding multiple media types for a response ([issue #494][issue_494]).
71
91
  - Support adding headers to the response schema ([issue #507][issue_507]).
72
92
  - Add support for adding decorators to the OpenAPI spec and documentation UI endpoints ([issue #483][issue_483]).
73
- - Fix the base repsonse schema inconsistency issue.
93
+ - Fix the base response schema inconsistency issue.
74
94
 
75
95
  [issue_494]: https://github.com/apiflask/apiflask/issues/494
76
96
  [issue_507]: https://github.com/apiflask/apiflask/issues/507
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: APIFlask
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: A lightweight web API framework based on Flask and marshmallow-code projects.
5
5
  Author-email: Grey Li <withlihui@gmail.com>
6
6
  License: MIT
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
22
23
  Classifier: License :: OSI Approved :: MIT License
23
24
  Classifier: Operating System :: OS Independent
24
25
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
@@ -4,7 +4,7 @@ description = "A lightweight web API framework based on Flask and marshmallow-co
4
4
  readme = "README.md"
5
5
  license = { text = "MIT" }
6
6
  authors = [{ name = "Grey Li", email = "withlihui@gmail.com" }]
7
- version = "2.3.0"
7
+ version = "2.3.2"
8
8
  classifiers = [
9
9
  "Development Status :: 3 - Alpha",
10
10
  "Environment :: Web Environment",
@@ -16,6 +16,7 @@ classifiers = [
16
16
  "Programming Language :: Python :: 3.10",
17
17
  "Programming Language :: Python :: 3.11",
18
18
  "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
19
20
  "License :: OSI Approved :: MIT License",
20
21
  "Operating System :: OS Independent",
21
22
  "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: APIFlask
3
- Version: 2.3.0
3
+ Version: 2.3.2
4
4
  Summary: A lightweight web API framework based on Flask and marshmallow-code projects.
5
5
  Author-email: Grey Li <withlihui@gmail.com>
6
6
  License: MIT
@@ -19,6 +19,7 @@ Classifier: Programming Language :: Python :: 3.9
19
19
  Classifier: Programming Language :: Python :: 3.10
20
20
  Classifier: Programming Language :: Python :: 3.11
21
21
  Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
22
23
  Classifier: License :: OSI Approved :: MIT License
23
24
  Classifier: Operating System :: OS Independent
24
25
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
@@ -1253,12 +1253,14 @@ class APIFlask(APIScaffold, Flask):
1253
1253
  if links is not None:
1254
1254
  operation['responses'][status_code]['links'] = links
1255
1255
  if headers_schema is not None:
1256
- headers = self._ma_plugin.converter.schema2parameters( # type: ignore
1256
+ header_params = self._ma_plugin.converter.schema2parameters( # type: ignore
1257
1257
  headers_schema, location='headers'
1258
1258
  )
1259
- operation['responses'][status_code]['headers'] = {
1260
- header['name']: header for header in headers
1261
- }
1259
+ headers = {header['name']: header for header in header_params}
1260
+ for header in headers.values():
1261
+ header.pop('in', None)
1262
+ header.pop('name', None)
1263
+ operation['responses'][status_code]['headers'] = headers
1262
1264
 
1263
1265
  def _add_response_with_schema(
1264
1266
  self,
@@ -6,7 +6,7 @@ from functools import wraps
6
6
 
7
7
  from flask import current_app
8
8
  from flask import jsonify
9
- from flask import request
9
+ from flask import request as flask_request
10
10
  from flask import Response
11
11
  from marshmallow import ValidationError as MarshmallowValidationError
12
12
  from webargs.flaskparser import FlaskParser as BaseFlaskParser
@@ -55,9 +55,14 @@ class FlaskParser(BaseFlaskParser):
55
55
  error_headers,
56
56
  )
57
57
 
58
+ def load_location_data(self, schema: Schema, location: str) -> t.Any:
59
+ """
60
+ Expose the internal `_load_location_data` method to support loading data without validation
61
+ """
62
+ return self._load_location_data(schema=schema, req=flask_request, location=location)
63
+
58
64
 
59
65
  parser: FlaskParser = FlaskParser()
60
- use_args: t.Callable = parser.use_args
61
66
 
62
67
 
63
68
  def _get_files_and_form(request, schema):
@@ -101,46 +106,6 @@ def _generate_schema_from_mapping(schema: DictSchemaType, schema_name: str | Non
101
106
  return Schema.from_dict(schema, name=schema_name)() # type: ignore
102
107
 
103
108
 
104
- def _load_raw_data(location: str = 'json') -> dict[t.Any, t.Any]:
105
- """
106
- Load raw data from the specified location in the request.
107
-
108
- Arguments:
109
- location (str): Specifies the source of the data to load, which can be one of the following:
110
- - `json`: Load data from the request's JSON body.
111
- - `form`: Load data from the request's form.
112
- - `files` or `form_and_files`: Load data from both the request's form and files.
113
- - `json_or_form`: Preferably load data from the request's JSON body or request's form.
114
- - `query`: Load data from the request's query parameters.
115
- - `path`: Load data from the request's view arguments.
116
-
117
- `headers` and `cookies` are not supported by this function.
118
-
119
- Returns:
120
- dict: A dictionary containing the data loaded from the specified location.
121
-
122
- Raises:
123
- ValueError: If the provided location argument is not one of the expected values.
124
- """
125
- if location == 'json':
126
- if request.is_json:
127
- return request.get_json() or {}
128
- return {}
129
- if location == 'form':
130
- return request.form.to_dict()
131
- if location in ['files', 'form_and_files']:
132
- return {**request.files.to_dict(), **request.form.to_dict()}
133
- if location == 'json_or_form':
134
- if request.is_json:
135
- return request.get_json() or {}
136
- return request.form.to_dict()
137
- if location in ['query', 'querystring']:
138
- return request.args.to_dict()
139
- if location == 'path':
140
- return request.view_args or {}
141
- raise ValueError(f'Invalid location argument: {location}')
142
-
143
-
144
109
  class APIScaffold:
145
110
  """A base class for [`APIFlask`][apiflask.app.APIFlask] and
146
111
  [`APIBlueprint`][apiflask.blueprint.APIBlueprint].
@@ -338,16 +303,6 @@ class APIScaffold:
338
303
  def decorator(f):
339
304
  f = _ensure_sync(f)
340
305
 
341
- if not validation:
342
-
343
- @wraps(f)
344
- def wrapper(*args: t.Any, **kwargs: t.Any):
345
- raw_data = _load_raw_data(location)
346
- kwargs[f'{location}_data'] = raw_data
347
- return f(*args, **kwargs)
348
-
349
- return wrapper
350
-
351
306
  is_body_location = location in BODY_LOCATIONS
352
307
  if is_body_location and hasattr(f, '_spec') and 'body' in f._spec:
353
308
  raise RuntimeError(
@@ -394,9 +349,20 @@ class APIScaffold:
394
349
  _annotate(f, omit_default_path_parameters=True)
395
350
  # TODO: Support set example for request parameters
396
351
  f._spec['args'].append((schema, location))
397
- return use_args(
398
- schema, location=location, arg_name=arg_name or f'{location}_data', **kwargs
399
- )(f)
352
+
353
+ arg_name_val = arg_name or f'{location}_data'
354
+
355
+ if not validation:
356
+
357
+ @wraps(f)
358
+ def wrapper(*args: t.Any, **kwargs: t.Any):
359
+ location_data = parser.load_location_data(schema=schema, location=location)
360
+ kwargs[arg_name_val] = location_data
361
+ return f(*args, **kwargs)
362
+
363
+ return wrapper
364
+
365
+ return parser.use_args(schema, location=location, arg_name=arg_name_val, **kwargs)(f)
400
366
 
401
367
  return decorator
402
368
 
@@ -435,117 +435,50 @@ def test_skip_validation(app, client):
435
435
  assert no_validated_rv.json['json_data']['name'] == 'Kitty'
436
436
  assert no_validated_rv.json['json_data']['category'] == 'unknown'
437
437
 
438
+ rv = client.get('/openapi.json')
439
+ assert rv.status_code == 200
440
+ assert (
441
+ rv.json['paths']['/pets_without_validation/{pet_id}']['patch']['requestBody']['content'][
442
+ 'application/json'
443
+ ]['schema']['$ref']
444
+ == '#/components/schemas/PetIn'
445
+ )
446
+ assert 'PetIn' in rv.json['components']['schemas']
438
447
 
439
- def test_location_raw_data(app):
440
- from apiflask.scaffold import _load_raw_data
441
-
442
- # will raises ValueError
443
- location = 'unknown'
444
- with pytest.raises(ValueError, match=f'Invalid location argument: {location}'):
445
- _load_raw_data('unknown')
446
-
447
- # `json` location
448
- with app.test_request_context('/load_raw_data', method='POST', json={'name': 'Kitty'}):
449
- json_data = _load_raw_data()
450
- assert json_data['name'] == 'Kitty'
451
-
452
- with app.test_request_context('/load_raw_data', method='POST', data={'name': 'Kitty'}):
453
- json_data = _load_raw_data()
454
- assert json_data == {}
455
-
456
- # `form` location
457
- with app.test_request_context(
458
- '/load_raw_data',
459
- method='POST',
460
- data={'username': 'Kitty', 'password': '<PASSWORD>'},
461
- content_type='application/x-www-form-urlencoded',
462
- ):
463
- form_data = _load_raw_data('form')
464
- assert form_data['username'] == 'Kitty'
465
- assert form_data['password'] == '<PASSWORD>'
466
-
467
- with app.test_request_context(
468
- '/load_raw_data',
469
- method='POST',
470
- json={'username': 'Kitty', 'password': '<PASSWORD>'},
471
- ):
472
- form_data = _load_raw_data('form')
473
- assert form_data == {}
474
-
475
- # `files` or `form_and_files` location
476
- with app.test_request_context(
477
- '/load_raw_data',
478
- method='POST',
479
- data={'file': (io.BytesIO(b'Hello World!'), 'demo.txt')},
480
- content_type='multipart/form-data',
481
- ):
482
- files_data = _load_raw_data('files')
483
- assert files_data['file'].filename == 'demo.txt'
484
- assert files_data['file'].stream.read() == b'Hello World!'
485
-
486
- with app.test_request_context(
487
- '/load_raw_data',
488
- method='POST',
489
- data={'name': 'Kitty', 'file': (io.BytesIO(b'Hello World2!'), 'demo2.txt')},
490
- content_type='multipart/form-data',
491
- ):
492
- files_and_form_rv = _load_raw_data('form_and_files')
493
- assert files_and_form_rv['file'].filename == 'demo2.txt'
494
- assert files_and_form_rv['file'].stream.read() == b'Hello World2!'
495
- assert files_and_form_rv['name'] == 'Kitty'
496
-
497
- with app.test_request_context(
498
- '/load_raw_data',
499
- method='POST',
500
- json={'username': 'Kitty', 'password': '<PASSWORD>'},
501
- ):
502
- form_and_files_data = _load_raw_data('form_and_files')
503
- assert form_and_files_data == {}
504
-
505
- # `query` location
506
- with app.test_request_context(
507
- '/load_raw_data?name=Kitty',
508
- method='GET',
509
- ):
510
- query_data = _load_raw_data('query')
511
- assert query_data == {'name': 'Kitty'}
512
-
513
- with app.test_request_context(
514
- '/load_raw_data',
515
- method='POST',
516
- ):
517
- query_data = _load_raw_data('query')
518
- assert query_data == {}
519
-
520
- # `json_or_form`
521
- with app.test_request_context(
522
- '/load_raw_data',
523
- data={'username': 'Kitty', 'password': '<PASSWORD>'},
524
- content_type='application/x-www-form-urlencoded',
525
- ):
526
- json_or_form_rv = _load_raw_data('json_or_form')
527
- assert json_or_form_rv['username'] == 'Kitty'
528
- assert json_or_form_rv['password'] == '<PASSWORD>'
529
-
530
- with app.test_request_context(
531
- '/load_raw_data',
532
- json={'currentId': '<current_id>'},
533
- ):
534
- json_or_form_data = _load_raw_data('json_or_form')
535
- json_or_form_data['currentId'] = '<current_id>'
536
-
537
- with app.test_request_context(
538
- '/load_raw_data',
539
- method='POST',
540
- ):
541
- json_or_form_data = _load_raw_data('json_or_form')
542
- assert json_or_form_data == {}
543
-
544
- # `path` location
545
- @app.get('/load_raw_data/<int:pet_id>')
546
- def load_raw_data(pet_id):
547
- return _load_raw_data('path')
548
-
549
- with app.test_request_context('/load_raw_data/1', method='GET'):
550
- path_data = app.dispatch_request()
551
- assert path_data == {'pet_id': 1}
448
+
449
+ @pytest.mark.parametrize('validation', [True, False])
450
+ @pytest.mark.parametrize('payload', [[], [{'bar': 'baz'}], [{'qux': 'baz'}]])
451
+ def test_skip_validation_list_input(app, client, validation, payload):
452
+ class FooIn(Schema):
453
+ bar = String(required=True)
454
+
455
+ @app.put('/foo/bulk')
456
+ @app.input(FooIn(many=True), validation=validation)
457
+ def bulk_put_foo(json_data):
458
+ return json_data
459
+
460
+ rv = client.put('/foo/bulk', json=payload)
461
+ if validation and payload and 'bar' not in payload[0]:
462
+ assert rv.status_code == 422
463
+ else:
464
+ assert rv.status_code == 200
465
+ assert rv.json == payload
466
+
467
+
468
+ @pytest.mark.parametrize('validation', [True, False])
469
+ @pytest.mark.parametrize('payload', [{}, {'bar': 'qux'}])
470
+ def test_skip_validation_arg_name(app, client, validation, payload):
471
+ class FooIn(Schema):
472
+ bar = String(required=True)
473
+
474
+ @app.post('/foo')
475
+ @app.input(FooIn, arg_name='baz', validation=validation)
476
+ def post_foo(baz):
477
+ return baz
478
+
479
+ rv = client.post('/foo', json=payload)
480
+ if validation and not payload:
481
+ assert rv.status_code == 422
482
+ else:
483
+ assert rv.status_code == 200
484
+ assert rv.json == payload
@@ -1,3 +1,6 @@
1
+ import openapi_spec_validator as osv
2
+ import pytest
3
+
1
4
  from .schemas import Foo
2
5
  from .schemas import ResponseHeader
3
6
  from apiflask.fields import Boolean
@@ -25,36 +28,26 @@ def test_spec_with_dict_headers(app, client):
25
28
  rv = client.get('/openapi.json')
26
29
  assert rv.json['paths']['/foo']['get']['responses']['200']['headers'] == {
27
30
  'X-boolean': {
28
- 'name': 'X-boolean',
29
- 'in': 'header',
30
31
  'description': 'A boolean header',
31
32
  'required': False,
32
33
  'schema': {'type': 'boolean'},
33
34
  },
34
35
  'X-integer': {
35
- 'name': 'X-integer',
36
- 'in': 'header',
37
36
  'description': 'An integer header',
38
37
  'required': False,
39
38
  'schema': {'type': 'integer'},
40
39
  },
41
40
  'X-number': {
42
- 'name': 'X-number',
43
- 'in': 'header',
44
41
  'description': 'A number header',
45
42
  'required': False,
46
43
  'schema': {'type': 'number'},
47
44
  },
48
45
  'X-string': {
49
- 'name': 'X-string',
50
- 'in': 'header',
51
46
  'description': 'A string header',
52
47
  'required': False,
53
48
  'schema': {'type': 'string'},
54
49
  },
55
50
  'X-array': {
56
- 'name': 'X-array',
57
- 'in': 'header',
58
51
  'description': 'An array header',
59
52
  'required': False,
60
53
  'schema': {'items': {'type': 'string'}, 'type': 'array'},
@@ -83,10 +76,22 @@ def test_spec_with_schema_headers(app, client):
83
76
  rv = client.get('/openapi.json')
84
77
  assert rv.json['paths']['/foo']['get']['responses']['200']['headers'] == {
85
78
  'X-Token': {
86
- 'name': 'X-Token',
87
- 'in': 'header',
88
79
  'description': 'A custom token header',
89
80
  'required': True,
90
81
  'schema': {'type': 'string'},
91
82
  },
92
83
  }
84
+
85
+
86
+ @pytest.mark.parametrize('openapi_version', ['3.0.0', '3.1.0'])
87
+ def test_spec_validity_with_headers(app, client, openapi_version):
88
+ app.config['OPENAPI_VERSION'] = openapi_version
89
+
90
+ @app.route('/foo')
91
+ @app.output(Foo, headers=ResponseHeader)
92
+ def foo():
93
+ pass
94
+
95
+ rv = client.get('/openapi.json')
96
+ assert rv.status_code == 200
97
+ osv.validate(rv.json)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes