Skip to content

APIFlask

Bases: APIScaffold, Flask

The Flask object with some web API support.

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

Attributes:

Name Type Description
openapi_version str

The version of OpenAPI Specification (openapi.openapi). This attribute can also be configured from the config with the OPENAPI_VERSION configuration key. Defaults to '3.0.3'.

servers list[dict[str, str]] | None

The servers information of the API (openapi.servers), accepts multiple server dicts. Example value:

app.servers = [
    {
        'name': 'Production Server',
        'url': 'http://api.example.com'
    }
]

This attribute can also be configured from the config with the SERVERS configuration key. Defaults to None.

tags TagsType | None

The list of tags of the OpenAPI spec documentation (openapi.tags), accepts a list of dicts. You can also pass a simple list contains the tag name:

app.tags = ['foo', 'bar', 'baz']

A standard OpenAPI tags list will look like this:

app.tags = [
    {'name': 'foo', 'description': 'The description of foo'},
    {'name': 'bar', 'description': 'The description of bar'},
    {'name': 'baz', 'description': 'The description of baz'}
]

If not set, the blueprint names will be used as tags.

This attribute can also be configured from the config with the TAGS configuration key. Defaults to None.

external_docs dict[str, str] | None

The external documentation information of the API (openapi.externalDocs). Example:

app.external_docs = {
    'description': 'Find more info here',
    'url': 'http://docs.example.com'
}

This attribute can also be configured from the config with the EXTERNAL_DOCS configuration key. Defaults to None.

info dict[str, str | dict] | None

The info object (openapi.info), it accepts a dict contains following info fields: description, termsOfService, contact, license. You can use separate configuration variables to overwrite this dict. Example:

app.info = {
    'description': '...',
    'termsOfService': 'http://example.com',
    'contact': {
        'name': 'API Support',
        'url': 'http://www.example.com/support',
        'email': 'support@example.com'
    },
    'license': {
        'name': 'Apache 2.0',
        'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
    }
}
description str | None

The description of the API (openapi.info.description). This attribute can also be configured from the config with the DESCRIPTION configuration key. Defaults to None.

contact dict[str, str] | None

The contact information of the API (openapi.info.contact). Example:

app.contact = {
    'name': 'API Support',
    'url': 'http://www.example.com/support',
    'email': 'support@example.com'
}

This attribute can also be configured from the config with the CONTACT configuration key. Defaults to None.

license dict[str, str] | None

The license of the API (openapi.info.license). Example:

app.license = {
    'name': 'Apache 2.0',
    'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
}

This attribute can also be configured from the config with the LICENSE configuration key. Defaults to None.

terms_of_service str | None

The terms of service URL of the API (openapi.info.termsOfService). Example:

app.terms_of_service = 'http://example.com/terms/'

This attribute can also be configured from the config with the TERMS_OF_SERVICE configuration key. Defaults to None.

security_schemes dict[str, Any] | None

The security schemes of the API (openapi.components.securitySchemes). Example:

app.security_schemes = [
    'ApiKeyAuth': {
        'type': 'apiKey',
        'in': 'header',
        'name': 'X-API-Key'
    }
]

This attribute can also be configured from the config with the SECURITY_SCHEMES configuration key. Defaults to None.

spec_callback SpecCallbackType | None

It stores the function object registered by spec_processor. You can also pass a callback function to it directly without using spec_processor. Example:

def update_spec(spec):
    spec['title'] = 'Updated Title'
    return spec

app.spec_callback = update_spec
error_callback ErrorCallbackType

It stores the function object registered by error_processor. You can also pass a callback function to it directly without using error_processor. See the docstring of error_processor for more details. Example:

def my_error_handler(error):
    return {
        'status_code': error.status_code,
        'message': error.message,
        'detail': error.detail
    }, error.status_code, error.headers

app.error_processor = my_error_handler
schema_name_resolver

It stores the function that used to decided the schema name. The schema name resolver should accept the schema object as argument and return the name. Example:

# this is the default schema name resolver used in APIFlask
def schema_name_resolver(schema):
    name = schema.__class__.__name__  # get schema class name
    if name.endswith('Schema'):  # remove the "Schema" suffix
        name = name[:-6] or name
    if schema.partial:  # add a "Update" suffix for partial schema
        name += 'Update'
    return name

app.schema_name_resolver = schema_name_resolver

Version changed: 1.0

  • Add instance attribute security_schemes as an alias of config SECURITY_SCHEMES.

Version changed: 0.9.0

  • Add instance attribute schema_name_resolver.
Source code in apiflask/app.py
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
@route_patch
class APIFlask(APIScaffold, Flask):
    """The `Flask` object with some web API support.

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)
    ```

    Attributes:
        openapi_version: The version of OpenAPI Specification (openapi.openapi).
            This attribute can also be configured from the config with the
            `OPENAPI_VERSION` configuration key. Defaults to `'3.0.3'`.
        servers: The servers information of the API (openapi.servers), accepts
            multiple server dicts. Example value:

            ```python
            app.servers = [
                {
                    'name': 'Production Server',
                    'url': 'http://api.example.com'
                }
            ]
            ```

            This attribute can also be configured from the config with the
            `SERVERS` configuration key. Defaults to `None`.
        tags: The list of tags of the OpenAPI spec documentation (openapi.tags),
            accepts a list of dicts. You can also pass a simple list contains the
            tag name:

            ```python
            app.tags = ['foo', 'bar', 'baz']
            ```

            A standard OpenAPI tags list will look like this:

            ```python
            app.tags = [
                {'name': 'foo', 'description': 'The description of foo'},
                {'name': 'bar', 'description': 'The description of bar'},
                {'name': 'baz', 'description': 'The description of baz'}
            ]
            ```

            If not set, the blueprint names will be used as tags.

            This attribute can also be configured from the config with the
            `TAGS` configuration key. Defaults to `None`.
        external_docs: The external documentation information of the API
            (openapi.externalDocs). Example:

            ```python
            app.external_docs = {
                'description': 'Find more info here',
                'url': 'http://docs.example.com'
            }
            ```

            This attribute can also be configured from the config with the
            `EXTERNAL_DOCS` configuration key. Defaults to `None`.
        info: The info object (openapi.info), it accepts a dict contains following info fields:
            `description`, `termsOfService`, `contact`, `license`. You can use separate
            configuration variables to overwrite this dict. Example:

            ```python
            app.info = {
                'description': '...',
                'termsOfService': 'http://example.com',
                'contact': {
                    'name': 'API Support',
                    'url': 'http://www.example.com/support',
                    'email': 'support@example.com'
                },
                'license': {
                    'name': 'Apache 2.0',
                    'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
                }
            }
            ```

        description: The description of the API (openapi.info.description).
            This attribute can also be configured from the config with the
            `DESCRIPTION` configuration key. Defaults to `None`.
        contact: The contact information of the API (openapi.info.contact). Example:

            ```python
            app.contact = {
                'name': 'API Support',
                'url': 'http://www.example.com/support',
                'email': 'support@example.com'
            }
            ```

            This attribute can also be configured from the config with the
            `CONTACT` configuration key. Defaults to `None`.
        license: The license of the API (openapi.info.license). Example:

            ```python
            app.license = {
                'name': 'Apache 2.0',
                'url': 'http://www.apache.org/licenses/LICENSE-2.0.html'
            }
            ```

            This attribute can also be configured from the config with the
            `LICENSE` configuration key. Defaults to `None`.
        terms_of_service: The terms of service URL of the API
            (openapi.info.termsOfService). Example:

            ```python
            app.terms_of_service = 'http://example.com/terms/'
            ```

            This attribute can also be configured from the config with the
            `TERMS_OF_SERVICE` configuration key. Defaults to `None`.
        security_schemes: The security schemes of the API
            (openapi.components.securitySchemes). Example:

            ```python
            app.security_schemes = [
                'ApiKeyAuth': {
                    'type': 'apiKey',
                    'in': 'header',
                    'name': 'X-API-Key'
                }
            ]
            ```

            This attribute can also be configured from the config with the
            `SECURITY_SCHEMES` configuration key. Defaults to `None`.
        spec_callback: It stores the function object registered by
            [`spec_processor`][apiflask.APIFlask.spec_processor]. You can also
            pass a callback function to it directly without using `spec_processor`.
            Example:

            ```python
            def update_spec(spec):
                spec['title'] = 'Updated Title'
                return spec

            app.spec_callback = update_spec
            ```

        error_callback: It stores the function object registered by
            [`error_processor`][apiflask.APIFlask.error_processor]. You can also
            pass a callback function to it directly without using `error_processor`.
            See the docstring of `error_processor` for more details.
            Example:

            ```python
            def my_error_handler(error):
                return {
                    'status_code': error.status_code,
                    'message': error.message,
                    'detail': error.detail
                }, error.status_code, error.headers

            app.error_processor = my_error_handler
            ```

        schema_name_resolver: It stores the function that used to decided the schema name.
            The schema name resolver should accept the schema object as argument and return
            the name.
            Example:

            ```python
            # this is the default schema name resolver used in APIFlask
            def schema_name_resolver(schema):
                name = schema.__class__.__name__  # get schema class name
                if name.endswith('Schema'):  # remove the "Schema" suffix
                    name = name[:-6] or name
                if schema.partial:  # add a "Update" suffix for partial schema
                    name += 'Update'
                return name

            app.schema_name_resolver = schema_name_resolver
            ```

    *Version changed: 1.0*

    - Add instance attribute `security_schemes` as an alias of config `SECURITY_SCHEMES`.

    *Version changed: 0.9.0*

    - Add instance attribute `schema_name_resolver`.
    """

    openapi_version: str = ConfigAttribute('OPENAPI_VERSION')  # type: ignore
    tags: TagsType | None = ConfigAttribute('TAGS')  # type: ignore
    servers: list[dict[str, str]] | None = ConfigAttribute('SERVERS')  # type: ignore
    info: dict[str, str | dict] | None = ConfigAttribute('INFO')  # type: ignore
    description: str | None = ConfigAttribute('DESCRIPTION')  # type: ignore
    contact: dict[str, str] | None = ConfigAttribute('CONTACT')  # type: ignore
    license: dict[str, str] | None = ConfigAttribute('LICENSE')  # type: ignore
    external_docs: dict[str, str] | None = ConfigAttribute('EXTERNAL_DOCS')  # type: ignore
    terms_of_service: str | None = ConfigAttribute('TERMS_OF_SERVICE')  # type: ignore
    security_schemes: dict[str, t.Any] | None = ConfigAttribute('SECURITY_SCHEMES')  # type: ignore

    def __init__(
        self,
        import_name: str,
        title: str = 'APIFlask',
        version: str = '0.1.0',
        spec_path: str | None = '/openapi.json',
        docs_path: str | None = '/docs',
        docs_oauth2_redirect_path: str | None = '/docs/oauth2-redirect',
        docs_ui: str = 'swagger-ui',
        openapi_blueprint_url_prefix: str | None = None,
        json_errors: bool = True,
        enable_openapi: bool = True,
        spec_plugins: list[BasePlugin] | None = None,
        static_url_path: str | None = None,
        static_folder: str = 'static',
        static_host: str | None = None,
        host_matching: bool = False,
        subdomain_matching: bool = False,
        template_folder: str = 'templates',
        instance_path: str | None = None,
        instance_relative_config: bool = False,
        root_path: str | None = None,
    ) -> None:
        """Make an app instance.

        Arguments:
            import_name: The name of the application package, usually
                `__name__`. This helps locate the `root_path` for the
                application.
            title: The title of the API (openapi.info.title), defaults to "APIFlask".
                You can change it to the name of your API (e.g., "Pet API").
            version: The version of the API (openapi.info.version), defaults to "0.1.0".
            spec_path: The path to OpenAPI Spec documentation. It
                defaults to `/openapi.json`, if the path ends with `.yaml`
                or `.yml`, the YAML format of the OAS will be returned.
            docs_path: The path to API UI documentation, defaults to `/docs`.
            docs_ui: The UI of API documentation, one of `swagger-ui` (default), `redoc`,
                `elements`, `rapidoc`, and `rapipdf`.
            docs_oauth2_redirect_path: The path to Swagger UI OAuth redirect.
            openapi_blueprint_url_prefix: The url prefix of the OpenAPI blueprint. This
                prefix will append before all the OpenAPI-related paths (`sepc_path`,
                `docs_path`, etc.), defaults to `None`.
            json_errors: If `True`, APIFlask will return a JSON response for HTTP errors.
            enable_openapi: If `False`, will disable OpenAPI spec and API docs views.
            spec_plugins: List of apispec-compatible plugins (subclasses of `apispec.BasePlugin`),
                defaults to `None`. The `MarshmallowPlugin` for apispec is already included
                by default, so it doesn't need to be provided here.

        Other keyword arguments are directly passed to `flask.Flask`.

        *Version changed: 2.0.0*

        - Remove the deprecated `redoc_path` parameter.

        *Version changed: 1.2.0*

        - Add `spec_plugins` parameter.

        *Version changed: 1.1.0*

        - Add `docs_ui` parameter.

        *Version changed: 0.7.0*

        - Add `openapi_blueprint_url_prefix` parameter.
        """
        super().__init__(
            import_name,
            static_url_path=static_url_path,
            static_folder=static_folder,
            static_host=static_host,
            host_matching=host_matching,
            subdomain_matching=subdomain_matching,
            template_folder=template_folder,
            instance_path=instance_path,
            instance_relative_config=instance_relative_config,
            root_path=root_path,
        )

        # Set default config
        self.config.from_object('apiflask.settings')

        self.title = title
        self.version = version
        self.spec_path = spec_path
        self.docs_ui = docs_ui
        self.docs_path = docs_path
        self.docs_oauth2_redirect_path = docs_oauth2_redirect_path
        self.openapi_blueprint_url_prefix = openapi_blueprint_url_prefix
        self.enable_openapi = enable_openapi
        self.json_errors = json_errors

        self.spec_callback: SpecCallbackType | None = None
        self.error_callback: ErrorCallbackType = self._error_handler
        self.schema_name_resolver = self._schema_name_resolver

        self.spec_plugins: list[BasePlugin] = spec_plugins or []
        self._spec: dict | str | None = None
        self._auth_blueprints: dict[str, t.Dict[str, t.Any]] = {}

        self._register_openapi_blueprint()
        self._register_error_handlers()

    def _register_error_handlers(self) -> None:
        """Register default error handlers for HTTPError and WerkzeugHTTPException.

        *Version changed: 0.9.0*

        - Always pass an `HTTPError` instance to error handlers.
        """

        @self.errorhandler(HTTPError)  # type: ignore
        def handle_http_errors(error: HTTPError) -> ResponseReturnValueType:
            return self.error_callback(error)

        if self.json_errors:
            self._apply_error_callback_to_werkzeug_errors()

    def _apply_error_callback_to_werkzeug_errors(self) -> None:
        @self.errorhandler(WerkzeugHTTPException)  # type: ignore
        def handle_werkzeug_errors(e: WerkzeugHTTPException) -> ResponseReturnValueType:
            headers = dict(e.get_headers())
            # remove the original MIME header
            del headers['Content-Type']
            error = HTTPError(e.code, message=e.name, headers=headers)
            return self.error_callback(error)

    # TODO: remove this function when we drop the Flask < 2.2 support
    # List return values are supported in Flask 2.2
    def make_response(self, rv) -> Response:
        """Patch the make_response form Flask to allow returning list as JSON.
        *Version added: 1.1.0*
        """
        if isinstance(rv, list):
            rv = jsonify(rv)
        elif isinstance(rv, tuple) and isinstance(rv[0], list):
            rv = (jsonify(rv[0]), *rv[1:])
        return super().make_response(rv)

    @staticmethod
    def _error_handler(error: HTTPError) -> ResponseReturnValueType:
        """The default error handler.

        Arguments:
            error: An instance of [`HTTPError`][apiflask.exceptions.HTTPError].

        *Version changed: 0.10.0*

        - Remove the `status_code` field from the response.
        - Add `HTTPError.extra_data` to the response body.
        """
        body = {'detail': error.detail, 'message': error.message, **error.extra_data}
        return body, error.status_code, error.headers

    def error_processor(self, f: ErrorCallbackType) -> ErrorCallbackType:
        """A decorator to register a custom error response processor function.

        The decorated callback function will be called in the following situations:

        - Any HTTP exception is raised by Flask when handling request.
        - A validation error happened when parsing a request.
        - An exception triggered with [`HTTPError`][apiflask.exceptions.HTTPError]
        - An exception triggered with [`abort`][apiflask.exceptions.abort].

        You can still register a specific error handler for a specific error code
        or exception with the `app.errorhandler(code_or_execution)` decorator,
        in that case, the return value of the specific error handler will be used as the
        response when the corresponding error or exception happened.

        The callback function must accept an error object as argument and return a valid
        response.

        Examples:

        ```python
        @app.error_processor
        def my_error_processor(error):
            return {
                'status_code': error.status_code,
                'message': error.message,
                'detail': error.detail,
                **error.extra_data
            }, error.status_code, error.headers
        ```

        The error object is an instance of [`HTTPError`][apiflask.exceptions.HTTPError],
        so you can get error information via it's attributes:

        - status_code: If the error is triggered by a validation error, the value will be
          422 (default) or the value you passed in config `VALIDATION_ERROR_STATUS_CODE`.
          If the error is triggered by [`HTTPError`][apiflask.exceptions.HTTPError]
          or [`abort`][apiflask.exceptions.abort], it will be the status code
          you passed. Otherwise, it will be the status code set by Werkzeug when
          processing the request.
        - message: The error description for this error, either you passed or grabbed from
          Werkzeug.
        - detail: The detail of the error. When the validation error happens, it will
          be filled automatically in the following structure:

            ```python
            "<location>": {
                "<field_name>": ["<error_message>", ...],
                "<field_name>": ["<error_message>", ...],
                ...
            },
            "<location>": {
                ...
            },
            ...
            ```

          The value of `location` can be `json` (i.e., request body) or `query`
          (i.e., query string) depending on the place where the validation error
          happened.
        - headers: The value will be `{}` unless you pass it in `HTTPError` or `abort`.
        - extra_data: Additional error information.

        If you want, you can rewrite the whole response body to anything you like:

        ```python
        @app.error_processor
        def my_error_processor(error):
            body = {'error_detail': error.detail, **error.extra_data}
            return body, error.status_code, error.headers
        ```

        However, I would recommend keeping the `detail` in the response since it contains
        the detailed information about the validation error when the validation error
        happened.

        *Version changed: 1.0*

        - Apply this error processor to normal HTTP errors even when
          `json_error` is set to `False` when creating `APIFlask` instance.

        *Version changed: 0.7.0*

        - Support registering an async callback function.
        """
        self.error_callback = self.ensure_sync(f)
        self._apply_error_callback_to_werkzeug_errors()
        return f

    def _register_openapi_blueprint(self) -> None:
        """Register a blueprint for OpenAPI support.

        The name of the blueprint is "openapi". This blueprint will hold the view
        functions for spec file and API docs.

        *Version changed: 2.1.0*

        - Inject the OpenAPI endpoints decorators.

        *Version changed: 1.1.0*

        - Deprecate the redoc view at /redoc path.

        *Version changed: 0.7.0*

        - The format of the spec now rely on the `SPEC_FORMAT` config.
        """
        bp = Blueprint('openapi', __name__, url_prefix=self.openapi_blueprint_url_prefix)

        if self.spec_path:

            @bp.route(self.spec_path)
            @self._apply_decorators(config_name='SPEC_DECORATORS')
            def spec():
                if self.config['SPEC_FORMAT'] == 'json':
                    response = jsonify(self._get_spec('json'))
                    response.mimetype = self.config['JSON_SPEC_MIMETYPE']
                    return response
                return (
                    self._get_spec('yaml'),
                    200,
                    {'Content-Type': self.config['YAML_SPEC_MIMETYPE']},
                )

        if self.docs_path:
            if self.docs_ui not in ui_templates:
                valid_values = list(ui_templates.keys())
                raise ValueError(
                    f'Invalid docs_ui value, expected one of {valid_values!r}, '
                    f'got {self.docs_ui!r}.'
                )

            @bp.route(self.docs_path)
            @self._apply_decorators(config_name='DOCS_DECORATORS')
            def docs():
                return render_template_string(
                    ui_templates[self.docs_ui],
                    title=self.title,
                    version=self.version,
                    oauth2_redirect_path=self.docs_oauth2_redirect_path,
                )

            if self.docs_ui == 'swagger-ui':
                if self.docs_oauth2_redirect_path:

                    @bp.route(self.docs_oauth2_redirect_path)
                    @self._apply_decorators(config_name='SWAGGER_UI_OAUTH_REDIRECT_DECORATORS')
                    def swagger_ui_oauth_redirect() -> str:
                        return render_template_string(swagger_ui_oauth2_redirect_template)

        if self.enable_openapi and (self.spec_path or self.docs_path):
            self.register_blueprint(bp)

    def _get_spec(self, spec_format: str | None = None, force_update: bool = False) -> dict | str:
        """Get the current OAS document file.

        This method will return the cached spec on the first call. If you want
        to get the latest spec, set the `force_update` to `True` or use the
        public attribute `app.spec`, which will always return the newly generated
        spec when you call it.

        If the config `SYNC_LOCAL_SPEC` is `True`, the local spec
        specified in config `LOCAL_SPEC_PATH` will be automatically updated
        when the spec changes.

        Arguments:
            spec_format: The format of the spec file, one of `'json'`, `'yaml'`
                and `'yml'`, defaults to the `SPEC_FORMAT` config.
            force_update: If true, will generate the spec for every call instead
                of using the cache.

        *Version changed: 0.7.0*

        - The default format now rely on the `SPEC_FORMAT` config.
        - Support to sync local spec file.

        *Version changed: 0.7.1*

        - Rename the method name to `_get_spec`.
        - Add the `force_update` parameter.

        *Version changed: 1.3.0*

        - Add the `SPEC_PROCESSOR_PASS_OBJECT` config to control the argument type
          when calling the spec processor.
        """
        if spec_format is None:
            spec_format = self.config['SPEC_FORMAT']
        if self._spec is None or force_update:
            spec_object: APISpec = self._generate_spec()
            if self.spec_callback:
                if self.config['SPEC_PROCESSOR_PASS_OBJECT']:
                    self._spec = self.spec_callback(
                        spec_object  # type: ignore
                    ).to_dict()
                else:
                    self._spec = self.spec_callback(spec_object.to_dict())
            else:
                self._spec = spec_object.to_dict()
            if spec_format in ['yml', 'yaml']:
                from apispec.yaml_utils import dict_to_yaml

                self._spec = dict_to_yaml(self._spec)  # type: ignore
        # sync local spec
        if self.config['SYNC_LOCAL_SPEC']:
            spec_path = self.config['LOCAL_SPEC_PATH']
            if spec_path is None:
                raise TypeError('The spec path (LOCAL_SPEC_PATH) should be a valid path string.')
            spec: str
            if spec_format == 'json':
                spec = json.dumps(self._spec, indent=self.config['LOCAL_SPEC_JSON_INDENT'])
            else:
                spec = str(self._spec)
            with open(spec_path, 'w') as f:
                f.write(spec)
        return self._spec  # type: ignore

    def spec_processor(self, f: SpecCallbackType) -> SpecCallbackType:
        """A decorator to register a spec handler callback function.

        You can register a function to update the spec. The callback function
        should accept the spec as an argument and return it in the end. The
        callback function will be called when generating the spec file.

        Examples:

        ```python
        @app.spec_processor
        def update_spec(spec):
            spec['info']['title'] = 'Updated Title'
            return spec
        ```

        Notice the format of the spec is depends on the the value of configuration
        variable `SPEC_FORMAT` (defaults to `'json'`):

        - `'json'` -> dict
        - `'yaml'` -> string

        *Version Changed: 0.7.0*

        - Support registering an async callback function.
        """
        self.spec_callback = self.ensure_sync(f)
        return f

    @property
    def spec(self) -> dict | str:
        """Get the current OAS document file.

        This property will call `app._get_spec()` method and set the
        `force_update` parameter to `True`.

        *Version changed: 0.7.1*

        - Generate the spec on every call.
        """
        return self._get_spec(force_update=True)

    @staticmethod
    def _schema_name_resolver(schema: type[Schema]) -> str:
        """Default schema name resolver."""
        # some schema are passed through the `doc(responses=...)`
        # we need to make sure the schema is an instance of `Schema`
        if isinstance(schema, type):  # pragma: no cover
            schema = schema()  # type: ignore

        name = schema.__class__.__name__

        if name.endswith('Schema'):
            name = name[:-6] or name
        if schema.partial:
            name += 'Update'
        return name

    def _make_info(self) -> dict:
        """Make OpenAPI info object."""
        info: dict
        if self.info:
            info = self.info
        else:
            info = {}
        if self.contact:
            info['contact'] = self.contact
        if self.license:
            info['license'] = self.license
        if self.terms_of_service:
            info['termsOfService'] = self.terms_of_service
        if self.description:
            info['description'] = self.description
        return info

    def _make_tags(self) -> list[dict[str, t.Any]]:
        """Make OpenAPI tags object."""
        tags: TagsType | None = self.tags
        if tags is not None:
            # convert simple tags list into standard OpenAPI tags
            if isinstance(tags[0], str):
                for index, tag_name in enumerate(tags):
                    tags[index] = {'name': tag_name}  # type: ignore
        else:
            tags: list[dict[str, t.Any]] = []  # type: ignore
            if self.config['AUTO_TAGS']:
                # auto-generate tags from blueprints
                for blueprint_name, blueprint in self.blueprints.items():
                    if (
                        blueprint_name == 'openapi'
                        or not hasattr(blueprint, 'enable_openapi')
                        or not blueprint.enable_openapi
                    ):  # type: ignore
                        continue
                    tag: dict[str, t.Any] = get_tag(blueprint, blueprint_name)  # type: ignore
                    tags.append(tag)  # type: ignore
        return tags  # type: ignore

    def _collect_security_info(self) -> t.Tuple[list[str], list[HTTPAuthType]]:
        """Detect `auth_required` on blueprint before_request functions and view functions."""
        # security schemes
        auth_names: list[str] = []
        auth_schemes: list[HTTPAuthType] = []

        def _update_auth_info(auth: HTTPAuthType) -> None:
            # update auth_schemes and auth_names
            auth_schemes.append(auth)
            auth_name: str = get_auth_name(auth, auth_names)
            auth_names.append(auth_name)

        # collect auth info on blueprint before_request functions
        for blueprint_name, funcs in self.before_request_funcs.items():
            # skip app-level before_request functions (blueprint_name is None)
            if blueprint_name is None or not self.blueprints[blueprint_name].enable_openapi:  # type: ignore
                continue
            for f in funcs:
                if hasattr(f, '_spec'):  # pragma: no cover
                    auth = f._spec.get('auth')  # type: ignore
                    if auth is not None and auth not in auth_schemes:
                        self._auth_blueprints[blueprint_name] = {
                            'auth': auth,
                            'roles': f._spec.get('roles'),  # type: ignore
                        }
                        _update_auth_info(auth)
        # collect auth info on view functions
        for rule in self.url_map.iter_rules():
            view_func: ViewFuncType = self.view_functions[rule.endpoint]  # type: ignore
            if hasattr(view_func, '_spec'):
                auth = view_func._spec.get('auth')
                if auth is not None and auth not in auth_schemes:
                    _update_auth_info(auth)
            # method views
            if hasattr(view_func, '_method_spec'):
                for method_spec in view_func._method_spec.values():
                    auth = method_spec.get('auth')
                    if auth is not None and auth not in auth_schemes:
                        _update_auth_info(auth)

        return auth_names, auth_schemes

    def _generate_spec(self) -> APISpec:
        """Generate the spec, return an instance of `apispec.APISpec`.

        *Version changed: 1.3.0*

        - Support setting custom response content type.

        *Version changed: 1.2.1*

        - Set default `servers` value.

        *Version changed: 0.10.0*

        - Add support for `operationId`.
        - Add support for response `links`.

        *Version changed: 0.9.0*

        - Add base response customization support.

        *Version changed: 0.8.0*

        - Add automatic 404 response support.
        """
        kwargs: dict = {}
        if self.servers:
            kwargs['servers'] = self.servers
        else:
            if self.config['AUTO_SERVERS'] and has_request_context():
                kwargs['servers'] = [{'url': request.url_root}]
        if self.external_docs:
            kwargs['externalDocs'] = self.external_docs

        self._ma_plugin: MarshmallowPlugin = MarshmallowPlugin(
            schema_name_resolver=self.schema_name_resolver  # type: ignore
        )

        spec_plugins: list[BasePlugin] = [self._ma_plugin, *self.spec_plugins]

        spec: APISpec = APISpec(
            title=self.title,
            version=self.version,
            openapi_version=self.config['OPENAPI_VERSION'],
            plugins=spec_plugins,
            info=self._make_info(),
            tags=self._make_tags(),
            **kwargs,
        )

        # configure flask-marshmallow URL types
        self._ma_plugin.converter.field_mapping[fields.URLFor] = ('string', 'url')  # type: ignore
        self._ma_plugin.converter.field_mapping[fields.AbsoluteURLFor] = (  # type: ignore
            'string',
            'url',
        )
        if sqla is not None:  # pragma: no cover
            self._ma_plugin.converter.field_mapping[sqla.HyperlinkRelated] = (  # type: ignore
                'string',
                'url',
            )

        auth_names, auth_schemes = self._collect_security_info()
        security, security_schemes = get_security_and_security_schemes(auth_names, auth_schemes)

        if self.config['SECURITY_SCHEMES'] is not None:
            security_schemes.update(self.config['SECURITY_SCHEMES'])

        for name, scheme in security_schemes.items():
            spec.components.security_scheme(name, scheme)

        # paths
        paths: dict[str, dict[str, t.Any]] = {}
        rules: list[t.Any] = sorted(
            list(self.url_map.iter_rules()), key=lambda rule: len(rule.rule)
        )
        for rule in rules:
            operations: dict[str, t.Any] = {}
            view_func: ViewFuncType = self.view_functions[rule.endpoint]  # type: ignore
            # skip endpoints from openapi blueprint and the built-in static endpoint
            if rule.endpoint in default_bypassed_endpoints:
                continue
            blueprint_name: str | None = None  # type: ignore
            if '.' in rule.endpoint:
                blueprint_name: str = rule.endpoint.rsplit('.', 1)[0]  # type: ignore
                blueprint = self.blueprints.get(blueprint_name)  # type: ignore
                if blueprint is None:
                    # just a normal view with dots in its endpoint, reset blueprint_name
                    blueprint_name = None
                else:
                    if (
                        rule.endpoint == (f'{blueprint_name}.static')
                        or not hasattr(blueprint, 'enable_openapi')
                        or not blueprint.enable_openapi
                    ):  # type: ignore
                        continue
            # add a default 200 response for bare views
            if not hasattr(view_func, '_spec'):
                if not inspect.ismethod(view_func) and self.config['AUTO_200_RESPONSE']:  # type: ignore
                    view_func._spec = {'response': default_response}
                else:
                    continue  # pragma: no cover
            # method views
            if hasattr(view_func, '_method_spec'):
                skip = True
                for method, method_spec in view_func._method_spec.items():
                    if method_spec.get('no_spec'):
                        if self.config['AUTO_200_RESPONSE']:
                            view_func._method_spec[method]['response'] = default_response
                            skip = False
                    else:
                        skip = False
                if skip:
                    continue
            # skip views flagged with @app.doc(hide=True)
            if view_func._spec.get('hide'):
                continue

            # operation tags
            operation_tags: list[str] | None = None
            if view_func._spec.get('tags'):
                operation_tags = view_func._spec.get('tags')
            else:
                # use blueprint name as tag
                if self.tags is None and self.config['AUTO_TAGS'] and blueprint_name is not None:
                    blueprint = self.blueprints[blueprint_name]
                    operation_tags = get_operation_tags(blueprint, blueprint_name)  # type: ignore

            for method in ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']:
                if method not in rule.methods:
                    continue
                # method views
                if hasattr(view_func, '_method_spec'):
                    if method not in view_func._method_spec:
                        continue  # pragma: no cover
                    view_func._spec = view_func._method_spec[method]

                    if view_func._spec.get('no_spec') and not self.config['AUTO_200_RESPONSE']:
                        continue
                    if (
                        view_func._spec.get('generated_summary')
                        and not self.config['AUTO_OPERATION_SUMMARY']
                    ):
                        view_func._spec['summary'] = ''
                    if (
                        view_func._spec.get('generated_description')
                        and not self.config['AUTO_OPERATION_DESCRIPTION']
                    ):
                        view_func._spec['description'] = ''
                    if view_func._spec.get('hide'):
                        continue
                    if view_func._spec.get('tags'):
                        operation_tags = view_func._spec.get('tags')
                    else:
                        if (
                            self.tags is None
                            and self.config['AUTO_TAGS']
                            and blueprint_name is not None
                        ):
                            blueprint = self.blueprints[blueprint_name]
                            operation_tags = get_operation_tags(blueprint, blueprint_name)  # type: ignore

                operation: dict[str, t.Any] = {
                    'parameters': [
                        {'in': location, 'schema': schema}
                        for schema, location in view_func._spec.get('args', [])
                    ],
                    'responses': {},
                }
                if operation_tags:
                    operation['tags'] = operation_tags

                # summary
                if view_func._spec.get('summary'):
                    operation['summary'] = view_func._spec.get('summary')
                else:
                    # auto-generate summary from dotstring or view function name
                    if self.config['AUTO_OPERATION_SUMMARY']:
                        operation['summary'] = get_path_summary(view_func)  # type: ignore

                # description
                if view_func._spec.get('description'):
                    operation['description'] = view_func._spec.get('description')
                else:
                    # auto-generate description from dotstring
                    if self.config['AUTO_OPERATION_DESCRIPTION']:
                        docs = [
                            line.strip() for line in (view_func.__doc__ or '').strip().split('\n')
                        ]
                        if len(docs) > 1:
                            # use the remain lines of docstring as description
                            operation['description'] = '\n'.join(docs[1:]).strip()

                # deprecated
                if view_func._spec.get('deprecated'):
                    operation['deprecated'] = view_func._spec.get('deprecated')

                # operationId
                operation_id = view_func._spec.get('operation_id')
                if operation_id is None:
                    if self.config['AUTO_OPERATION_ID']:
                        operation['operationId'] = (
                            f"{method.lower()}_{rule.endpoint.replace('.', '_')}"
                        )
                else:
                    operation['operationId'] = operation_id

                # responses
                if view_func._spec.get('response'):
                    schema = view_func._spec.get('response')['schema']
                    status_code: str = str(view_func._spec.get('response')['status_code'])
                    description: str = (
                        view_func._spec.get('response')['description']
                        or self.config['SUCCESS_DESCRIPTION']
                    )
                    example = view_func._spec.get('response')['example']
                    examples = view_func._spec.get('response')['examples']
                    links = view_func._spec.get('response')['links']
                    content_type = view_func._spec.get('response')['content_type']
                    headers = view_func._spec.get('response')['headers']
                    self._add_response(
                        operation,
                        status_code,
                        schema,
                        description,
                        example=example,
                        examples=examples,
                        links=links,
                        content_type=content_type,
                        headers_schema=headers,
                    )
                else:
                    # add a default 200 response for views without using @app.output
                    # or @app.doc(responses={...})
                    if not view_func._spec.get('responses') and self.config['AUTO_200_RESPONSE']:
                        self._add_response(
                            operation,
                            '200',
                            {},
                            self.config['SUCCESS_DESCRIPTION'],
                        )

                # add validation error response
                if self.config['AUTO_VALIDATION_ERROR_RESPONSE'] and (
                    view_func._spec.get('body') or view_func._spec.get('args')
                ):
                    status_code: str = str(  # type: ignore
                        self.config['VALIDATION_ERROR_STATUS_CODE']
                    )
                    description: str = self.config[  # type: ignore
                        'VALIDATION_ERROR_DESCRIPTION'
                    ]
                    schema: SchemaType = self.config['VALIDATION_ERROR_SCHEMA']  # type: ignore
                    self._add_response_with_schema(
                        spec, operation, status_code, schema, 'ValidationError', description
                    )

                # add authentication error response
                has_bp_level_auth = (
                    blueprint_name is not None and blueprint_name in self._auth_blueprints
                )
                view_func_auth = view_func._spec.get('auth')
                custom_security = view_func._spec.get('security')
                operation_extensions = view_func._spec.get('extensions')
                if self.config['AUTO_AUTH_ERROR_RESPONSE'] and (
                    has_bp_level_auth or view_func_auth or custom_security
                ):
                    status_code: str = str(  # type: ignore
                        self.config['AUTH_ERROR_STATUS_CODE']
                    )
                    description: str = self.config['AUTH_ERROR_DESCRIPTION']  # type: ignore
                    schema: SchemaType = self.config['HTTP_ERROR_SCHEMA']  # type: ignore
                    self._add_response_with_schema(
                        spec, operation, status_code, schema, 'HTTPError', description
                    )

                # add 404 error response
                if self.config['AUTO_404_RESPONSE'] and rule.arguments:
                    description: str = self.config['NOT_FOUND_DESCRIPTION']  # type: ignore
                    schema: SchemaType = self.config['HTTP_ERROR_SCHEMA']  # type: ignore
                    self._add_response_with_schema(
                        spec, operation, '404', schema, 'HTTPError', description
                    )

                if view_func._spec.get('responses'):
                    responses: ResponsesType = view_func._spec.get('responses')
                    # turn status_code list to dict {status_code: reason_phrase}
                    if isinstance(responses, list):
                        responses: dict[int, str] = {}  # type: ignore
                        for status_code in view_func._spec.get('responses'):
                            responses[  # type: ignore
                                status_code
                            ] = get_reason_phrase(int(status_code), '')
                    for status_code, value in responses.items():  # type: ignore
                        status_code: str = str(status_code)  # type: ignore
                        # custom complete response spec
                        if isinstance(value, dict):
                            existing_response = operation['responses'].setdefault(status_code, {})
                            existing_response_content = existing_response.setdefault('content', {})
                            existing_response_content.update(value.get('content', {}))
                            if (new_description := value.get('description')) is not None:
                                existing_response['description'] = new_description
                            continue
                        else:
                            description = value
                        # overwrite existing response description
                        if status_code in operation['responses']:
                            if not isinstance(
                                view_func._spec.get('responses'), list
                            ):  # pragma: no cover
                                operation['responses'][status_code]['description'] = description
                            continue
                        # add error response schema for error responses
                        if status_code.startswith('4') or status_code.startswith('5'):
                            schema: SchemaType = self.config['HTTP_ERROR_SCHEMA']  # type: ignore
                            self._add_response_with_schema(
                                spec, operation, status_code, schema, 'HTTPError', description
                            )
                        else:  # add default response for other responses
                            self._add_response(operation, status_code, {}, description)

                # requestBody
                if view_func._spec.get('body'):
                    content_types = view_func._spec.get('content_type')
                    if not isinstance(content_types, list):
                        content_types = [content_types]
                    operation['requestBody'] = {'content': {}}
                    for content_type in content_types:
                        operation['requestBody']['content'][content_type] = {
                            'schema': view_func._spec['body'],
                        }
                        if view_func._spec.get('body_example'):
                            example = view_func._spec.get('body_example')
                            operation['requestBody']['content'][content_type]['example'] = example
                        if view_func._spec.get('body_examples'):
                            examples = view_func._spec.get('body_examples')
                            operation['requestBody']['content'][content_type]['examples'] = examples

                # security
                if custom_security:  # custom security
                    # TODO: validate the security name and the format
                    operation['security'] = []
                    operation_security = custom_security
                    if isinstance(operation_security, str):  # 'A' -> [{'A': []}]
                        operation['security'] = [{operation_security: []}]
                    elif isinstance(operation_security, list):
                        # ['A', 'B'] -> [{'A': []}, {'B': []}]
                        if isinstance(operation_security[0], str):
                            operation['security'] = [{name: []} for name in operation_security]
                        else:
                            operation['security'] = operation_security
                    else:
                        raise ValueError('The operation security must be a string or a list.')
                else:
                    if has_bp_level_auth:
                        bp_auth_info = self._auth_blueprints[blueprint_name]  # type: ignore
                        operation['security'] = [
                            {security[bp_auth_info['auth']]: bp_auth_info['roles']}
                        ]

                    # view-wide auth
                    if view_func_auth:
                        operation['security'] = [
                            {security[view_func_auth]: view_func._spec['roles']}
                        ]

                operations[method.lower()] = operation

                if operation_extensions:
                    for extension, value in operation_extensions.items():
                        operation[extension] = value

            # parameters
            path_arguments: t.Iterable = re.findall(r'<(([^<:]+:)?([^>]+))>', rule.rule)
            if path_arguments and not (
                hasattr(view_func, '_spec')
                and view_func._spec.get('omit_default_path_parameters', False)
            ):
                arguments: list[dict[str, str]] = []
                for _, argument_type, argument_name in path_arguments:
                    argument = get_argument(argument_type, argument_name)
                    arguments.append(argument)

                for _method, operation in operations.items():
                    operation['parameters'] = arguments + operation['parameters']

            path: str = re.sub(r'<([^<:]+:)?', '{', rule.rule).replace('>', '}')
            if path not in paths:
                paths[path] = operations
            else:
                paths[path].update(operations)

        for path, operations in paths.items():
            # sort by method before adding them to the spec
            sorted_operations: dict[str, t.Any] = {}
            for method in ['get', 'post', 'put', 'patch', 'delete']:
                if method in operations:
                    sorted_operations[method] = operations[method]
            spec.path(path=path, operations=sorted_operations)

        return spec

    def _apply_decorators(self, config_name: str):
        """Apply the decorators to the OpenAPI endpoints at runtime.

        Arguments:
            config_name: The config name to get the list of decorators.
        """

        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                decorated_func = f
                decorators = self.config[config_name]
                if decorators:
                    for decorator in decorators:
                        decorated_func = decorator(decorated_func)
                return decorated_func(*args, **kwargs)

            return wrapper

        return decorator

    def _add_response(
        self,
        operation: dict,
        status_code: str,
        schema: SchemaType | dict,
        description: str,
        example: t.Any | None = None,
        examples: dict[str, t.Any] | None = None,
        links: dict[str, t.Any] | None = None,
        content_type: str | None = 'application/json',
        headers_schema: SchemaType | None = None,
    ) -> None:
        """Add response to operation.

        *Version changed: 2.1.0*

        - Add parameter `headers_schema`.

        *Version changed: 1.3.0*

        - Add parameter `content_type`.

        *Version changed: 0.10.0*

        - Add `links` parameter.
        """
        base_schema: OpenAPISchemaType | None = self.config['BASE_RESPONSE_SCHEMA']
        data_key: str = self.config['BASE_RESPONSE_DATA_KEY']
        if base_schema is not None:
            base_schema_spec: dict[str, t.Any]
            if isinstance(base_schema, type):
                base_schema_spec = self._ma_plugin.converter.schema2jsonschema(  # type: ignore
                    base_schema()
                )
            elif isinstance(base_schema, dict):
                base_schema_spec = base_schema
            else:
                raise TypeError(_bad_schema_message)
            if data_key not in base_schema_spec['properties']:  # type: ignore
                raise RuntimeError(
                    f'The data key {data_key!r} is not found in the base response schema spec.'
                )
            base_schema_spec['properties'][data_key] = schema  # type: ignore
            schema = base_schema_spec

        operation['responses'][status_code] = {}
        if status_code != '204':
            if isinstance(schema, FileSchema):
                schema = {'type': schema.type, 'format': schema.format}
            elif isinstance(schema, EmptySchema):
                schema = {}
            operation['responses'][status_code]['content'] = {content_type: {'schema': schema}}
        operation['responses'][status_code]['description'] = description
        if example is not None:
            operation['responses'][status_code]['content'][content_type]['example'] = example
        if examples is not None:
            operation['responses'][status_code]['content'][content_type]['examples'] = examples
        if links is not None:
            operation['responses'][status_code]['links'] = links
        if headers_schema is not None:
            headers = self._ma_plugin.converter.schema2parameters(  # type: ignore
                headers_schema, location='headers'
            )
            operation['responses'][status_code]['headers'] = {
                header['name']: header for header in headers
            }

    def _add_response_with_schema(
        self,
        spec: APISpec,
        operation: dict,
        status_code: str,
        schema: OpenAPISchemaType,
        schema_name: str,
        description: str,
    ) -> None:
        """Add response with given schema to operation."""
        if isinstance(schema, type):
            schema = schema()
            self._add_response(operation, status_code, schema, description)
        elif isinstance(schema, dict):
            if schema_name not in spec.components.schemas:
                spec.components.schema(schema_name, schema)
            schema_ref = {'$ref': f'#/components/schemas/{schema_name}'}
            self._add_response(operation, status_code, schema_ref, description)
        else:
            raise TypeError(_bad_schema_message)

spec: dict | str property

Get the current OAS document file.

This property will call app._get_spec() method and set the force_update parameter to True.

Version changed: 0.7.1

  • Generate the spec on every call.

__init__(import_name, title='APIFlask', version='0.1.0', spec_path='/openapi.json', docs_path='/docs', docs_oauth2_redirect_path='/docs/oauth2-redirect', docs_ui='swagger-ui', openapi_blueprint_url_prefix=None, json_errors=True, enable_openapi=True, spec_plugins=None, static_url_path=None, static_folder='static', static_host=None, host_matching=False, subdomain_matching=False, template_folder='templates', instance_path=None, instance_relative_config=False, root_path=None)

Make an app instance.

Parameters:

Name Type Description Default
import_name str

The name of the application package, usually __name__. This helps locate the root_path for the application.

required
title str

The title of the API (openapi.info.title), defaults to "APIFlask". You can change it to the name of your API (e.g., "Pet API").

'APIFlask'
version str

The version of the API (openapi.info.version), defaults to "0.1.0".

'0.1.0'
spec_path str | None

The path to OpenAPI Spec documentation. It defaults to /openapi.json, if the path ends with .yaml or .yml, the YAML format of the OAS will be returned.

'/openapi.json'
docs_path str | None

The path to API UI documentation, defaults to /docs.

'/docs'
docs_ui str

The UI of API documentation, one of swagger-ui (default), redoc, elements, rapidoc, and rapipdf.

'swagger-ui'
docs_oauth2_redirect_path str | None

The path to Swagger UI OAuth redirect.

'/docs/oauth2-redirect'
openapi_blueprint_url_prefix str | None

The url prefix of the OpenAPI blueprint. This prefix will append before all the OpenAPI-related paths (sepc_path, docs_path, etc.), defaults to None.

None
json_errors bool

If True, APIFlask will return a JSON response for HTTP errors.

True
enable_openapi bool

If False, will disable OpenAPI spec and API docs views.

True
spec_plugins list[BasePlugin] | None

List of apispec-compatible plugins (subclasses of apispec.BasePlugin), defaults to None. The MarshmallowPlugin for apispec is already included by default, so it doesn't need to be provided here.

None

Other keyword arguments are directly passed to flask.Flask.

Version changed: 2.0.0

  • Remove the deprecated redoc_path parameter.

Version changed: 1.2.0

  • Add spec_plugins parameter.

Version changed: 1.1.0

  • Add docs_ui parameter.

Version changed: 0.7.0

  • Add openapi_blueprint_url_prefix parameter.
Source code in apiflask/app.py
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
def __init__(
    self,
    import_name: str,
    title: str = 'APIFlask',
    version: str = '0.1.0',
    spec_path: str | None = '/openapi.json',
    docs_path: str | None = '/docs',
    docs_oauth2_redirect_path: str | None = '/docs/oauth2-redirect',
    docs_ui: str = 'swagger-ui',
    openapi_blueprint_url_prefix: str | None = None,
    json_errors: bool = True,
    enable_openapi: bool = True,
    spec_plugins: list[BasePlugin] | None = None,
    static_url_path: str | None = None,
    static_folder: str = 'static',
    static_host: str | None = None,
    host_matching: bool = False,
    subdomain_matching: bool = False,
    template_folder: str = 'templates',
    instance_path: str | None = None,
    instance_relative_config: bool = False,
    root_path: str | None = None,
) -> None:
    """Make an app instance.

    Arguments:
        import_name: The name of the application package, usually
            `__name__`. This helps locate the `root_path` for the
            application.
        title: The title of the API (openapi.info.title), defaults to "APIFlask".
            You can change it to the name of your API (e.g., "Pet API").
        version: The version of the API (openapi.info.version), defaults to "0.1.0".
        spec_path: The path to OpenAPI Spec documentation. It
            defaults to `/openapi.json`, if the path ends with `.yaml`
            or `.yml`, the YAML format of the OAS will be returned.
        docs_path: The path to API UI documentation, defaults to `/docs`.
        docs_ui: The UI of API documentation, one of `swagger-ui` (default), `redoc`,
            `elements`, `rapidoc`, and `rapipdf`.
        docs_oauth2_redirect_path: The path to Swagger UI OAuth redirect.
        openapi_blueprint_url_prefix: The url prefix of the OpenAPI blueprint. This
            prefix will append before all the OpenAPI-related paths (`sepc_path`,
            `docs_path`, etc.), defaults to `None`.
        json_errors: If `True`, APIFlask will return a JSON response for HTTP errors.
        enable_openapi: If `False`, will disable OpenAPI spec and API docs views.
        spec_plugins: List of apispec-compatible plugins (subclasses of `apispec.BasePlugin`),
            defaults to `None`. The `MarshmallowPlugin` for apispec is already included
            by default, so it doesn't need to be provided here.

    Other keyword arguments are directly passed to `flask.Flask`.

    *Version changed: 2.0.0*

    - Remove the deprecated `redoc_path` parameter.

    *Version changed: 1.2.0*

    - Add `spec_plugins` parameter.

    *Version changed: 1.1.0*

    - Add `docs_ui` parameter.

    *Version changed: 0.7.0*

    - Add `openapi_blueprint_url_prefix` parameter.
    """
    super().__init__(
        import_name,
        static_url_path=static_url_path,
        static_folder=static_folder,
        static_host=static_host,
        host_matching=host_matching,
        subdomain_matching=subdomain_matching,
        template_folder=template_folder,
        instance_path=instance_path,
        instance_relative_config=instance_relative_config,
        root_path=root_path,
    )

    # Set default config
    self.config.from_object('apiflask.settings')

    self.title = title
    self.version = version
    self.spec_path = spec_path
    self.docs_ui = docs_ui
    self.docs_path = docs_path
    self.docs_oauth2_redirect_path = docs_oauth2_redirect_path
    self.openapi_blueprint_url_prefix = openapi_blueprint_url_prefix
    self.enable_openapi = enable_openapi
    self.json_errors = json_errors

    self.spec_callback: SpecCallbackType | None = None
    self.error_callback: ErrorCallbackType = self._error_handler
    self.schema_name_resolver = self._schema_name_resolver

    self.spec_plugins: list[BasePlugin] = spec_plugins or []
    self._spec: dict | str | None = None
    self._auth_blueprints: dict[str, t.Dict[str, t.Any]] = {}

    self._register_openapi_blueprint()
    self._register_error_handlers()

error_processor(f)

A decorator to register a custom error response processor function.

The decorated callback function will be called in the following situations:

  • Any HTTP exception is raised by Flask when handling request.
  • A validation error happened when parsing a request.
  • An exception triggered with HTTPError
  • An exception triggered with abort.

You can still register a specific error handler for a specific error code or exception with the app.errorhandler(code_or_execution) decorator, in that case, the return value of the specific error handler will be used as the response when the corresponding error or exception happened.

The callback function must accept an error object as argument and return a valid response.

Examples:

@app.error_processor
def my_error_processor(error):
    return {
        'status_code': error.status_code,
        'message': error.message,
        'detail': error.detail,
        **error.extra_data
    }, error.status_code, error.headers

The error object is an instance of HTTPError, so you can get error information via it's attributes:

  • status_code: If the error is triggered by a validation error, the value will be 422 (default) or the value you passed in config VALIDATION_ERROR_STATUS_CODE. If the error is triggered by HTTPError or abort, it will be the status code you passed. Otherwise, it will be the status code set by Werkzeug when processing the request.
  • message: The error description for this error, either you passed or grabbed from Werkzeug.
  • detail: The detail of the error. When the validation error happens, it will be filled automatically in the following structure:

    "<location>": {
        "<field_name>": ["<error_message>", ...],
        "<field_name>": ["<error_message>", ...],
        ...
    },
    "<location>": {
        ...
    },
    ...
    

The value of location can be json (i.e., request body) or query (i.e., query string) depending on the place where the validation error happened. - headers: The value will be {} unless you pass it in HTTPError or abort. - extra_data: Additional error information.

If you want, you can rewrite the whole response body to anything you like:

@app.error_processor
def my_error_processor(error):
    body = {'error_detail': error.detail, **error.extra_data}
    return body, error.status_code, error.headers

However, I would recommend keeping the detail in the response since it contains the detailed information about the validation error when the validation error happened.

Version changed: 1.0

  • Apply this error processor to normal HTTP errors even when json_error is set to False when creating APIFlask instance.

Version changed: 0.7.0

  • Support registering an async callback function.
Source code in apiflask/app.py
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
def error_processor(self, f: ErrorCallbackType) -> ErrorCallbackType:
    """A decorator to register a custom error response processor function.

    The decorated callback function will be called in the following situations:

    - Any HTTP exception is raised by Flask when handling request.
    - A validation error happened when parsing a request.
    - An exception triggered with [`HTTPError`][apiflask.exceptions.HTTPError]
    - An exception triggered with [`abort`][apiflask.exceptions.abort].

    You can still register a specific error handler for a specific error code
    or exception with the `app.errorhandler(code_or_execution)` decorator,
    in that case, the return value of the specific error handler will be used as the
    response when the corresponding error or exception happened.

    The callback function must accept an error object as argument and return a valid
    response.

    Examples:

    ```python
    @app.error_processor
    def my_error_processor(error):
        return {
            'status_code': error.status_code,
            'message': error.message,
            'detail': error.detail,
            **error.extra_data
        }, error.status_code, error.headers
    ```

    The error object is an instance of [`HTTPError`][apiflask.exceptions.HTTPError],
    so you can get error information via it's attributes:

    - status_code: If the error is triggered by a validation error, the value will be
      422 (default) or the value you passed in config `VALIDATION_ERROR_STATUS_CODE`.
      If the error is triggered by [`HTTPError`][apiflask.exceptions.HTTPError]
      or [`abort`][apiflask.exceptions.abort], it will be the status code
      you passed. Otherwise, it will be the status code set by Werkzeug when
      processing the request.
    - message: The error description for this error, either you passed or grabbed from
      Werkzeug.
    - detail: The detail of the error. When the validation error happens, it will
      be filled automatically in the following structure:

        ```python
        "<location>": {
            "<field_name>": ["<error_message>", ...],
            "<field_name>": ["<error_message>", ...],
            ...
        },
        "<location>": {
            ...
        },
        ...
        ```

      The value of `location` can be `json` (i.e., request body) or `query`
      (i.e., query string) depending on the place where the validation error
      happened.
    - headers: The value will be `{}` unless you pass it in `HTTPError` or `abort`.
    - extra_data: Additional error information.

    If you want, you can rewrite the whole response body to anything you like:

    ```python
    @app.error_processor
    def my_error_processor(error):
        body = {'error_detail': error.detail, **error.extra_data}
        return body, error.status_code, error.headers
    ```

    However, I would recommend keeping the `detail` in the response since it contains
    the detailed information about the validation error when the validation error
    happened.

    *Version changed: 1.0*

    - Apply this error processor to normal HTTP errors even when
      `json_error` is set to `False` when creating `APIFlask` instance.

    *Version changed: 0.7.0*

    - Support registering an async callback function.
    """
    self.error_callback = self.ensure_sync(f)
    self._apply_error_callback_to_werkzeug_errors()
    return f

make_response(rv)

Patch the make_response form Flask to allow returning list as JSON. Version added: 1.1.0

Source code in apiflask/app.py
392
393
394
395
396
397
398
399
400
def make_response(self, rv) -> Response:
    """Patch the make_response form Flask to allow returning list as JSON.
    *Version added: 1.1.0*
    """
    if isinstance(rv, list):
        rv = jsonify(rv)
    elif isinstance(rv, tuple) and isinstance(rv[0], list):
        rv = (jsonify(rv[0]), *rv[1:])
    return super().make_response(rv)

spec_processor(f)

A decorator to register a spec handler callback function.

You can register a function to update the spec. The callback function should accept the spec as an argument and return it in the end. The callback function will be called when generating the spec file.

Examples:

@app.spec_processor
def update_spec(spec):
    spec['info']['title'] = 'Updated Title'
    return spec

Notice the format of the spec is depends on the the value of configuration variable SPEC_FORMAT (defaults to 'json'):

  • 'json' -> dict
  • 'yaml' -> string

Version Changed: 0.7.0

  • Support registering an async callback function.
Source code in apiflask/app.py
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
def spec_processor(self, f: SpecCallbackType) -> SpecCallbackType:
    """A decorator to register a spec handler callback function.

    You can register a function to update the spec. The callback function
    should accept the spec as an argument and return it in the end. The
    callback function will be called when generating the spec file.

    Examples:

    ```python
    @app.spec_processor
    def update_spec(spec):
        spec['info']['title'] = 'Updated Title'
        return spec
    ```

    Notice the format of the spec is depends on the the value of configuration
    variable `SPEC_FORMAT` (defaults to `'json'`):

    - `'json'` -> dict
    - `'yaml'` -> string

    *Version Changed: 0.7.0*

    - Support registering an async callback function.
    """
    self.spec_callback = self.ensure_sync(f)
    return f

APIScaffold

A base class for APIFlask and APIBlueprint.

This class contains the route shortcut decorators (i.e. get, post, etc.) and API-related decorators (i.e. auth_required, input, output, doc).

Version added: 1.0

Source code in apiflask/scaffold.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
class APIScaffold:
    """A base class for [`APIFlask`][apiflask.app.APIFlask] and
    [`APIBlueprint`][apiflask.blueprint.APIBlueprint].

    This class contains the route shortcut decorators (i.e. `get`, `post`, etc.) and
    API-related decorators (i.e. `auth_required`, `input`, `output`, `doc`).

    *Version added: 1.0*
    """

    def _method_route(self, method: str, rule: str, options: t.Any):
        if 'methods' in options:
            raise RuntimeError('Use the "route" decorator to use the "methods" argument.')

        def decorator(f):
            if isinstance(f, type(MethodView)):
                raise RuntimeError(
                    'The route shortcuts cannot be used with "MethodView" classes, '
                    'use the "route" decorator instead.'
                )
            return self.route(rule, methods=[method], **options)(f)

        return decorator

    def get(self, rule: str, **options: t.Any):
        """Shortcut for `app.route()` or `app.route(methods=['GET'])`."""
        return self._method_route('GET', rule, options)

    def post(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['POST'])`."""
        return self._method_route('POST', rule, options)

    def put(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['PUT'])`."""
        return self._method_route('PUT', rule, options)

    def patch(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['PATCH'])`."""
        return self._method_route('PATCH', rule, options)

    def delete(self, rule: str, **options: t.Any):
        """Shortcut for `app.route(methods=['DELETE'])`."""
        return self._method_route('DELETE', rule, options)

    def auth_required(
        self, auth: HTTPAuthType, roles: list | None = None, optional: str | None = None
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Protect a view with provided authentication settings.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        Examples:

        ```python
        from apiflask import APIFlask, HTTPTokenAuth

        app = APIFlask(__name__)
        auth = HTTPTokenAuth()

        @app.get('/')
        @app.auth_required(auth)
        def hello():
            return 'Hello'!
        ```

        Arguments:
            auth: The `auth` object, an instance of
                [`HTTPBasicAuth`][apiflask.security.HTTPBasicAuth]
                or [`HTTPTokenAuth`][apiflask.security.HTTPTokenAuth].
            roles: The selected roles to allow to visit this view, accepts a list of role names.
                See [Flask-HTTPAuth's documentation][_role]{target:_blank} for more details.
                [_role]: https://flask-httpauth.readthedocs.io/en/latest/#user-roles
            optional: Set to `True` to allow the view to execute even the authentication
                information is not included with the request, in which case the attribute
                `auth.current_user` will be `None`.

        *Version changed: 2.0.0*

        - Remove the deprecated `role` parameter.

        *Version changed: 1.0.0*

        - The `role` parameter is deprecated.

        *Version changed: 0.12.0*

        - Move to `APIFlask` and `APIBlueprint` classes.

        *Version changed: 0.4.0*

        - Add parameter `roles`.
        """

        def decorator(f):
            f = _ensure_sync(f)
            _annotate(f, auth=auth, roles=roles or [])
            return auth.login_required(role=roles, optional=optional)(f)

        return decorator

    def input(
        self,
        schema: SchemaType,
        location: str = 'json',
        arg_name: str | None = None,
        schema_name: str | None = None,
        example: t.Any | None = None,
        examples: dict[str, t.Any] | None = None,
        validation: bool = True,
        **kwargs: t.Any,
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Add input settings for view functions.

        If the validation passed, the data will be injected into the view
        function as a keyword argument in the form of `dict` and named `{location}_data`.
        Otherwise, an error response with the detail of the validation result will be
        returned.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        Examples:

        ```python
        from apiflask import APIFlask

        app = APIFlask(__name__)

        @app.get('/')
        @app.input(PetIn, location='json')
        def hello(json_data):
            print(json_data)
            return 'Hello'!
        ```

        Arguments:
            schema: The marshmallow schema of the input data.
            location: The location of the input data, one of `'json'` (default),
                `'files'`, `'form'`, `'cookies'`, `'headers'`, `'query'`
                (same as `'querystring'`).
            arg_name: The name of the argument passed to the view function,
                defaults to `{location}_data`.
            schema_name: The schema name for dict schema, only needed when you pass
                a schema dict (e.g., `{'name': String(required=True)}`) for `json`
                location.
            example: The example data in dict for request body, you should use either
                `example` or `examples`, not both.
            examples: Multiple examples for request body, you should pass a dict
                that contains multiple examples. Example:

                ```python
                {
                    'example foo': {  # example name
                        'summary': 'an example of foo',  # summary field is optional
                        'value': {'name': 'foo', 'id': 1}  # example value
                    },
                    'example bar': {
                        'summary': 'an example of bar',
                        'value': {'name': 'bar', 'id': 2}
                    },
                }
                ```
            validation: Flag to allow disabling of validation on input. Default to `True`.

        *Version changed: 2.2.2

        - Add parameter `validation` to allow disabling of validation on input.

        *Version changed: 2.0.0*

        - Always pass parsed data to view function as a keyword argument.
          The argument name will be in the form of `{location}_data`.

        *Version changed: 1.0*

        - Ensure only one input body location was used.
        - Add `form_and_files` and `json_or_form` (from webargs) location.
        - Rewrite `files` to act as `form_and_files`.
        - Use correct request content type for `form` and `files`.

        *Version changed: 0.12.0*

        - Move to APIFlask and APIBlueprint classes.

        *Version changed: 0.4.0*

        - Add parameter `examples`.
        """
        if isinstance(schema, ABCMapping):
            schema = _generate_schema_from_mapping(schema, schema_name)
        if isinstance(schema, type):  # pragma: no cover
            schema = schema()

        def decorator(f):
            f = _ensure_sync(f)

            if not validation:

                @wraps(f)
                def wrapper(*args: t.Any, **kwargs: t.Any):
                    raw_data = _load_raw_data(location)
                    kwargs[f'{location}_data'] = raw_data
                    return f(*args, **kwargs)

                return wrapper

            is_body_location = location in BODY_LOCATIONS
            if is_body_location and hasattr(f, '_spec') and 'body' in f._spec:
                raise RuntimeError(
                    'When using the app.input() decorator, you can only declare one request '
                    'body location (one of "json", "form", "files", "form_and_files", '
                    'and "json_or_form").'
                )
            if location == 'json':
                _annotate(
                    f,
                    body=schema,
                    body_example=example,
                    body_examples=examples,
                    content_type='application/json',
                )
            elif location == 'form':
                _annotate(
                    f,
                    body=schema,
                    body_example=example,
                    body_examples=examples,
                    content_type='application/x-www-form-urlencoded',
                )
            elif location in ['files', 'form_and_files']:
                _annotate(
                    f,
                    body=schema,
                    body_example=example,
                    body_examples=examples,
                    content_type='multipart/form-data',
                )
            elif location == 'json_or_form':
                _annotate(
                    f,
                    body=schema,
                    body_example=example,
                    body_examples=examples,
                    content_type=['application/x-www-form-urlencoded', 'application/json'],
                )
            else:
                if not hasattr(f, '_spec') or f._spec.get('args') is None:
                    _annotate(f, args=[])
                if location == 'path':
                    _annotate(f, omit_default_path_parameters=True)
                # TODO: Support set example for request parameters
                f._spec['args'].append((schema, location))
            return use_args(
                schema, location=location, arg_name=arg_name or f'{location}_data', **kwargs
            )(f)

        return decorator

    def output(
        self,
        schema: SchemaType,
        status_code: int = 200,
        description: str | None = None,
        schema_name: str | None = None,
        example: t.Any | None = None,
        examples: dict[str, t.Any] | None = None,
        links: dict[str, t.Any] | None = None,
        content_type: str | None = 'application/json',
        headers: SchemaType | None = None,
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Add output settings for view functions.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        The decorator will format the return value of your view function with
        provided marshmallow schema. You can return a dict or an object (such
        as a model class instance of ORMs). APIFlask will handle the formatting
        and turn your return value into a JSON response.

        P.S. The output data will not be validated; it's a design choice of marshmallow.
        marshmallow 4.0 may be support the output validation.

        Examples:

        ```python
        from apiflask import APIFlask

        app = APIFlask(__name__)

        @app.get('/')
        @app.output(PetOut)
        def hello():
            return the_dict_or_object_match_petout_schema
        ```

        Arguments:
            schema: The schemas of the output data.
            status_code: The status code of the response, defaults to `200`.
            description: The description of the response.
            schema_name: The schema name for dict schema, only needed when you pass
                a schema dict (e.g., `{'name': String()}`).
            example: The example data in dict for response body, you should use either
                `example` or `examples`, not both.
            examples: Multiple examples for response body, you should pass a dict
                that contains multiple examples. Example:

                ```python
                {
                    'example foo': {  # example name
                        'summary': 'an example of foo',  # summary field is optional
                        'value': {'name': 'foo', 'id': 1}  # example value
                    },
                    'example bar': {
                        'summary': 'an example of bar',
                        'value': {'name': 'bar', 'id': 2}
                    },
                }
                ```
            links: The `links` of response. It accepts a dict which maps a link name to
                a link object. Example:

                ```python
                {
                    'getAddressByUserId': {
                        'operationId': 'getUserAddress',
                        'parameters': {
                            'userId': '$request.path.id'
                        }
                    }
                }
                ```

                See the [docs](https://apiflask.com/openapi/#response-links) for more details
                about setting response links.

            content_type: The content/media type of the response. It defaults to `application/json`.
            headers: The schemas of the headers.

        *Version changed: 2.1.0*

        - Add parameter `headers`.

        *Version changed: 2.0.0*

        - Don't change the status code to 204 for EmptySchema.

        *Version changed: 1.3.0*

        - Add parameter `content_type`.

        *Version changed: 0.12.0*

        - Move to APIFlask and APIBlueprint classes.

        *Version changed: 0.10.0*

        - Add `links` parameter.

        *Version changed: 0.9.0*

        - Add base response customization support.

        *Version changed: 0.6.0*

        - Support decorating async views.

        *Version changed: 0.5.2*

        - Return the `Response` object directly.

        *Version changed: 0.4.0*

        - Add parameter `examples`.
        """
        if schema == {}:
            schema = EmptySchema
        if isinstance(schema, ABCMapping):
            schema = _generate_schema_from_mapping(schema, schema_name)
        if isinstance(schema, type):  # pragma: no cover
            schema = schema()

        if headers is not None:
            if headers == {}:
                headers = EmptySchema
            if isinstance(headers, ABCMapping):
                headers = _generate_schema_from_mapping(headers, None)
            if isinstance(headers, type):
                headers = headers()

        def decorator(f):
            f = _ensure_sync(f)
            _annotate(
                f,
                response={
                    'schema': schema,
                    'status_code': status_code,
                    'description': description,
                    'example': example,
                    'examples': examples,
                    'links': links,
                    'content_type': content_type,
                    'headers': headers,
                },
            )

            def _jsonify(
                obj: t.Any,
                many: bool = _sentinel,  # type: ignore
                *args: t.Any,
                **kwargs: t.Any,
            ) -> Response:  # pragma: no cover
                """From Flask-Marshmallow, see the NOTICE file for license information."""
                if isinstance(schema, FileSchema):
                    return obj  # type: ignore
                if many is _sentinel:
                    many = schema.many  # type: ignore
                base_schema: OpenAPISchemaType = current_app.config['BASE_RESPONSE_SCHEMA']
                if base_schema is not None and status_code != 204:
                    data_key: str = current_app.config['BASE_RESPONSE_DATA_KEY']

                    if isinstance(obj, dict):
                        if data_key not in obj:
                            raise RuntimeError(
                                f'The data key {data_key!r} is not found in the returned dict.'
                            )
                        obj[data_key] = schema.dump(obj[data_key], many=many)  # type: ignore
                    else:
                        if not hasattr(obj, data_key):
                            raise RuntimeError(
                                f'The data key {data_key!r} is not found in the returned object.'
                            )
                        setattr(
                            obj,
                            data_key,
                            schema.dump(getattr(obj, data_key), many=many),  # type: ignore
                        )

                    data = base_schema().dump(obj)  # type: ignore
                else:
                    data = schema.dump(obj, many=many)  # type: ignore
                return jsonify(data, *args, **kwargs)

            @wraps(f)
            def _response(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValueType:
                rv = f(*args, **kwargs)
                if isinstance(rv, Response):
                    return rv
                if not isinstance(rv, tuple):
                    return _jsonify(rv), status_code
                json = _jsonify(rv[0])
                if len(rv) == 2:
                    rv = (json, rv[1]) if isinstance(rv[1], int) else (json, status_code, rv[1])
                elif len(rv) >= 3:
                    rv = (json, rv[1], rv[2])
                else:
                    rv = (json, status_code)
                return rv  # type: ignore

            return _response

        return decorator

    def doc(
        self,
        summary: str | None = None,
        description: str | None = None,
        tags: list[str] | None = None,
        responses: ResponsesType | None = None,
        deprecated: bool | None = None,
        hide: bool | None = None,
        operation_id: str | None = None,
        security: str | list[str | dict[str, list]] | None = None,
        extensions: dict[str, t.Any] | None = None,
    ) -> t.Callable[[DecoratedType], DecoratedType]:
        """Set up the OpenAPI Spec for view functions.

        > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
        `app.post`, etc.).

        Examples:

        ```python
        from apiflask import APIFlask

        app = APIFlask(__name__)

        @app.get('/')
        @app.doc(summary='Say hello', tags=['Foo'])
        def hello():
            return 'Hello'
        ```

        Arguments:
            summary: The summary of this endpoint. If not set, the name of the view function
                will be used. If your view function is named with `get_pet`, then the summary
                will be "Get Pet". If the view function has a docstring, then the first
                line of the docstring will be used. The precedence will be:

                ```
                @app.doc(summary='blah') > the first line of docstring > the view function name
                ```

            description: The description of this endpoint. If not set, the lines after the empty
                line of the docstring will be used.
            tags: A list of tag names of this endpoint, map the tags you passed in the `app.tags`
                attribute. If `app.tags` is not set, the blueprint name will be used as tag name.
            responses: The other responses for this view function, accepts a list of status codes
                (`[404, 418]`) or a dict in a format of either `{404: 'Not Found'}` or
                `{404: {'description': 'Not Found', 'content': {'application/json':
                {'schema': FooSchema}}}}`. If a dict is passed and a response with the same status
                code is already present, the existing data will be overwritten.
            deprecated: Flag this endpoint as deprecated in API docs.
            hide: Hide this endpoint in API docs.
            operation_id: The `operationId` of this endpoint. Set config `AUTO_OPERATION_ID` to
                `True` to enable the auto-generating of operationId (in the format of
                `{method}_{endpoint}`).
            security: The `security` used for this endpoint. Match the security info specified in
                the `SECURITY_SCHEMES` configuration. If you don't need specify the scopes, just
                pass a security name (equals to `[{'foo': []}]`) or a list of security names (equals
                to `[{'foo': []}, {'bar': []}]`).
            extensions: The spec extensions of this endpoint (OpenAPI operation object). The fields
                in this extensions dict should start with "x-" prefix. See more details in the
                [Specification Extensions](https://spec.openapis.org/oas/v3.1.0#specification-extensions)
                chapter of OpenAPI docs.

        *Version changed: 2.2.0*

        - Add `extensions` parameter to support setting spec extensions.

        *Version changed: 2.0.0*

        - Remove the deprecated `tag` parameter.
        - Expand `responses` to support additional structure and parameters.

        *Version changed: 1.0*

        - Add `security` parameter to support customizing security info.
        - The `role` parameter is deprecated.

        *Version changed: 0.12.0*

        - Move to `APIFlask` and `APIBlueprint` classes.

        *Version changed: 0.10.0*

        - Add parameter `operation_id`.

        *Version changed: 0.5.0*

        - Change the default value of parameters `hide` and `deprecated` from `False` to `None`.

        *Version changed: 0.4.0*

        - Add parameter `tag`.

        *Version changed: 0.3.0*

        - Change the default value of `deprecated` from `None` to `False`.
        - Rename parameter `tags` to `tag`.

        *Version added: 0.2.0*
        """

        def decorator(f):
            f = _ensure_sync(f)
            _annotate(
                f,
                summary=summary,
                description=description,
                tags=tags,
                responses=responses,
                deprecated=deprecated,
                hide=hide,
                operation_id=operation_id,
                security=security,
                extensions=extensions,
            )
            return f

        return decorator

auth_required(auth, roles=None, optional=None)

Protect a view with provided authentication settings.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Examples:

from apiflask import APIFlask, HTTPTokenAuth

app = APIFlask(__name__)
auth = HTTPTokenAuth()

@app.get('/')
@app.auth_required(auth)
def hello():
    return 'Hello'!

Parameters:

Name Type Description Default
auth HTTPAuthType

The auth object, an instance of HTTPBasicAuth or HTTPTokenAuth.

required
roles list | None

The selected roles to allow to visit this view, accepts a list of role names. See Flask-HTTPAuth's documentation for more details.

None
optional str | None

Set to True to allow the view to execute even the authentication information is not included with the request, in which case the attribute auth.current_user will be None.

None

Version changed: 2.0.0

  • Remove the deprecated role parameter.

Version changed: 1.0.0

  • The role parameter is deprecated.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.4.0

  • Add parameter roles.
Source code in apiflask/scaffold.py
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
def auth_required(
    self, auth: HTTPAuthType, roles: list | None = None, optional: str | None = None
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Protect a view with provided authentication settings.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    Examples:

    ```python
    from apiflask import APIFlask, HTTPTokenAuth

    app = APIFlask(__name__)
    auth = HTTPTokenAuth()

    @app.get('/')
    @app.auth_required(auth)
    def hello():
        return 'Hello'!
    ```

    Arguments:
        auth: The `auth` object, an instance of
            [`HTTPBasicAuth`][apiflask.security.HTTPBasicAuth]
            or [`HTTPTokenAuth`][apiflask.security.HTTPTokenAuth].
        roles: The selected roles to allow to visit this view, accepts a list of role names.
            See [Flask-HTTPAuth's documentation][_role]{target:_blank} for more details.
            [_role]: https://flask-httpauth.readthedocs.io/en/latest/#user-roles
        optional: Set to `True` to allow the view to execute even the authentication
            information is not included with the request, in which case the attribute
            `auth.current_user` will be `None`.

    *Version changed: 2.0.0*

    - Remove the deprecated `role` parameter.

    *Version changed: 1.0.0*

    - The `role` parameter is deprecated.

    *Version changed: 0.12.0*

    - Move to `APIFlask` and `APIBlueprint` classes.

    *Version changed: 0.4.0*

    - Add parameter `roles`.
    """

    def decorator(f):
        f = _ensure_sync(f)
        _annotate(f, auth=auth, roles=roles or [])
        return auth.login_required(role=roles, optional=optional)(f)

    return decorator

delete(rule, **options)

Shortcut for app.route(methods=['DELETE']).

Source code in apiflask/scaffold.py
184
185
186
def delete(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['DELETE'])`."""
    return self._method_route('DELETE', rule, options)

doc(summary=None, description=None, tags=None, responses=None, deprecated=None, hide=None, operation_id=None, security=None, extensions=None)

Set up the OpenAPI Spec for view functions.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

@app.get('/')
@app.doc(summary='Say hello', tags=['Foo'])
def hello():
    return 'Hello'

Parameters:

Name Type Description Default
summary str | None

The summary of this endpoint. If not set, the name of the view function will be used. If your view function is named with get_pet, then the summary will be "Get Pet". If the view function has a docstring, then the first line of the docstring will be used. The precedence will be:

@app.doc(summary='blah') > the first line of docstring > the view function name
None
description str | None

The description of this endpoint. If not set, the lines after the empty line of the docstring will be used.

None
tags list[str] | None

A list of tag names of this endpoint, map the tags you passed in the app.tags attribute. If app.tags is not set, the blueprint name will be used as tag name.

None
responses ResponsesType | None

The other responses for this view function, accepts a list of status codes ([404, 418]) or a dict in a format of either {404: 'Not Found'} or {404: {'description': 'Not Found', 'content': {'application/json': {'schema': FooSchema}}}}. If a dict is passed and a response with the same status code is already present, the existing data will be overwritten.

None
deprecated bool | None

Flag this endpoint as deprecated in API docs.

None
hide bool | None

Hide this endpoint in API docs.

None
operation_id str | None

The operationId of this endpoint. Set config AUTO_OPERATION_ID to True to enable the auto-generating of operationId (in the format of {method}_{endpoint}).

None
security str | list[str | dict[str, list]] | None

The security used for this endpoint. Match the security info specified in the SECURITY_SCHEMES configuration. If you don't need specify the scopes, just pass a security name (equals to [{'foo': []}]) or a list of security names (equals to [{'foo': []}, {'bar': []}]).

None
extensions dict[str, Any] | None

The spec extensions of this endpoint (OpenAPI operation object). The fields in this extensions dict should start with "x-" prefix. See more details in the Specification Extensions chapter of OpenAPI docs.

None

Version changed: 2.2.0

  • Add extensions parameter to support setting spec extensions.

Version changed: 2.0.0

  • Remove the deprecated tag parameter.
  • Expand responses to support additional structure and parameters.

Version changed: 1.0

  • Add security parameter to support customizing security info.
  • The role parameter is deprecated.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.10.0

  • Add parameter operation_id.

Version changed: 0.5.0

  • Change the default value of parameters hide and deprecated from False to None.

Version changed: 0.4.0

  • Add parameter tag.

Version changed: 0.3.0

  • Change the default value of deprecated from None to False.
  • Rename parameter tags to tag.

Version added: 0.2.0

Source code in apiflask/scaffold.py
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
def doc(
    self,
    summary: str | None = None,
    description: str | None = None,
    tags: list[str] | None = None,
    responses: ResponsesType | None = None,
    deprecated: bool | None = None,
    hide: bool | None = None,
    operation_id: str | None = None,
    security: str | list[str | dict[str, list]] | None = None,
    extensions: dict[str, t.Any] | None = None,
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Set up the OpenAPI Spec for view functions.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)

    @app.get('/')
    @app.doc(summary='Say hello', tags=['Foo'])
    def hello():
        return 'Hello'
    ```

    Arguments:
        summary: The summary of this endpoint. If not set, the name of the view function
            will be used. If your view function is named with `get_pet`, then the summary
            will be "Get Pet". If the view function has a docstring, then the first
            line of the docstring will be used. The precedence will be:

            ```
            @app.doc(summary='blah') > the first line of docstring > the view function name
            ```

        description: The description of this endpoint. If not set, the lines after the empty
            line of the docstring will be used.
        tags: A list of tag names of this endpoint, map the tags you passed in the `app.tags`
            attribute. If `app.tags` is not set, the blueprint name will be used as tag name.
        responses: The other responses for this view function, accepts a list of status codes
            (`[404, 418]`) or a dict in a format of either `{404: 'Not Found'}` or
            `{404: {'description': 'Not Found', 'content': {'application/json':
            {'schema': FooSchema}}}}`. If a dict is passed and a response with the same status
            code is already present, the existing data will be overwritten.
        deprecated: Flag this endpoint as deprecated in API docs.
        hide: Hide this endpoint in API docs.
        operation_id: The `operationId` of this endpoint. Set config `AUTO_OPERATION_ID` to
            `True` to enable the auto-generating of operationId (in the format of
            `{method}_{endpoint}`).
        security: The `security` used for this endpoint. Match the security info specified in
            the `SECURITY_SCHEMES` configuration. If you don't need specify the scopes, just
            pass a security name (equals to `[{'foo': []}]`) or a list of security names (equals
            to `[{'foo': []}, {'bar': []}]`).
        extensions: The spec extensions of this endpoint (OpenAPI operation object). The fields
            in this extensions dict should start with "x-" prefix. See more details in the
            [Specification Extensions](https://spec.openapis.org/oas/v3.1.0#specification-extensions)
            chapter of OpenAPI docs.

    *Version changed: 2.2.0*

    - Add `extensions` parameter to support setting spec extensions.

    *Version changed: 2.0.0*

    - Remove the deprecated `tag` parameter.
    - Expand `responses` to support additional structure and parameters.

    *Version changed: 1.0*

    - Add `security` parameter to support customizing security info.
    - The `role` parameter is deprecated.

    *Version changed: 0.12.0*

    - Move to `APIFlask` and `APIBlueprint` classes.

    *Version changed: 0.10.0*

    - Add parameter `operation_id`.

    *Version changed: 0.5.0*

    - Change the default value of parameters `hide` and `deprecated` from `False` to `None`.

    *Version changed: 0.4.0*

    - Add parameter `tag`.

    *Version changed: 0.3.0*

    - Change the default value of `deprecated` from `None` to `False`.
    - Rename parameter `tags` to `tag`.

    *Version added: 0.2.0*
    """

    def decorator(f):
        f = _ensure_sync(f)
        _annotate(
            f,
            summary=summary,
            description=description,
            tags=tags,
            responses=responses,
            deprecated=deprecated,
            hide=hide,
            operation_id=operation_id,
            security=security,
            extensions=extensions,
        )
        return f

    return decorator

get(rule, **options)

Shortcut for app.route() or app.route(methods=['GET']).

Source code in apiflask/scaffold.py
168
169
170
def get(self, rule: str, **options: t.Any):
    """Shortcut for `app.route()` or `app.route(methods=['GET'])`."""
    return self._method_route('GET', rule, options)

input(schema, location='json', arg_name=None, schema_name=None, example=None, examples=None, validation=True, **kwargs)

Add input settings for view functions.

If the validation passed, the data will be injected into the view function as a keyword argument in the form of dict and named {location}_data. Otherwise, an error response with the detail of the validation result will be returned.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

@app.get('/')
@app.input(PetIn, location='json')
def hello(json_data):
    print(json_data)
    return 'Hello'!

Parameters:

Name Type Description Default
schema SchemaType

The marshmallow schema of the input data.

required
location str

The location of the input data, one of 'json' (default), 'files', 'form', 'cookies', 'headers', 'query' (same as 'querystring').

'json'
arg_name str | None

The name of the argument passed to the view function, defaults to {location}_data.

None
schema_name str | None

The schema name for dict schema, only needed when you pass a schema dict (e.g., {'name': String(required=True)}) for json location.

None
example Any | None

The example data in dict for request body, you should use either example or examples, not both.

None
examples dict[str, Any] | None

Multiple examples for request body, you should pass a dict that contains multiple examples. Example:

{
    'example foo': {  # example name
        'summary': 'an example of foo',  # summary field is optional
        'value': {'name': 'foo', 'id': 1}  # example value
    },
    'example bar': {
        'summary': 'an example of bar',
        'value': {'name': 'bar', 'id': 2}
    },
}
None
validation bool

Flag to allow disabling of validation on input. Default to True.

True

*Version changed: 2.2.2

  • Add parameter validation to allow disabling of validation on input.

Version changed: 2.0.0

  • Always pass parsed data to view function as a keyword argument. The argument name will be in the form of {location}_data.

Version changed: 1.0

  • Ensure only one input body location was used.
  • Add form_and_files and json_or_form (from webargs) location.
  • Rewrite files to act as form_and_files.
  • Use correct request content type for form and files.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.4.0

  • Add parameter examples.
Source code in apiflask/scaffold.py
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
def input(
    self,
    schema: SchemaType,
    location: str = 'json',
    arg_name: str | None = None,
    schema_name: str | None = None,
    example: t.Any | None = None,
    examples: dict[str, t.Any] | None = None,
    validation: bool = True,
    **kwargs: t.Any,
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Add input settings for view functions.

    If the validation passed, the data will be injected into the view
    function as a keyword argument in the form of `dict` and named `{location}_data`.
    Otherwise, an error response with the detail of the validation result will be
    returned.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)

    @app.get('/')
    @app.input(PetIn, location='json')
    def hello(json_data):
        print(json_data)
        return 'Hello'!
    ```

    Arguments:
        schema: The marshmallow schema of the input data.
        location: The location of the input data, one of `'json'` (default),
            `'files'`, `'form'`, `'cookies'`, `'headers'`, `'query'`
            (same as `'querystring'`).
        arg_name: The name of the argument passed to the view function,
            defaults to `{location}_data`.
        schema_name: The schema name for dict schema, only needed when you pass
            a schema dict (e.g., `{'name': String(required=True)}`) for `json`
            location.
        example: The example data in dict for request body, you should use either
            `example` or `examples`, not both.
        examples: Multiple examples for request body, you should pass a dict
            that contains multiple examples. Example:

            ```python
            {
                'example foo': {  # example name
                    'summary': 'an example of foo',  # summary field is optional
                    'value': {'name': 'foo', 'id': 1}  # example value
                },
                'example bar': {
                    'summary': 'an example of bar',
                    'value': {'name': 'bar', 'id': 2}
                },
            }
            ```
        validation: Flag to allow disabling of validation on input. Default to `True`.

    *Version changed: 2.2.2

    - Add parameter `validation` to allow disabling of validation on input.

    *Version changed: 2.0.0*

    - Always pass parsed data to view function as a keyword argument.
      The argument name will be in the form of `{location}_data`.

    *Version changed: 1.0*

    - Ensure only one input body location was used.
    - Add `form_and_files` and `json_or_form` (from webargs) location.
    - Rewrite `files` to act as `form_and_files`.
    - Use correct request content type for `form` and `files`.

    *Version changed: 0.12.0*

    - Move to APIFlask and APIBlueprint classes.

    *Version changed: 0.4.0*

    - Add parameter `examples`.
    """
    if isinstance(schema, ABCMapping):
        schema = _generate_schema_from_mapping(schema, schema_name)
    if isinstance(schema, type):  # pragma: no cover
        schema = schema()

    def decorator(f):
        f = _ensure_sync(f)

        if not validation:

            @wraps(f)
            def wrapper(*args: t.Any, **kwargs: t.Any):
                raw_data = _load_raw_data(location)
                kwargs[f'{location}_data'] = raw_data
                return f(*args, **kwargs)

            return wrapper

        is_body_location = location in BODY_LOCATIONS
        if is_body_location and hasattr(f, '_spec') and 'body' in f._spec:
            raise RuntimeError(
                'When using the app.input() decorator, you can only declare one request '
                'body location (one of "json", "form", "files", "form_and_files", '
                'and "json_or_form").'
            )
        if location == 'json':
            _annotate(
                f,
                body=schema,
                body_example=example,
                body_examples=examples,
                content_type='application/json',
            )
        elif location == 'form':
            _annotate(
                f,
                body=schema,
                body_example=example,
                body_examples=examples,
                content_type='application/x-www-form-urlencoded',
            )
        elif location in ['files', 'form_and_files']:
            _annotate(
                f,
                body=schema,
                body_example=example,
                body_examples=examples,
                content_type='multipart/form-data',
            )
        elif location == 'json_or_form':
            _annotate(
                f,
                body=schema,
                body_example=example,
                body_examples=examples,
                content_type=['application/x-www-form-urlencoded', 'application/json'],
            )
        else:
            if not hasattr(f, '_spec') or f._spec.get('args') is None:
                _annotate(f, args=[])
            if location == 'path':
                _annotate(f, omit_default_path_parameters=True)
            # TODO: Support set example for request parameters
            f._spec['args'].append((schema, location))
        return use_args(
            schema, location=location, arg_name=arg_name or f'{location}_data', **kwargs
        )(f)

    return decorator

output(schema, status_code=200, description=None, schema_name=None, example=None, examples=None, links=None, content_type='application/json', headers=None)

Add output settings for view functions.

Be sure to put it under the routes decorators (i.e., app.route, app.get, app.post, etc.).

The decorator will format the return value of your view function with provided marshmallow schema. You can return a dict or an object (such as a model class instance of ORMs). APIFlask will handle the formatting and turn your return value into a JSON response.

P.S. The output data will not be validated; it's a design choice of marshmallow. marshmallow 4.0 may be support the output validation.

Examples:

from apiflask import APIFlask

app = APIFlask(__name__)

@app.get('/')
@app.output(PetOut)
def hello():
    return the_dict_or_object_match_petout_schema

Parameters:

Name Type Description Default
schema SchemaType

The schemas of the output data.

required
status_code int

The status code of the response, defaults to 200.

200
description str | None

The description of the response.

None
schema_name str | None

The schema name for dict schema, only needed when you pass a schema dict (e.g., {'name': String()}).

None
example Any | None

The example data in dict for response body, you should use either example or examples, not both.

None
examples dict[str, Any] | None

Multiple examples for response body, you should pass a dict that contains multiple examples. Example:

{
    'example foo': {  # example name
        'summary': 'an example of foo',  # summary field is optional
        'value': {'name': 'foo', 'id': 1}  # example value
    },
    'example bar': {
        'summary': 'an example of bar',
        'value': {'name': 'bar', 'id': 2}
    },
}
None
links dict[str, Any] | None

The links of response. It accepts a dict which maps a link name to a link object. Example:

{
    'getAddressByUserId': {
        'operationId': 'getUserAddress',
        'parameters': {
            'userId': '$request.path.id'
        }
    }
}

See the docs for more details about setting response links.

None
content_type str | None

The content/media type of the response. It defaults to application/json.

'application/json'
headers SchemaType | None

The schemas of the headers.

None

Version changed: 2.1.0

  • Add parameter headers.

Version changed: 2.0.0

  • Don't change the status code to 204 for EmptySchema.

Version changed: 1.3.0

  • Add parameter content_type.

Version changed: 0.12.0

  • Move to APIFlask and APIBlueprint classes.

Version changed: 0.10.0

  • Add links parameter.

Version changed: 0.9.0

  • Add base response customization support.

Version changed: 0.6.0

  • Support decorating async views.

Version changed: 0.5.2

  • Return the Response object directly.

Version changed: 0.4.0

  • Add parameter examples.
Source code in apiflask/scaffold.py
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
def output(
    self,
    schema: SchemaType,
    status_code: int = 200,
    description: str | None = None,
    schema_name: str | None = None,
    example: t.Any | None = None,
    examples: dict[str, t.Any] | None = None,
    links: dict[str, t.Any] | None = None,
    content_type: str | None = 'application/json',
    headers: SchemaType | None = None,
) -> t.Callable[[DecoratedType], DecoratedType]:
    """Add output settings for view functions.

    > Be sure to put it under the routes decorators (i.e., `app.route`, `app.get`,
    `app.post`, etc.).

    The decorator will format the return value of your view function with
    provided marshmallow schema. You can return a dict or an object (such
    as a model class instance of ORMs). APIFlask will handle the formatting
    and turn your return value into a JSON response.

    P.S. The output data will not be validated; it's a design choice of marshmallow.
    marshmallow 4.0 may be support the output validation.

    Examples:

    ```python
    from apiflask import APIFlask

    app = APIFlask(__name__)

    @app.get('/')
    @app.output(PetOut)
    def hello():
        return the_dict_or_object_match_petout_schema
    ```

    Arguments:
        schema: The schemas of the output data.
        status_code: The status code of the response, defaults to `200`.
        description: The description of the response.
        schema_name: The schema name for dict schema, only needed when you pass
            a schema dict (e.g., `{'name': String()}`).
        example: The example data in dict for response body, you should use either
            `example` or `examples`, not both.
        examples: Multiple examples for response body, you should pass a dict
            that contains multiple examples. Example:

            ```python
            {
                'example foo': {  # example name
                    'summary': 'an example of foo',  # summary field is optional
                    'value': {'name': 'foo', 'id': 1}  # example value
                },
                'example bar': {
                    'summary': 'an example of bar',
                    'value': {'name': 'bar', 'id': 2}
                },
            }
            ```
        links: The `links` of response. It accepts a dict which maps a link name to
            a link object. Example:

            ```python
            {
                'getAddressByUserId': {
                    'operationId': 'getUserAddress',
                    'parameters': {
                        'userId': '$request.path.id'
                    }
                }
            }
            ```

            See the [docs](https://apiflask.com/openapi/#response-links) for more details
            about setting response links.

        content_type: The content/media type of the response. It defaults to `application/json`.
        headers: The schemas of the headers.

    *Version changed: 2.1.0*

    - Add parameter `headers`.

    *Version changed: 2.0.0*

    - Don't change the status code to 204 for EmptySchema.

    *Version changed: 1.3.0*

    - Add parameter `content_type`.

    *Version changed: 0.12.0*

    - Move to APIFlask and APIBlueprint classes.

    *Version changed: 0.10.0*

    - Add `links` parameter.

    *Version changed: 0.9.0*

    - Add base response customization support.

    *Version changed: 0.6.0*

    - Support decorating async views.

    *Version changed: 0.5.2*

    - Return the `Response` object directly.

    *Version changed: 0.4.0*

    - Add parameter `examples`.
    """
    if schema == {}:
        schema = EmptySchema
    if isinstance(schema, ABCMapping):
        schema = _generate_schema_from_mapping(schema, schema_name)
    if isinstance(schema, type):  # pragma: no cover
        schema = schema()

    if headers is not None:
        if headers == {}:
            headers = EmptySchema
        if isinstance(headers, ABCMapping):
            headers = _generate_schema_from_mapping(headers, None)
        if isinstance(headers, type):
            headers = headers()

    def decorator(f):
        f = _ensure_sync(f)
        _annotate(
            f,
            response={
                'schema': schema,
                'status_code': status_code,
                'description': description,
                'example': example,
                'examples': examples,
                'links': links,
                'content_type': content_type,
                'headers': headers,
            },
        )

        def _jsonify(
            obj: t.Any,
            many: bool = _sentinel,  # type: ignore
            *args: t.Any,
            **kwargs: t.Any,
        ) -> Response:  # pragma: no cover
            """From Flask-Marshmallow, see the NOTICE file for license information."""
            if isinstance(schema, FileSchema):
                return obj  # type: ignore
            if many is _sentinel:
                many = schema.many  # type: ignore
            base_schema: OpenAPISchemaType = current_app.config['BASE_RESPONSE_SCHEMA']
            if base_schema is not None and status_code != 204:
                data_key: str = current_app.config['BASE_RESPONSE_DATA_KEY']

                if isinstance(obj, dict):
                    if data_key not in obj:
                        raise RuntimeError(
                            f'The data key {data_key!r} is not found in the returned dict.'
                        )
                    obj[data_key] = schema.dump(obj[data_key], many=many)  # type: ignore
                else:
                    if not hasattr(obj, data_key):
                        raise RuntimeError(
                            f'The data key {data_key!r} is not found in the returned object.'
                        )
                    setattr(
                        obj,
                        data_key,
                        schema.dump(getattr(obj, data_key), many=many),  # type: ignore
                    )

                data = base_schema().dump(obj)  # type: ignore
            else:
                data = schema.dump(obj, many=many)  # type: ignore
            return jsonify(data, *args, **kwargs)

        @wraps(f)
        def _response(*args: t.Any, **kwargs: t.Any) -> ResponseReturnValueType:
            rv = f(*args, **kwargs)
            if isinstance(rv, Response):
                return rv
            if not isinstance(rv, tuple):
                return _jsonify(rv), status_code
            json = _jsonify(rv[0])
            if len(rv) == 2:
                rv = (json, rv[1]) if isinstance(rv[1], int) else (json, status_code, rv[1])
            elif len(rv) >= 3:
                rv = (json, rv[1], rv[2])
            else:
                rv = (json, status_code)
            return rv  # type: ignore

        return _response

    return decorator

patch(rule, **options)

Shortcut for app.route(methods=['PATCH']).

Source code in apiflask/scaffold.py
180
181
182
def patch(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['PATCH'])`."""
    return self._method_route('PATCH', rule, options)

post(rule, **options)

Shortcut for app.route(methods=['POST']).

Source code in apiflask/scaffold.py
172
173
174
def post(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['POST'])`."""
    return self._method_route('POST', rule, options)

put(rule, **options)

Shortcut for app.route(methods=['PUT']).

Source code in apiflask/scaffold.py
176
177
178
def put(self, rule: str, **options: t.Any):
    """Shortcut for `app.route(methods=['PUT'])`."""
    return self._method_route('PUT', rule, options)