Skip to content

Commit

Permalink
feat: ✨ make response_model_exclude_none=True a default (#47)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucas-labs authored Oct 12, 2024
1 parent 46e2f6a commit e9a8d98
Show file tree
Hide file tree
Showing 5 changed files with 475 additions and 427 deletions.
20 changes: 11 additions & 9 deletions pest/core/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,16 @@ def __init__(
]
)

self.add_exception_handlers([
(HTTPException, handle.http),
(ValidationError, handle.request_validation),
(RequestValidationError, handle.request_validation),
(WebSocketRequestValidationError, handle.websocket_request_validation),
# for everything else, there's Mastercard (or was it Bancard? 🤔)
(Exception, handle.the_rest),
])
self.add_exception_handlers(
[
(HTTPException, handle.http),
(ValidationError, handle.request_validation),
(RequestValidationError, handle.request_validation),
(WebSocketRequestValidationError, handle.websocket_request_validation),
# for everything else, there's Mastercard (or was it Bancard? 🤔)
(Exception, handle.the_rest),
]
)

def add_exception_handlers(
self, handlers: List[Tuple[Union[int, Type[Exception]], Callable]]
Expand Down Expand Up @@ -129,7 +131,7 @@ def add_api_route(
response_model_by_alias: bool = True,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
response_model_exclude_none: bool = True,
include_in_schema: bool = True,
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
name: Optional[str] = None,
Expand Down
8 changes: 5 additions & 3 deletions pest/core/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ def setup_handler(cls: Type['Controller'], handler: HandlerTuple) -> APIRoute:
meta_dict = clean_dict(asdict(handler_meta), HandlerMetaDict)
_patch_handler_fn(cls, handler_fn)

# by default, exclude None values from the response
if 'response_model_exclude_none' not in meta_dict:
meta_dict['response_model_exclude_none'] = True

route = APIRoute(
endpoint=handler_fn,
path=handler_meta.path,
Expand Down Expand Up @@ -129,9 +133,7 @@ def _get_pest_injection(parameter: Parameter) -> Union[List[_Inject], None]:
token = (
parameter.default.token
if parameter.default.token is not None
else parameter.annotation
if parameter.annotation is not Parameter.empty
else None
else parameter.annotation if parameter.annotation is not Parameter.empty else None
)
annotations = (token, inject)
else:
Expand Down
288 changes: 144 additions & 144 deletions pest/utils/fastapi/router.py
Original file line number Diff line number Diff line change
@@ -1,144 +1,144 @@
from enum import Enum
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Optional,
Sequence,
Set,
Type,
Union,
)

from fastapi import APIRouter, params
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.routing import APIRoute
from fastapi.types import IncEx
from fastapi.utils import (
generate_unique_id,
)
from starlette.responses import JSONResponse, Response
from starlette.routing import BaseRoute

if TYPE_CHECKING: # pragma: no cover
from ...core.controller import Controller


class PestRouter(APIRouter):
"""
Extends the `APIRouter` class from FastAPI to handle / at the end of API routes.
By default, FastAPI redirects routes that end in / to the route without /. This
class avoids that behavior and for each route that is added, it adds an alternative
route with or without /, as appropriate.
"""

routes: List[APIRoute]
controller: Type['Controller']

def add_api_route(
self,
path: str,
endpoint: Callable[..., Any],
*,
response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = 'Successful Response',
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[IncEx] = None,
response_model_exclude: Optional[IncEx] = None,
response_model_by_alias: bool = True,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = False,
include_in_schema: bool = True,
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
name: Optional[str] = None,
route_class_override: Optional[Type[APIRoute]] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
generate_unique_id_function: Union[Callable[[APIRoute], str], DefaultPlaceholder] = Default(
generate_unique_id
),
**kwargs: Any,
) -> None:
"""
Registra un endpoint de la API con la ruta proporcionada y con su ruta alternativa
dependiendo de si la ruta proporcionada termina en `/` o no.
Por ejemplo, si se registra la ruta `/users`, se registrará también la ruta `/users/`
y viceversa.
"""

if path.endswith('/'):
path = path[:-1]

alternate_path = path + '/'

if (self.prefix + path) != '':
super().add_api_route(
path,
endpoint,
response_model=response_model,
status_code=status_code,
tags=tags,
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
responses=responses,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
response_model_include=response_model_include,
response_model_exclude=response_model_exclude,
response_model_by_alias=response_model_by_alias,
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
route_class_override=route_class_override,
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
)

if (self.prefix + alternate_path) != '':
super().add_api_route(
alternate_path,
endpoint,
response_model=response_model,
status_code=status_code,
tags=tags,
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
responses=responses,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
response_model_include=response_model_include,
response_model_exclude=response_model_exclude,
response_model_by_alias=response_model_by_alias,
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
include_in_schema=False,
response_class=response_class,
name=name,
route_class_override=route_class_override,
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
)
from enum import Enum
from typing import (
TYPE_CHECKING,
Any,
Callable,
Dict,
List,
Optional,
Sequence,
Set,
Type,
Union,
)

from fastapi import APIRouter, params
from fastapi.datastructures import Default, DefaultPlaceholder
from fastapi.routing import APIRoute
from fastapi.types import IncEx
from fastapi.utils import (
generate_unique_id,
)
from starlette.responses import JSONResponse, Response
from starlette.routing import BaseRoute

if TYPE_CHECKING: # pragma: no cover
from ...core.controller import Controller


class PestRouter(APIRouter):
"""
Extends the `APIRouter` class from FastAPI to handle / at the end of API routes.
By default, FastAPI redirects routes that end in / to the route without /. This
class avoids that behavior and for each route that is added, it adds an alternative
route with or without /, as appropriate.
"""

routes: List[APIRoute]
controller: Type['Controller']

def add_api_route(
self,
path: str,
endpoint: Callable[..., Any],
*,
response_model: Any = Default(None),
status_code: Optional[int] = None,
tags: Optional[List[Union[str, Enum]]] = None,
dependencies: Optional[Sequence[params.Depends]] = None,
summary: Optional[str] = None,
description: Optional[str] = None,
response_description: str = 'Successful Response',
responses: Optional[Dict[Union[int, str], Dict[str, Any]]] = None,
deprecated: Optional[bool] = None,
methods: Optional[Union[Set[str], List[str]]] = None,
operation_id: Optional[str] = None,
response_model_include: Optional[IncEx] = None,
response_model_exclude: Optional[IncEx] = None,
response_model_by_alias: bool = True,
response_model_exclude_unset: bool = False,
response_model_exclude_defaults: bool = False,
response_model_exclude_none: bool = True,
include_in_schema: bool = True,
response_class: Union[Type[Response], DefaultPlaceholder] = Default(JSONResponse),
name: Optional[str] = None,
route_class_override: Optional[Type[APIRoute]] = None,
callbacks: Optional[List[BaseRoute]] = None,
openapi_extra: Optional[Dict[str, Any]] = None,
generate_unique_id_function: Union[Callable[[APIRoute], str], DefaultPlaceholder] = Default(
generate_unique_id
),
**kwargs: Any,
) -> None:
"""
Registra un endpoint de la API con la ruta proporcionada y con su ruta alternativa
dependiendo de si la ruta proporcionada termina en `/` o no.
Por ejemplo, si se registra la ruta `/users`, se registrará también la ruta `/users/`
y viceversa.
"""

if path.endswith('/'):
path = path[:-1]

alternate_path = path + '/'

if (self.prefix + path) != '':
super().add_api_route(
path,
endpoint,
response_model=response_model,
status_code=status_code,
tags=tags,
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
responses=responses,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
response_model_include=response_model_include,
response_model_exclude=response_model_exclude,
response_model_by_alias=response_model_by_alias,
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
include_in_schema=include_in_schema,
response_class=response_class,
name=name,
route_class_override=route_class_override,
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
)

if (self.prefix + alternate_path) != '':
super().add_api_route(
alternate_path,
endpoint,
response_model=response_model,
status_code=status_code,
tags=tags,
dependencies=dependencies,
summary=summary,
description=description,
response_description=response_description,
responses=responses,
deprecated=deprecated,
methods=methods,
operation_id=operation_id,
response_model_include=response_model_include,
response_model_exclude=response_model_exclude,
response_model_by_alias=response_model_by_alias,
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
include_in_schema=False,
response_class=response_class,
name=name,
route_class_override=route_class_override,
callbacks=callbacks,
openapi_extra=openapi_extra,
generate_unique_id_function=generate_unique_id_function,
)
Loading

0 comments on commit e9a8d98

Please sign in to comment.