Source code for statham.serializers.json

import inspect
from functools import partial
from typing import Any, Dict, Optional

from statham.schema.elements import (
    Array,
    Boolean,
    CompositionElement,
    Element,
    Integer,
    Not,
    Nothing,
    Null,
    Number,
    String,
)
from statham.schema.elements.meta import ObjectMeta
from statham.schema.property import _Property
from statham.serializers.orderer import get_object_classes


[docs]def serialize_json( *elements: Element, definitions: Dict[str, Element] = None ) -> Dict[str, Any]: """Serialize elements to a JSON Schema dictionary. Object classes are included in definitions. The first element is the top-level schema. :param elements: The :class:`~statham.schema.elements.Element` objects to serialize. :param definitions: A dictionary of elements which should be members of the schema definitions keyword, and referenced everywhere else. :return: A JSON-serializable dictionary containing the JSON Schema for the provided element(s). """ primary = elements[0] object_classes = get_object_classes(*elements) serialize = partial( _serialize_element, object_refs=True, definitions=definitions ) schema: Dict[str, Any] = { **serialize(primary), "definitions": { object_class.__name__: serialize(object_class) for object_class in object_classes if object_class is not primary }, } if definitions: schema["definitions"].update( {key: serialize(element) for key, element in definitions.items()} ) if not schema["definitions"]: del schema["definitions"] return schema
def _serialize_element( element: Element, object_refs: bool = False, definitions: Dict[str, Any] = None, ): """Convert a schema element to a JSON Schema dictionary. This does the heavy lifting behind `serialize_json`. """ if isinstance(element, Nothing): return False schema = { param.name: getattr(element, param.name, param.default) for param in inspect.signature(Element.__init__).parameters.values() if param.kind == param.KEYWORD_ONLY and getattr(element, param.name, param.default) != param.default } if not schema.get("properties", True): del schema["properties"] if "properties" in schema: schema["required"] = [ prop.source or name for name, prop in schema["properties"].items() if prop.required ] if not schema.get("required", True): del schema["required"] if isinstance(element, CompositionElement): schema[element.mode] = element.elements if isinstance(element, Not): schema["not"] = element.element if type(element) in _TYPE_MAPPING: # pylint: disable=unidiomatic-typecheck schema["type"] = _TYPE_MAPPING[type(element)] if isinstance(element, ObjectMeta): schema["title"] = element.__name__ return _serialize_recursive( schema, object_refs=object_refs, definitions=definitions ) _TYPE_MAPPING = { Array: "array", Boolean: "boolean", Integer: "integer", Null: "null", ObjectMeta: "object", Number: "number", String: "string", } def _serialize_recursive( data: Any, object_refs: bool = False, definitions: Dict[str, Element] = None ) -> Any: """Recursively serialize schema elements.""" recur = partial( _serialize_recursive, object_refs=object_refs, definitions=definitions ) if isinstance(data, _Property): data = data.element if isinstance(data, ObjectMeta) and object_refs: return {"$ref": f"#/definitions/{data.__name__}"} if isinstance(data, Element): return _from_definitions( definitions, data, _serialize_element( data, object_refs=object_refs, definitions=definitions ), ) if not isinstance(data, (list, dict)): return data if isinstance(data, list): return [recur(item) for item in data] return {key: recur(value) for key, value in data.items()} def _from_definitions( definitions: Optional[Dict[str, Element]], element: Element, default: Any = None, ): """Check if this element is present in definitions.""" if not definitions: return default return next( ( {"$ref": f"#/definitions/{key}"} for key, definition in definitions.items() if definition == element ), default, )