diff --git a/ninja/openapi/schema.py b/ninja/openapi/schema.py index 72226e770..7325b1222 100644 --- a/ninja/openapi/schema.py +++ b/ninja/openapi/schema.py @@ -4,6 +4,8 @@ from http.client import responses from typing import TYPE_CHECKING, Any, Dict, Generator, List, Optional, Set, Tuple +from pydantic.json_schema import JsonSchemaMode + from ninja.constants import NOT_SET from ninja.operation import Operation from ninja.params.models import TModel, TModels @@ -207,14 +209,19 @@ def _create_schema_from_model( model: TModel, by_alias: bool = True, remove_level: bool = True, + is_response: bool = False, ) -> Tuple[DictStrAny, bool]: if hasattr(model, "__ninja_flatten_map__"): schema = self._flatten_schema(model) else: + mode: JsonSchemaMode = "validation" + if is_response: + mode = "serialization" schema = model.model_json_schema( ref_template=REF_TEMPLATE, by_alias=by_alias, schema_generator=NinjaGenerateJsonSchema, + mode=mode, ).copy() # move Schemas from definitions @@ -284,7 +291,7 @@ def responses(self, operation: Operation) -> Dict[int, DictStrAny]: if model not in [None, NOT_SET]: # ::TODO:: test this: by_alias == True schema = self._create_schema_from_model( - model, by_alias=operation.by_alias + model, by_alias=operation.by_alias, is_response=True )[0] details[status]["content"] = { self.api.renderer.media_type: {"schema": schema} diff --git a/ninja/schema.py b/ninja/schema.py index f0e785822..3db8c5a9a 100644 --- a/ninja/schema.py +++ b/ninja/schema.py @@ -199,6 +199,7 @@ def default_schema(self, schema: Any) -> JsonSchemaValue: class Schema(BaseModel, metaclass=ResolverMetaclass): class Config: from_attributes = True # aka orm_mode + json_schema_serialization_defaults_required = True @model_validator(mode="wrap") @classmethod diff --git a/tests/test_alias.py b/tests/test_alias.py index 3430ed6b4..21e531ef6 100644 --- a/tests/test_alias.py +++ b/tests/test_alias.py @@ -23,6 +23,9 @@ def test_alias(): "properties": { "foo": {"type": "string", "default": "", "title": "Foo"} }, + "required": [ + "foo", + ], "title": "SchemaWithAlias", } } diff --git a/tests/test_openapi_schema.py b/tests/test_openapi_schema.py index aec67f472..c469e755f 100644 --- a/tests/test_openapi_schema.py +++ b/tests/test_openapi_schema.py @@ -17,6 +17,7 @@ class Payload(Schema): i: int f: float + i_default: int = Field(1) class TypeA(Schema): @@ -34,6 +35,7 @@ def to_camel(string: str) -> str: class Response(Schema): i: int f: float = Field(..., title="f title", description="f desc") + i_default: int = Field(1) class Config(Schema.Config): alias_generator = to_camel @@ -208,8 +210,13 @@ def test_schema(schema): "properties": { "i": {"title": "I", "type": "integer"}, "f": {"description": "f desc", "title": "f title", "type": "number"}, + "i_default": { + "default": 1, + "title": "I Default", + "type": "integer", + }, }, - "required": ["i", "f"], + "required": ["i", "f", "i_default"], }, "Payload": { "title": "Payload", @@ -217,6 +224,11 @@ def test_schema(schema): "properties": { "i": {"title": "I", "type": "integer"}, "f": {"title": "F", "type": "number"}, + "i_default": { + "default": 1, + "title": "I Default", + "type": "integer", + }, }, "required": ["i", "f"], }, @@ -318,6 +330,11 @@ def test_schema_list(schema): "properties": { "f": {"title": "F", "type": "number"}, "i": {"title": "I", "type": "integer"}, + "i_default": { + "default": 1, + "title": "I Default", + "type": "integer", + }, }, "required": ["i", "f"], "title": "Payload", @@ -343,8 +360,13 @@ def test_schema_list(schema): "properties": { "f": {"description": "f desc", "title": "f title", "type": "number"}, "i": {"title": "I", "type": "integer"}, + "i_default": { + "default": 1, + "title": "I Default", + "type": "integer", + }, }, - "required": ["i", "f"], + "required": ["i", "f", "i_default"], "title": "Response", "type": "object", }, @@ -447,6 +469,11 @@ def test_schema_form(schema): "properties": { "i": {"title": "I", "type": "integer"}, "f": {"title": "F", "type": "number"}, + "i_default": { + "default": 1, + "title": "I Default", + "type": "integer", + }, }, "required": ["i", "f"], } @@ -539,6 +566,11 @@ def test_schema_form_file(schema): }, "i": {"title": "I", "type": "integer"}, "f": {"title": "F", "type": "number"}, + "i_default": { + "default": 1, + "title": "I Default", + "type": "integer", + }, }, "required": ["files", "i", "f"], "title": "MultiPartBodyParams",