class ListUnionSchema(marshmallow.Schema):
    """Schema with a list of unions."""

    l = marshmallow.fields.List(
        marshmallow_union.Union(
            [marshmallow.fields.Int(),
             marshmallow.fields.String()]))
Пример #2
0
    def test_union(self):
        import marshmallow_union

        self.assertFieldsEqual(
            field_for_schema(Union[int, str]),
            marshmallow_union.Union(fields=[fields.Integer(),
                                            fields.String()],
                                    required=True),
        )
class MappingSchema(marshmallow.Schema):
    """Schema with union inside mapping."""

    items = marshmallow.fields.Dict(
        marshmallow.fields.String(),
        marshmallow_union.Union([
            marshmallow.fields.Integer(),
            marshmallow.fields.List(marshmallow.fields.Integer()),
        ]),
    )
class PersonSchema(marshmallow.Schema):
    """Schema with reverse candidates."""

    name = marshmallow.fields.String()
    number_or_numbers = marshmallow_union.Union(
        [
            marshmallow.fields.List(marshmallow.fields.Integer()),
            marshmallow.fields.Integer(),
        ],
        reverse_serialize_candidates=True,
    )
class OtherSchema(marshmallow.Schema):
    """Schema with forward candidates."""

    name = marshmallow.fields.String()
    number_or_numbers = marshmallow_union.Union(
        [
            marshmallow.fields.List(marshmallow.fields.Integer()),
            marshmallow.fields.Integer(),
        ],
        reverse_serialize_candidates=False,
    )
Пример #6
0
    def _get_schema_for_field(self, obj, field):
        """Patch marshmallow_jsonschema.base.JSONSchema to support marshmallow-dataclass[union]."""
        if isinstance(field, fields.Raw) and field.allow_none and not field.validate:
            # raw fields shouldn't be type string but type any. bug in marshmallow_dataclass:__init__.py:
            #  if typ is Any:
            #      metadata.setdefault("allow_none", True)
            #      return marshmallow.fields.Raw(**metadata)
            return {"type": ["string", "number", "object", "array", "boolean", "null"]}

        if isinstance(field, marshmallow_dataclass.union_field.Union):
            # convert to marshmallow_union.Union
            field = marshmallow_union.Union([subfield for _, subfield in field.union_fields],
                                            metadata=field.metadata,
                                            required=field.required, name=field.name,
                                            parent=field.parent, root=field.root, error_messages=field.error_messages,
                                            default_error_messages=field.default_error_messages, default=field.default,
                                            allow_none=field.allow_none)

        return super()._get_schema_for_field(obj, field)
Пример #7
0
def field_for_schema(
    typ: type,
    default=marshmallow.missing,
    metadata: Mapping[str, Any] = None,
    base_schema: Optional[Type[marshmallow.Schema]] = None,
) -> marshmallow.fields.Field:
    """
    Get a marshmallow Field corresponding to the given python type.
    The metadata of the dataclass field is used as arguments to the marshmallow Field.

    :param typ: The type for which a field should be generated
    :param default: value to use for (de)serialization when the field is missing
    :param metadata: Additional parameters to pass to the marshmallow field constructor
    :param base_schema: marshmallow schema used as a base class when deriving dataclass schema

    >>> int_field = field_for_schema(int, default=9, metadata=dict(required=True))
    >>> int_field.__class__
    <class 'marshmallow.fields.Integer'>

    >>> int_field.default
    9

    >>> field_for_schema(str, metadata={"marshmallow_field": marshmallow.fields.Url()}).__class__
    <class 'marshmallow.fields.Url'>
    """

    metadata = {} if metadata is None else dict(metadata)
    if default is not marshmallow.missing:
        metadata.setdefault("default", default)
        # 'missing' must not be set for required fields.
        if not metadata.get("required"):
            metadata.setdefault("missing", default)
    else:
        metadata.setdefault("required", True)

    # If the field was already defined by the user
    predefined_field = metadata.get("marshmallow_field")
    if predefined_field:
        return predefined_field

    # Generic types specified without type arguments
    if typ is list:
        typ = List[Any]
    elif typ is dict:
        typ = Dict[Any, Any]

    # Base types
    field = _field_by_type(typ, base_schema)
    if field:
        return field(**metadata)

    # Generic types
    origin = typing_inspect.get_origin(typ)
    if origin:
        arguments = typing_inspect.get_args(typ, True)
        if origin in (list, List):
            child_type = field_for_schema(arguments[0],
                                          base_schema=base_schema)
            return marshmallow.fields.List(child_type, **metadata)
        if origin in (tuple, Tuple):
            children = tuple(
                field_for_schema(arg, base_schema=base_schema)
                for arg in arguments)
            return marshmallow.fields.Tuple(children, **metadata)
        elif origin in (dict, Dict):
            return marshmallow.fields.Dict(
                keys=field_for_schema(arguments[0], base_schema=base_schema),
                values=field_for_schema(arguments[1], base_schema=base_schema),
                **metadata,
            )
        elif typing_inspect.is_optional_type(typ):
            subtyp = next(t for t in arguments
                          if t is not NoneType)  # type: ignore
            # Treat optional types as types with a None default
            metadata["default"] = metadata.get("default", None)
            metadata["missing"] = metadata.get("missing", None)
            metadata["required"] = False
            return field_for_schema(subtyp,
                                    metadata=metadata,
                                    base_schema=base_schema)
        elif typing_inspect.is_union_type(typ):
            subfields = [
                field_for_schema(subtyp,
                                 metadata=metadata,
                                 base_schema=base_schema)
                for subtyp in arguments
            ]
            import marshmallow_union

            return marshmallow_union.Union(subfields, **metadata)

    # typing.NewType returns a function with a __supertype__ attribute
    newtype_supertype = getattr(typ, "__supertype__", None)
    if newtype_supertype and inspect.isfunction(typ):
        # Add the information coming our custom NewType implementation
        metadata = {
            "description": typ.__name__,
            **getattr(typ, "_marshmallow_args", {}),
            **metadata,
        }
        field = getattr(typ, "_marshmallow_field", None)
        if field:
            return field(**metadata)
        else:
            return field_for_schema(
                newtype_supertype,
                metadata=metadata,
                default=default,
                base_schema=base_schema,
            )

    # enumerations
    if isinstance(typ, EnumMeta):
        import marshmallow_enum

        return marshmallow_enum.EnumField(typ, **metadata)

    # Nested marshmallow dataclass
    nested_schema = getattr(typ, "Schema", None)

    # Nested dataclasses
    forward_reference = getattr(typ, "__forward_arg__", None)
    nested = (nested_schema or forward_reference
              or class_schema(typ, base_schema=base_schema))

    return marshmallow.fields.Nested(nested, **metadata)
class IntStrSchema(marshmallow.Schema):
    """Schema with int and str candidates."""

    x = marshmallow_union.Union(
        [marshmallow.fields.Int(),
         marshmallow.fields.String()])
Пример #9
0
def field_for_schema(
        typ: type,
        default=marshmallow.missing,
        metadata: t.Mapping[str, t.Any] = None) -> marshmallow.fields.Field:
    """
    Get a marshmallow Field corresponding to the given python type.
    The metadata of the dataclass field is used as arguments to the marshmallow Field.
    >>> int_field = field_for_schema(int, default=9, metadata=dict(required=True))
    >>> int_field.__class__
    <class 'marshmallow.fields.Integer'>

    >>> int_field.default
    9
    >>> field_for_schema(t.Dict[str,str]).__class__
    <class 'marshmallow.fields.Dict'>
    >>> field_for_schema(t.Optional[str]).__class__
    <class 'marshmallow.fields.String'>
    >>> import marshmallow_enum
    >>> field_for_schema(enum.Enum("X", "a b c")).__class__
    <class 'marshmallow_enum.EnumField'>
    >>> import typing
    >>> field_for_schema(t.Union[int,str]).__class__
    <class 'marshmallow_union.Union'>
    >>> field_for_schema(t.NewType('UserId', int)).__class__
    <class 'marshmallow.fields.Integer'>
    >>> field_for_schema(t.NewType('UserId', int), default=0).default
    0
    >>> class Color(enum.Enum):
    ...   red = 1
    >>> field_for_schema(Color).__class__
    <class 'marshmallow_enum.EnumField'>
    >>> field_for_schema(t.Any).__class__
    <class 'marshmallow.fields.Raw'>
    """

    if metadata is None:
        metadata = {}
    else:
        metadata = dict(metadata)

    desert_metadata = dict(metadata).get(_DESERT_SENTINEL, {})
    metadata[_DESERT_SENTINEL] = desert_metadata

    if default is not marshmallow.missing:
        desert_metadata.setdefault("default", default)
        desert_metadata.setdefault("allow_none", True)
        desert_metadata.setdefault("missing", default)

    field = None

    # If the field was already defined by the user
    predefined_field = desert_metadata.get("marshmallow_field")

    if predefined_field:
        field = predefined_field
        field.metadata.update(metadata)
        return field

    # Base types
    if not field and typ in _native_to_marshmallow:
        field = _native_to_marshmallow[typ](default=default)

    # Generic types
    origin = typing_inspect.get_origin(typ)

    if origin:
        arguments = typing_inspect.get_args(typ, True)

        if origin in (list, t.List):
            field = marshmallow.fields.List(field_for_schema(arguments[0]))

        if origin in (tuple, t.Tuple) and Ellipsis not in arguments:
            field = marshmallow.fields.Tuple(
                tuple(field_for_schema(arg) for arg in arguments))
        elif origin in (tuple, t.Tuple) and Ellipsis in arguments:

            field = VariadicTuple(
                field_for_schema(
                    only(arg for arg in arguments if arg != Ellipsis)))
        elif origin in (dict, t.Dict):
            field = marshmallow.fields.Dict(
                keys=field_for_schema(arguments[0]),
                values=field_for_schema(arguments[1]),
            )
        elif typing_inspect.is_optional_type(typ):
            [subtyp] = (t for t in arguments if t is not NoneType)
            # Treat optional types as types with a None default
            metadata[_DESERT_SENTINEL]["default"] = metadata.get(
                "default", None)
            metadata[_DESERT_SENTINEL]["missing"] = metadata.get(
                "missing", None)
            metadata[_DESERT_SENTINEL]["required"] = False

            field = field_for_schema(subtyp, metadata=metadata, default=None)
            field.default = None
            field.missing = None
            field.allow_none = True

        elif typing_inspect.is_union_type(typ):
            subfields = [field_for_schema(subtyp) for subtyp in arguments]
            import marshmallow_union

            field = marshmallow_union.Union(subfields)

    # t.NewType returns a function with a __supertype__ attribute
    newtype_supertype = getattr(typ, "__supertype__", None)
    if newtype_supertype and inspect.isfunction(typ):
        metadata.setdefault("description", typ.__name__)
        field = field_for_schema(newtype_supertype, default=default)

    # enumerations
    if type(typ) is enum.EnumMeta:
        import marshmallow_enum

        field = marshmallow_enum.EnumField(typ, metadata=metadata)

    # Nested dataclasses
    forward_reference = getattr(typ, "__forward_arg__", None)

    if field is None:
        nested = forward_reference or class_schema(typ)
        field = marshmallow.fields.Nested(nested)

    field.metadata.update(metadata)

    for key in ["default", "missing", "required", "marshmallow_field"]:
        if key in metadata.keys():
            metadata[_DESERT_SENTINEL][key] = metadata.pop(key)

    if field.default == field.missing == default == marshmallow.missing:
        field.required = True

    return field
Пример #10
0
def field_for_schema(
    typ: type,
    default=marshmallow.missing,
    metadata: Mapping[str, Any] = None,
    base_schema: Optional[Type[marshmallow.Schema]] = None,
) -> marshmallow.fields.Field:
    """
    Get a marshmallow Field corresponding to the given python type.
    The metadata of the dataclass field is used as arguments to the marshmallow Field.

    :param base_schema: marshmallow schema used as a base class when deriving dataclass schema

    >>> int_field = field_for_schema(int, default=9, metadata=dict(required=True))
    >>> int_field.__class__
    <class 'marshmallow.fields.Integer'>

    >>> int_field.default
    9

    >>> int_field.required
    True

    >>> field_for_schema(Dict[str,str]).__class__
    <class 'marshmallow.fields.Dict'>

    >>> field_for_schema(str, metadata={"marshmallow_field": marshmallow.fields.Url()}).__class__
    <class 'marshmallow.fields.Url'>

    >>> field_for_schema(Optional[str]).__class__
    <class 'marshmallow.fields.String'>

    >>> from enum import Enum
    >>> import marshmallow_enum
    >>> field_for_schema(Enum("X", "a b c")).__class__
    <class 'marshmallow_enum.EnumField'>

    >>> import typing
    >>> field_for_schema(typing.Union[int,str]).__class__
    <class 'marshmallow_union.Union'>

    >>> field_for_schema(typing.NewType('UserId', int)).__class__
    <class 'marshmallow.fields.Integer'>

    >>> field_for_schema(typing.NewType('UserId', int), default=0).default
    0

    >>> class Color(Enum):
    ...   red = 1
    >>> field_for_schema(Color).__class__
    <class 'marshmallow_enum.EnumField'>

    >>> field_for_schema(Any).__class__
    <class 'marshmallow.fields.Raw'>
    """

    metadata = {} if metadata is None else dict(metadata)
    if default is not marshmallow.missing:
        metadata.setdefault("default", default)
        if not metadata.get(
                "required"):  # 'missing' must not be set for required fields.
            metadata.setdefault("missing", default)
    else:
        metadata.setdefault("required", True)

    # If the field was already defined by the user
    predefined_field = metadata.get("marshmallow_field")
    if predefined_field:
        return predefined_field

    # Base types
    if typ in _native_to_marshmallow:
        return _native_to_marshmallow[typ](**metadata)

    # Generic types
    origin = typing_inspect.get_origin(typ)
    if origin:
        arguments = typing_inspect.get_args(typ, True)
        if origin in (list, List):
            return marshmallow.fields.List(
                field_for_schema(arguments[0], base_schema=base_schema),
                **metadata)
        if origin in (tuple, Tuple):
            return marshmallow.fields.Tuple(
                tuple(
                    field_for_schema(arg, base_schema=base_schema)
                    for arg in arguments),
                **metadata,
            )
        elif origin in (dict, Dict):
            return marshmallow.fields.Dict(
                keys=field_for_schema(arguments[0], base_schema=base_schema),
                values=field_for_schema(arguments[1], base_schema=base_schema),
                **metadata,
            )
        elif typing_inspect.is_optional_type(typ):
            subtyp = next(t for t in arguments
                          if t is not NoneType)  # type: ignore
            # Treat optional types as types with a None default
            metadata["default"] = metadata.get("default", None)
            metadata["missing"] = metadata.get("missing", None)
            metadata["required"] = False
            return field_for_schema(subtyp,
                                    metadata=metadata,
                                    base_schema=base_schema)
        elif typing_inspect.is_union_type(typ):
            subfields = [
                field_for_schema(subtyp,
                                 metadata=metadata,
                                 base_schema=base_schema)
                for subtyp in arguments
            ]
            import marshmallow_union

            return marshmallow_union.Union(subfields, **metadata)

    # typing.NewType returns a function with a __supertype__ attribute
    newtype_supertype = getattr(typ, "__supertype__", None)
    if newtype_supertype and inspect.isfunction(typ):
        # Add the information coming our custom NewType implementation
        metadata = {
            "description": typ.__name__,
            **getattr(typ, "_marshmallow_args", {}),
            **metadata,
        }
        field = getattr(typ, "_marshmallow_field", None)
        if field:
            return field(**metadata)
        else:
            return field_for_schema(
                newtype_supertype,
                metadata=metadata,
                default=default,
                base_schema=base_schema,
            )

    # enumerations
    if type(typ) is EnumMeta:
        import marshmallow_enum

        return marshmallow_enum.EnumField(typ, **metadata)

    # Nested dataclasses
    forward_reference = getattr(typ, "__forward_arg__", None)
    nested = forward_reference or class_schema(typ, base_schema=base_schema)
    return marshmallow.fields.Nested(nested, **metadata)
Пример #11
0
def field_for_schema(
        typ: type,
        default=marshmallow.missing,
        metadata: Mapping[str, Any] = None) -> marshmallow.fields.Field:
    """
    Get a marshmallow Field corresponding to the given python type.
    The metadata of the dataclass field is used as arguments to the marshmallow Field.
    >>> int_field = field_for_schema(int, default=9, metadata=dict(required=True))
    >>> int_field.__class__
    <class 'marshmallow.fields.Integer'>

    >>> int_field.default
    9
    >>> field_for_schema(Dict[str,str]).__class__
    <class 'marshmallow.fields.Dict'>
    >>> field_for_schema(Optional[str]).__class__
    <class 'marshmallow.fields.String'>
    >>> import marshmallow_enum
    >>> field_for_schema(Enum("X", "a b c")).__class__
    <class 'marshmallow_enum.EnumField'>
    >>> import typing
    >>> field_for_schema(typing.Union[int,str]).__class__
    <class 'marshmallow_union.Union'>
    >>> field_for_schema(NewType('UserId', int)).__class__
    <class 'marshmallow.fields.Integer'>
    >>> field_for_schema(NewType('UserId', int), default=0).default
    0
    >>> class Color(Enum):
    ...   red = 1
    >>> field_for_schema(Color).__class__
    <class 'marshmallow_enum.EnumField'>
    >>> field_for_schema(Any).__class__
    <class 'marshmallow.fields.Raw'>
    """

    metadata = {} if metadata is None else dict(metadata)

    if default is not marshmallow.missing:
        metadata.setdefault("default", default)
        if not metadata.get(
                "required"):  # 'missing' must not be set for required fields.
            metadata.setdefault("missing", default)
    else:
        metadata.setdefault("required", True)

    # If the field was already defined by the user
    predefined_field = metadata.get("marshmallow_field")
    if predefined_field:
        return predefined_field

    # Base types
    if typ in _native_to_marshmallow:
        return _native_to_marshmallow[typ](**metadata)

    # Generic types
    origin = typing_inspect.get_origin(typ)
    if origin:
        arguments = typing_inspect.get_args(typ, True)
        if origin in (list, List):
            return marshmallow.fields.List(field_for_schema(arguments[0]),
                                           **metadata)
        if origin in (tuple, Tuple):
            return marshmallow.fields.Tuple(
                tuple(field_for_schema(arg) for arg in arguments), **metadata)
        elif origin in (dict, Dict):
            return marshmallow.fields.Dict(
                keys=field_for_schema(arguments[0]),
                values=field_for_schema(arguments[1]),
                **metadata,
            )
        elif typing_inspect.is_optional_type(typ):
            subtyp = next(t for t in arguments if t is not NoneType)
            # Treat optional types as types with a None default
            metadata["default"] = metadata.get("default", None)
            metadata["missing"] = metadata.get("missing", None)
            metadata["required"] = False
            return field_for_schema(subtyp, metadata=metadata)
        elif typing_inspect.is_union_type(typ):
            subfields = [
                field_for_schema(subtyp, metadata=metadata)
                for subtyp in arguments
            ]
            import marshmallow_union

            return marshmallow_union.Union(subfields, **metadata)

    # typing.NewType returns a function with a __supertype__ attribute
    newtype_supertype = getattr(typ, "__supertype__", None)
    if newtype_supertype and inspect.isfunction(typ):
        metadata.setdefault("description", typ.__name__)
        return field_for_schema(newtype_supertype,
                                metadata=metadata,
                                default=default)

    # enumerations
    if type(typ) is EnumMeta:
        import marshmallow_enum

        return marshmallow_enum.EnumField(typ, **metadata)

    # Nested dataclasses
    forward_reference = getattr(typ, "__forward_arg__", None)
    nested = forward_reference or class_schema(typ)
    try:
        nested.help = typ.__doc__
    except AttributeError:
        # TODO need to handle the case where nested is a string forward reference.
        pass
    return marshmallow.fields.Nested(nested, **metadata)