]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
:sparkles: Implement response_model_exclude_defaults and response_model_exclude_none...
authorvoegtlel <5764745+voegtlel@users.noreply.github.com>
Sun, 5 Apr 2020 13:04:46 +0000 (15:04 +0200)
committerGitHub <noreply@github.com>
Sun, 5 Apr 2020 13:04:46 +0000 (15:04 +0200)
* Implemented response_model_exclude_defaults and response_model_exclude_none to be compatible pydantic options.

* :truck: Rename and invert include_none to exclude_none to keep in sync with Pydantic

Co-authored-by: Lukas Voegtle <lukas.voegtle@sick.de>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
fastapi/applications.py
fastapi/encoders.py
fastapi/openapi/utils.py
fastapi/routing.py
tests/test_jsonable_encoder.py
tests/test_skip_defaults.py

index 8270e54fdf4d0a8b3b0529adaf3a00aacc2c8423..84a1b6de290b35f13d6855aa3561ebafc10dfe2f 100644 (file)
@@ -171,6 +171,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -197,6 +199,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -222,6 +226,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -250,6 +256,8 @@ class FastAPI(Starlette):
                 response_model_exclude_unset=bool(
                     response_model_exclude_unset or response_model_skip_defaults
                 ),
+                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 or self.default_response_class,
                 name=name,
@@ -309,6 +317,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -334,6 +344,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -359,6 +371,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -384,6 +398,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -409,6 +425,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -434,6 +452,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -459,6 +479,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -484,6 +506,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -509,6 +533,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -534,6 +560,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -559,6 +587,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -584,6 +614,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -609,6 +641,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -634,6 +668,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -659,6 +695,8 @@ class FastAPI(Starlette):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -684,6 +722,8 @@ class FastAPI(Starlette):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
index ae4794bae8d948f3809e04729df6e548775e16e3..26ceb21445f158c943cf22197d98bd4d575f4323 100644 (file)
@@ -34,7 +34,8 @@ def jsonable_encoder(
     by_alias: bool = True,
     skip_defaults: bool = None,
     exclude_unset: bool = False,
-    include_none: bool = True,
+    exclude_defaults: bool = False,
+    exclude_none: bool = False,
     custom_encoder: dict = {},
     sqlalchemy_safe: bool = True,
 ) -> Any:
@@ -58,8 +59,12 @@ def jsonable_encoder(
                 exclude=exclude,
                 by_alias=by_alias,
                 exclude_unset=bool(exclude_unset or skip_defaults),
+                exclude_none=exclude_none,
+                exclude_defaults=exclude_defaults,
             )
         else:  # pragma: nocover
+            if exclude_defaults:
+                raise ValueError("Cannot use exclude_defaults")
             obj_dict = obj.dict(
                 include=include,
                 exclude=exclude,
@@ -68,7 +73,8 @@ def jsonable_encoder(
             )
         return jsonable_encoder(
             obj_dict,
-            include_none=include_none,
+            exclude_none=exclude_none,
+            exclude_defaults=exclude_defaults,
             custom_encoder=encoder,
             sqlalchemy_safe=sqlalchemy_safe,
         )
@@ -87,14 +93,14 @@ def jsonable_encoder(
                     or (not isinstance(key, str))
                     or (not key.startswith("_sa"))
                 )
-                and (value is not None or include_none)
+                and (value is not None or not exclude_none)
                 and ((include and key in include) or key not in exclude)
             ):
                 encoded_key = jsonable_encoder(
                     key,
                     by_alias=by_alias,
                     exclude_unset=exclude_unset,
-                    include_none=include_none,
+                    exclude_none=exclude_none,
                     custom_encoder=custom_encoder,
                     sqlalchemy_safe=sqlalchemy_safe,
                 )
@@ -102,7 +108,7 @@ def jsonable_encoder(
                     value,
                     by_alias=by_alias,
                     exclude_unset=exclude_unset,
-                    include_none=include_none,
+                    exclude_none=exclude_none,
                     custom_encoder=custom_encoder,
                     sqlalchemy_safe=sqlalchemy_safe,
                 )
@@ -118,7 +124,8 @@ def jsonable_encoder(
                     exclude=exclude,
                     by_alias=by_alias,
                     exclude_unset=exclude_unset,
-                    include_none=include_none,
+                    exclude_defaults=exclude_defaults,
+                    exclude_none=exclude_none,
                     custom_encoder=custom_encoder,
                     sqlalchemy_safe=sqlalchemy_safe,
                 )
@@ -153,7 +160,8 @@ def jsonable_encoder(
         data,
         by_alias=by_alias,
         exclude_unset=exclude_unset,
-        include_none=include_none,
+        exclude_defaults=exclude_defaults,
+        exclude_none=exclude_none,
         custom_encoder=custom_encoder,
         sqlalchemy_safe=sqlalchemy_safe,
     )
index 91f90ec583785ac0089aa547ca60d75303f96e93..c1e66fc8d29b1dda904716dec45a4540dde7b2aa 100644 (file)
@@ -81,7 +81,7 @@ def get_openapi_security_definitions(flat_dependant: Dependant) -> Tuple[Dict, L
         security_definition = jsonable_encoder(
             security_requirement.security_scheme.model,
             by_alias=True,
-            include_none=False,
+            exclude_none=True,
         )
         security_name = security_requirement.security_scheme.scheme_name
         security_definitions[security_name] = security_definition
@@ -310,4 +310,4 @@ def get_openapi(
     if components:
         output["components"] = components
     output["paths"] = paths
-    return jsonable_encoder(OpenAPI(**output), by_alias=True, include_none=False)
+    return jsonable_encoder(OpenAPI(**output), by_alias=True, exclude_none=True)
index 1ec0b693c87b0165e760a0cc9b71f3558d02afac..3ac420e6e229633efe9e54a02a9a10e5d3216e93 100644 (file)
@@ -49,22 +49,43 @@ except ImportError:  # pragma: nocover
 
 
 def _prepare_response_content(
-    res: Any, *, by_alias: bool = True, exclude_unset: bool
+    res: Any,
+    *,
+    by_alias: bool = True,
+    exclude_unset: bool,
+    exclude_defaults: bool = False,
+    exclude_none: bool = False,
 ) -> Any:
     if isinstance(res, BaseModel):
         if PYDANTIC_1:
-            return res.dict(by_alias=by_alias, exclude_unset=exclude_unset)
+            return res.dict(
+                by_alias=by_alias,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
         else:
             return res.dict(
-                by_alias=by_alias, skip_defaults=exclude_unset
+                by_alias=by_alias, skip_defaults=exclude_unset,
             )  # pragma: nocover
     elif isinstance(res, list):
         return [
-            _prepare_response_content(item, exclude_unset=exclude_unset) for item in res
+            _prepare_response_content(
+                item,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
+            for item in res
         ]
     elif isinstance(res, dict):
         return {
-            k: _prepare_response_content(v, exclude_unset=exclude_unset)
+            k: _prepare_response_content(
+                v,
+                exclude_unset=exclude_unset,
+                exclude_defaults=exclude_defaults,
+                exclude_none=exclude_none,
+            )
             for k, v in res.items()
         }
     return res
@@ -78,12 +99,18 @@ async def serialize_response(
     exclude: Union[SetIntStr, DictIntStrAny] = set(),
     by_alias: bool = True,
     exclude_unset: bool = False,
+    exclude_defaults: bool = False,
+    exclude_none: bool = False,
     is_coroutine: bool = True,
 ) -> Any:
     if field:
         errors = []
         response_content = _prepare_response_content(
-            response_content, by_alias=by_alias, exclude_unset=exclude_unset
+            response_content,
+            by_alias=by_alias,
+            exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
         )
         if is_coroutine:
             value, errors_ = field.validate(response_content, {}, loc=("response",))
@@ -103,6 +130,8 @@ async def serialize_response(
             exclude=exclude,
             by_alias=by_alias,
             exclude_unset=exclude_unset,
+            exclude_defaults=exclude_defaults,
+            exclude_none=exclude_none,
         )
     else:
         return jsonable_encoder(response_content)
@@ -131,6 +160,8 @@ def get_request_handler(
     response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
     response_model_by_alias: bool = True,
     response_model_exclude_unset: bool = False,
+    response_model_exclude_defaults: bool = False,
+    response_model_exclude_none: bool = False,
     dependency_overrides_provider: Any = None,
 ) -> Callable:
     assert dependant.call is not None, "dependant.call must be a function"
@@ -177,6 +208,8 @@ def get_request_handler(
                 exclude=response_model_exclude,
                 by_alias=response_model_by_alias,
                 exclude_unset=response_model_exclude_unset,
+                exclude_defaults=response_model_exclude_defaults,
+                exclude_none=response_model_exclude_none,
                 is_coroutine=is_coroutine,
             )
             response = response_class(
@@ -255,6 +288,8 @@ class APIRoute(routing.Route):
         response_model_exclude: Union[SetIntStr, DictIntStrAny] = set(),
         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: Optional[Type[Response]] = None,
         dependency_overrides_provider: Any = None,
@@ -326,6 +361,8 @@ class APIRoute(routing.Route):
         self.response_model_exclude = response_model_exclude
         self.response_model_by_alias = response_model_by_alias
         self.response_model_exclude_unset = response_model_exclude_unset
+        self.response_model_exclude_defaults = response_model_exclude_defaults
+        self.response_model_exclude_none = response_model_exclude_none
         self.include_in_schema = include_in_schema
         self.response_class = response_class
 
@@ -352,6 +389,8 @@ class APIRoute(routing.Route):
             response_model_exclude=self.response_model_exclude,
             response_model_by_alias=self.response_model_by_alias,
             response_model_exclude_unset=self.response_model_exclude_unset,
+            response_model_exclude_defaults=self.response_model_exclude_defaults,
+            response_model_exclude_none=self.response_model_exclude_none,
             dependency_overrides_provider=self.dependency_overrides_provider,
         )
 
@@ -400,6 +439,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -429,6 +470,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -457,6 +500,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -486,6 +531,8 @@ class APIRouter(routing.Router):
                 response_model_exclude_unset=bool(
                     response_model_exclude_unset or response_model_skip_defaults
                 ),
+                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 or self.default_response_class,
                 name=name,
@@ -560,6 +607,8 @@ class APIRouter(routing.Router):
                     response_model_exclude=route.response_model_exclude,
                     response_model_by_alias=route.response_model_by_alias,
                     response_model_exclude_unset=route.response_model_exclude_unset,
+                    response_model_exclude_defaults=route.response_model_exclude_defaults,
+                    response_model_exclude_none=route.response_model_exclude_none,
                     include_in_schema=route.include_in_schema,
                     response_class=route.response_class or default_response_class,
                     name=route.name,
@@ -606,6 +655,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -632,6 +683,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -657,6 +710,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -683,6 +738,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -708,6 +765,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -734,6 +793,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -759,6 +820,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -785,6 +848,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -810,6 +875,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -836,6 +903,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -861,6 +930,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -887,6 +958,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -912,6 +985,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -938,6 +1013,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
@@ -963,6 +1040,8 @@ class APIRouter(routing.Router):
         response_model_by_alias: bool = True,
         response_model_skip_defaults: bool = None,
         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: Type[Response] = None,
         name: str = None,
@@ -989,6 +1068,8 @@ class APIRouter(routing.Router):
             response_model_exclude_unset=bool(
                 response_model_exclude_unset or response_model_skip_defaults
             ),
+            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 or self.default_response_class,
             name=name,
index f94771aaeed94ed8bdca1853635e226bf73c1f6d..adee443a8943c52c5349d78b534ef24ff0cf54a6 100644 (file)
@@ -70,6 +70,12 @@ class ModelWithAlias(BaseModel):
     foo: str = Field(..., alias="Foo")
 
 
+class ModelWithDefault(BaseModel):
+    foo: str = ...
+    bar: str = "bar"
+    bla: str = "bla"
+
+
 @pytest.fixture(
     name="model_with_path", params=[PurePath, PurePosixPath, PureWindowsPath]
 )
@@ -121,6 +127,16 @@ def test_encode_model_with_alias():
     assert jsonable_encoder(model) == {"Foo": "Bar"}
 
 
+def test_encode_model_with_default():
+    model = ModelWithDefault(foo="foo", bar="bar")
+    assert jsonable_encoder(model) == {"foo": "foo", "bar": "bar", "bla": "bla"}
+    assert jsonable_encoder(model, exclude_unset=True) == {"foo": "foo", "bar": "bar"}
+    assert jsonable_encoder(model, exclude_defaults=True) == {"foo": "foo"}
+    assert jsonable_encoder(model, exclude_unset=True, exclude_defaults=True) == {
+        "foo": "foo"
+    }
+
+
 def test_custom_encoders():
     class safe_datetime(datetime):
         pass
index c9a85a305a3c301fbbb992c35f8b05fa0dd1f06c..ed84df66c40b2fa0d8118155fe102af0a7f98fc1 100644 (file)
@@ -18,11 +18,53 @@ class Model(BaseModel):
 
 class ModelSubclass(Model):
     y: int
+    z: int = 0
+    w: int = None
+
+
+class ModelDefaults(BaseModel):
+    w: Optional[str] = None
+    x: Optional[str] = None
+    y: str = "y"
+    z: str = "z"
 
 
 @app.get("/", response_model=Model, response_model_exclude_unset=True)
 def get() -> ModelSubclass:
-    return ModelSubclass(sub={}, y=1)
+    return ModelSubclass(sub={}, y=1, z=0)
+
+
+@app.get(
+    "/exclude_unset", response_model=ModelDefaults, response_model_exclude_unset=True
+)
+def get() -> ModelDefaults:
+    return ModelDefaults(x=None, y="y")
+
+
+@app.get(
+    "/exclude_defaults",
+    response_model=ModelDefaults,
+    response_model_exclude_defaults=True,
+)
+def get() -> ModelDefaults:
+    return ModelDefaults(x=None, y="y")
+
+
+@app.get(
+    "/exclude_none", response_model=ModelDefaults, response_model_exclude_none=True
+)
+def get() -> ModelDefaults:
+    return ModelDefaults(x=None, y="y")
+
+
+@app.get(
+    "/exclude_unset_none",
+    response_model=ModelDefaults,
+    response_model_exclude_unset=True,
+    response_model_exclude_none=True,
+)
+def get() -> ModelDefaults:
+    return ModelDefaults(x=None, y="y")
 
 
 client = TestClient(app)
@@ -31,3 +73,23 @@ client = TestClient(app)
 def test_return_defaults():
     response = client.get("/")
     assert response.json() == {"sub": {}}
+
+
+def test_return_exclude_unset():
+    response = client.get("/exclude_unset")
+    assert response.json() == {"x": None, "y": "y"}
+
+
+def test_return_exclude_defaults():
+    response = client.get("/exclude_defaults")
+    assert response.json() == {}
+
+
+def test_return_exclude_none():
+    response = client.get("/exclude_none")
+    assert response.json() == {"y": "y", "z": "z"}
+
+
+def test_return_exclude_unset_none():
+    response = client.get("/exclude_unset_none")
+    assert response.json() == {"y": "y"}