Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Responses key supports both string, int, and HTTPStatus #87

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/Usage/Response.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,32 @@ def get_book(path: BookPath, query: BookBody):
return {"code": 0, "message": "ok", "data": {}}
```

*New in v2.5.0*

Now you can use `string`, `int`, and `HTTPStatus` as response's key.

```python hl_lines="5 7"
from http import HTTPStatus


class BookResponse(BaseModel):
message: str = Field(..., description="The message")


@api.get("/hello/<string:name>",
responses={
HTTPStatus.OK: BookResponse,
"201": {"content": {"text/csv": {"schema": {"type": "string"}}}},
204: None
})
def hello(path: HelloPath):
message = {"message": f"""Hello {path.name}!"""}

response = make_response(json.dumps(message), HTTPStatus.OK)
response.mimetype = "application/json"
return response
```


![image-20210526104627124](../assets/image-20210526104627124.png)

Expand Down
4 changes: 2 additions & 2 deletions examples/response_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class Config:


@bp.get("/hello/<string:name>",
responses={"200": Message, "201": {"content": {"text/csv": {"schema": {"type": "string"}}}}})
responses={HTTPStatus.OK: Message, "201": {"content": {"text/csv": {"schema": {"type": "string"}}}}})
def hello(path: HelloPath):
message = {"message": f"""Hello {path.name}!"""}

Expand All @@ -54,7 +54,7 @@ def hello(path: HelloPath):
return response


@bp.get("/hello_no_response/<string:name>", responses={"204": None})
@bp.get("/hello_no_response/<string:name>", responses={204: None})
def hello_no_response(path: HelloPath):
message = {"message": f"""Hello {path.name}!"""}

Expand Down
19 changes: 19 additions & 0 deletions flask_openapi3/_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# @Author : llc
# @Time : 2021/6/21 15:54
from enum import Enum
from http import HTTPStatus

HTTP_STATUS = {str(status.value): status.phrase for status in HTTPStatus}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do that though 🤔 you can just get rid of this entirely. It's not used anywhere despite this being labelled an internal module.
Not a blocker just curious.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to be consistent with the built-in modules, HTTPStatus is not useless, it is used in utils.py



class HTTPMethod(str, Enum):
GET = "GET"
POST = "POST"
PUT = "PUT"
DELETE = "DELETE"
PATCH = "PATCH"
HEAD = "HEAD"
OPTIONS = "OPTIONS"
TRACE = "TRACE"
CONNECT = "CONNECT"
24 changes: 16 additions & 8 deletions flask_openapi3/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@
# @Author : llc
# @Time : 2022/4/1 16:54
from copy import deepcopy
from typing import Optional, List, Dict, Any, Type, Callable, Tuple, Union
from typing import Optional, List, Dict, Any, Type, Callable, Tuple

from flask import Blueprint
from pydantic import BaseModel

from .http import HTTPMethod
from ._http import HTTPMethod
from .models import Tag, ExternalDocumentation
from .models.common import ExtraRequestBody
from .models.server import Server
from .scaffold import APIScaffold
from .types import ResponseDict
from .utils import get_operation, get_responses, parse_and_store_tags, parse_parameters, parse_method, \
get_operation_id_for_path, parse_rule
get_operation_id_for_path, parse_rule, convert_responses_key_to_string


class APIBlueprint(APIScaffold, Blueprint):
Expand All @@ -24,7 +25,7 @@ def __init__(
*,
abp_tags: Optional[List[Tag]] = None,
abp_security: Optional[List[Dict[str, List[str]]]] = None,
abp_responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
abp_responses: Optional[ResponseDict] = None,
doc_ui: bool = True,
operation_id_callback: Callable = get_operation_id_for_path,
**kwargs: Any
Expand Down Expand Up @@ -56,7 +57,11 @@ def __init__(
# Set values from arguments or default values
self.abp_tags = abp_tags or []
self.abp_security = abp_security or []
self.abp_responses = abp_responses or {}

abp_responses = abp_responses or {}
# Convert key to string
self.abp_responses = convert_responses_key_to_string(abp_responses)

self.doc_ui = doc_ui

# Set the operation ID callback function
Expand Down Expand Up @@ -99,7 +104,7 @@ def _do_decorator(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, Dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -132,12 +137,15 @@ def _do_decorator(
"""
if self.doc_ui is True and doc_ui is True:
if responses is None:
responses = {}
new_responses = {}
else:
# Convert key to string
new_responses = convert_responses_key_to_string(responses)
if extra_responses is None:
extra_responses = {}
# Global response: combine API responses
combine_responses = deepcopy(self.abp_responses)
combine_responses.update(**responses)
combine_responses.update(**new_responses)
# Create operation
operation = get_operation(
func,
Expand Down
87 changes: 0 additions & 87 deletions flask_openapi3/http.py

This file was deleted.

27 changes: 17 additions & 10 deletions flask_openapi3/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,22 @@
import os
import re
from copy import deepcopy
from typing import Optional, List, Dict, Union, Any, Type, Callable, Tuple
from typing import Optional, List, Dict, Any, Type, Callable, Tuple

from flask import Flask, Blueprint, render_template_string
from pydantic import BaseModel

from ._http import HTTPMethod
from .blueprint import APIBlueprint
from .commands import openapi_command
from .http import HTTPMethod
from .models import Info, APISpec, Tag, Components, Server
from .models.common import ExternalDocumentation, ExtraRequestBody
from .models.oauth import OAuthConfig
from .models.security import SecurityScheme
from .scaffold import APIScaffold
from .templates import openapi_html_string, redoc_html_string, rapidoc_html_string, swagger_html_string
from .types import ResponseDict, SecuritySchemesDict
from .utils import get_operation, get_responses, parse_and_store_tags, parse_parameters, parse_method, \
get_operation_id_for_path
get_operation_id_for_path, convert_responses_key_to_string
from .view import APIView


Expand All @@ -30,9 +30,9 @@ def __init__(
import_name: str,
*,
info: Optional[Info] = None,
security_schemes: Optional[Dict[str, Union[SecurityScheme, Dict[str, Any]]]] = None,
security_schemes: Optional[SecuritySchemesDict] = None,
oauth_config: Optional[OAuthConfig] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
doc_ui: bool = True,
doc_expansion: str = "list",
doc_prefix: str = "/openapi",
Expand Down Expand Up @@ -87,7 +87,11 @@ def __init__(

# Set security schemes, responses, paths and components
self.security_schemes = security_schemes
self.responses = responses or {}

responses = responses or {}
# Convert key to string
self.responses = convert_responses_key_to_string(responses)

self.paths: Dict = dict()
self.components_schemas: Dict = dict()
self.components = Components()
Expand Down Expand Up @@ -287,7 +291,7 @@ def _do_decorator(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, Dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -320,12 +324,15 @@ def _do_decorator(
"""
if doc_ui is True:
if responses is None:
responses = {}
new_responses = {}
else:
# Convert key to string
new_responses = convert_responses_key_to_string(responses)
if extra_responses is None:
extra_responses = {}
# Global response: combine API responses
combine_responses = deepcopy(self.responses)
combine_responses.update(**responses)
combine_responses.update(**new_responses)
# Create operation
operation = get_operation(
func,
Expand Down
17 changes: 9 additions & 8 deletions flask_openapi3/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@
import warnings
from abc import ABC
from functools import wraps
from typing import Callable, List, Optional, Dict, Type, Any, Tuple, Union
from typing import Callable, List, Optional, Dict, Type, Any, Tuple

from flask.scaffold import Scaffold
from flask.wrappers import Response
from pydantic import BaseModel

from .http import HTTPMethod
from ._http import HTTPMethod
from .models import ExternalDocumentation
from .models.common import ExtraRequestBody
from .models.server import Server
from .models.tag import Tag
from .request import _do_request
from .types import ResponseDict

if sys.version_info >= (3, 8):
iscoroutinefunction = inspect.iscoroutinefunction
Expand Down Expand Up @@ -48,7 +49,7 @@ def _do_decorator(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use inheritance for GET, PUT, DELETE, etc and only overwrite the http method?
Every time there is a change, you need to change like 5-6 classes. It's inconvenient for you.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this code is more straightforward and easier to understand, and if you have a better way, I suggest reopening a PR to discuss.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, I'm not sure if my way is technically better. I'll propose one and show what I mean later. Maybe you'll like it.

extra_responses: Optional[Dict[str, dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -146,7 +147,7 @@ def get(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -232,7 +233,7 @@ def post(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -316,7 +317,7 @@ def put(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -400,7 +401,7 @@ def delete(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down Expand Up @@ -484,7 +485,7 @@ def patch(
operation_id: Optional[str] = None,
extra_form: Optional[ExtraRequestBody] = None,
extra_body: Optional[ExtraRequestBody] = None,
responses: Optional[Dict[str, Union[Type[BaseModel], Dict[Any, Any], None]]] = None,
responses: Optional[ResponseDict] = None,
extra_responses: Optional[Dict[str, dict]] = None,
deprecated: Optional[bool] = None,
security: Optional[List[Dict[str, List[Any]]]] = None,
Expand Down
Loading