def test_default_conversion_type_name(): assert ( print_schema(graphql_schema(query=[b])) == """\ type Query { b: B! } type B { a: Int! } """ ) assert serialization_schema(B, all_refs=True) == { "$ref": "#/$defs/B", "$defs": { "B": {"$ref": "#/$defs/A"}, "A": { "type": "object", "properties": {"a": {"type": "integer"}}, "required": ["a"], "additionalProperties": False, }, }, "$schema": "http://json-schema.org/draft/2019-09/schema#", } assert serialization_schema(B) == { "type": "object", "properties": {"a": {"type": "integer"}}, "required": ["a"], "additionalProperties": False, "$schema": "http://json-schema.org/draft/2019-09/schema#", }
def test_flattened_serialized(): assert (serialization_schema(Base) == serialization_schema(WithFlattened) == base_schema) assert (serialize(Base, Base()) == serialize(WithFlattened, WithFlattened()) == { "serialized": 0 })
def test_inherited_serialized(): assert (serialization_schema(Base) == serialization_schema(Inherited) == serialization_schema(InheritedOverriden) == base_schema) assert (serialize(Base, Base()) == serialize(Inherited, Inherited()) == { "serialized": 0 }) assert serialize(InheritedOverriden, InheritedOverriden()) == { "serialized": 1 }
def test_flattened_converted_error(): with raises(TypeError): deserialize(Data3, {"attr": 0}) with raises(TypeError): serialize(Data3, Data3(Field2(0))) with raises(TypeError): deserialization_schema(Data3) with raises(TypeError): serialization_schema(Data3) with raises(TypeError): graphql_schema(query=[get_data3])
def test_annotated_schema(): assert (deserialization_schema(A) == serialization_schema(A) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": { "a": { "type": "integer", "maximum": 10, "minimum": 0, "description": "field description", } }, "required": ["a"], "additionalProperties": False, }) assert (deserialization_schema(A, all_refs=True) == serialization_schema( A, all_refs=True) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "$ref": "#/$defs/A", "$defs": { "A": { "additionalProperties": False, "properties": { "a": { "$ref": "#/$defs/someInt", "description": "field description", "minimum": 0, } }, "required": ["a"], "type": "object", }, "someInt": { "description": "type description", "maximum": 10, "type": "integer", }, }, }) assert (print_schema(graphql_schema(query=[a])) == '''\ type Query { a: A! } type A { """field description""" a: someInt! } """type description""" scalar someInt ''')
def test_typed_dict(): assert (deserialization_schema(TD3) == serialization_schema(TD3) == { "type": "object", "properties": { "key1": { "type": "string" }, "key2": { "type": "integer" }, "key3": { "type": "boolean" }, }, "required": ["key2"], "additionalProperties": False, "$schema": "http://json-schema.org/draft/2019-09/schema#", }) assert deserialize(TD3, { "Key2": 0, "Key3": True }, aliaser=str.capitalize) == { "key2": 0, "key3": True, } with raises(ValidationError): assert deserialize(TD3, {}) assert serialize(TD1, {"key1": ""}) == {"key1": ""}
def test_simple_dataclass_model(d_conv, s_conv, alias): assert deserialize(Data, {alias: 0}, conversion=d_conv) == Data(0) assert serialize(Data, Data(0), conversion=s_conv) == {alias: 0} assert (deserialization_schema(Data, conversion=d_conv) == serialization_schema(Data, conversion=s_conv) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": { alias: { "type": "integer" } }, "required": [alias], "additionalProperties": False, })
def test_field_generic_conversion(): assert serialize(Foo[str], Foo({1: "a", 0: "b"})) == {"values": ["b", "a"]} assert serialization_schema(Foo[str]) == { "type": "object", "properties": { "values": { "type": "array", "items": { "type": "string" } } }, "required": ["values"], "additionalProperties": False, "$schema": "http://json-schema.org/draft/2019-09/schema#", }
def test_flattened_converted(): data2 = deserialize(Data2, {"attr": 0}) assert isinstance(data2.data_field2, Field2) and data2.data_field2.attr == 0 assert serialize(Data2, data2) == {"attr": 0} assert (deserialization_schema(Data) == serialization_schema(Data) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "allOf": [ { "type": "object", "additionalProperties": False }, { "type": "object", "properties": { "attr": { "type": "integer" } }, "required": ["attr"], "additionalProperties": False, }, ], "unevaluatedProperties": False, }) schema = graphql_schema(query=[get_data2]) assert graphql_sync(schema, "{getData2{attr}}").data == { "getData2": { "attr": 0 } } assert (print_schema(schema) == """\ type Query { getData2: Data2! } type Data2 { attr: Int! } """)
def test_resolver_position(): assert serialization_schema(B) == { "type": "object", "properties": { "a": { "type": "integer" }, "b": { "type": "integer" }, "c": { "type": "integer" }, "d": { "type": "integer" }, "e": { "type": "integer" }, }, "required": ["a", "b", "c", "d", "e"], "additionalProperties": False, "$schema": "http://json-schema.org/draft/2019-09/schema#", } assert (print_schema(graphql_schema(query=[query])) == """\ type Query { query: B! } type B { a: Int! b: Int! c: Int! d: Int! e: Int! } """)
pass @dataclass class Bar: pass def foo_to_bar(_: Foo) -> Bar: return Bar() type_name("Bars")(list[Bar]) assert serialization_schema( list[Foo], conversion=foo_to_bar, all_refs=True ) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "$ref": "#/$defs/Bars", "$defs": { # Bars is present because `list[Foo]` is dynamically converted to `list[Bar]` "Bars": { "type": "array", "items": { "$ref": "#/$defs/Bar" } }, "Bar": { "type": "object", "additionalProperties": False },
def query_to_scalar(q: Query[T]) -> Optional[T]: ... @dataclass class FooModel: bar: int class Foo: def serialize(self) -> FooModel: ... assert serialization_schema( Query[Foo], conversion=Conversion(query_to_list, sub_conversion=Foo.serialize)) == { # We get an array of Foo "type": "array", "items": { "type": "object", "properties": { "bar": { "type": "integer" } }, "required": ["bar"], "additionalProperties": False, }, "$schema": "http://json-schema.org/draft/2019-09/schema#", }
from enum import Enum from apischema import deserialize, serialize from apischema.conversions import as_names from apischema.json_schema import deserialization_schema, serialization_schema @as_names class MyEnum(Enum): FOO = object() BAR = object() assert deserialize(MyEnum, "FOO") == MyEnum.FOO assert serialize(MyEnum, MyEnum.FOO) == "FOO" assert ( deserialization_schema(MyEnum) == serialization_schema(MyEnum) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "string", "enum": ["FOO", "BAR"], } )
from apischema.json_schema import deserialization_schema, serialization_schema from apischema.tagged_unions import Tagged, TaggedUnion @dataclass class Bar: field: str class Foo(TaggedUnion): bar: Tagged[Bar] baz: Tagged[int] assert (deserialization_schema(Foo) == serialization_schema(Foo) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": { "bar": { "type": "object", "properties": { "field": { "type": "string" } }, "required": ["field"], "additionalProperties": False, }, "baz": { "type": "integer"
) @dataclass class Foo(Base): foo: int @dataclass class Bar(Base): bar: str assert ( deserialization_schema(Base) == serialization_schema(Base) == { "anyOf": [ { "type": "object", "properties": {"foo": {"type": "integer"}}, "required": ["foo"], "additionalProperties": False, }, { "type": "object", "properties": {"bar": {"type": "string"}}, "required": ["bar"], "additionalProperties": False, }, ],
from typing import TypeVar from apischema import serialize from apischema.json_schema import serialization_schema T = TypeVar("T") Priority = int def sort_by_priority( values_with_priority: Mapping[T, Priority]) -> Sequence[T]: return [ k for k, _ in sorted(values_with_priority.items(), key=itemgetter(1)) ] assert serialize(dict[str, Priority], { "a": 1, "b": 0 }, conversion=sort_by_priority) == ["b", "a"] assert serialization_schema(dict[str, Priority], conversion=sort_by_priority) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "array", "items": { "type": "string" }, }
def test_recursive_conversion_without_ref(): tmp = None conversion = Conversion(rec_converter, sub_conversion=LazyConversion(lambda: tmp)) tmp = conversion with raises(TypeError, match=r"Recursive type <.*> need a ref"): serialization_schema(RecConv, conversion=conversion)
@property def value(self) -> T: if isinstance(self._value, RecoverableRaw): raise self._value return self._value @value.setter def value(self, value: T): self._value = value deserializer(Recoverable) serializer(Recoverable.value) assert deserialize(Recoverable[int], 0).value == 0 with raises(RecoverableRaw) as err: _ = deserialize(Recoverable[int], "bad").value assert err.value.raw == "bad" assert serialize(Recoverable[int], Recoverable(0)) == 0 with raises(RecoverableRaw) as err: serialize(Recoverable[int], Recoverable(RecoverableRaw("bad"))) assert err.value.raw == "bad" assert (deserialization_schema(Recoverable[int]) == serialization_schema( Recoverable[int]) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "integer" })
def value(self, value: T): self._value = value deserializer( Recoverable, Union[T, Annotated[RecoverableRaw, Skip(schema_only=True)]], Recoverable[T], ) @serializer def serialize_recoverable(recoverable: Recoverable[T]) -> T: return recoverable.value assert deserialize(Recoverable[int], 0).value == 0 with raises(RecoverableRaw) as err: assert deserialize(Recoverable[int], "bad").value assert err.value.raw == "bad" assert serialize(Recoverable(0)) == 0 with raises(RecoverableRaw) as err: assert serialize(Recoverable(RecoverableRaw("bad"))) assert err.value.raw == "bad" assert (deserialization_schema(Recoverable[int]) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "integer" } == serialization_schema(Recoverable[int]))
from dataclasses import dataclass, field from typing import Optional from pytest import raises from apischema import ValidationError, deserialize, serialize from apischema.json_schema import deserialization_schema, serialization_schema from apischema.metadata import none_as_undefined @dataclass class Foo: bar: Optional[str] = field(default=None, metadata=none_as_undefined) assert ( deserialization_schema(Foo) == serialization_schema(Foo) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": {"bar": {"type": "string"}}, "additionalProperties": False, } ) with raises(ValidationError): deserialize(Foo, {"bar": None}) assert serialize(Foo, Foo(None)) == {}
from pytest import raises from apischema import ValidationError, deserialize, deserializer, serialize, serializer from apischema.json_schema import deserialization_schema, serialization_schema T = TypeVar("T") class Wrapper(Generic[T]): def __init__(self, wrapped: T): self.wrapped = wrapped # serializer methods of generic class are not handled in Python 3.6 def unwrap(self) -> T: return self.wrapped serializer(Wrapper.unwrap, Wrapper[T], T) deserializer(Wrapper, T, Wrapper[T]) assert deserialize(Wrapper[list[int]], [0, 1]).wrapped == [0, 1] with raises(ValidationError): deserialize(Wrapper[int], "wrapped") assert serialize(Wrapper("wrapped")) == "wrapped" assert ( deserialization_schema(Wrapper[int]) == {"$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "integer"} == serialization_schema(Wrapper[int]) )
@schema(pattern=r"^#[0-9a-fA-F]{6}$") @dataclass class RGB: red: int green: int blue: int @serializer @property def hexa(self) -> str: return f"#{self.red:02x}{self.green:02x}{self.blue:02x}" # serializer can also be called with methods/properties outside of the class # For example, `serializer(RGB.hexa)` would have the same effect as the decorator above @deserializer def from_hexa(hexa: str) -> RGB: return RGB(int(hexa[1:3], 16), int(hexa[3:5], 16), int(hexa[5:7], 16)) assert deserialize(RGB, "#000000") == RGB(0, 0, 0) assert serialize(RGB, RGB(0, 0, 42)) == "#00002a" assert (deserialization_schema(RGB) == serialization_schema(RGB) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "string", "pattern": "^#[0-9a-fA-F]{6}$", })
def test_merged_serialized(): assert serialization_schema(Base) == serialization_schema( WithMerged) == base_schema assert serialize(Base()) == serialize(WithMerged()) == {"serialized": 0}
from dataclasses import dataclass, field from typing import Any from apischema.json_schema import deserialization_schema, serialization_schema from apischema.metadata import skip @dataclass class Foo: bar: Any deserialization_only: Any = field(metadata=skip(serialization=True)) serialization_only: Any = field(default=None, metadata=skip(deserialization=True)) baz: Any = field(default=None, metadata=skip) assert deserialization_schema(Foo) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": {"bar": {}, "deserialization_only": {}}, "required": ["bar", "deserialization_only"], "additionalProperties": False, } assert serialization_schema(Foo) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": {"bar": {}, "serialization_only": {}}, "required": ["bar", "serialization_only"], "additionalProperties": False, }
... serialized(Foo.bar) @serialized def baz(foo: Foo[U]) -> U: ... @dataclass class FooInt(Foo[int]): ... assert (serialization_schema(Foo[int]) == serialization_schema(FooInt) == { "$schema": "http://json-schema.org/draft/2019-09/schema#", "type": "object", "properties": { "bar": { "type": "integer" }, "baz": { "type": "integer" } }, "required": ["bar", "baz"], "additionalProperties": False, })