]> git.ipfire.org Git - thirdparty/fastapi/fastapi.git/commitdiff
✨ Add custom GenerateJsonSchema
authorMarcelo Trylesinski <marcelotryle@gmail.com>
Thu, 13 Jul 2023 15:58:14 +0000 (17:58 +0200)
committerMarcelo Trylesinski <marcelotryle@gmail.com>
Thu, 13 Jul 2023 15:58:42 +0000 (17:58 +0200)
fastapi/_compat.py
fastapi/params.py

index 2233fe33c72a3ac8888bb6d143922d031539c925..fa0065c3ad1302c57d7fcd34b5bc8a0c29e1694c 100644 (file)
@@ -44,8 +44,10 @@ sequence_annotation_to_type = {
 sequence_types = tuple(sequence_annotation_to_type.keys())
 
 if PYDANTIC_V2:
+    from typing import Sequence
+
     from pydantic import PydanticSchemaGenerationError as PydanticSchemaGenerationError
-    from pydantic import TypeAdapter
+    from pydantic import PydanticUserError, TypeAdapter
     from pydantic import ValidationError as ValidationError
     from pydantic._internal._schema_generation_shared import (  # type: ignore[attr-defined]
         GetJsonSchemaHandler as GetJsonSchemaHandler,
@@ -53,16 +55,78 @@ if PYDANTIC_V2:
     from pydantic._internal._typing_extra import eval_type_lenient
     from pydantic._internal._utils import lenient_issubclass as lenient_issubclass
     from pydantic.fields import FieldInfo
-    from pydantic.json_schema import GenerateJsonSchema as GenerateJsonSchema
+    from pydantic.json_schema import (
+        DEFAULT_REF_TEMPLATE,
+        DefsRef,
+        JsonSchemaKeyT,
+        JsonSchemaMode,
+        _sort_json_schema,
+    )
+    from pydantic.json_schema import GenerateJsonSchema as _GenerateJsonSchema
     from pydantic.json_schema import JsonSchemaValue as JsonSchemaValue
     from pydantic_core import CoreSchema as CoreSchema
     from pydantic_core import MultiHostUrl as MultiHostUrl
-    from pydantic_core import PydanticUndefined, PydanticUndefinedType
+    from pydantic_core import PydanticUndefined, PydanticUndefinedType, core_schema
     from pydantic_core import Url as Url
     from pydantic_core.core_schema import (
         general_plain_validator_function as general_plain_validator_function,
     )
 
+    class GenerateJsonSchema(_GenerateJsonSchema):
+        def __init__(
+            self, by_alias: bool = True, ref_template: str = DEFAULT_REF_TEMPLATE
+        ):
+            super().__init__(by_alias=by_alias, ref_template=ref_template)
+            self.skip_null_schema = False
+
+        def nullable_schema(
+            self, schema: core_schema.NullableSchema
+        ) -> JsonSchemaValue:
+            if self.skip_null_schema:
+                return super().generate_inner(schema["schema"])
+            return super().nullable_schema(schema)
+
+        def generate_definitions(
+            self,
+            inputs: Sequence[
+                tuple[JsonSchemaKeyT, JsonSchemaMode, core_schema.CoreSchema]
+            ],
+        ) -> tuple[
+            dict[tuple[JsonSchemaKeyT, JsonSchemaMode], JsonSchemaValue],
+            dict[DefsRef, JsonSchemaValue],
+        ]:
+            # Avoid circular import - Maybe there's a better way to check if it's a Param
+            from fastapi.params import Param
+
+            if self._used:
+                raise PydanticUserError(
+                    "This JSON schema generator has already been used to generate a JSON schema. "
+                    f"You must create a new instance of {type(self).__name__} to generate a new JSON schema.",
+                    code="json-schema-already-used",
+                )
+
+            for key, mode, schema in inputs:
+                self.mode = mode
+                self.skip_null_schema = isinstance(key, ModelField) and isinstance(
+                    key.field_info, Param
+                )
+                self.generate_inner(schema)
+
+            definitions_remapping = self._build_definitions_remapping()
+
+            json_schemas_map: dict[tuple[JsonSchemaKeyT, JsonSchemaMode], DefsRef] = {}
+            for key, mode, schema in inputs:
+                self.mode = mode
+                json_schema = self.generate_inner(schema)
+                json_schemas_map[(key, mode)] = definitions_remapping.remap_json_schema(
+                    json_schema
+                )
+
+            json_schema = {"$defs": self.definitions}
+            json_schema = definitions_remapping.remap_json_schema(json_schema)
+            self._used = True
+            return json_schemas_map, _sort_json_schema(json_schema["$defs"])  # type: ignore
+
     Required = PydanticUndefined
     Undefined = PydanticUndefined
     UndefinedType = PydanticUndefinedType
index 30af5713e73e47ae4949c369a1fa35581028e556..634890bedc72dbeff7eeba2984539d9fe0422b97 100644 (file)
@@ -98,7 +98,7 @@ class Param(FieldInfo):
             kwargs["examples"] = examples
         if regex is not None:
             warnings.warn(
-                "`regex` has been depreacated, please use `pattern` instead",
+                "`regex` has been deprecated, please use `pattern` instead",
                 category=DeprecationWarning,
                 stacklevel=4,
             )