From b655532f0cc438be630b3c241490929ac88aab57 Mon Sep 17 00:00:00 2001 From: ksauder Date: Tue, 20 Aug 2024 10:53:56 -0400 Subject: [PATCH] Router default options, addresses #1267 (#1268) * add pyenv-virtualenv .python-version to ignore * added Router operation options default dict * moved statements to a new test and added override checks * remove typeddict * optional = None, default False in Operation__init__ * added more testing * revert tests added to existing file --- .gitignore | 1 + ninja/main.py | 48 +++++++++++------------ ninja/operation.py | 24 ++++++------ ninja/router.py | 73 +++++++++++++++++++++-------------- tests/test_router_defaults.py | 62 +++++++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 tests/test_router_defaults.py diff --git a/.gitignore b/.gitignore index 9d4b2027..bfa32968 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ docs/site .DS_Store .idea +.python-version diff --git a/ninja/main.py b/ninja/main.py index b6ec5816..67216ac5 100644 --- a/ninja/main.py +++ b/ninja/main.py @@ -131,10 +131,10 @@ def get( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -174,10 +174,10 @@ def post( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -217,10 +217,10 @@ def delete( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -260,10 +260,10 @@ def patch( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -303,10 +303,10 @@ def put( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -347,10 +347,10 @@ def api_operation( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, diff --git a/ninja/operation.py b/ninja/operation.py index 455a1668..623c0b7d 100644 --- a/ninja/operation.py +++ b/ninja/operation.py @@ -48,10 +48,10 @@ def __init__( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, include_in_schema: bool = True, url_name: Optional[str] = None, openapi_extra: Optional[Dict[str, Any]] = None, @@ -99,10 +99,10 @@ def __init__( self.openapi_extra = openapi_extra # Exporting models params - self.by_alias = by_alias - self.exclude_unset = exclude_unset - self.exclude_defaults = exclude_defaults - self.exclude_none = exclude_none + self.by_alias = by_alias or False + self.exclude_unset = exclude_unset or False + self.exclude_defaults = exclude_defaults or False + self.exclude_none = exclude_none or False if hasattr(view_func, "_ninja_contribute_to_operation"): # Allow 3rd party code to contribute to the operation behavior @@ -407,10 +407,10 @@ def add_operation( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, diff --git a/ninja/router.py b/ninja/router.py index f471e80b..39da9020 100644 --- a/ninja/router.py +++ b/ninja/router.py @@ -35,11 +35,20 @@ def __init__( auth: Any = NOT_SET, throttle: Union[BaseThrottle, List[BaseThrottle], NOT_SET_TYPE] = NOT_SET, tags: Optional[List[str]] = None, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, ) -> None: self.api: Optional[NinjaAPI] = None self.auth = auth self.throttle = throttle self.tags = tags + self.by_alias = by_alias + self.exclude_unset = exclude_unset + self.exclude_defaults = exclude_defaults + self.exclude_none = exclude_none + self.path_operations: Dict[str, PathView] = {} self._routers: List[Tuple[str, Router]] = [] @@ -55,10 +64,10 @@ def get( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -95,10 +104,10 @@ def post( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -135,10 +144,10 @@ def delete( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -175,10 +184,10 @@ def patch( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -215,10 +224,10 @@ def put( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -256,10 +265,10 @@ def api_operation( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -303,10 +312,10 @@ def add_api_operation( description: Optional[str] = None, tags: Optional[List[str]] = None, deprecated: Optional[bool] = None, - by_alias: bool = False, - exclude_unset: bool = False, - exclude_defaults: bool = False, - exclude_none: bool = False, + by_alias: Optional[bool] = None, + exclude_unset: Optional[bool] = None, + exclude_defaults: Optional[bool] = None, + exclude_none: Optional[bool] = None, url_name: Optional[str] = None, include_in_schema: bool = True, openapi_extra: Optional[Dict[str, Any]] = None, @@ -316,6 +325,14 @@ def add_api_operation( self.path_operations[path] = path_view else: path_view = self.path_operations[path] + + by_alias = by_alias is None and self.by_alias or by_alias + exclude_unset = exclude_unset is None and self.exclude_unset or exclude_unset + exclude_defaults = ( + exclude_defaults is None and self.exclude_defaults or exclude_defaults + ) + exclude_none = exclude_none is None and self.exclude_none or exclude_none + path_view.add_operation( path=path, methods=methods, diff --git a/tests/test_router_defaults.py b/tests/test_router_defaults.py new file mode 100644 index 00000000..9506fe1d --- /dev/null +++ b/tests/test_router_defaults.py @@ -0,0 +1,62 @@ +from typing import Optional + +import pytest +from pydantic import Field + +from ninja import NinjaAPI, Router, Schema +from ninja.testing import TestClient + + +class SomeResponse(Schema): + field1: Optional[int] = 1 + field2: Optional[str] = "default value" + field3: Optional[int] = Field(None, alias="aliased") + + +@pytest.mark.parametrize( + "oparg,retdict,assertone,asserttwo", + [ + ( + "exclude_defaults", + {"field1": 3}, + {"field1": 3}, + {"field1": 3, "field2": "default value", "field3": None}, + ), + ( + "exclude_unset", + {"field2": "test"}, + {"field2": "test"}, + {"field1": 1, "field2": "test", "field3": None}, + ), + ( + "exclude_none", + {"field1": None, "field2": None, "aliased": 10}, + {"field3": 10}, + {"field1": None, "field2": None, "field3": 10}, + ), + ( + "by_alias", + {"aliased": 10}, + {"field1": 1, "field2": "default value", "aliased": 10}, + {"field1": 1, "field2": "default value", "field3": 10}, + ), + ], +) +def test_router_defaults(oparg, retdict, assertone, asserttwo): + """Test that the router level settings work and can be overriden at the op level""" + api = NinjaAPI() + router = Router(**{oparg: True}) + api.add_router("/", router) + + func1 = router.get("/test1", response=SomeResponse)(lambda request: retdict) + func2 = router.get("/test2", response=SomeResponse, **{oparg: False})( + lambda request: retdict + ) + + client = TestClient(api) + + assert getattr(func1._ninja_operation, oparg) is True + assert getattr(func2._ninja_operation, oparg) is False + + assert client.get("/test1").json() == assertone + assert client.get("/test2").json() == asserttwo