APIFlask 2.3.2__tar.gz → 3.0.0__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 (112) hide show
  1. {apiflask-2.3.2 → apiflask-3.0.0}/CHANGES.md +39 -2
  2. {apiflask-2.3.2 → apiflask-3.0.0}/PKG-INFO +62 -72
  3. {apiflask-2.3.2 → apiflask-3.0.0}/README.md +52 -64
  4. {apiflask-2.3.2 → apiflask-3.0.0}/examples/README.md +5 -0
  5. apiflask-3.0.0/examples/auth/apikey_auth/app.py +58 -0
  6. apiflask-3.0.0/examples/auth/multi_auth/app.py +78 -0
  7. {apiflask-2.3.2 → apiflask-3.0.0}/examples/auth/token_auth/app.py +2 -2
  8. apiflask-3.0.0/examples/base_response/pydantic/app.py +102 -0
  9. {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/index.html +3 -3
  10. apiflask-3.0.0/examples/otel/README.md +39 -0
  11. apiflask-3.0.0/examples/otel/app.py +36 -0
  12. apiflask-3.0.0/examples/otel/requirements.txt +27 -0
  13. apiflask-3.0.0/examples/pydantic/app.py +82 -0
  14. {apiflask-2.3.2 → apiflask-3.0.0}/examples/test_examples.py +8 -6
  15. {apiflask-2.3.2 → apiflask-3.0.0}/pyproject.toml +8 -7
  16. {apiflask-2.3.2 → apiflask-3.0.0}/requirements/dev.txt +13 -13
  17. {apiflask-2.3.2 → apiflask-3.0.0}/requirements/docs.txt +37 -31
  18. {apiflask-2.3.2 → apiflask-3.0.0}/requirements/examples.txt +27 -20
  19. {apiflask-2.3.2 → apiflask-3.0.0}/requirements/min-versions.txt +24 -18
  20. {apiflask-2.3.2 → apiflask-3.0.0}/requirements/tests.txt +35 -28
  21. {apiflask-2.3.2 → apiflask-3.0.0}/requirements/typing.txt +6 -4
  22. {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/PKG-INFO +62 -72
  23. {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/SOURCES.txt +19 -1
  24. {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/requires.txt +4 -3
  25. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/__init__.py +4 -19
  26. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/app.py +280 -62
  27. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/openapi.py +1 -54
  28. apiflask-3.0.0/src/apiflask/openapi_adapters.py +225 -0
  29. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/route.py +1 -1
  30. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/scaffold.py +79 -112
  31. apiflask-3.0.0/src/apiflask/schema_adapters/__init__.py +7 -0
  32. apiflask-3.0.0/src/apiflask/schema_adapters/base.py +90 -0
  33. apiflask-3.0.0/src/apiflask/schema_adapters/marshmallow.py +174 -0
  34. apiflask-3.0.0/src/apiflask/schema_adapters/pydantic.py +232 -0
  35. apiflask-3.0.0/src/apiflask/schema_adapters/registry.py +157 -0
  36. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/schemas.py +2 -20
  37. apiflask-3.0.0/src/apiflask/security.py +480 -0
  38. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/settings.py +7 -7
  39. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/types.py +19 -1
  40. {apiflask-2.3.2 → apiflask-3.0.0}/tests/conftest.py +5 -0
  41. {apiflask-2.3.2 → apiflask-3.0.0}/tests/schemas.py +5 -0
  42. apiflask-3.0.0/tests/test_decorator_auth_required.py +709 -0
  43. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorator_doc.py +16 -8
  44. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorator_input.py +15 -7
  45. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorator_output.py +14 -6
  46. apiflask-3.0.0/tests/test_decoupling_integration.py +369 -0
  47. apiflask-3.0.0/tests/test_openapi_adapters.py +212 -0
  48. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_blueprint.py +13 -0
  49. apiflask-3.0.0/tests/test_openapi_parameters.py +22 -0
  50. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_security.py +99 -9
  51. apiflask-3.0.0/tests/test_pydantic_integration.py +555 -0
  52. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_route.py +22 -0
  53. apiflask-3.0.0/tests/test_schema_adapters.py +337 -0
  54. {apiflask-2.3.2 → apiflask-3.0.0}/tox.ini +10 -1
  55. apiflask-2.3.2/src/apiflask/security.py +0 -169
  56. apiflask-2.3.2/tests/test_decorator_auth_required.py +0 -240
  57. {apiflask-2.3.2 → apiflask-3.0.0}/LICENSE +0 -0
  58. {apiflask-2.3.2 → apiflask-3.0.0}/MANIFEST.in +0 -0
  59. {apiflask-2.3.2 → apiflask-3.0.0}/NOTICE +0 -0
  60. {apiflask-2.3.2 → apiflask-3.0.0}/examples/auth/basic_auth/app.py +0 -0
  61. {apiflask-2.3.2/examples/base_response → apiflask-3.0.0/examples/base_response/marshmallow}/app.py +0 -0
  62. {apiflask-2.3.2 → apiflask-3.0.0}/examples/basic/app.py +0 -0
  63. {apiflask-2.3.2 → apiflask-3.0.0}/examples/blueprint_tags/app.py +0 -0
  64. {apiflask-2.3.2 → apiflask-3.0.0}/examples/cbv/app.py +0 -0
  65. {apiflask-2.3.2 → apiflask-3.0.0}/examples/dataclass/app.py +0 -0
  66. {apiflask-2.3.2 → apiflask-3.0.0}/examples/file_upload/app.py +0 -0
  67. {apiflask-2.3.2 → apiflask-3.0.0}/examples/file_upload/upload/.gitkeep +0 -0
  68. {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/basic/app.py +0 -0
  69. {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/custom_decorators/app.py +0 -0
  70. {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/app.py +0 -0
  71. {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/generate_docs.sh +0 -0
  72. {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/openapi.json +0 -0
  73. {apiflask-2.3.2 → apiflask-3.0.0}/examples/orm/app.py +0 -0
  74. {apiflask-2.3.2 → apiflask-3.0.0}/examples/pagination/app.py +0 -0
  75. {apiflask-2.3.2 → apiflask-3.0.0}/examples/requirements.txt +0 -0
  76. {apiflask-2.3.2 → apiflask-3.0.0}/setup.cfg +0 -0
  77. {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/dependency_links.txt +0 -0
  78. {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/entry_points.txt +0 -0
  79. {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/top_level.txt +0 -0
  80. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/blueprint.py +0 -0
  81. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/commands.py +0 -0
  82. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/exceptions.py +0 -0
  83. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/fields.py +0 -0
  84. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/helpers.py +0 -0
  85. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/py.typed +0 -0
  86. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/ui_templates.py +0 -0
  87. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/validators.py +0 -0
  88. {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/views.py +0 -0
  89. {apiflask-2.3.2 → apiflask-3.0.0}/tests/__init__.py +0 -0
  90. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_app.py +0 -0
  91. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_apps/__init__.py +0 -0
  92. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_async.py +0 -0
  93. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_base_response.py +0 -0
  94. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_blueprint.py +0 -0
  95. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_commands.py +0 -0
  96. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorators.py +0 -0
  97. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_exceptions.py +0 -0
  98. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_fields.py +0 -0
  99. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_helpers.py +0 -0
  100. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_basic.py +0 -0
  101. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_extensions.py +0 -0
  102. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_headers.py +0 -0
  103. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_info.py +0 -0
  104. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_paths.py +0 -0
  105. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_tags.py +0 -0
  106. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_schemas.py +0 -0
  107. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_security.py +0 -0
  108. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_api_docs.py +0 -0
  109. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_auto_behaviour.py +0 -0
  110. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_openapi_fields.py +0 -0
  111. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_openapi_spec.py +0 -0
  112. {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_response_customization.py +0 -0
@@ -1,3 +1,40 @@
1
+ ## Version 3.0.0
2
+
3
+ Released: 2025/11/15<br>Codename: Yixian | 逸仙
4
+
5
+ - Add support for Python 3.14.
6
+ - Drop support for Python 3.8 and PyPy 3.10.
7
+ - Decouple from marshmallow and add an adapter system to support different serialization/deserialization libraries ([pr #690](pr_690)).
8
+ - Add support for Pydantic models as data schemas ([issue #519][issue_519]).
9
+ - Fix subclassed MethodView resources cannot be added as URL rules ([issue #618][issue_618]).
10
+ - Add support for API key auth with `APIKeyHeaderAuth`, `APIKeyCookieAuth`, and `APIKeyQueryAuth`. Add support for runtime selection of authentication methods with `MultiAuth`. Deprecate the API key auth with HTTPTokenAuth ([issue #604][issue_604]).
11
+ - Remove implicit security scheme naming rules ([pr #665](pr_665)).
12
+ - Remove implicit schema naming change (i.e. 'Schema' suffix stripping) ([pr #693](pr_693)).
13
+ - Deprecate the `EmptySchema` class. Use empty dict `{}` instead ([pr #694][pr_694]).
14
+ - Remove the deprecated `__version__` attribute. Use feature detection or `importlib.metadata.version("apiflask")` instead.
15
+ - Fix the support for marshmallow DelimitedList field in OpenAPI spec generation ([issue #416][issue_416]).
16
+
17
+ [issue_519]: https://github.com/apiflask/apiflask/issues/519
18
+ [pr_690]: https://github.com/apiflask/apiflask/pull/690
19
+ [issue_618]: https://github.com/apiflask/apiflask/issues/618
20
+ [issue_604]: https://github.com/apiflask/apiflask/issues/604
21
+ [pr_655]: https://github.com/apiflask/apiflask/pull/655
22
+ [pr_693]: https://github.com/apiflask/apiflask/pull/693
23
+ [pr_694]: https://github.com/apiflask/apiflask/pull/694
24
+ [issue_416]: https://github.com/apiflask/apiflask/issues/416
25
+
26
+
27
+ ## Version 2.4.0
28
+
29
+ Released: 2025/3/25
30
+
31
+ - Add `docs_oauth2_redirect_path_external` parameter to support absolute OAuth2 redirect url ([issue #602][issue_602]).
32
+ - Change the default docs CDN to jsDelivr by default instead of unpkg ([pr #650](pr_650)).
33
+
34
+ [issue_602]: https://github.com/apiflask/apiflask/issues/602
35
+ [pr_650]: https://github.com/apiflask/apiflask/pull/650
36
+
37
+
1
38
  ## Version 2.3.2
2
39
 
3
40
  Released: 2024/12/15
@@ -125,7 +162,7 @@ Released: 2023/8/15
125
162
 
126
163
  ## Version 2.0.0
127
164
 
128
- Released: 2023/7/26<br>Codename: Gongqing
165
+ Released: 2023/7/26<br>Codename: Gongqing | 共青
129
166
 
130
167
  Please see the [migration guide](/migration_guide/#migrate-to-apiflask-2x) for APIFlask 1.x -> 2.0.0.
131
168
 
@@ -321,7 +358,7 @@ Released: 2022/5/17
321
358
 
322
359
  ## Version 1.0.0
323
360
 
324
- Released: 2022/5/4<br>Codename: Wujiaochang
361
+ Released: 2022/5/4<br>Codename: Wujiaochang | 五角场
325
362
 
326
363
  - Remove the deprecated standalone decorators: `input`, `output`, `doc`, and `auth_required`.
327
364
  Use app/blueprint decorators instead (e.g. `input()` -> `app.input()`/`bp.input()`).
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: APIFlask
3
- Version: 2.3.2
4
- Summary: A lightweight web API framework based on Flask and marshmallow-code projects.
3
+ Version: 3.0.0
4
+ Summary: A lightweight web API framework based on Flask.
5
5
  Author-email: Grey Li <withlihui@gmail.com>
6
6
  License: MIT
7
7
  Project-URL: Documentation, https://apiflask.com/docs
@@ -9,17 +9,17 @@ Project-URL: Changes, https://apiflask.com/changelog
9
9
  Project-URL: Source Code, https://github.com/apiflask/apiflask
10
10
  Project-URL: Issue Tracker, https://github.com/apiflask/apiflask/issues
11
11
  Project-URL: Donate, https://opencollective.com/apiflask
12
- Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Environment :: Web Environment
14
14
  Classifier: Framework :: Flask
15
15
  Classifier: Intended Audience :: Developers
16
16
  Classifier: Programming Language :: Python :: 3 :: Only
17
- Classifier: Programming Language :: Python :: 3.8
18
17
  Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
22
21
  Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
23
  Classifier: License :: OSI Approved :: MIT License
24
24
  Classifier: Operating System :: OS Independent
25
25
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
@@ -29,18 +29,20 @@ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
29
29
  Description-Content-Type: text/markdown
30
30
  License-File: LICENSE
31
31
  License-File: NOTICE
32
- Requires-Dist: flask>=2
32
+ Requires-Dist: flask>=2.1.0
33
33
  Requires-Dist: flask-marshmallow>=1.0.0
34
34
  Requires-Dist: marshmallow>=3.20
35
35
  Requires-Dist: webargs>=8.3
36
- Requires-Dist: flask-httpauth>=4
37
- Requires-Dist: apispec>=6
36
+ Requires-Dist: flask-httpauth>=4.8.0
37
+ Requires-Dist: apispec>=6.0.0
38
+ Requires-Dist: pydantic[email]>=2.0
38
39
  Provides-Extra: dotenv
39
40
  Requires-Dist: python-dotenv; extra == "dotenv"
40
41
  Provides-Extra: async
41
42
  Requires-Dist: asgiref>=3.2; extra == "async"
42
43
  Provides-Extra: yaml
43
44
  Requires-Dist: pyyaml; extra == "yaml"
45
+ Dynamic: license-file
44
46
 
45
47
 
46
48
  ![](https://apiflask.com/_assets/apiflask-logo.png)
@@ -49,7 +51,9 @@ Requires-Dist: pyyaml; extra == "yaml"
49
51
 
50
52
  [![Build status](https://github.com/apiflask/apiflask/actions/workflows/tests.yml/badge.svg)](https://github.com/apiflask/apiflask/actions) [![codecov](https://codecov.io/gh/apiflask/apiflask/branch/main/graph/badge.svg?token=2CFPCZ1DMY)](https://codecov.io/gh/apiflask/apiflask)
51
53
 
52
- APIFlask is a lightweight Python web API framework based on [Flask](https://github.com/pallets/flask) and [marshmallow-code](https://github.com/marshmallow-code) projects. It's easy to use, highly customizable, ORM/ODM-agnostic, and 100% compatible with the Flask ecosystem.
54
+ APIFlask is a lightweight Python web API framework based on [Flask](https://github.com/pallets/flask). It's easy to use, highly customizable, ORM/ODM-agnostic, and 100% compatible with the Flask ecosystem.
55
+
56
+ APIFlask supports both [marshmallow](https://github.com/marshmallow-code/marshmallow) schemas and [Pydantic](https://docs.pydantic.dev/) models through a pluggable schema adapter system, giving you the flexibility to choose the validation approach that best fits your project.
53
57
 
54
58
  With APIFlask, you will have:
55
59
 
@@ -64,8 +68,8 @@ With APIFlask, you will have:
64
68
 
65
69
  ## Requirements
66
70
 
67
- - Python 3.8+
68
- - Flask 2.0+
71
+ - Python 3.9+
72
+ - Flask 2.1+
69
73
 
70
74
 
71
75
  ## Installation
@@ -87,11 +91,13 @@ For Windows:
87
91
 
88
92
  - Website: <https://apiflask.com>
89
93
  - Documentation: <https://apiflask.com/docs>
94
+ - 中文文档: <https://zh.apiflask.com/docs>
90
95
  - PyPI Releases: <https://pypi.python.org/pypi/APIFlask>
91
96
  - Change Log: <https://apiflask.com/changelog>
92
97
  - Source Code: <https://github.com/apiflask/apiflask>
93
98
  - Issue Tracker: <https://github.com/apiflask/apiflask/issues>
94
99
  - Discussion: <https://github.com/apiflask/apiflask/discussions>
100
+ - 中文论坛: <https://codekitchen.community>
95
101
  - Twitter: <https://twitter.com/apiflask>
96
102
  - Open Collective: <https://opencollective.com/apiflask>
97
103
 
@@ -166,63 +172,70 @@ def update_pet(pet_id, json_data):
166
172
  ```
167
173
 
168
174
  <details>
169
- <summary>You can also use class-based views based on <code>MethodView</code></summary>
175
+ <summary>You can use Pydantic models for type-hint based validation</summary>
170
176
 
171
177
  ```python
172
- from apiflask import APIFlask, Schema, abort
173
- from apiflask.fields import Integer, String
174
- from apiflask.validators import Length, OneOf
175
- from flask.views import MethodView
178
+ from enum import Enum
179
+
180
+ from apiflask import APIFlask, abort
181
+ from pydantic import BaseModel, Field
176
182
 
177
183
  app = APIFlask(__name__)
178
184
 
179
- pets = [
180
- {'id': 0, 'name': 'Kitty', 'category': 'cat'},
181
- {'id': 1, 'name': 'Coco', 'category': 'dog'}
182
- ]
183
185
 
186
+ class PetCategory(str, Enum):
187
+ DOG = 'dog'
188
+ CAT = 'cat'
184
189
 
185
- class PetIn(Schema):
186
- name = String(required=True, validate=Length(0, 10))
187
- category = String(required=True, validate=OneOf(['dog', 'cat']))
188
190
 
191
+ class PetOut(BaseModel):
192
+ id: int
193
+ name: str
194
+ category: PetCategory
189
195
 
190
- class PetOut(Schema):
191
- id = Integer()
192
- name = String()
193
- category = String()
194
196
 
197
+ class PetIn(BaseModel):
198
+ name: str = Field(min_length=1, max_length=50)
199
+ category: PetCategory
200
+
201
+
202
+ pets = [
203
+ {'id': 0, 'name': 'Kitty', 'category': 'cat'},
204
+ {'id': 1, 'name': 'Coco', 'category': 'dog'}
205
+ ]
195
206
 
196
- class Hello(MethodView):
197
207
 
198
- # use HTTP method name as class method name
199
- def get(self):
200
- return {'message': 'Hello!'}
208
+ @app.get('/')
209
+ def say_hello():
210
+ return {'message': 'Hello, Pydantic!'}
201
211
 
202
212
 
203
- class Pet(MethodView):
213
+ @app.get('/pets')
214
+ @app.output(list[PetOut])
215
+ def get_pets():
216
+ return pets
204
217
 
205
- @app.output(PetOut)
206
- def get(self, pet_id):
207
- """Get a pet"""
208
- if pet_id > len(pets) - 1:
209
- abort(404)
210
- return pets[pet_id]
211
218
 
212
- @app.input(PetIn(partial=True))
213
- @app.output(PetOut)
214
- def patch(self, pet_id, json_data):
215
- """Update a pet"""
216
- if pet_id > len(pets) - 1:
217
- abort(404)
218
- for attr, value in json_data.items():
219
- pets[pet_id][attr] = value
220
- return pets[pet_id]
219
+ @app.get('/pets/<int:pet_id>')
220
+ @app.output(PetOut)
221
+ def get_pet(pet_id: int):
222
+ if pet_id > len(pets) or pet_id < 1:
223
+ abort(404)
224
+ return pets[pet_id - 1]
221
225
 
222
226
 
223
- app.add_url_rule('/', view_func=Hello.as_view('hello'))
224
- app.add_url_rule('/pets/<int:pet_id>', view_func=Pet.as_view('pet'))
227
+ @app.post('/pets')
228
+ @app.input(PetIn, location='json')
229
+ @app.output(PetOut, status_code=201)
230
+ def create_pet(json_data: PetIn):
231
+ # the validated and parsed input data will
232
+ # be injected into the view function as a Pydantic model instance
233
+ new_id = len(pets) + 1
234
+ new_pet = PetOut(id=new_id, name=json_data.name, category=json_data.category)
235
+ pets.append(new_pet)
236
+ return new_pet
225
237
  ```
238
+
226
239
  </details>
227
240
 
228
241
  <details>
@@ -252,12 +265,6 @@ See <em><a href="https://flask.palletsprojects.com/async-await">Using async and
252
265
 
253
266
  Save this as `app.py`, then run it with:
254
267
 
255
- ```bash
256
- $ flask run --reload
257
- ```
258
-
259
- Or run in debug mode:
260
-
261
268
  ```bash
262
269
  $ flask run --debug
263
270
  ```
@@ -331,23 +338,6 @@ def hello():
331
338
  In a word, to make Web API development in Flask more easily, APIFlask provides `APIFlask` and `APIBlueprint` to extend Flask's `Flask` and `Blueprint` objects and it also ships with some helpful utilities. Other than that, you are actually using Flask.
332
339
 
333
340
 
334
- ## Relationship with marshmallow
335
-
336
- APIFlask accepts marshmallow schema as data schema, uses webargs to validate the request data against the schema, and uses apispec to generate the OpenAPI representation from the schema.
337
-
338
- You can build marshmallow schemas just like before, but APIFlask also exposes some marshmallow APIs for convenience:
339
-
340
- - `apiflask.Schema`: The base marshmallow schema class.
341
- - `apiflask.fields`: The marshmallow fields, contain the fields from both marshmallow and Flask-Marshmallow. Beware that the aliases (`Url`, `Str`, `Int`, `Bool`, etc.) were removed.
342
- - `apiflask.validators`: The marshmallow validators.
343
-
344
- ```python
345
- from apiflask import Schema
346
- from apiflask.fields import Integer, String
347
- from apiflask.validators import Length, OneOf
348
- from marshmallow import pre_load, post_dump, ValidationError
349
- ```
350
-
351
341
  ## Credits
352
342
 
353
343
  APIFlask starts as a fork of [APIFairy](https://github.com/miguelgrinberg/APIFairy) and is inspired by [flask-smorest](https://github.com/marshmallow-code/flask-smorest) and [FastAPI](https://github.com/tiangolo/fastapi) (see *[Comparison and Motivations](https://apiflask.com/comparison)* for the comparison between these projects).
@@ -5,7 +5,9 @@
5
5
 
6
6
  [![Build status](https://github.com/apiflask/apiflask/actions/workflows/tests.yml/badge.svg)](https://github.com/apiflask/apiflask/actions) [![codecov](https://codecov.io/gh/apiflask/apiflask/branch/main/graph/badge.svg?token=2CFPCZ1DMY)](https://codecov.io/gh/apiflask/apiflask)
7
7
 
8
- APIFlask is a lightweight Python web API framework based on [Flask](https://github.com/pallets/flask) and [marshmallow-code](https://github.com/marshmallow-code) projects. It's easy to use, highly customizable, ORM/ODM-agnostic, and 100% compatible with the Flask ecosystem.
8
+ APIFlask is a lightweight Python web API framework based on [Flask](https://github.com/pallets/flask). It's easy to use, highly customizable, ORM/ODM-agnostic, and 100% compatible with the Flask ecosystem.
9
+
10
+ APIFlask supports both [marshmallow](https://github.com/marshmallow-code/marshmallow) schemas and [Pydantic](https://docs.pydantic.dev/) models through a pluggable schema adapter system, giving you the flexibility to choose the validation approach that best fits your project.
9
11
 
10
12
  With APIFlask, you will have:
11
13
 
@@ -20,8 +22,8 @@ With APIFlask, you will have:
20
22
 
21
23
  ## Requirements
22
24
 
23
- - Python 3.8+
24
- - Flask 2.0+
25
+ - Python 3.9+
26
+ - Flask 2.1+
25
27
 
26
28
 
27
29
  ## Installation
@@ -43,11 +45,13 @@ For Windows:
43
45
 
44
46
  - Website: <https://apiflask.com>
45
47
  - Documentation: <https://apiflask.com/docs>
48
+ - 中文文档: <https://zh.apiflask.com/docs>
46
49
  - PyPI Releases: <https://pypi.python.org/pypi/APIFlask>
47
50
  - Change Log: <https://apiflask.com/changelog>
48
51
  - Source Code: <https://github.com/apiflask/apiflask>
49
52
  - Issue Tracker: <https://github.com/apiflask/apiflask/issues>
50
53
  - Discussion: <https://github.com/apiflask/apiflask/discussions>
54
+ - 中文论坛: <https://codekitchen.community>
51
55
  - Twitter: <https://twitter.com/apiflask>
52
56
  - Open Collective: <https://opencollective.com/apiflask>
53
57
 
@@ -122,63 +126,70 @@ def update_pet(pet_id, json_data):
122
126
  ```
123
127
 
124
128
  <details>
125
- <summary>You can also use class-based views based on <code>MethodView</code></summary>
129
+ <summary>You can use Pydantic models for type-hint based validation</summary>
126
130
 
127
131
  ```python
128
- from apiflask import APIFlask, Schema, abort
129
- from apiflask.fields import Integer, String
130
- from apiflask.validators import Length, OneOf
131
- from flask.views import MethodView
132
+ from enum import Enum
133
+
134
+ from apiflask import APIFlask, abort
135
+ from pydantic import BaseModel, Field
132
136
 
133
137
  app = APIFlask(__name__)
134
138
 
135
- pets = [
136
- {'id': 0, 'name': 'Kitty', 'category': 'cat'},
137
- {'id': 1, 'name': 'Coco', 'category': 'dog'}
138
- ]
139
139
 
140
+ class PetCategory(str, Enum):
141
+ DOG = 'dog'
142
+ CAT = 'cat'
140
143
 
141
- class PetIn(Schema):
142
- name = String(required=True, validate=Length(0, 10))
143
- category = String(required=True, validate=OneOf(['dog', 'cat']))
144
144
 
145
+ class PetOut(BaseModel):
146
+ id: int
147
+ name: str
148
+ category: PetCategory
145
149
 
146
- class PetOut(Schema):
147
- id = Integer()
148
- name = String()
149
- category = String()
150
150
 
151
+ class PetIn(BaseModel):
152
+ name: str = Field(min_length=1, max_length=50)
153
+ category: PetCategory
154
+
155
+
156
+ pets = [
157
+ {'id': 0, 'name': 'Kitty', 'category': 'cat'},
158
+ {'id': 1, 'name': 'Coco', 'category': 'dog'}
159
+ ]
151
160
 
152
- class Hello(MethodView):
153
161
 
154
- # use HTTP method name as class method name
155
- def get(self):
156
- return {'message': 'Hello!'}
162
+ @app.get('/')
163
+ def say_hello():
164
+ return {'message': 'Hello, Pydantic!'}
157
165
 
158
166
 
159
- class Pet(MethodView):
167
+ @app.get('/pets')
168
+ @app.output(list[PetOut])
169
+ def get_pets():
170
+ return pets
160
171
 
161
- @app.output(PetOut)
162
- def get(self, pet_id):
163
- """Get a pet"""
164
- if pet_id > len(pets) - 1:
165
- abort(404)
166
- return pets[pet_id]
167
172
 
168
- @app.input(PetIn(partial=True))
169
- @app.output(PetOut)
170
- def patch(self, pet_id, json_data):
171
- """Update a pet"""
172
- if pet_id > len(pets) - 1:
173
- abort(404)
174
- for attr, value in json_data.items():
175
- pets[pet_id][attr] = value
176
- return pets[pet_id]
173
+ @app.get('/pets/<int:pet_id>')
174
+ @app.output(PetOut)
175
+ def get_pet(pet_id: int):
176
+ if pet_id > len(pets) or pet_id < 1:
177
+ abort(404)
178
+ return pets[pet_id - 1]
177
179
 
178
180
 
179
- app.add_url_rule('/', view_func=Hello.as_view('hello'))
180
- app.add_url_rule('/pets/<int:pet_id>', view_func=Pet.as_view('pet'))
181
+ @app.post('/pets')
182
+ @app.input(PetIn, location='json')
183
+ @app.output(PetOut, status_code=201)
184
+ def create_pet(json_data: PetIn):
185
+ # the validated and parsed input data will
186
+ # be injected into the view function as a Pydantic model instance
187
+ new_id = len(pets) + 1
188
+ new_pet = PetOut(id=new_id, name=json_data.name, category=json_data.category)
189
+ pets.append(new_pet)
190
+ return new_pet
181
191
  ```
192
+
182
193
  </details>
183
194
 
184
195
  <details>
@@ -208,12 +219,6 @@ See <em><a href="https://flask.palletsprojects.com/async-await">Using async and
208
219
 
209
220
  Save this as `app.py`, then run it with:
210
221
 
211
- ```bash
212
- $ flask run --reload
213
- ```
214
-
215
- Or run in debug mode:
216
-
217
222
  ```bash
218
223
  $ flask run --debug
219
224
  ```
@@ -287,23 +292,6 @@ def hello():
287
292
  In a word, to make Web API development in Flask more easily, APIFlask provides `APIFlask` and `APIBlueprint` to extend Flask's `Flask` and `Blueprint` objects and it also ships with some helpful utilities. Other than that, you are actually using Flask.
288
293
 
289
294
 
290
- ## Relationship with marshmallow
291
-
292
- APIFlask accepts marshmallow schema as data schema, uses webargs to validate the request data against the schema, and uses apispec to generate the OpenAPI representation from the schema.
293
-
294
- You can build marshmallow schemas just like before, but APIFlask also exposes some marshmallow APIs for convenience:
295
-
296
- - `apiflask.Schema`: The base marshmallow schema class.
297
- - `apiflask.fields`: The marshmallow fields, contain the fields from both marshmallow and Flask-Marshmallow. Beware that the aliases (`Url`, `Str`, `Int`, `Bool`, etc.) were removed.
298
- - `apiflask.validators`: The marshmallow validators.
299
-
300
- ```python
301
- from apiflask import Schema
302
- from apiflask.fields import Integer, String
303
- from apiflask.validators import Length, OneOf
304
- from marshmallow import pre_load, post_dump, ValidationError
305
- ```
306
-
307
295
  ## Credits
308
296
 
309
297
  APIFlask starts as a fork of [APIFairy](https://github.com/miguelgrinberg/APIFairy) and is inspired by [flask-smorest](https://github.com/marshmallow-code/flask-smorest) and [FastAPI](https://github.com/tiangolo/fastapi) (see *[Comparison and Motivations](https://apiflask.com/comparison)* for the comparison between these projects).
@@ -9,7 +9,9 @@
9
9
  - Token auth example: [/examples/auth/token_auth/app.py][_token_auth]
10
10
  - Basic auth example: [/examples/auth/basic_auth/app.py][_basic_auth]
11
11
  - Dataclass example (with marshmallow-dataclass): [/examples/dataclass/app.py][_dataclass]
12
+ - Pydantic example (with Pydantic models): [/examples/pydantic/app.py][_pydantic]
12
13
  - File upload example: [/examples/file_upload/app.py][_file_upload]
14
+ - OpenTelemetry example: [/examples/otel/app.py][_OpenTelemetry]
13
15
 
14
16
  [_basic]: https://github.com/apiflask/apiflask/tree/main/examples/basic/app.py
15
17
  [_cbv]: https://github.com/apiflask/apiflask/tree/main/examples/cbv/app.py
@@ -20,7 +22,9 @@
20
22
  [_token_auth]: https://github.com/apiflask/apiflask/tree/main/examples/auth/token_auth/app.py
21
23
  [_basic_auth]: https://github.com/apiflask/apiflask/tree/main/examples/auth/basic_auth/app.py
22
24
  [_dataclass]: https://github.com/apiflask/apiflask/tree/main/examples/dataclass/app.py
25
+ [_pydantic]: https://github.com/apiflask/apiflask/tree/main/examples/pydantic/app.py
23
26
  [_file_upload]: https://github.com/apiflask/apiflask/tree/main/examples/file_upload/app.py
27
+ [_OpenTelemetry]: https://github.com/apiflask/apiflask/tree/main/examples/otel/app.py
24
28
 
25
29
  If you have built an application with APIFlask, feel free to submit a pull request to add the source link here.
26
30
 
@@ -68,6 +72,7 @@ Each example application store in a sub-folder:
68
72
  - `/openapi/static_docs`: OpenAPI example with standalone static HTML OpenAPI docs
69
73
  - `/base_response`: Base response example
70
74
  - `/dataclass`: Dataclass example (with marshmallow-dataclass)
75
+ - `/pydantic`: Pydantic example (with Pydantic models)
71
76
 
72
77
  To run a specific example, you have to change into the corresponding folder.
73
78
  For example, if you want to run the basic example:
@@ -0,0 +1,58 @@
1
+ import typing as t
2
+ from flask import current_app
3
+ from apiflask import APIFlask, APIKeyHeaderAuth, abort
4
+ from authlib.jose import jwt, JoseError
5
+
6
+ app = APIFlask(__name__)
7
+ auth = APIKeyHeaderAuth()
8
+ app.config['SECRET_KEY'] = 'secret-key'
9
+
10
+
11
+ class User:
12
+ def __init__(self, id: int, username: str):
13
+ self.id = id
14
+ self.username = username
15
+
16
+ def get_apikey(self):
17
+ header = {'alg': 'HS256'}
18
+ payload = {'id': self.id}
19
+ return jwt.encode(header, payload, current_app.config['SECRET_KEY']).decode()
20
+
21
+
22
+ users = [
23
+ User(1, 'lorem'),
24
+ User(2, 'ipsum'),
25
+ ]
26
+
27
+
28
+ def get_user_by_id(id: int) -> t.Union[User, None]:
29
+ return tuple(filter(lambda u: u.id == id, users))[0]
30
+
31
+
32
+ @auth.verify_token
33
+ def verify_apikey(apikey: str) -> t.Union[User, None]:
34
+ try:
35
+ data = jwt.decode(
36
+ apikey.encode('ascii'),
37
+ current_app.config['SECRET_KEY'],
38
+ )
39
+ id = data['id']
40
+ user = get_user_by_id(id)
41
+ except JoseError:
42
+ return None
43
+ except IndexError:
44
+ return None
45
+ return user
46
+
47
+
48
+ @app.post('/apikey/<int:id>')
49
+ def get_token(id: int):
50
+ if get_user_by_id(id) is None:
51
+ abort(404)
52
+ return {'api_key': f'{get_user_by_id(id).get_apikey()}'}
53
+
54
+
55
+ @app.get('/name')
56
+ @app.auth_required(auth)
57
+ def get_username():
58
+ return auth.current_user.username
@@ -0,0 +1,78 @@
1
+ import typing as t
2
+
3
+ from flask import current_app
4
+ from apiflask import APIFlask, HTTPBasicAuth, HTTPTokenAuth, MultiAuth, Schema, abort
5
+ from werkzeug.security import generate_password_hash, check_password_hash
6
+ from apiflask.fields import String
7
+ from authlib.jose import jwt, JoseError
8
+
9
+ app = APIFlask(__name__)
10
+ basic_auth = HTTPBasicAuth()
11
+ token_auth = HTTPTokenAuth()
12
+ multi_auth = MultiAuth(basic_auth, token_auth)
13
+ app.config['SECRET_KEY'] = 'secret-key'
14
+
15
+
16
+ class User:
17
+ def __init__(self, id: int, username: str, password: str):
18
+ self.id = id
19
+ self.username = username
20
+ self.password = generate_password_hash(password)
21
+
22
+ def get_token(self):
23
+ header = {'alg': 'HS256'}
24
+ payload = {'id': self.id}
25
+ return jwt.encode(header, payload, current_app.config['SECRET_KEY']).decode()
26
+
27
+
28
+ users = [
29
+ User(1, 'lorem', 'foo'),
30
+ User(2, 'ipsum', 'bar'),
31
+ ]
32
+
33
+ username_map = {user.username: user for user in users}
34
+
35
+
36
+ def get_user_by_id(id: int) -> t.Union[User, None]:
37
+ return tuple(filter(lambda u: u.id == id, users))[0]
38
+
39
+
40
+ @basic_auth.verify_password
41
+ def verify_password(username: str, password: str) -> t.Union[User, None]:
42
+ if username in username_map and check_password_hash(username_map[username].password, password):
43
+ return username_map[username]
44
+ return None
45
+
46
+
47
+ @token_auth.verify_token
48
+ def verify_token(token: str) -> t.Union[User, None]:
49
+ try:
50
+ data = jwt.decode(
51
+ token.encode('ascii'),
52
+ current_app.config['SECRET_KEY'],
53
+ )
54
+ id = data['id']
55
+ user = get_user_by_id(id)
56
+ except JoseError:
57
+ return None
58
+ except IndexError:
59
+ return None
60
+ return user
61
+
62
+
63
+ class Token(Schema):
64
+ token = String()
65
+
66
+
67
+ @app.post('/token/<int:id>')
68
+ @app.output(Token)
69
+ def get_token(id: int):
70
+ if get_user_by_id(id) is None:
71
+ abort(404)
72
+ return {'token': f'Bearer {get_user_by_id(id).get_token()}'}
73
+
74
+
75
+ @app.route('/')
76
+ @app.auth_required(multi_auth)
77
+ def index():
78
+ return f'Hello, {multi_auth.current_user.username}'
@@ -59,7 +59,7 @@ def get_token(id: int):
59
59
  return {'token': f'Bearer {get_user_by_id(id).get_token()}'}
60
60
 
61
61
 
62
- @app.get('/name/<int:id>')
62
+ @app.get('/name')
63
63
  @app.auth_required(auth)
64
- def get_secret(id):
64
+ def get_secret():
65
65
  return auth.current_user.secret