From fd85ada01b71a2f1318794eea7c252369caf54be Mon Sep 17 00:00:00 2001 From: Zhiwei Date: Tue, 31 Dec 2024 17:43:26 -0500 Subject: [PATCH] Fix coerce in Hint (#251) * Fix nullable type null value being converted * Unpin setuptools version * Update test * make format --- pyproject.toml | 2 +- sanic_ext/exceptions.py | 6 ++---- sanic_ext/extensions/base.py | 3 +-- sanic_ext/extensions/health/monitor.py | 3 +-- sanic_ext/extensions/openapi/blueprint.py | 6 +++--- sanic_ext/extensions/openapi/definitions.py | 1 + sanic_ext/extensions/openapi/openapi.py | 16 ++++++---------- sanic_ext/extensions/templating/render.py | 3 +-- sanic_ext/extras/validation/check.py | 2 ++ tests/extra/test_validation_dataclass.py | 5 +++++ 10 files changed, 23 insertions(+), 24 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ced5c23..58cf7dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools<60.0", "wheel"] +requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.ruff] diff --git a/sanic_ext/exceptions.py b/sanic_ext/exceptions.py index f88b61c..c04230c 100644 --- a/sanic_ext/exceptions.py +++ b/sanic_ext/exceptions.py @@ -5,9 +5,7 @@ class ValidationError(SanicException): status_code = 400 -class InitError(SanicException): - ... +class InitError(SanicException): ... -class ExtensionNotFound(SanicException): - ... +class ExtensionNotFound(SanicException): ... diff --git a/sanic_ext/extensions/base.py b/sanic_ext/extensions/base.py index ad5fa67..e5bf922 100644 --- a/sanic_ext/extensions/base.py +++ b/sanic_ext/extensions/base.py @@ -46,8 +46,7 @@ def _startup(self, bootstrap): self._started = True @abstractmethod - def startup(self, bootstrap) -> None: - ... + def startup(self, bootstrap) -> None: ... def label(self): return "" diff --git a/sanic_ext/extensions/health/monitor.py b/sanic_ext/extensions/health/monitor.py index d43d8d7..ba48669 100644 --- a/sanic_ext/extensions/health/monitor.py +++ b/sanic_ext/extensions/health/monitor.py @@ -17,8 +17,7 @@ from sanic import Sanic -class Stale(ValueError): - ... +class Stale(ValueError): ... @dataclass diff --git a/sanic_ext/extensions/openapi/blueprint.py b/sanic_ext/extensions/openapi/blueprint.py index c7cdbc6..89f8609 100644 --- a/sanic_ext/extensions/openapi/blueprint.py +++ b/sanic_ext/extensions/openapi/blueprint.py @@ -173,9 +173,9 @@ def build_spec(app, loop): ): operation.autodoc(docstring) - operation._default[ - "operationId" - ] = f"{method.lower()}~{route_name}" + operation._default["operationId"] = ( + f"{method.lower()}~{route_name}" + ) operation._default["summary"] = clean_route_name(route_name) if host: diff --git a/sanic_ext/extensions/openapi/definitions.py b/sanic_ext/extensions/openapi/definitions.py index e29c66f..0b93fa4 100644 --- a/sanic_ext/extensions/openapi/definitions.py +++ b/sanic_ext/extensions/openapi/definitions.py @@ -4,6 +4,7 @@ I.e., the objects described https://swagger.io/docs/specification """ + from __future__ import annotations from inspect import isclass diff --git a/sanic_ext/extensions/openapi/openapi.py b/sanic_ext/extensions/openapi/openapi.py index 6f0330b..8d26cec 100644 --- a/sanic_ext/extensions/openapi/openapi.py +++ b/sanic_ext/extensions/openapi/openapi.py @@ -3,6 +3,7 @@ documentation to OperationStore() and components created in the blueprints. """ + from functools import wraps from inspect import isawaitable, isclass from typing import ( @@ -94,13 +95,11 @@ def _content_or_component(content): @overload -def exclude(flag: bool = True, *, bp: Blueprint) -> None: - ... +def exclude(flag: bool = True, *, bp: Blueprint) -> None: ... @overload -def exclude(flag: bool = True) -> Callable: - ... +def exclude(flag: bool = True) -> Callable: ... def exclude(flag: bool = True, *, bp: Optional[Blueprint] = None): @@ -247,8 +246,7 @@ def parameter( *, parameter: definitions.Parameter, **kwargs, -) -> Callable[[T], T]: - ... +) -> Callable[[T], T]: ... @overload @@ -258,8 +256,7 @@ def parameter( location: None, parameter: definitions.Parameter, **kwargs, -) -> Callable[[T], T]: - ... +) -> Callable[[T], T]: ... @overload @@ -269,8 +266,7 @@ def parameter( location: Optional[str] = None, parameter: None = None, **kwargs, -) -> Callable[[T], T]: - ... +) -> Callable[[T], T]: ... def parameter( diff --git a/sanic_ext/extensions/templating/render.py b/sanic_ext/extensions/templating/render.py index 0650f9d..ce67066 100644 --- a/sanic_ext/extensions/templating/render.py +++ b/sanic_ext/extensions/templating/render.py @@ -16,8 +16,7 @@ from jinja2 import Environment -class TemplateResponse(HTTPResponse): - ... +class TemplateResponse(HTTPResponse): ... class LazyResponse(TemplateResponse): diff --git a/sanic_ext/extras/validation/check.py b/sanic_ext/extras/validation/check.py index b7c6150..fb3f9c8 100644 --- a/sanic_ext/extras/validation/check.py +++ b/sanic_ext/extras/validation/check.py @@ -141,6 +141,8 @@ def coerce(self, value): try: if isinstance(value, list): value = [coerce_type(item) for item in value] + elif value is None and self.nullable: + value = None else: value = coerce_type(value) except (ValueError, TypeError): diff --git a/tests/extra/test_validation_dataclass.py b/tests/extra/test_validation_dataclass.py index bf4ce8c..2a85ab3 100644 --- a/tests/extra/test_validation_dataclass.py +++ b/tests/extra/test_validation_dataclass.py @@ -340,6 +340,7 @@ def test_validate_form(app): class Pet: name: str alter_ego: List[str] + description: Optional[str] = None @app.post("/function") @validate(form=Pet) @@ -348,6 +349,7 @@ async def handler(_, body: Pet): { "is_pet": isinstance(body, Pet), "pet": {"name": body.name, "alter_ego": body.alter_ego}, + "description": body.description if body.description else "", } ) @@ -359,6 +361,7 @@ async def post(self, _, body: Pet): { "is_pet": isinstance(body, Pet), "pet": {"name": body.name, "alter_ego": body.alter_ego}, + "description": body.description if body.description else "", } ) @@ -366,11 +369,13 @@ async def post(self, _, body: Pet): assert response.status == 200 assert response.json["is_pet"] assert response.json["pet"] == SNOOPY_DATA + assert response.json["description"] == "" _, response = app.test_client.post("/method", data=SNOOPY_DATA) assert response.status == 200 assert response.json["is_pet"] assert response.json["pet"] == SNOOPY_DATA + assert response.json["description"] == "" def test_validate_query(app):