Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use serialization aliases for responses in OpenAPI schema #1162

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

camuthig
Copy link

When generating JSON schemas from the Pydantic types, the mode must be set in order for the correct aliases to be used. The default is to generate the schema for validation mode, which means that for building response schemas, the mode needs to be explicitly set to serialization.

This fixes issues I brought up in discussion #1132

When generating JSON schemas from the Pydantic types, the `mode` must be
set in order for the corret aliases to be used. The default is to
generate the schema for validation mode, which means that for building
response schemas, the mode needs to be explicitly set to serialization.
@pmdevita
Copy link

This might be the same thing as #1139

@camuthig
Copy link
Author

@pmdevita thanks for calling that out. There is some duplication here in that both pulls depend on leveraging the mode argument in model_json_schema. However, the other issue/pull request are also tackling a more nuanced issue around definition of "required" field.

I'm open to closing the pull request, but considering the other one is also facing some open questions of backwards compatibility, I don't want to slow down this fix because of those unrelated topics.

@nstonic
Copy link

nstonic commented Aug 29, 2024

@camuthig So, what about this PR?

@camuthig
Copy link
Author

@nstonic I'm not sure what the situation here is. @vitalik would you consider this pull request as an simpler version of the referenced #1139, that one is tackling a much deeper problem. This pull request has a smaller scope and only makes sure that the alias configurations are respected in the JSON schema generation. I think the other PR can still be considered as a progression of what this one accomplishes.

@tu-pm
Copy link

tu-pm commented Oct 2, 2024

This also fixes the problem of computed fields missing from the schema, as reported by issue #1292 and #960

@oubeichen
Copy link

@vitalik any update on this?

@rayansostenes
Copy link

rayansostenes commented Dec 3, 2024

Had to solve this at work, here's the monkey patch I applied to make it work.

# pyright: basic
from http.client import responses
from typing import Any

import ninja
import pydantic
from ninja.constants import NOT_SET
from ninja.openapi.schema import REF_TEMPLATE
from ninja.openapi.schema import OpenAPISchema
from ninja.schema import NinjaGenerateJsonSchema
from pydantic.json_schema import JsonSchemaMode


def _create_schema_from_model(
    self,
    model: pydantic.BaseModel,
    by_alias: bool = True,  # noqa: FBT001, FBT002
    remove_level: bool = True,  # noqa: FBT001, FBT002
    mode: JsonSchemaMode = "validation",
) -> tuple[dict[str, Any], bool]:
    if hasattr(model, "__ninja_flatten_map__"):
        schema = self._flatten_schema(model)
    else:
        schema = model.model_json_schema(
            ref_template=REF_TEMPLATE,
            by_alias=by_alias,
            schema_generator=NinjaGenerateJsonSchema,
            mode=mode,
        ).copy()

    # schema = _rewrite_schema(self.schemas, schema, mode)

    if schema.get("$defs"):
        self.add_schema_definitions(schema.pop("$defs"))

    if remove_level and len(schema["properties"]) == 1:
        name, details = next(iter(schema["properties"].items()))

        required = name in schema.get("required", {})
        return details, required
    return schema, True


def _responses(self, operation: Any) -> dict[int, dict[str, Any]]:
    assert bool(operation.response_models), f"{operation.response_models} empty"

    result = {}
    for status, model in operation.response_models.items():
        if status == Ellipsis:
            continue  # it's not yet clear what it means if user wants to output any other code

        description = responses.get(status, "Unknown Status Code")
        details: dict[int, Any] = {status: {"description": description}}
        if model not in [None, NOT_SET]:
            schema = self._create_schema_from_model(
                model,
                by_alias=operation.by_alias,
                mode="serialization",
            )[0]
            details[status]["content"] = {self.api.renderer.media_type: {"schema": schema}}
        result.update(details)

    return result


def apply_ninja_monkey_patch():
    version_tuple = tuple(map(int, ninja.__version__.split(".")))
    if version_tuple > (1, 3, 0):
        msg = "This monkey patch is only tested for ninja<=1.3.0"
        raise ImportError(msg)
    OpenAPISchema._create_schema_from_model = _create_schema_from_model  # noqa: SLF001
    OpenAPISchema.responses = _responses
# settings.py
# ...
from my_package.ninja_api_schema_monkey_patch import apply_ninja_monkey_patch
apply_ninja_monkey_patch()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants