예제 #1
0
def test_generic_schema():
    assert deserialization_schema(DataGeneric, all_refs=True) == {
        "$schema": "http://json-schema.org/draft/2019-09/schema#",
        "type": "object",
        "properties": {"a": {}},
        "required": ["a"],
        "additionalProperties": False,
    }
    assert deserialization_schema(DataGeneric[int], all_refs=True) == {
        "$schema": "http://json-schema.org/draft/2019-09/schema#",
        "type": "object",
        "properties": {"a": {"type": "integer"}},
        "required": ["a"],
        "additionalProperties": False,
    }
    assert deserialization_schema(DataGeneric[str], all_refs=True) == {
        "$schema": "http://json-schema.org/draft/2019-09/schema#",
        "$ref": "#/$defs/StrData",
        "$defs": {
            "StrData": {
                "type": "object",
                "properties": {"a": {"type": "string"}},
                "required": ["a"],
                "additionalProperties": False,
            }
        },
    }
예제 #2
0
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])
예제 #3
0
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
''')
예제 #4
0
def test_global_aliaser():
    settings.aliaser(camel_case=True)
    assert deserialization_schema(CamelCase)["properties"] == {
        "snakeCase": {
            "type": "integer"
        }
    }
    settings.aliaser(camel_case=False)
    # dataclasses cache is reset
    assert deserialization_schema(CamelCase)["properties"] == {
        "snake_case": {
            "type": "integer"
        }
    }
예제 #5
0
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": ""}
예제 #6
0
def test_lazy_dataclass_model():
    assert deserialize(Lazy, {"a": 0}) == Lazy(0)
    assert serialize(Lazy(0)) == {"a": 0}
    assert deserialization_schema(Lazy) == {
        "$schema": "http://json-schema.org/draft/2019-09/schema#",
        "type": "object",
        "properties": {
            "a": {
                "type": "integer"
            }
        },
        "required": ["a"],
        "additionalProperties": False,
    }
예제 #7
0
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,
            })
예제 #8
0
def test_flattened_schema():
    assert deserialization_schema(WithSchema) == {
        "$schema": "http://json-schema.org/draft/2019-09/schema#",
        "type": "object",
        "properties": {
            "attr1": {
                "type": "integer",
                "minimum": 3
            },
            "attr2": {
                "type": "integer",
                "minimum": 2
            },
        },
        "required": ["attr1", "attr2"],
        "additionalProperties": False,
    }
예제 #9
0
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!
}
""")
예제 #10
0
def test_recursive_by_conversion_schema():
    assert deserialization_schema(Foo) == {
        "$ref": "#/$defs/Foo",
        "$defs": {
            "Foo": {
                "type": "object",
                "properties": {
                    "foo": {
                        "anyOf": [{
                            "$ref": "#/$defs/Foo"
                        }, {
                            "type": "null"
                        }]
                    }
                },
                "required": ["foo"],
                "additionalProperties": False,
            }
        },
        "$schema": "http://json-schema.org/draft/2019-09/schema#",
    }
예제 #11
0
    definitions_schema,
    deserialization_schema,
)


@dataclass
class Bar:
    baz: Optional[int]


@dataclass
class Foo:
    bar: Bar


assert deserialization_schema(Foo, all_refs=True) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "$ref": "#/$defs/Foo",
    "$defs": {
        "Foo": {
            "type": "object",
            "properties": {
                "bar": {
                    "$ref": "#/$defs/Bar"
                }
            },
            "required": ["bar"],
            "additionalProperties": False,
        },
        "Bar": {
            "type": "object",
예제 #12
0
from dataclasses import dataclass

from apischema.json_schema import deserialization_schema


@dataclass
class Foo:
    bar: int


def ref_factory(ref: str) -> str:
    return f"http://some-domain.org/path/to/{ref}.json#"


assert deserialization_schema(Foo, ref_factory=ref_factory) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "$ref": "http://some-domain.org/path/to/Foo.json#",
}
예제 #13
0
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"],
    }
)
예제 #14
0
# Deserialize data
resource = deserialize(Resource, data)
assert resource == Resource(uuid, "wyfo", {"some_tag"})
# Serialize objects
assert serialize(resource) == data
# Validate during deserialization
with raises(ValidationError) as err:  # pytest check exception is raised
    deserialize(Resource, {"id": "42", "name": "wyfo"})
assert serialize(err.value) == [  # ValidationError is serializable
    {
        "loc": ["id"],
        "err": ["badly formed hexadecimal UUID string"]
    }
]
# Generate JSON Schema
assert deserialization_schema(Resource) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "type": "object",
    "properties": {
        "id": {
            "type": "string",
            "format": "uuid"
        },
        "name": {
            "type": "string"
        },
        "tags": {
            "type": "array",
            "items": {
                "type": "string"
            },
예제 #15
0
                                   metadata=alias("$defs"))
    # This field schema is flattened inside the owning one
    json_schema: JsonSchema = field(default=JsonSchema(), metadata=flatten)


data = {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "title": "flattened example",
}
root_schema = RootJsonSchema(
    schema="http://json-schema.org/draft/2019-09/schema#",
    json_schema=JsonSchema(title="flattened example"),
)
assert deserialize(RootJsonSchema, data) == root_schema
assert serialize(RootJsonSchema, root_schema) == data
assert deserialization_schema(RootJsonSchema) == {
    "$schema":
    "http://json-schema.org/draft/2019-09/schema#",
    "$defs": {
        "JsonSchema": {
            "type": "object",
            "properties": {
                "title": {
                    "type": "string"
                },
                "description": {
                    "type": "string"
                },
                "format": {
                    "type": "string"
                },
예제 #16
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,
}
예제 #17
0
    if "anyOf" in schema:
        schema["oneOf"] = schema.pop("anyOf")


OneOf = schema(extra=to_one_of)


# or extra can be a dictionary which will update the schema
@schema(
    extra={"$ref": "http://some-domain.org/path/to/schema.json#/$defs/Foo"},
    override=True,  # override apischema generated schema, using only extra
)
@dataclass
class Foo:
    bar: int


# Use Annotated with OneOf to make a "strict" Union
assert deserialization_schema(Annotated[Union[Foo, int], OneOf]) == {
    "$schema":
    "http://json-schema.org/draft/2019-09/schema#",
    "oneOf": [  # oneOf instead of anyOf
        {
            "$ref": "http://some-domain.org/path/to/schema.json#/$defs/Foo"
        },
        {
            "type": "integer"
        },
    ],
}
예제 #18
0
from typing import NewType

from apischema import schema_ref
from apischema.json_schema import deserialization_schema

Tags = NewType("Tags", Set[str])
schema_ref(...)(Tags)


@dataclass
class Resource:
    id: int
    tags: Tags


assert deserialization_schema(Resource, all_refs=True) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "$defs": {
        "Resource": {
            "type": "object",
            "properties": {
                "id": {
                    "type": "integer"
                },
                "tags": {
                    "$ref": "#/$defs/Tags"
                }
            },
            "required": ["id", "tags"],
            "additionalProperties": False,
        },
예제 #19
0
            Conversion(identity, source=Base, target=Base._union, inherited=False)
        )


@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,
            },
예제 #20
0
# Annotated can be used to add metadata to mapped fields
def prefixed_foo(baz: str, pfx: Annotated[str, alias("prefix")] = "") -> Foo:
    return Foo(pfx + baz)


wrapper, input_cls = dataclass_input_wrapper(prefixed_foo)

assert wrapper(input_cls("oo", "f")) == prefixed_foo("oo", "f") == Foo("foo")

# Used as conversion
assert deserialize(Foo, {
    "baz": "oo",
    "prefix": "f"
}, conversion=wrapper) == Foo("foo")
assert deserialization_schema(Foo, conversion=wrapper) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "type": "object",
    "properties": {
        "baz": {
            "type": "string"
        },
        "prefix": {
            "type": "string",
            "default": ""
        },
    },
    "required": ["baz"],
    "additionalProperties": False,
}
예제 #21
0

deserializer(identity, Bar, Base)
# Roughly equivalent to
# @deserializer
# def from_bar(bar: Bar) -> Base:
#     return bar
# but identity is optimized by Apischema

# You can even add deserializers which are not subclass
@deserializer
def from_list_of_int(ints: list[int]) -> Base:
    return Base()


assert deserialization_schema(Base) == {
    "anyOf": [
        {
            "type": "object",
            "properties": {"foo": {"type": "integer"}},
            "required": ["foo"],
            "additionalProperties": False,
        },
        {
            "type": "object",
            "properties": {"bar": {"type": "string"}},
            "required": ["bar"],
            "additionalProperties": False,
        },
        {"type": "array", "items": {"type": "integer"}},
    ],
예제 #22
0
def make_schema(cls: type) -> Mapping[str, Any]:
    return deserialization_schema(cls, version=JsonSchemaVersion.DRAFT_7)
예제 #23
0
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)) == {}
예제 #24
0

@dataclass
class Billing:
    name: str
    # Fields used in dependencies MUST be declared with `field`
    credit_card: NotNull[int] = field(default=None)
    billing_address: NotNull[str] = field(default=None)

    dependencies = dependent_required({credit_card: [billing_address]})


# it can also be done outside the class with
# dependent_required({"credit_card": ["billing_address"]}, owner=Billing)

assert deserialization_schema(Billing) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "additionalProperties": False,
    "dependentRequired": {
        "credit_card": ["billing_address"]
    },
    "properties": {
        "name": {
            "type": "string"
        },
        "credit_card": {
            "type": "integer"
        },
        "billing_address": {
            "type": "string"
        },
예제 #25
0
@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}$",
})
예제 #26
0
from apischema import deserialize, deserializer, type_name
from apischema.json_schema import deserialization_schema
from apischema.objects import object_deserialization


def create_range(start: int, stop: int, step: int = 1) -> range:
    return range(start, stop, step)


range_conv = object_deserialization(create_range, type_name("Range"))
# Conversion can be registered
deserializer(range_conv)
assert deserialize(range, {"start": 0, "stop": 10}) == range(0, 10)
assert deserialization_schema(range) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "type": "object",
    "properties": {
        "start": {
            "type": "integer"
        },
        "stop": {
            "type": "integer"
        },
        "step": {
            "type": "integer",
            "default": 1
        },
    },
    "required": ["start", "stop"],
    "additionalProperties": False,
}
예제 #27
0
from dataclasses import dataclass
from typing import Optional

from apischema.json_schema import deserialization_schema


@dataclass
class Node:
    value: int
    child: Optional["Node"] = None


assert deserialization_schema(Node) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "$ref": "#/$defs/Node",
    "$defs": {
        "Node": {
            "type": "object",
            "properties": {
                "value": {
                    "type": "integer"
                },
                "child": {
                    "anyOf": [{
                        "$ref": "#/$defs/Node"
                    }, {
                        "type": "null"
                    }]
                },
            },
            "required": ["value"],
예제 #28
0
import os
import time
from datetime import datetime

from apischema import deserialize, deserializer
from apischema.json_schema import deserialization_schema

# Set UTC timezone for example
os.environ["TZ"] = "UTC"
time.tzset()

# There is already `deserializer(datetime.fromisoformat, str, datetime) in apischema
# Let's add an other deserializer for datetime from a timestamp


@deserializer
def datetime_from_timestamp(timestamp: int) -> datetime:
    return datetime.fromtimestamp(timestamp)


assert deserialization_schema(datetime) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "anyOf": [{"type": "string", "format": "date-time"}, {"type": "integer"}],
}
assert (
    deserialize(datetime, "2019-10-13")
    == datetime(2019, 10, 13)
    == deserialize(datetime, 1570924800)
)
예제 #29
0
    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]))
예제 #30
0
from apischema.json_schema import deserialization_schema


@dataclass
class Bar:
    baz: str


@dataclass
class Foo:
    bar1: Bar
    bar2: Bar


assert deserialization_schema(Foo, all_refs=False) == {
    "$schema": "http://json-schema.org/draft/2019-09/schema#",
    "$defs": {
        "Bar": {
            "additionalProperties": False,
            "properties": {
                "baz": {
                    "type": "string"
                }
            },
            "required": ["baz"],
            "type": "object",
        }
    },
    "additionalProperties": False,
    "properties": {