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.
- {apiflask-2.3.2 → apiflask-3.0.0}/CHANGES.md +39 -2
- {apiflask-2.3.2 → apiflask-3.0.0}/PKG-INFO +62 -72
- {apiflask-2.3.2 → apiflask-3.0.0}/README.md +52 -64
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/README.md +5 -0
- apiflask-3.0.0/examples/auth/apikey_auth/app.py +58 -0
- apiflask-3.0.0/examples/auth/multi_auth/app.py +78 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/auth/token_auth/app.py +2 -2
- apiflask-3.0.0/examples/base_response/pydantic/app.py +102 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/index.html +3 -3
- apiflask-3.0.0/examples/otel/README.md +39 -0
- apiflask-3.0.0/examples/otel/app.py +36 -0
- apiflask-3.0.0/examples/otel/requirements.txt +27 -0
- apiflask-3.0.0/examples/pydantic/app.py +82 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/test_examples.py +8 -6
- {apiflask-2.3.2 → apiflask-3.0.0}/pyproject.toml +8 -7
- {apiflask-2.3.2 → apiflask-3.0.0}/requirements/dev.txt +13 -13
- {apiflask-2.3.2 → apiflask-3.0.0}/requirements/docs.txt +37 -31
- {apiflask-2.3.2 → apiflask-3.0.0}/requirements/examples.txt +27 -20
- {apiflask-2.3.2 → apiflask-3.0.0}/requirements/min-versions.txt +24 -18
- {apiflask-2.3.2 → apiflask-3.0.0}/requirements/tests.txt +35 -28
- {apiflask-2.3.2 → apiflask-3.0.0}/requirements/typing.txt +6 -4
- {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/PKG-INFO +62 -72
- {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/SOURCES.txt +19 -1
- {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/requires.txt +4 -3
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/__init__.py +4 -19
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/app.py +280 -62
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/openapi.py +1 -54
- apiflask-3.0.0/src/apiflask/openapi_adapters.py +225 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/route.py +1 -1
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/scaffold.py +79 -112
- apiflask-3.0.0/src/apiflask/schema_adapters/__init__.py +7 -0
- apiflask-3.0.0/src/apiflask/schema_adapters/base.py +90 -0
- apiflask-3.0.0/src/apiflask/schema_adapters/marshmallow.py +174 -0
- apiflask-3.0.0/src/apiflask/schema_adapters/pydantic.py +232 -0
- apiflask-3.0.0/src/apiflask/schema_adapters/registry.py +157 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/schemas.py +2 -20
- apiflask-3.0.0/src/apiflask/security.py +480 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/settings.py +7 -7
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/types.py +19 -1
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/conftest.py +5 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/schemas.py +5 -0
- apiflask-3.0.0/tests/test_decorator_auth_required.py +709 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorator_doc.py +16 -8
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorator_input.py +15 -7
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorator_output.py +14 -6
- apiflask-3.0.0/tests/test_decoupling_integration.py +369 -0
- apiflask-3.0.0/tests/test_openapi_adapters.py +212 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_blueprint.py +13 -0
- apiflask-3.0.0/tests/test_openapi_parameters.py +22 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_security.py +99 -9
- apiflask-3.0.0/tests/test_pydantic_integration.py +555 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_route.py +22 -0
- apiflask-3.0.0/tests/test_schema_adapters.py +337 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tox.ini +10 -1
- apiflask-2.3.2/src/apiflask/security.py +0 -169
- apiflask-2.3.2/tests/test_decorator_auth_required.py +0 -240
- {apiflask-2.3.2 → apiflask-3.0.0}/LICENSE +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/MANIFEST.in +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/NOTICE +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/auth/basic_auth/app.py +0 -0
- {apiflask-2.3.2/examples/base_response → apiflask-3.0.0/examples/base_response/marshmallow}/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/basic/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/blueprint_tags/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/cbv/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/dataclass/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/file_upload/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/file_upload/upload/.gitkeep +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/basic/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/custom_decorators/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/generate_docs.sh +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/openapi/static_docs/openapi.json +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/orm/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/pagination/app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/examples/requirements.txt +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/setup.cfg +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/dependency_links.txt +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/entry_points.txt +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/APIFlask.egg-info/top_level.txt +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/blueprint.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/commands.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/exceptions.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/fields.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/helpers.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/py.typed +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/ui_templates.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/validators.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/src/apiflask/views.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/__init__.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_app.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_apps/__init__.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_async.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_base_response.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_blueprint.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_commands.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_decorators.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_exceptions.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_fields.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_helpers.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_basic.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_extensions.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_headers.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_info.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_paths.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_openapi_tags.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_schemas.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_security.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_api_docs.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_auto_behaviour.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_openapi_fields.py +0 -0
- {apiflask-2.3.2 → apiflask-3.0.0}/tests/test_settings_openapi_spec.py +0 -0
- {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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: APIFlask
|
|
3
|
-
Version:
|
|
4
|
-
Summary: A lightweight web API framework based on Flask
|
|
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 ::
|
|
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
|

|
|
@@ -49,7 +51,9 @@ Requires-Dist: pyyaml; extra == "yaml"
|
|
|
49
51
|
|
|
50
52
|
[](https://github.com/apiflask/apiflask/actions) [](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)
|
|
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.
|
|
68
|
-
- Flask 2.
|
|
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
|
|
175
|
+
<summary>You can use Pydantic models for type-hint based validation</summary>
|
|
170
176
|
|
|
171
177
|
```python
|
|
172
|
-
from
|
|
173
|
-
|
|
174
|
-
from apiflask
|
|
175
|
-
from
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
@app.get('/')
|
|
209
|
+
def say_hello():
|
|
210
|
+
return {'message': 'Hello, Pydantic!'}
|
|
201
211
|
|
|
202
212
|
|
|
203
|
-
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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.
|
|
224
|
-
app.
|
|
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
|
[](https://github.com/apiflask/apiflask/actions) [](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)
|
|
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.
|
|
24
|
-
- Flask 2.
|
|
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
|
|
129
|
+
<summary>You can use Pydantic models for type-hint based validation</summary>
|
|
126
130
|
|
|
127
131
|
```python
|
|
128
|
-
from
|
|
129
|
-
|
|
130
|
-
from apiflask
|
|
131
|
-
from
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
162
|
+
@app.get('/')
|
|
163
|
+
def say_hello():
|
|
164
|
+
return {'message': 'Hello, Pydantic!'}
|
|
157
165
|
|
|
158
166
|
|
|
159
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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.
|
|
180
|
-
app.
|
|
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}'
|