Exemple #1
0
def test_raw_with_kwargs():
    deserializer(to_raw_deserializer(int_dict))
    assert deserialize(IntDict, {"mandatory": 42, "a": 0}) == IntDict({"a": 0})
    with raises(ValidationError):
        deserialize(IntDict, {"mandatory": 42, "a": "0"})
    with raises(ValidationError):
        deserialize(IntDict, {"a": 0})
Exemple #2
0
 def __init_subclass__(cls, **kwargs):
     # Deserializers stack directly as a Union
     deserializer(Conversion(identity, source=cls, target=Base))
     # Only Base serializer must be registered (and updated for each subclass) as
     # a Union, and not be inherited
     Base._union = cls if Base._union is None else Union[Base._union, cls]
     serializer(
         Conversion(identity, source=Base, target=Base._union, inherited=False)
     )
Exemple #3
0
def as_tagged_union(cls: Type):
    def serialization() -> Conversion:
        serialization_union = new_class(
            f"Tagged{cls.__name__}Union",
            (TaggedUnion, ),
            exec_body=lambda ns: ns.update({
                "__annotations__": {
                    sub.__name__: Tagged[sub]  # type: ignore
                    for sub in rec_subclasses(cls)
                }
            }),
        )
        return Conversion(
            lambda obj: serialization_union(**{obj.__class__.__name__: obj}),
            source=cls,
            target=serialization_union,
            # Conversion must not be inherited because it would lead to infinite
            # recursion otherwise
            inherited=False,
        )

    def deserialization() -> Conversion:
        annotations: Dict[str, Any] = {}
        deserialization_namespace: Dict[str, Any] = {
            "__annotations__": annotations
        }
        for sub in rec_subclasses(cls):
            annotations[sub.__name__] = Tagged[sub]  # type: ignore
            # Add tagged fields for all its alternative constructors
            for constructor in _alternative_constructors.get(sub.__name__, ()):
                # Build the alias of the field
                alias = ("".join(
                    map(str.capitalize, constructor.__name__.split("_"))) +
                         sub.__name__)
                # object_deserialization uses get_type_hints, but the constructor
                # return type is stringified and the class not defined yet,
                # so it must be assigned manually
                constructor.__annotations__["return"] = sub
                # Add constructor tagged field with its conversion
                annotations[alias] = Tagged[sub]  # type: ignore
                deserialization_namespace[alias] = Tagged(
                    conversion(
                        # Use object_deserialization to wrap constructor as deserializer
                        deserialization=object_deserialization(
                            constructor, type_name(alias))))
        # Create the deserialization tagged union class
        deserialization_union = new_class(
            f"Tagged{cls.__name__}Union",
            (TaggedUnion, ),
            exec_body=lambda ns: ns.update(deserialization_namespace),
        )
        return Conversion(lambda obj: get_tagged(obj)[1],
                          source=deserialization_union,
                          target=cls)

    deserializer(lazy=deserialization, target=cls)
    serializer(lazy=serialization, source=cls)
Exemple #4
0
def test_raw():
    deserializer(to_raw_deserializer(sfx_version))
    assert deserialize(SuffixedVersion, {"version": 42}) == "42"
    assert deserialize(SuffixedVersion, {
        "version": 42,
        "suffix": "ok"
    }) == "42ok"
    deserializer(to_raw_deserializer(sfx_version))
    with raises(ValidationError):
        deserialize(SuffixedVersion, {"version": "42"})
Exemple #5
0
    def __init_subclass__(cls, **kwargs):
        # Registers new subclasses automatically in the union cls._union.

        # Deserializers stack directly as a Union
        apischema.deserializer(
            apischema.conversions.Conversion(apischema.identity,
                                             source=cls,
                                             target=AsynPortBase))

        # Only AsynPortBase serializer must be registered (and updated for each
        # subclass) as a Union, and not be inherited
        AsynPortBase._union = (cls if AsynPortBase._union is None else
                               Union[AsynPortBase._union, cls])
        apischema.serializer(
            apischema.conversions.Conversion(
                apischema.identity,
                source=AsynPortBase,
                target=AsynPortBase._union,
                inherited=False,
            ))
Exemple #6
0
 def __init_subclass__(cls, **kwargs):
     super().__init_subclass__(**kwargs)
     # Register subclasses' conversion in __init_subclass__
     deserializer(Conversion(cls.deserialize, target=cls))
Exemple #7
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,
}
Exemple #8
0
def make_entity_class(definition: Definition,
                      support: Support) -> Type[Entity]:
    """
    We can get a set of Definitions by deserializing an ibek
    support module definition YAML file.

    This function then creates an Entity derived class from each Definition.

    See :ref:`entities`
    """
    fields: List[Tuple[str, type, Field[Any]]] = []

    # add in each of the arguments
    for arg in definition.args:
        # make_dataclass can cope with string types, so cast them here rather
        # than lookup
        metadata: Any = None
        arg_type: type
        if isinstance(arg, ObjectArg):

            def lookup_instance(id):
                try:
                    return id_to_entity[id]
                except KeyError:
                    raise ValidationError(
                        f"{id} is not in {list(id_to_entity)}")

            metadata = conversion(deserialization=Conversion(
                lookup_instance, str, Entity)) | schema(
                    extra={"vscode_ibek_plugin_type": "type_object"})
            arg_type = Entity
        elif isinstance(arg, IdArg):
            arg_type = str
            metadata = schema(extra={"vscode_ibek_plugin_type": "type_id"})
        else:
            # arg.type is str, int, float, etc.
            arg_type = getattr(builtins, arg.type)
        if arg.description:
            arg_type = A[arg_type, desc(arg.description)]
        if arg.default is Undefined:
            fld = field(metadata=metadata)
        else:
            fld = field(metadata=metadata, default=arg.default)
        fields.append((arg.name, arg_type, fld))

    # put the literal name in as 'type' for this Entity this gives us
    # a unique key for each of the entity types we may instantiate
    full_name = f"{support.module}.{definition.name}"
    fields.append(
        ("type", Literal[full_name], field(default=cast(Any, full_name))))

    # add a field so we can control rendering of the entity without having to delete
    # it
    fields.append(("entity_enabled", bool, field(default=cast(Any, True))))

    namespace = dict(__definition__=definition)

    # make the Entity derived dataclass for this EntityClass, with a reference
    # to the Definition that created it
    entity_cls = make_dataclass(full_name,
                                fields,
                                bases=(Entity, ),
                                namespace=namespace)
    deserializer(Conversion(identity, source=entity_cls, target=Entity))
    return entity_cls
Exemple #9
0
@dataclass
class Foo:
    value: int

    @classmethod
    def deserialize(cls: type[Foo_], value: int) -> Foo_:
        return cls(value)

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        # Register subclasses' conversion in __init_subclass__
        deserializer(Conversion(cls.deserialize, target=cls))


# Register main conversion after the class definition
deserializer(Conversion(Foo.deserialize, target=Foo))


class Bar(Foo):
    pass


assert deserialize(Foo, 0) == Foo(0)
assert deserialize(Bar, 0) == Bar(0)

# For external types (defines in imported library)


@dataclass
class ForeignType:
    value: int
Exemple #10
0
@dataclass
class Foo(Base):
    foo: int


@dataclass
class Bar(Base):
    bar: str


@deserializer
def from_foo(foo: Foo) -> Base:
    return foo


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": [
        {
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])
)
Exemple #12
0
from base64 import b64decode

from apischema import deserialize, deserializer
from apischema.conversions import Conversion

deserializer(Conversion(b64decode, source=str, target=bytes))
# Roughly equivalent to:
# def decode_bytes(source: str) -> bytes:
#     return b64decode(source)
# but saving a function call

assert deserialize(bytes, "Zm9v") == b"foo"
Exemple #13
0
 def __init_subclass__(cls):
     # Deserializers stack directly as a Union
     deserializer(Conversion(identity, source=cls, target=Arg))
Exemple #14
0
def as_tagged_union(cls: Cls) -> Cls:
    """
    Tagged union decorator, to be used on base class.

    Supports generics as well, with names generated by way of
    `_get_generic_name_factory`.
    """
    params = tuple(getattr(cls, "__parameters__", ()))
    tagged_union_bases: Tuple[type, ...] = (TaggedUnion, )

    # Generic handling is here:
    if params:
        tagged_union_bases = (TaggedUnion, Generic[params])
        generic_name(cls)
        prev_init_subclass = getattr(cls, "__init_subclass__", None)

        def __init_subclass__(cls, **kwargs):
            if prev_init_subclass is not None:
                prev_init_subclass(**kwargs)
            generic_name(cls)

        cls.__init_subclass__ = classmethod(__init_subclass__)

    def with_params(cls: type) -> Any:
        """Specify type of Generic if set."""
        return cls[params] if params else cls

    def serialization() -> Conversion:
        """
        Define the serializer Conversion for the tagged union.

        source is the base ``cls`` (or ``cls[T]``).
        target is the new tagged union class ``TaggedUnion`` which gets the
        dictionary {cls.__name__: obj} as its arguments.
        """
        annotations = {
            # Assume that subclasses have same generic parameters than cls
            sub.__name__: Tagged[with_params(sub)]
            for sub in get_all_subclasses(cls)
        }
        namespace = {"__annotations__": annotations}
        tagged_union = new_class(cls.__name__,
                                 tagged_union_bases,
                                 exec_body=lambda ns: ns.update(namespace))
        return Conversion(
            lambda obj: tagged_union(**{obj.__class__.__name__: obj}),
            source=with_params(cls),
            target=with_params(tagged_union),
            # Conversion must not be inherited because it would lead to
            # infinite recursion otherwise
            inherited=False,
        )

    def deserialization() -> Conversion:
        """
        Define the deserializer Conversion for the tagged union.

        Allows for alternative standalone constructors as per the apischema
        example.
        """
        annotations: dict[str, Any] = {}
        namespace: dict[str, Any] = {"__annotations__": annotations}
        for sub in get_all_subclasses(cls):
            annotations[sub.__name__] = Tagged[with_params(sub)]
            for constructor in _alternative_constructors.get(sub, ()):
                # Build the alias of the field
                alias = to_pascal_case(constructor.__name__)
                # object_deserialization uses get_type_hints, but the constructor
                # return type is stringified and the class not defined yet,
                # so it must be assigned manually
                constructor.__annotations__["return"] = with_params(sub)
                # Use object_deserialization to wrap constructor as deserializer
                deserialization = object_deserialization(
                    constructor, generic_name)
                # Add constructor tagged field with its conversion
                annotations[alias] = Tagged[with_params(sub)]
                namespace[alias] = Tagged(
                    conversion(deserialization=deserialization))
        # Create the deserialization tagged union class
        tagged_union = new_class(cls.__name__,
                                 tagged_union_bases,
                                 exec_body=lambda ns: ns.update(namespace))
        return Conversion(
            lambda obj: get_tagged(obj)[1],
            source=with_params(tagged_union),
            target=with_params(cls),
        )

    deserializer(lazy=deserialization, target=cls)
    serializer(lazy=serialization, source=cls)
    return cls
Exemple #15
0
from dataclasses import dataclass

from apischema import deserialize, deserializer, serialize, serializer
from apischema.conversions import Conversion


@dataclass
class Foo:
    bar: int


deserializer(
    lazy=lambda: Conversion(lambda bar: Foo(bar), source=int, target=Foo), target=Foo
)
serializer(
    lazy=lambda: Conversion(lambda foo: foo.bar, source=Foo, target=int), source=Foo
)

assert deserialize(Foo, 0) == Foo(0)
assert serialize(Foo, Foo(0)) == 0
from typing import Annotated, Any, Generic, TypeVar, Union

from pytest import raises

from apischema import deserialize, deserializer, serialize, serializer
from apischema.json_schema import deserialization_schema, serialization_schema
from apischema.skip import Skip


class RecoverableRaw(Exception):
    def __init__(self, raw):
        self.raw = raw


deserializer(RecoverableRaw, Any, RecoverableRaw)

T = TypeVar("T")


class Recoverable(Generic[T]):
    def __init__(self, value: T):
        self._value = value

    @property
    def value(self) -> T:
        if isinstance(self._value, RecoverableRaw):
            raise self._value
        return self._value

    @value.setter
    def value(self, value: T):
Exemple #17
0
from pytest import raises

from apischema import deserialize, deserializer, schema, serialize, serializer
from apischema.json_schema import deserialization_schema, serialization_schema


# Add a dummy placeholder comment in order to not have an empty schema
# (because Union member with empty schema would "contaminate" whole Union schema)
@schema(extra={"$comment": "recoverable"})
class RecoverableRaw(Exception):
    def __init__(self, raw: Any):
        self.raw = raw


deserializer(RecoverableRaw)

T = TypeVar("T")


def remove_recoverable_schema(json_schema: Dict[str, Any]):
    if "anyOf" in json_schema:  # deserialization schema
        value_schema, recoverable_comment = json_schema.pop("anyOf")
        assert recoverable_comment == {"$comment": "recoverable"}
        json_schema.update(value_schema)


@schema(extra=remove_recoverable_schema)
class Recoverable(Generic[T]):
    def __init__(self, value: Union[T, RecoverableRaw]):
        self._value = value
Exemple #18
0
    PurePath,
    PurePosixPath,
    PureWindowsPath,
    WindowsPath,
)
from typing import Deque, List, TypeVar
from uuid import UUID

from apischema import deserializer, schema, serializer, type_name
from apischema.conversions import Conversion, as_str

T = TypeVar("T")

# =================== bytes =====================

deserializer(Conversion(b64decode, source=str, target=bytes))


@serializer
def to_base64(b: bytes) -> str:
    return b64encode(b).decode()


type_name(graphql="Bytes")(bytes)
schema(encoding="base64")(bytes)

# ================ collections ==================

deserializer(Conversion(deque, source=List[T], target=Deque[T]))
serializer(Conversion(list, source=Deque[T], target=List[T]))
if sys.version_info < (3, 7):