Files
divkit/json-builder/python/pydivkit/core/schema.py
T
p-mosein 2987d93ba7 pydivkit. migrate to uv+hatch, decompose entities.py, fix Python 3.14 compat
commit_hash:cf5070a543788fa57136adb1a4a7ea42f4490329
2026-02-10 16:34:59 +03:00

138 lines
3.4 KiB
Python

from __future__ import annotations
import enum
from types import MappingProxyType
from typing import (
Any,
Dict,
Mapping,
Optional,
Sequence,
Type,
Union,
get_args,
get_origin,
)
from .fields import Expr, _Field
TYPE_FIELD = "type"
# ExcludeFieldsType: str -> ExcludeFieldsType | bool
ExcludeFieldsType = Mapping[str, Any]
SchemaType = Dict[str, Any]
BUILTIN_TYPES_TO_SCHEMA: Mapping[type, Mapping[str, Any]] = MappingProxyType(
{
int: MappingProxyType({"type": "integer"}),
float: MappingProxyType({"type": "number"}),
bool: MappingProxyType(
{
"type": "integer",
"enum": [0, 1],
"format": "boolean",
},
),
str: MappingProxyType({"type": "string"}),
bytes: MappingProxyType({"type": "string"}),
Expr: MappingProxyType({"type": "string", "pattern": "^@{.*}$"}),
Any: MappingProxyType({}),
},
)
def _type_field_to_schema(field: _Field) -> SchemaType:
return {
"type": "string",
"enum": [field.default],
}
def _enum_to_schema(type_: Type[enum.Enum]) -> SchemaType:
return {
"type": "string",
"enum": [enum_el.value for enum_el in type_],
}
def _list_to_schema(
type_: Any,
definitions: Dict[str, SchemaType],
exclude: ExcludeFieldsType,
) -> SchemaType:
item_type, *_ = get_args(type_)
return {
"type": "array",
"items": _field_to_schema(None, item_type, exclude, definitions),
}
def _dict_to_schema() -> SchemaType:
return {
"type": "object",
"additionalProperties": True,
}
def _union_to_schema(
field: Optional[_Field],
type_: Any,
exclude: ExcludeFieldsType,
definitions: Dict[str, SchemaType],
) -> SchemaType:
return {
"anyOf": [
_field_to_schema(field, arg_type, exclude, definitions)
for arg_type in get_args(type_)
],
}
def _add_field_extra_to_schema(
field: _Field,
schema: SchemaType,
) -> None:
if field.description:
schema["description"] = field.description
if field.default:
schema["default"] = field.default
schema.update(**field.constraints)
def _field_to_schema(
field: Optional[_Field],
type_: Any,
exclude: ExcludeFieldsType,
definitions: Dict[str, SchemaType],
) -> SchemaType:
from .entities import BaseEntity
from .serialization import _unpack_optional_type
type_ = _unpack_optional_type(type_)
origin = get_origin(type_)
schema: Optional[SchemaType] = None
if field and field.name == TYPE_FIELD and field.default:
schema = _type_field_to_schema(field)
elif type_ in BUILTIN_TYPES_TO_SCHEMA:
schema = {**BUILTIN_TYPES_TO_SCHEMA[type_]}
elif isinstance(origin, type) and issubclass(origin, Sequence):
schema = _list_to_schema(type_, definitions, exclude)
elif isinstance(origin, type) and issubclass(origin, Mapping):
schema = _dict_to_schema()
elif origin is Union:
schema = _union_to_schema(field, type_, exclude, definitions)
elif issubclass(type_, BaseEntity):
schema = type_.schema_as_ref(definitions, exclude)
elif issubclass(type_, enum.Enum):
schema = _enum_to_schema(type_)
if schema is None:
raise TypeError(f"Schema building error for unknown type {type_}")
if field:
_add_field_extra_to_schema(field, schema)
return schema