Skip to content

Commit e9a8d98

Browse files
authored
feat: ✨ make response_model_exclude_none=True a default (#47)
1 parent 46e2f6a commit e9a8d98

File tree

5 files changed

+475
-427
lines changed

5 files changed

+475
-427
lines changed

pest/core/application.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,16 @@ def __init__(
7979
]
8080
)
8181

82-
self.add_exception_handlers([
83-
(HTTPException, handle.http),
84-
(ValidationError, handle.request_validation),
85-
(RequestValidationError, handle.request_validation),
86-
(WebSocketRequestValidationError, handle.websocket_request_validation),
87-
# for everything else, there's Mastercard (or was it Bancard? 🤔)
88-
(Exception, handle.the_rest),
89-
])
82+
self.add_exception_handlers(
83+
[
84+
(HTTPException, handle.http),
85+
(ValidationError, handle.request_validation),
86+
(RequestValidationError, handle.request_validation),
87+
(WebSocketRequestValidationError, handle.websocket_request_validation),
88+
# for everything else, there's Mastercard (or was it Bancard? 🤔)
89+
(Exception, handle.the_rest),
90+
]
91+
)
9092

9193
def add_exception_handlers(
9294
self, handlers: List[Tuple[Union[int, Type[Exception]], Callable]]
@@ -129,7 +131,7 @@ def add_api_route(
129131
response_model_by_alias: bool = True,
130132
response_model_exclude_unset: bool = False,
131133
response_model_exclude_defaults: bool = False,
132-
response_model_exclude_none: bool = False,
134+
response_model_exclude_none: bool = True,
133135
include_in_schema: bool = True,
134136
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
135137
name: Optional[str] = None,

pest/core/handler.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ def setup_handler(cls: Type['Controller'], handler: HandlerTuple) -> APIRoute:
3737
meta_dict = clean_dict(asdict(handler_meta), HandlerMetaDict)
3838
_patch_handler_fn(cls, handler_fn)
3939

40+
# by default, exclude None values from the response
41+
if 'response_model_exclude_none' not in meta_dict:
42+
meta_dict['response_model_exclude_none'] = True
43+
4044
route = APIRoute(
4145
endpoint=handler_fn,
4246
path=handler_meta.path,
@@ -129,9 +133,7 @@ def _get_pest_injection(parameter: Parameter) -> Union[List[_Inject], None]:
129133
token = (
130134
parameter.default.token
131135
if parameter.default.token is not None
132-
else parameter.annotation
133-
if parameter.annotation is not Parameter.empty
134-
else None
136+
else parameter.annotation if parameter.annotation is not Parameter.empty else None
135137
)
136138
annotations = (token, inject)
137139
else:

pest/utils/fastapi/router.py

Lines changed: 144 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -1,144 +1,144 @@
1-
from enum import Enum
2-
from typing import (
3-
TYPE_CHECKING,
4-
Any,
5-
Callable,
6-
Dict,
7-
List,
8-
Optional,
9-
Sequence,
10-
Set,
11-
Type,
12-
Union,
13-
)
14-
15-
from fastapi import APIRouter, params
16-
from fastapi.datastructures import Default, DefaultPlaceholder
17-
from fastapi.routing import APIRoute
18-
from fastapi.types import IncEx
19-
from fastapi.utils import (
20-
generate_unique_id,
21-
)
22-
from starlette.responses import JSONResponse, Response
23-
from starlette.routing import BaseRoute
24-
25-
if TYPE_CHECKING: # pragma: no cover
26-
from ...core.controller import Controller
27-
28-
29-
class PestRouter(APIRouter):
30-
"""
31-
Extends the `APIRouter` class from FastAPI to handle / at the end of API routes.
32-
By default, FastAPI redirects routes that end in / to the route without /. This
33-
class avoids that behavior and for each route that is added, it adds an alternative
34-
route with or without /, as appropriate.
35-
"""
36-
37-
routes: List[APIRoute]
38-
controller: Type['Controller']
39-
40-
def add_api_route(
41-
self,
42-
path: str,
43-
endpoint: Callable[..., Any],
44-
*,
45-
response_model: Any = Default(None),
46-
status_code: Optional[int] = None,
47-
tags: Optional[List[Union[str, Enum]]] = None,
48-
dependencies: Optional[Sequence[params.Depends]] = None,
49-
summary: Optional[str] = None,
50-
description: Optional[str] = None,
51-
response_description: str = 'Successful Response',
52-
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
53-
deprecated: Optional[bool] = None,
54-
methods: Optional[Union[Set[str], List[str]]] = None,
55-
operation_id: Optional[str] = None,
56-
response_model_include: Optional[IncEx] = None,
57-
response_model_exclude: Optional[IncEx] = None,
58-
response_model_by_alias: bool = True,
59-
response_model_exclude_unset: bool = False,
60-
response_model_exclude_defaults: bool = False,
61-
response_model_exclude_none: bool = False,
62-
include_in_schema: bool = True,
63-
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
64-
name: Optional[str] = None,
65-
route_class_override: Optional[Type[APIRoute]] = None,
66-
callbacks: Optional[List[BaseRoute]] = None,
67-
openapi_extra: Optional[Dict[str, Any]] = None,
68-
generate_unique_id_function: Union[Callable[[APIRoute], str], DefaultPlaceholder] = Default(
69-
generate_unique_id
70-
),
71-
**kwargs: Any,
72-
) -> None:
73-
"""
74-
Registra un endpoint de la API con la ruta proporcionada y con su ruta alternativa
75-
dependiendo de si la ruta proporcionada termina en `/` o no.
76-
77-
Por ejemplo, si se registra la ruta `/users`, se registrará también la ruta `/users/`
78-
y viceversa.
79-
"""
80-
81-
if path.endswith('/'):
82-
path = path[:-1]
83-
84-
alternate_path = path + '/'
85-
86-
if (self.prefix + path) != '':
87-
super().add_api_route(
88-
path,
89-
endpoint,
90-
response_model=response_model,
91-
status_code=status_code,
92-
tags=tags,
93-
dependencies=dependencies,
94-
summary=summary,
95-
description=description,
96-
response_description=response_description,
97-
responses=responses,
98-
deprecated=deprecated,
99-
methods=methods,
100-
operation_id=operation_id,
101-
response_model_include=response_model_include,
102-
response_model_exclude=response_model_exclude,
103-
response_model_by_alias=response_model_by_alias,
104-
response_model_exclude_unset=response_model_exclude_unset,
105-
response_model_exclude_defaults=response_model_exclude_defaults,
106-
response_model_exclude_none=response_model_exclude_none,
107-
include_in_schema=include_in_schema,
108-
response_class=response_class,
109-
name=name,
110-
route_class_override=route_class_override,
111-
callbacks=callbacks,
112-
openapi_extra=openapi_extra,
113-
generate_unique_id_function=generate_unique_id_function,
114-
)
115-
116-
if (self.prefix + alternate_path) != '':
117-
super().add_api_route(
118-
alternate_path,
119-
endpoint,
120-
response_model=response_model,
121-
status_code=status_code,
122-
tags=tags,
123-
dependencies=dependencies,
124-
summary=summary,
125-
description=description,
126-
response_description=response_description,
127-
responses=responses,
128-
deprecated=deprecated,
129-
methods=methods,
130-
operation_id=operation_id,
131-
response_model_include=response_model_include,
132-
response_model_exclude=response_model_exclude,
133-
response_model_by_alias=response_model_by_alias,
134-
response_model_exclude_unset=response_model_exclude_unset,
135-
response_model_exclude_defaults=response_model_exclude_defaults,
136-
response_model_exclude_none=response_model_exclude_none,
137-
include_in_schema=False,
138-
response_class=response_class,
139-
name=name,
140-
route_class_override=route_class_override,
141-
callbacks=callbacks,
142-
openapi_extra=openapi_extra,
143-
generate_unique_id_function=generate_unique_id_function,
144-
)
1+
from enum import Enum
2+
from typing import (
3+
TYPE_CHECKING,
4+
Any,
5+
Callable,
6+
Dict,
7+
List,
8+
Optional,
9+
Sequence,
10+
Set,
11+
Type,
12+
Union,
13+
)
14+
15+
from fastapi import APIRouter, params
16+
from fastapi.datastructures import Default, DefaultPlaceholder
17+
from fastapi.routing import APIRoute
18+
from fastapi.types import IncEx
19+
from fastapi.utils import (
20+
generate_unique_id,
21+
)
22+
from starlette.responses import JSONResponse, Response
23+
from starlette.routing import BaseRoute
24+
25+
if TYPE_CHECKING: # pragma: no cover
26+
from ...core.controller import Controller
27+
28+
29+
class PestRouter(APIRouter):
30+
"""
31+
Extends the `APIRouter` class from FastAPI to handle / at the end of API routes.
32+
By default, FastAPI redirects routes that end in / to the route without /. This
33+
class avoids that behavior and for each route that is added, it adds an alternative
34+
route with or without /, as appropriate.
35+
"""
36+
37+
routes: List[APIRoute]
38+
controller: Type['Controller']
39+
40+
def add_api_route(
41+
self,
42+
path: str,
43+
endpoint: Callable[..., Any],
44+
*,
45+
response_model: Any = Default(None),
46+
status_code: Optional[int] = None,
47+
tags: Optional[List[Union[str, Enum]]] = None,
48+
dependencies: Optional[Sequence[params.Depends]] = None,
49+
summary: Optional[str] = None,
50+
description: Optional[str] = None,
51+
response_description: str = 'Successful Response',
52+
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
53+
deprecated: Optional[bool] = None,
54+
methods: Optional[Union[Set[str], List[str]]] = None,
55+
operation_id: Optional[str] = None,
56+
response_model_include: Optional[IncEx] = None,
57+
response_model_exclude: Optional[IncEx] = None,
58+
response_model_by_alias: bool = True,
59+
response_model_exclude_unset: bool = False,
60+
response_model_exclude_defaults: bool = False,
61+
response_model_exclude_none: bool = True,
62+
include_in_schema: bool = True,
63+
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
64+
name: Optional[str] = None,
65+
route_class_override: Optional[Type[APIRoute]] = None,
66+
callbacks: Optional[List[BaseRoute]] = None,
67+
openapi_extra: Optional[Dict[str, Any]] = None,
68+
generate_unique_id_function: Union[Callable[[APIRoute], str], DefaultPlaceholder] = Default(
69+
generate_unique_id
70+
),
71+
**kwargs: Any,
72+
) -> None:
73+
"""
74+
Registra un endpoint de la API con la ruta proporcionada y con su ruta alternativa
75+
dependiendo de si la ruta proporcionada termina en `/` o no.
76+
77+
Por ejemplo, si se registra la ruta `/users`, se registrará también la ruta `/users/`
78+
y viceversa.
79+
"""
80+
81+
if path.endswith('/'):
82+
path = path[:-1]
83+
84+
alternate_path = path + '/'
85+
86+
if (self.prefix + path) != '':
87+
super().add_api_route(
88+
path,
89+
endpoint,
90+
response_model=response_model,
91+
status_code=status_code,
92+
tags=tags,
93+
dependencies=dependencies,
94+
summary=summary,
95+
description=description,
96+
response_description=response_description,
97+
responses=responses,
98+
deprecated=deprecated,
99+
methods=methods,
100+
operation_id=operation_id,
101+
response_model_include=response_model_include,
102+
response_model_exclude=response_model_exclude,
103+
response_model_by_alias=response_model_by_alias,
104+
response_model_exclude_unset=response_model_exclude_unset,
105+
response_model_exclude_defaults=response_model_exclude_defaults,
106+
response_model_exclude_none=response_model_exclude_none,
107+
include_in_schema=include_in_schema,
108+
response_class=response_class,
109+
name=name,
110+
route_class_override=route_class_override,
111+
callbacks=callbacks,
112+
openapi_extra=openapi_extra,
113+
generate_unique_id_function=generate_unique_id_function,
114+
)
115+
116+
if (self.prefix + alternate_path) != '':
117+
super().add_api_route(
118+
alternate_path,
119+
endpoint,
120+
response_model=response_model,
121+
status_code=status_code,
122+
tags=tags,
123+
dependencies=dependencies,
124+
summary=summary,
125+
description=description,
126+
response_description=response_description,
127+
responses=responses,
128+
deprecated=deprecated,
129+
methods=methods,
130+
operation_id=operation_id,
131+
response_model_include=response_model_include,
132+
response_model_exclude=response_model_exclude,
133+
response_model_by_alias=response_model_by_alias,
134+
response_model_exclude_unset=response_model_exclude_unset,
135+
response_model_exclude_defaults=response_model_exclude_defaults,
136+
response_model_exclude_none=response_model_exclude_none,
137+
include_in_schema=False,
138+
response_class=response_class,
139+
name=name,
140+
route_class_override=route_class_override,
141+
callbacks=callbacks,
142+
openapi_extra=openapi_extra,
143+
generate_unique_id_function=generate_unique_id_function,
144+
)

0 commit comments

Comments
 (0)