Upgrading marshmallow 3 to 4
Breaking3.26.2 → 4.3.1Breaking29Security1Platform1New5Changed7Fixed17Other1
SecurityMedium · CVSS 5.3Fixes CVE-2025-68480 by merging error store messages without rebuilding collections.GHSA-428g-f7cq-pgp5 · CVE-2025-68480 ↗
BreakingUpgrade verdict
29 breaking changes and a platform requirement change and a security fix.
In plain terms
- Code that relied on UUID fields being a kind of String field will no longer match.
- Date parsing now uses Python's built-in parsers, which also accepts time offsets and bare YYYY-MM-DD datetimes, but removes the old helper functions.
- These date formatting helpers are gone from the utils module.
- The RFC 2822 date parsing and formatting helpers are gone from the utils module.
Must act Top 5 of 31 that need code or config changes
Python requirement changed in 4.1.0: >=3.9 → >=3.10.
Drops packaging.
Installs 0 packages in all (was 1).
What you must change
Breaking29
marshmallow.fields.UUID no longer subclasses marshmallow.fields.String.
marshmallow.fields.UUIDmarshmallow.fields.StringUses stdlib fromisoformat for date, time and datetime deserialization, changing accepted formats and removing utils helpers.
datetime.date.fromisoformatdatetime.time.fromisoformatdatetime.datetime.fromisoformatfrom_iso_datefrom_iso_timefrom_iso_datetimemarshmallow.utilsRemoves isoformat, to_iso_time and to_iso_datetime from marshmallow.utils.
isoformatto_iso_timeto_iso_datetimemarshmallow.utilsRemoves from_rfc and rfcformat from marshmallow.utils.
from_rfcrfcformatmarshmallow.utilsRemoves is_keyed_tuple from marshmallow.utils.
is_keyed_tuplemarshmallow.utilsRemoves get_fixed_timezone from marshmallow.utils.
get_fixed_timezonemarshmallow.utilsmarshmallow.fields.Boolean no longer serializes non-boolean values.
marshmallow.fields.BooleanRenames the pass_many parameter to pass_collection in pre/post processing methods.
pass_manypass_collectionTimeDelta no longer truncates float values when deserializing, preserving microseconds.
marshmallow.fields.TimeDeltaRemoves the serialization_type parameter from TimeDelta.
marshmallow.fields.TimeDeltaserialization_typeRemoves implicit field creation via the fields or additional Meta options with undeclared fields.
fieldsadditionalRemoves the ordered class Meta option; field order is preserved by default.
orderedSchema.dict_classOrderedDictRemoves the marshmallow.base module.
marshmallow.baseRemoves the ordered class Meta option, deprecated in 3.26.0.
orderedmarshmallow.Schema.MetaCustom validators must raise ValidationError for invalid values; returning False is no longer supported.
marshmallow.exceptions.ValidationErrormarshmallow.fields.DictRemoves the __version__, __parsed_version__, and __version_info__ attributes.
__version____parsed_version____version_info__Removes the default and missing parameters, replaced by dump_default and load_default.
defaultmissingdump_defaultload_defaultRemoves passing field metadata via keyword arguments; use the metadata argument instead.
metadataRemoves marshmallow.utils.pprint; use pprint.pprint instead.
marshmallow.utils.pprintpprint.pprintRemoves passing "self" to fields.Nested; use a callable instead.
fields.NestedRemoves Field.fail, replaced by Field.make_error in 3.0.0.
Field.failField.make_errorRemoves the json_module class Meta option; use render_module instead.
json_modulerender_module@validates accepts multiple field names, and decorated methods now receive data_key as a keyword argument.
validatesRenames the schema parameter to parent in Field._bind_to_schema.
marshmallow.fields.Field._bind_to_schemaRemoves Schema's context attribute; use contextvars.ContextVar or the experimental Context manager instead.
Schemacontextcontextvars.ContextVarContextArguments to decorators are now keyword-only.
decoratorsRenames the json_data parameter of Schema.loads to s and makes it positional-only.
marshmallow.Schema.loadsjson_datasmarshmallow.fields.Number is no longer usable as a field in a schema.
marshmallow.fields.Numbermarshmallow.fields.Integermarshmallow.fields.Floatmarshmallow.fields.Decimalmarshmallow.fields.Mapping is no longer usable as a field in a schema.
marshmallow.fields.MappingSecurity1
Fixes CVE-2025-68480 by merging error store messages without rebuilding collections.
Medium · CVSS 5.3Platform1
Drops support for Python 3.9.
API changes
Generated by DepDelta from published Python source. Not mixed with prose notes.
- removed:
export pprint
- removed:
class FieldABC(ABC) - removed:
class SchemaABC(ABC) - removed:
method FieldABC.deserialize(value) - removed:
method FieldABC.serialize(attr, obj, accessor=…) - removed:
method SchemaABC.dump(obj, *, many: bool | None=…) - removed:
method SchemaABC.dumps(obj, *, many: bool | None=…) - removed:
method SchemaABC.load(data, *, many: bool | None=…, partial=…, unknown=…) - removed:
method SchemaABC.loads(json_data, *, many: bool | None=…, partial=…, unknown=…, **kwargs)
- added:
variable EXCLUDE: typing.Final - added:
variable INCLUDE: typing.Final - added:
variable RAISE: typing.Final - added:
variable missing: typing.Final
- changed:
function post_dump(fn: Callable[..., Any] | None=…, pass_many: bool=…, pass_original: bool=…) -> Callable[..., Any] → function post_dump(fn: typing.Callable[..., typing.Any] | None=…, *, pass_collection: bool=…, pass_original: bool=…) -> typing.Callable[..., typing.Any] - changed:
function post_load(fn: Callable[..., Any] | None=…, pass_many: bool=…, pass_original: bool=…) -> Callable[..., Any] → function post_load(fn: typing.Callable[..., typing.Any] | None=…, *, pass_collection: bool=…, pass_original: bool=…) -> typing.Callable[..., typing.Any] - changed:
function pre_dump(fn: Callable[..., Any] | None=…, pass_many: bool=…) -> Callable[..., Any] → function pre_dump(fn: typing.Callable[..., typing.Any] | None=…, *, pass_collection: bool=…) -> typing.Callable[..., typing.Any] - changed:
function pre_load(fn: Callable[..., Any] | None=…, pass_many: bool=…) -> Callable[..., Any] → function pre_load(fn: typing.Callable[..., typing.Any] | None=…, *, pass_collection: bool=…) -> typing.Callable[..., typing.Any] - changed:
function set_hook(fn: Callable[..., Any] | None, tag: str, many: bool=…, **kwargs: Any) -> Callable[..., Any] → function set_hook(fn: typing.Callable[..., typing.Any] | None, tag: str, *, many: bool=…, **kwargs: typing.Any) -> typing.Callable[..., typing.Any] - changed:
function validates(field_name: str) -> Callable[..., Any] → function validates(*field_names: str) -> typing.Callable[..., typing.Any] - changed:
function validates_schema(fn: Callable[..., Any] | None=…, pass_many: bool=…, pass_original: bool=…, skip_on_field_errors: bool=…) -> Callable[..., Any] → function validates_schema(fn: typing.Callable[..., typing.Any] | None=…, *, pass_collection: bool=…, pass_original: bool=…, skip_on_field_errors: bool=…) -> typing.Callable[..., typing.Any]
- removed:
class FieldInstanceResolutionError(MarshmallowError, TypeError) - changed:
constructor ValidationError(message: str | list | dict, field_name: str=…, data: typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] | None=…, valid_data: list[dict[str, typing.Any]] | dict[str, typing.Any] | None=…, **kwargs) → constructor ValidationError(message: str | list | dict, field_name: str=…, data: typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]] | None=…, valid_data: list[typing.Any] | dict[str, typing.Any] | None=…, **kwargs)
- added:
class Context(contextlib.AbstractContextManager, typing.Generic[_ContextT]) - added:
constructor Context(context: _ContextT) -> None - added:
static method Context.get(default: _DefaultT | EllipsisType=…) -> _ContextT | _DefaultT - added:
variable EllipsisType
- removed:
constructor DateTime(format: str | None=…, **kwargs) -> None - removed:
field DateTime.default_error_messages - removed:
field Mapping.mapping_type - removed:
field Number.num_type: type - removed:
method Field.fail(key: str, **kwargs) - removed:
property Field.context -> dict | None - removed:
property Field.default - removed:
property Field.missing - changed:
class Boolean(Field) → class Boolean(Field[bool]) - changed:
class Constant(Field) → class Constant(Field[_ContantT]) - changed:
class Date(DateTime) → class Date(_TemporalField[dt.date]) - changed:
class DateTime(Field) → class DateTime(_TemporalField[dt.datetime]) - changed:
class Dict(Mapping) → class Dict(Mapping[dict]) - changed:
class Enum(Field) → class Enum(Field[_EnumT]) - changed:
class Field(FieldABC) → class Field(typing.Generic[_InternalT]) - changed:
class IP(Field) → class IP(Field[ipaddress.IPv4Address | ipaddress.IPv6Address]) - changed:
class IPInterface(Field) → class IPInterface(Field[ipaddress.IPv4Interface | ipaddress.IPv6Interface]) - changed:
class List(Field) → class List(Field[list[_InternalT | None]]) - changed:
class Mapping(Field) → class Mapping(Field[_MappingT]) - changed:
class Number(Field, typing.Generic[_NumType]) → class Number(Field[_NumT]) - changed:
class Raw(Field) → class Raw(Field[typing.Any]) - changed:
class String(Field) → class String(Field[str]) - changed:
class Time(DateTime) → class Time(_TemporalField[dt.time]) - changed:
class TimeDelta(Field) → class TimeDelta(Field[dt.timedelta]) - changed:
class Tuple(Field) → class Tuple(Field[tuple]) - changed:
class UUID(String) → class UUID(Field[uuid.UUID]) - changed:
constructor AwareDateTime(format: str | None=…, *, default_timezone: dt.tzinfo | None=…, **kwargs) -> None → constructor AwareDateTime(format: str | None=…, *, default_timezone: dt.tzinfo | None=…, **kwargs: Unpack[_BaseFieldKwargs]) -> None - changed:
constructor Boolean(*, truthy: typing.Iterable | None=…, falsy: typing.Iterable | None=…, **kwargs) → constructor Boolean(*, truthy: typing.Iterable | None=…, falsy: typing.Iterable | None=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Constant(constant: typing.Any, **kwargs) → constructor Constant(constant: _ContantT, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Decimal(places: int | None=…, rounding: str | None=…, *, allow_nan: bool=…, as_string: bool=…, **kwargs) → constructor Decimal(places: int | None=…, rounding: str | None=…, *, allow_nan: bool=…, as_string: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Email(*args, **kwargs) -> None → constructor Email(**kwargs: Unpack[_BaseFieldKwargs]) -> None - changed:
constructor Enum(enum: type[EnumType], *, by_value: bool | Field | type[Field]=…, **kwargs) → constructor Enum(enum: type[_EnumT], *, by_value: bool | Field | type[Field]=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Field(*, load_default: typing.Any=…, missing: typing.Any=…, dump_default: typing.Any=…, default: typing.Any=…, data_key: str | None=…, attribute: str | None=…, validate: types.Validator | typing.Iterable[types.Validator] | None=…, required: bool=…, allow_none: bool | None=…, load_only: bool=…, dump_only: bool=…, error_messages: dict[str, str] | None=…, metadata: typing.Mapping[str, ty… → constructor Field(*, load_default: typing.Any=…, dump_default: typing.Any=…, data_key: str | None=…, attribute: str | None=…, validate: types.Validator | typing.Iterable[types.Validator] | None=…, pre_load: types.PreLoadCallable | typing.Iterable[types.PreLoadCallable] | None=…, post_load: types.PostLoadCallable | typing.Iterable[types.PostLoadCallable] | None=…, required: bool=…, allow_none: boo… - changed:
constructor Float(*, allow_nan: bool=…, as_string: bool=…, **kwargs) → constructor Float(*, allow_nan: bool=…, as_string: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Function(serialize: typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] | None=…, deserialize: typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] | None=…, **kwargs) → constructor Function(serialize: typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] | None=…, deserialize: typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] | None=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor IP(*args, exploded=…, **kwargs) → constructor IP(*, exploded: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor IPInterface(*args, exploded: bool=…, **kwargs) → constructor IPInterface(*, exploded: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Integer(*, strict: bool=…, **kwargs) → constructor Integer(*, strict: bool=…, as_string: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor List(cls_or_instance: Field | type[Field], **kwargs) → constructor List(cls_or_instance: Field[_InternalT] | type[Field[_InternalT]], **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Mapping(keys: Field | type[Field] | None=…, values: Field | type[Field] | None=…, **kwargs) → constructor Mapping(keys: Field | type[Field] | None=…, values: Field | type[Field] | None=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Method(serialize: str | None=…, deserialize: str | None=…, **kwargs) → constructor Method(serialize: str | None=…, deserialize: str | None=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor NaiveDateTime(format: str | None=…, *, timezone: dt.timezone | None=…, **kwargs) -> None → constructor NaiveDateTime(format: str | None=…, *, timezone: dt.timezone | None=…, **kwargs: Unpack[_BaseFieldKwargs]) -> None - changed:
constructor Nested(nested: Schema | SchemaMeta | str | dict[str, Field] | typing.Callable[[], Schema | SchemaMeta | dict[str, Field]], *, dump_default: typing.Any=…, default: typing.Any=…, only: types.StrSequenceOrSet | None=…, exclude: types.StrSequenceOrSet=…, many: bool=…, unknown: str | None=…, **kwargs) → constructor Nested(nested: Schema | SchemaMeta | str | dict[str, Field] | typing.Callable[[], Schema | SchemaMeta | str | dict[str, Field]], *, only: types.StrSequenceOrSet | None=…, exclude: types.StrSequenceOrSet=…, many: bool | None=…, unknown: types.UnknownOption | None=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Number(*, as_string: bool=…, **kwargs) → constructor Number(*, as_string: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Pluck(nested: Schema | SchemaMeta | str | typing.Callable[[], Schema], field_name: str, *, many: bool=…, unknown: str | None=…, **kwargs) → constructor Pluck(nested: Schema | SchemaMeta | str | typing.Callable[[], Schema], field_name: str, *, many: bool=…, unknown: types.UnknownOption | None=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor TimeDelta(precision: str=…, serialization_type: type[int | float]=…, **kwargs) → constructor TimeDelta(precision: str=…, **kwargs: Unpack[_BaseFieldKwargs]) -> None - changed:
constructor Tuple(tuple_fields: typing.Iterable[Field] | typing.Iterable[type[Field]], **kwargs) → constructor Tuple(tuple_fields: typing.Iterable[Field | type[Field]], **kwargs: Unpack[_BaseFieldKwargs]) - changed:
constructor Url(*, relative: bool=…, absolute: bool=…, schemes: types.StrSequenceOrSet | None=…, require_tld: bool=…, **kwargs) → constructor Url(*, relative: bool=…, absolute: bool=…, schemes: types.StrSequenceOrSet | None=…, require_tld: bool=…, **kwargs: Unpack[_BaseFieldKwargs]) - changed:
field DateTime.DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], typing.Any]] → field DateTime.DESERIALIZATION_FUNCS: dict[str, typing.Callable[[str], dt.datetime]] - changed:
field DateTime.SERIALIZATION_FUNCS: dict[str, typing.Callable[[typing.Any], str | float]] → field DateTime.SERIALIZATION_FUNCS: dict[str, typing.Callable[[dt.datetime], str | float]] - changed:
field Field.default_error_messages: dict[str, str] → field Field.default_error_messages: types.ErrorMessages - changed:
method Field.deserialize(value: typing.Any, attr: str | None=…, data: typing.Mapping[str, typing.Any] | None=…, **kwargs) → method Field.deserialize(value: None, attr: str | None=…, data: typing.Mapping[str, typing.Any] | None=…, **kwargs) -> _InternalT | None | (value: typing.Any, attr: str | None=…, data: typing.Mapping[str, typing.Any] | None=…, **kwargs) -> _InternalT - changed:
method Field.get_value(obj: typing.Any, attr: str, accessor: typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None=…, default: typing.Any=…) → method Field.get_value(obj: typing.Any, attr: str, accessor: typing.Callable[[typing.Any, str, typing.Any], typing.Any] | None=…, default: typing.Any=…) -> _InternalT - added:
property Mapping.mapping_type -> type[_MappingT] - added:
property Number.num_type -> type[_NumT]
- removed:
field Schema.Meta.ordered: typing.ClassVar[bool] - removed:
property Schema.dict_class -> type[dict] - changed:
class Schema(base.SchemaABC) → class Schema - changed:
constructor Schema(*, only: types.StrSequenceOrSet | None=…, exclude: types.StrSequenceOrSet=…, many: bool | None=…, context: dict | None=…, load_only: types.StrSequenceOrSet=…, dump_only: types.StrSequenceOrSet=…, partial: bool | types.StrSequenceOrSet | None=…, unknown: str | None=…) → constructor Schema(*, only: types.StrSequenceOrSet | None=…, exclude: types.StrSequenceOrSet=…, many: bool | None=…, load_only: types.StrSequenceOrSet=…, dump_only: types.StrSequenceOrSet=…, partial: bool | types.StrSequenceOrSet | None=…, unknown: types.UnknownOption | None=…) - changed:
constructor SchemaOpts(meta: type, ordered: bool=…) → constructor SchemaOpts(meta: type) - changed:
field Schema.Meta.unknown: typing.ClassVar[str] → field Schema.Meta.unknown: typing.ClassVar[types.UnknownOption] - changed:
method Schema.load(data: typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]], *, many: bool | None=…, partial: bool | types.StrSequenceOrSet | None=…, unknown: str | None=…) → method Schema.load(data: Mapping[str, typing.Any] | Sequence[Mapping[str, typing.Any]], *, many: bool | None=…, partial: bool | types.StrSequenceOrSet | None=…, unknown: types.UnknownOption | None=…) - changed:
method Schema.loads(json_data: str | bytes | bytearray, *, many: bool | None=…, partial: bool | types.StrSequenceOrSet | None=…, unknown: str | None=…, **kwargs) → method Schema.loads(s: str | bytes | bytearray, /, *, many: bool | None=…, partial: bool | types.StrSequenceOrSet | None=…, unknown: types.UnknownOption | None=…, **kwargs) - changed:
method Schema.validate(data: typing.Mapping[str, typing.Any] | typing.Iterable[typing.Mapping[str, typing.Any]], *, many: bool | None=…, partial: bool | types.StrSequenceOrSet | None=…) -> dict[str, list[str]] → method Schema.validate(data: Mapping[str, typing.Any] | Sequence[Mapping[str, typing.Any]], *, many: bool | None=…, partial: bool | types.StrSequenceOrSet | None=…) -> dict[str, list[str]] - added:
field Schema.dict_class: type[dict]
- changed:
method RenderModule.loads(json_data: str | bytes | bytearray, *args: typing.Any, **kwargs: typing.Any) -> typing.Any → method RenderModule.loads(s: str | bytes | bytearray, *args: typing.Any, **kwargs: typing.Any) -> typing.Any - changed:
variable StrSequenceOrSet → variable StrSequenceOrSet: typing.TypeAlias - changed:
variable Validator → variable Validator: typing.TypeAlias - added:
variable ErrorMessageValue: typing.TypeAlias - added:
variable ErrorMessages: typing.TypeAlias - added:
variable PostLoadCallable - added:
variable PreLoadCallable - added:
variable T - added:
variable UnknownOption: typing.TypeAlias
- removed:
function from_iso_date(value) - removed:
function from_iso_datetime(value) - removed:
function from_iso_time(value) - removed:
function from_rfc(datestring: str) -> dt.datetime - removed:
function get_fixed_timezone(offset: float | dt.timedelta) -> dt.timezone - removed:
function get_func_args(func: typing.Callable) -> list[str] - removed:
function is_instance_or_subclass(val, class_) -> bool - removed:
function is_keyed_tuple(obj) -> bool - removed:
function isoformat(datetime: dt.datetime) -> str - removed:
function pprint(obj, *args, **kwargs) -> None - removed:
function resolve_field_instance(cls_or_instance: type[Field] | Field) -> Field - removed:
function rfcformat(datetime: dt.datetime) -> str - removed:
function to_iso_date(date: dt.date) -> str - removed:
function to_iso_time(time: dt.time) -> str - removed:
function validate_unknown_parameter_value(obj: typing.Any) -> str - removed:
variable EXCLUDE - removed:
variable INCLUDE - removed:
variable RAISE - removed:
variable missing - changed:
function is_collection(obj) -> bool → function is_collection(obj) -> typing.TypeGuard[typing.Iterable] - changed:
function is_generator(obj) -> bool → function is_generator(obj) -> typing.TypeGuard[typing.Generator] - changed:
function is_iterable_but_not_string(obj) -> bool → function is_iterable_but_not_string(obj) -> typing.TypeGuard[typing.Iterable] - added:
function is_sequence_but_not_string(obj) -> typing.TypeGuard[Sequence]
- removed:
field And.default_error_message - changed:
constructor And(*validators: types.Validator, error: str | None=…) → constructor And(*validators: types.Validator) - changed:
constructor Range(min=…, max=…, *, min_inclusive: bool=…, max_inclusive: bool=…, error: str | None=…) → constructor Range(min: typing.Any=…, max: typing.Any=…, *, min_inclusive: bool=…, max_inclusive: bool=…, error: str | None=…) - changed:
constructor Regexp(regex: str | bytes | typing.Pattern, flags: int=…, *, error: str | None=…) → constructor Regexp(regex: str | bytes | re.Pattern[str] | re.Pattern[bytes], flags: int=…, *, error: str | None=…)
- removed:
class ChangedInMarshmallow4Warning(Marshmallow4Warning) - removed:
class Marshmallow4Warning(DeprecationWarning) - removed:
class RemovedInMarshmallow4Warning(Marshmallow4Warning)