Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-mashumaro for
openSUSE:Factory checked in at 2025-10-07 18:28:18
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-mashumaro (Old)
and /work/SRC/openSUSE:Factory/.python-mashumaro.new.11973 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-mashumaro"
Tue Oct 7 18:28:18 2025 rev:5 rq:1309470 version:3.17
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-mashumaro/python-mashumaro.changes
2025-05-22 16:56:37.413288914 +0200
+++
/work/SRC/openSUSE:Factory/.python-mashumaro.new.11973/python-mashumaro.changes
2025-10-07 18:29:53.257309271 +0200
@@ -1,0 +2,16 @@
+Tue Oct 7 06:05:40 UTC 2025 - Johannes Kastl
<[email protected]>
+
+- update to 3.17:
+ * Added support for Python 3.14 (#285)
+ * Improved generating JSON Schema references and titles for
+ generic dataclasses (#291)
+ * Fixed JSON Schema for a generic dataclass with a field type T
+ (#290)
+ * Fixed ignoring NotRequired when using from __future__ import
+ annotations (#292)
+ * Added support for user extra args in field_options function
+ (#286)
+ * Improved packaging by switching from setup.py to pyproject.toml
+ (#284)
+
+-------------------------------------------------------------------
Old:
----
mashumaro-3.16.tar.gz
New:
----
mashumaro-3.17.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-mashumaro.spec ++++++
--- /var/tmp/diff_new_pack.NI39vq/_old 2025-10-07 18:29:53.757330419 +0200
+++ /var/tmp/diff_new_pack.NI39vq/_new 2025-10-07 18:29:53.761330589 +0200
@@ -1,7 +1,7 @@
#
# spec file for package python-mashumaro
#
-# Copyright (c) 2025 SUSE LLC
+# Copyright (c) 2025 SUSE LLC and contributors
#
# All modifications and additions to the file contributed by third parties
# remain the property of their copyright owners, unless otherwise agreed
@@ -18,14 +18,14 @@
%{?sle15_python_module_pythons}
Name: python-mashumaro
-Version: 3.16
+Version: 3.17
Release: 0
Summary: Fast and well tested serialization library
License: Apache-2.0
URL: https://github.com/Fatal1ty/mashumaro
Source:
https://github.com/Fatal1ty/mashumaro/archive/refs/tags/v%{version}.tar.gz#/mashumaro-%{version}.tar.gz
BuildRequires: %{python_module pip}
-BuildRequires: %{python_module setuptools}
+BuildRequires: %{python_module setuptools >= 77.0}
BuildRequires: %{python_module wheel}
BuildRequires: python-rpm-macros
# Add (optional) runtime dependencies as BuildRequires,
@@ -39,7 +39,7 @@
# SECTION test requirements
BuildRequires: %{python_module pytest >= 6.2.1}
BuildRequires: %{python_module ciso8601 >= 2.1.3}
-BuildRequires: %{python_module pendulum >= 2.1.2 if %python-base < 3.13}
+BuildRequires: %{python_module pendulum >= 2.1.2}
BuildRequires: %{python_module pytest-mock >= 3.5.1}
BuildRequires: %{python_module pytest-xdist >= 3.5.0}
# /SECTION
++++++ mashumaro-3.16.tar.gz -> mashumaro-3.17.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/.github/workflows/main.yml
new/mashumaro-3.17/.github/workflows/main.yml
--- old/mashumaro-3.16/.github/workflows/main.yml 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/.github/workflows/main.yml 2025-10-03
23:05:12.000000000 +0200
@@ -13,8 +13,9 @@
name: Code style tests
runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -52,8 +53,9 @@
- test-code-style
runs-on: ubuntu-latest
strategy:
+ fail-fast: false
matrix:
- python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -86,7 +88,9 @@
- test-code-style
runs-on: windows-latest
strategy:
+ fail-fast: false
matrix:
+ # TODO add 3.14 once msgpack support Python 3.14
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/MANIFEST.in
new/mashumaro-3.17/MANIFEST.in
--- old/mashumaro-3.16/MANIFEST.in 2025-05-20 20:36:49.000000000 +0200
+++ new/mashumaro-3.17/MANIFEST.in 2025-10-03 23:05:12.000000000 +0200
@@ -1 +1,2 @@
-recursive-include tests *
+graft tests
+recursive-exclude * *.py[co]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/core/const.py
new/mashumaro-3.17/mashumaro/core/const.py
--- old/mashumaro-3.16/mashumaro/core/const.py 2025-05-20 20:36:49.000000000
+0200
+++ new/mashumaro-3.17/mashumaro/core/const.py 2025-10-03 23:05:12.000000000
+0200
@@ -8,6 +8,7 @@
"PY_311_MIN",
"PY_312_MIN",
"PY_313_MIN",
+ "PY_314_MIN",
"Sentinel",
]
@@ -16,8 +17,10 @@
PY_310 = sys.version_info.major == 3 and sys.version_info.minor == 10
PY_311 = sys.version_info.major == 3 and sys.version_info.minor == 11
PY_312 = sys.version_info.major == 3 and sys.version_info.minor == 12
-PY_313_MIN = sys.version_info.major == 3 and sys.version_info.minor >= 13
+PY_313 = sys.version_info.major == 3 and sys.version_info.minor == 13
+PY_314_MIN = sys.version_info.major == 3 and sys.version_info.minor >= 14
+PY_313_MIN = PY_313 or PY_314_MIN
PY_312_MIN = PY_312 or PY_313_MIN
PY_311_MIN = PY_311 or PY_312_MIN
PY_310_MIN = PY_310 or PY_311_MIN
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/core/meta/code/builder.py
new/mashumaro-3.17/mashumaro/core/meta/code/builder.py
--- old/mashumaro-3.16/mashumaro/core/meta/code/builder.py 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/mashumaro/core/meta/code/builder.py 2025-10-03
23:05:12.000000000 +0200
@@ -2,6 +2,7 @@
import importlib
import inspect
import math
+import sys
import types
import typing
import uuid
@@ -31,11 +32,9 @@
from mashumaro.core.helpers import ConfigValue
from mashumaro.core.meta.code.lines import CodeLines
from mashumaro.core.meta.helpers import (
- evaluate_forward_ref,
get_args,
get_class_that_defines_field,
get_class_that_defines_method,
- get_forward_ref_referencing_globals,
get_literal_values,
get_name_error_name,
get_type_annotations,
@@ -85,6 +84,11 @@
)
from mashumaro.types import Alias, Discriminator
+if sys.version_info >= (3, 14):
+ from annotationlib import get_annotations
+else:
+ from typing_extensions import get_annotations
+
__PRE_SERIALIZE__ = "__pre_serialize__"
__PRE_DESERIALIZE__ = "__pre_deserialize__"
__POST_SERIALIZE__ = "__post_serialize__"
@@ -167,7 +171,7 @@
@property
def annotations(self) -> dict[str, typing.Any]:
- return self.namespace.get("__annotations__", {})
+ return get_annotations(self.cls, eval_str=True)
@property
def is_nailed(self) -> bool:
@@ -329,16 +333,6 @@
print(code)
exec(code, self.globals, self.__dict__)
- def evaluate_forward_ref(
- self,
- typ: typing.ForwardRef,
- owner: typing.Optional[typing.Type],
- ) -> typing.Optional[typing.Type]:
- globalns = get_forward_ref_referencing_globals(
- typ, owner, self.globals
- )
- return evaluate_forward_ref(typ, globalns, self.__dict__)
-
def get_declared_hook(self, method_name: str) -> typing.Any:
cls = get_class_that_defines_method(method_name, self.cls)
if cls is not None and not is_dataclass_dict_mixin(cls):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/core/meta/helpers.py
new/mashumaro-3.17/mashumaro/core/meta/helpers.py
--- old/mashumaro-3.16/mashumaro/core/meta/helpers.py 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/mashumaro/core/meta/helpers.py 2025-10-03
23:05:12.000000000 +0200
@@ -2,7 +2,6 @@
import enum
import inspect
import re
-import sys
import types
import typing
from collections.abc import Callable, Hashable, Iterable, Iterator
@@ -34,7 +33,7 @@
PY_310_MIN,
PY_311_MIN,
PY_312_MIN,
- PY_313_MIN,
+ PY_314_MIN,
)
from mashumaro.dialect import Dialect
@@ -81,8 +80,6 @@
"iter_all_subclasses",
"is_hashable",
"is_hashable_type",
- "evaluate_forward_ref",
- "get_forward_ref_referencing_globals",
"is_type_alias_type",
]
@@ -289,7 +286,7 @@
def is_special_typing_primitive(typ: Any) -> bool:
try:
issubclass(typ, object)
- return False
+ return PY_314_MIN and issubclass(typ, typing.Union) # type:
ignore[arg-type]
except TypeError:
return True
@@ -756,42 +753,8 @@
def str_to_forward_ref(
annotation: str, module: Optional[types.ModuleType] = None
) -> ForwardRef:
- return ForwardRef(annotation, module=module)
-
-
-def evaluate_forward_ref(
- typ: ForwardRef, globalns: dict[str, Any], localns: dict[str, Any]
-) -> Optional[Type]:
- if PY_313_MIN:
- return typ._evaluate(
- globalns, localns, type_params=(), recursive_guard=frozenset()
- ) # type: ignore[call-arg]
- else:
- return typ._evaluate(
- globalns, localns, recursive_guard=frozenset()
- ) # type: ignore[call-arg]
-
-
-def get_forward_ref_referencing_globals(
- referenced_type: ForwardRef,
- referencing_object: Optional[Any] = None,
- fallback: Optional[dict[str, Any]] = None,
-) -> dict[str, Any]:
- if fallback is None:
- fallback = {}
- forward_module = getattr(referenced_type, "__forward_module__", None)
- if not forward_module and referencing_object:
- # We can't get the module in which ForwardRef's value is defined on
- # Python < 3.10, ForwardRef evaluation might not work properly
- # without this information, so we will consider the namespace of
- # the module in which this ForwardRef is used as globalns.
- return getattr(
- sys.modules.get(referencing_object.__module__, None),
- "__dict__",
- fallback,
- )
- else:
- return getattr(forward_module, "__dict__", fallback)
+ module_name = module.__name__ if module else None
+ return ForwardRef(annotation, module=module_name)
def is_type_alias_type(typ: Type) -> bool:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/core/meta/types/pack.py
new/mashumaro-3.17/mashumaro/core/meta/types/pack.py
--- old/mashumaro-3.16/mashumaro/core/meta/types/pack.py 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/mashumaro/core/meta/types/pack.py 2025-10-03
23:05:12.000000000 +0200
@@ -3,6 +3,7 @@
import ipaddress
import os
import re
+import sys
import typing
import uuid
import zoneinfo
@@ -16,6 +17,7 @@
from typing import Any, ForwardRef, Optional, Tuple, Union
import typing_extensions
+from typing_extensions import NotRequired
from mashumaro.core.const import PY_311_MIN
from mashumaro.core.meta.code.lines import CodeLines
@@ -75,6 +77,13 @@
SerializationStrategy,
)
+if sys.version_info >= (3, 14):
+ from typing import evaluate_forward_ref
+
+ from annotationlib import get_annotations
+else:
+ from typing_extensions import evaluate_forward_ref, get_annotations
+
__all__ = ["PackerRegistry"]
@@ -94,9 +103,7 @@
except (KeyError, ValueError):
value_type = Any
if isinstance(value_type, ForwardRef):
- value_type = spec.builder.evaluate_forward_ref(
- value_type, spec.origin_type
- )
+ value_type = evaluate_forward_ref(value_type)
value_type = substitute_type_params(
value_type, # type: ignore
resolve_type_params(strategy_type, get_args(spec.type))[strategy_type],
@@ -191,9 +198,7 @@
if is_self(value_type):
return f"{spec.expression}._serialize()"
if isinstance(value_type, ForwardRef):
- value_type = spec.builder.evaluate_forward_ref(
- value_type, spec.origin_type
- )
+ value_type = evaluate_forward_ref(value_type)
value_type = substitute_type_params(
value_type,
resolve_type_params(spec.origin_type, get_args(spec.type))[
@@ -539,9 +544,7 @@
elif is_type_var_tuple(spec.type):
return PackerRegistry.get(spec.copy(type=tuple[Any, ...]))
elif isinstance(spec.type, ForwardRef):
- evaluated = spec.builder.evaluate_forward_ref(
- spec.type, spec.owner
- )
+ evaluated = evaluate_forward_ref(spec.type)
if evaluated is not None:
return PackerRegistry.get(spec.copy(type=evaluated))
elif is_type_alias_type(spec.type):
@@ -674,7 +677,7 @@
]
annotations = {
k: resolved.get(v, v)
- for k, v in getattr(spec.origin_type, "__annotations__", {}).items()
+ for k, v in get_annotations(spec.origin_type, eval_str=True).items()
}
fields = getattr(spec.type, "_fields", ())
packers = []
@@ -716,11 +719,20 @@
]
annotations = {
k: resolved.get(v, v)
- for k, v in spec.origin_type.__annotations__.items()
+ for k, v in get_annotations(spec.origin_type, eval_str=True).items()
}
all_keys = list(annotations.keys())
- required_keys = getattr(spec.type, "__required_keys__", all_keys)
- optional_keys = getattr(spec.type, "__optional_keys__", [])
+ required_keys = set(getattr(spec.type, "__required_keys__", all_keys))
+ optional_keys = set(getattr(spec.type, "__optional_keys__", []))
+
+ # workaround for https://github.com/python/cpython/issues/97727
+ for key, annotation in annotations.items():
+ if isinstance(annotation, ForwardRef):
+ annotation = evaluate_forward_ref(annotation)
+ if get_type_origin(annotation) is NotRequired:
+ required_keys.discard(key)
+ optional_keys.add(key)
+
lines = CodeLines()
method_name = (
f"__pack_typed_dict_{spec.builder.cls.__name__}_"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/core/meta/types/unpack.py
new/mashumaro-3.17/mashumaro/core/meta/types/unpack.py
--- old/mashumaro-3.16/mashumaro/core/meta/types/unpack.py 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/mashumaro/core/meta/types/unpack.py 2025-10-03
23:05:12.000000000 +0200
@@ -6,6 +6,7 @@
import os
import pathlib
import re
+import sys
import types
import typing
import uuid
@@ -27,6 +28,7 @@
from typing import Any, ForwardRef, Optional, Tuple, Union
import typing_extensions
+from typing_extensions import NotRequired
from mashumaro.core.const import PY_311_MIN
from mashumaro.core.helpers import parse_timezone
@@ -36,6 +38,7 @@
get_class_that_defines_method,
get_function_arg_annotation,
get_literal_values,
+ get_type_origin,
get_type_var_default,
is_final,
is_generic,
@@ -92,6 +95,13 @@
SerializationStrategy,
)
+if sys.version_info >= (3, 14):
+ from typing import evaluate_forward_ref
+
+ from annotationlib import get_annotations
+else:
+ from typing_extensions import evaluate_forward_ref, get_annotations
+
try:
import ciso8601
except ImportError: # pragma: no cover
@@ -561,9 +571,7 @@
except (KeyError, ValueError):
value_type = Any
if isinstance(value_type, ForwardRef):
- value_type = spec.builder.evaluate_forward_ref(
- value_type, spec.origin_type
- )
+ value_type = evaluate_forward_ref(value_type)
value_type = substitute_type_params(
value_type, # type: ignore
resolve_type_params(strategy_type, get_args(spec.type))[strategy_type],
@@ -649,9 +657,7 @@
f"._deserialize({spec.expression})"
)
if isinstance(value_type, ForwardRef):
- value_type = spec.builder.evaluate_forward_ref(
- value_type, spec.origin_type
- )
+ value_type = evaluate_forward_ref(value_type)
value_type = substitute_type_params(
value_type,
resolve_type_params(spec.origin_type, get_args(spec.type))[
@@ -867,9 +873,7 @@
elif is_type_var_tuple(spec.type):
return UnpackerRegistry.get(spec.copy(type=tuple[Any, ...]))
elif isinstance(spec.type, ForwardRef):
- evaluated = spec.builder.evaluate_forward_ref(
- spec.type, spec.owner
- )
+ evaluated = evaluate_forward_ref(spec.type)
if evaluated is not None:
return UnpackerRegistry.get(spec.copy(type=evaluated))
elif is_type_alias_type(spec.type):
@@ -1063,7 +1067,7 @@
]
annotations = {
k: resolved.get(v, v)
- for k, v in getattr(spec.origin_type, "__annotations__", {}).items()
+ for k, v in get_annotations(spec.origin_type, eval_str=True).items()
}
fields = getattr(spec.type, "_fields", ())
defaults = getattr(spec.type, "_field_defaults", {})
@@ -1151,11 +1155,20 @@
]
annotations = {
k: resolved.get(v, v)
- for k, v in spec.origin_type.__annotations__.items()
+ for k, v in get_annotations(spec.origin_type, eval_str=True).items()
}
all_keys = list(annotations.keys())
- required_keys = getattr(spec.type, "__required_keys__", all_keys)
- optional_keys = getattr(spec.type, "__optional_keys__", [])
+ required_keys = set(getattr(spec.type, "__required_keys__", all_keys))
+ optional_keys = set(getattr(spec.type, "__optional_keys__", []))
+
+ # workaround for https://github.com/python/cpython/issues/97727
+ for key, annotation in annotations.items():
+ if isinstance(annotation, ForwardRef):
+ annotation = evaluate_forward_ref(annotation)
+ if get_type_origin(annotation) is NotRequired:
+ required_keys.discard(key)
+ optional_keys.add(key)
+
lines = CodeLines()
method_name = (
f"__unpack_typed_dict_{spec.builder.cls.__name__}_"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/helper.py
new/mashumaro-3.17/mashumaro/helper.py
--- old/mashumaro-3.16/mashumaro/helper.py 2025-05-20 20:36:49.000000000
+0200
+++ new/mashumaro-3.17/mashumaro/helper.py 2025-10-03 23:05:12.000000000
+0200
@@ -36,12 +36,14 @@
] = None,
serialization_strategy: Optional[SerializationStrategy] = None,
alias: Optional[str] = None,
+ **kwargs: Any,
) -> dict[str, Any]:
return {
"serialize": serialize,
"deserialize": deserialize,
"serialization_strategy": serialization_strategy,
"alias": alias,
+ **kwargs,
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/mashumaro/jsonschema/schema.py
new/mashumaro-3.17/mashumaro/jsonschema/schema.py
--- old/mashumaro-3.16/mashumaro/jsonschema/schema.py 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/mashumaro/jsonschema/schema.py 2025-10-03
23:05:12.000000000 +0200
@@ -1,10 +1,11 @@
import datetime
import ipaddress
import os
+import sys
import warnings
from base64 import encodebytes
from collections import ChainMap, Counter, deque
-from collections.abc import (
+from collections.abc import ( # type: ignore[attr-defined]
ByteString,
Callable,
Collection,
@@ -22,15 +23,13 @@
from uuid import UUID
from zoneinfo import ZoneInfo
-from typing_extensions import TypeAlias
+from typing_extensions import NotRequired, TypeAlias
from mashumaro.config import BaseConfig
from mashumaro.core.const import PY_311_MIN
from mashumaro.core.meta.code.builder import CodeBuilder
from mashumaro.core.meta.helpers import (
- evaluate_forward_ref,
get_args,
- get_forward_ref_referencing_globals,
get_function_return_annotation,
get_literal_values,
get_type_origin,
@@ -52,7 +51,7 @@
resolve_type_params,
type_name,
)
-from mashumaro.core.meta.types.common import NoneType
+from mashumaro.core.meta.types.common import NoneType, clean_id
from mashumaro.helper import pass_through
from mashumaro.jsonschema.annotations import (
Annotation,
@@ -94,6 +93,13 @@
except ImportError: # pragma: no cover
from mashumaro.mixins.json import DataClassJSONMixin # type: ignore
+if sys.version_info >= (3, 14):
+ from typing import evaluate_forward_ref
+
+ from annotationlib import get_annotations
+else:
+ from typing_extensions import evaluate_forward_ref, get_annotations
+
UTC_OFFSET_PATTERN = r"^UTC([+-][0-2][0-9]:[0-5][0-9])?$"
@@ -143,14 +149,11 @@
def derive(self, **changes: Any) -> "Instance":
new_type = changes.get("type")
if isinstance(new_type, ForwardRef):
- changes["type"] = evaluate_forward_ref(
- new_type,
- get_forward_ref_referencing_globals(new_type, self.type),
- self.__dict__,
- )
+ changes["type"] = evaluate_forward_ref(new_type)
new_instance = replace(self, **changes)
if is_dataclass(self.origin_type):
new_instance.__owner_builder = self.__self_builder
+ new_instance.update_type(new_instance.type)
return new_instance
def __post_init__(self) -> None:
@@ -296,7 +299,9 @@
return schema
-def _default(f_type: Type, f_value: Any, config_cls: Type[BaseConfig]) -> Any:
+def _default(
+ f_type: Optional[Type], f_value: Any, config_cls: Type[BaseConfig]
+) -> Any:
@dataclass
class CC(DataClassJSONMixin):
x: f_type = f_value # type: ignore
@@ -351,9 +356,14 @@
def on_dataclass(instance: Instance, ctx: Context) -> Optional[JSONSchema]:
# TODO: Self references might not work
if is_dataclass(instance.origin_type):
+ if ctx.all_refs:
+ title = clean_id(type_name(instance.type, short=True))
+ title = title.strip("_")
+ else:
+ title = instance.origin_type.__name__
jsonschema_config = instance.get_self_config().json_schema
schema = JSONObjectSchema(
- title=instance.origin_type.__name__,
+ title=title,
additionalProperties=jsonschema_config.get(
"additionalProperties", False
),
@@ -385,11 +395,9 @@
if required:
schema.required = required
if ctx.all_refs:
- ctx.definitions[instance.origin_type.__name__] = schema
+ ctx.definitions[title] = schema
ref_prefix = ctx.ref_prefix or ctx.dialect.definitions_root_pointer
- return JSONSchema(
- reference=f"{ref_prefix}/{instance.origin_type.__name__}"
- )
+ return JSONSchema(reference=f"{ref_prefix}/{title}")
else:
return schema
@@ -461,11 +469,7 @@
elif is_readonly(instance.type):
return get_schema(instance.derive(type=args[0]), ctx)
elif isinstance(instance.type, ForwardRef):
- evaluated = evaluate_forward_ref(
- instance.type,
- get_forward_ref_referencing_globals(instance.type),
- None,
- )
+ evaluated = evaluate_forward_ref(instance.type)
if evaluated is not None:
return get_schema(instance.derive(type=evaluated), ctx)
@@ -639,8 +643,8 @@
)[instance.origin_type]
annotations = {
k: resolved.get(v, v)
- for k, v in getattr(
- instance.origin_type, "__annotations__", {}
+ for k, v in get_annotations(
+ instance.origin_type, eval_str=True
).items()
}
fields = getattr(instance.type, "_fields", ())
@@ -685,10 +689,20 @@
)[instance.origin_type]
annotations = {
k: resolved.get(v, v)
- for k, v in instance.origin_type.__annotations__.items()
+ for k, v in get_annotations(
+ instance.origin_type, eval_str=True
+ ).items()
}
all_keys = list(annotations.keys())
- required_keys = getattr(instance.type, "__required_keys__", all_keys)
+ required_keys = set(getattr(instance.type, "__required_keys__", all_keys))
+
+ # workaround for https://github.com/python/cpython/issues/97727
+ for key, annotation in annotations.items():
+ if isinstance(annotation, ForwardRef):
+ annotation = evaluate_forward_ref(annotation)
+ if get_type_origin(annotation) is NotRequired:
+ required_keys.discard(key)
+
return JSONObjectSchema(
properties={
key: get_schema(instance.derive(type=annotations[key]), ctx)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/pyproject.toml
new/mashumaro-3.17/pyproject.toml
--- old/mashumaro-3.16/pyproject.toml 2025-05-20 20:36:49.000000000 +0200
+++ new/mashumaro-3.17/pyproject.toml 2025-10-03 23:05:12.000000000 +0200
@@ -1,3 +1,52 @@
+[build-system]
+build-backend = "setuptools.build_meta"
+requires = ["setuptools>=77.0"]
+
+[project]
+name = "mashumaro"
+version = "3.17"
+license = "Apache-2.0"
+description = "Fast and well tested serialization library"
+readme = "README.md"
+authors = [{ name = "Alexander Tikhonov", email = "[email protected]" }]
+requires-python = ">=3.9"
+classifiers = [
+ "Intended Audience :: Developers",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "Development Status :: 5 - Production/Stable",
+]
+dependencies = [
+ "typing_extensions>=4.14.0",
+]
+
+[project.urls]
+Homepage = "https://github.com/Fatal1ty/mashumaro"
+
+[project.optional-dependencies]
+orjson = ["orjson"]
+msgpack = ["msgpack>=0.5.6"]
+yaml = ["pyyaml>=3.13"]
+toml = [
+ "tomli-w>=1.0",
+ "tomli>=1.1.0;python_version<'3.11'",
+]
+
+[tool.setuptools.packages.find]
+include = ["mashumaro*"]
+
+[tool.setuptools.package-data]
+mashumaro = [
+ "py.typed",
+ "mixins/orjson.pyi",
+]
+
[tool.mypy]
ignore_missing_imports = true
disallow_untyped_defs = true
@@ -34,9 +83,6 @@
[tool.ruff]
line-length = 79
-[tool.coverage.run]
-omit = ["setup.py"]
-
[tool.coverage.report]
exclude_lines = ["pragma: no cover", "@overload", "@abstractmethod"]
ignore_errors = true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/requirements-dev.txt
new/mashumaro-3.17/requirements-dev.txt
--- old/mashumaro-3.16/requirements-dev.txt 2025-05-20 20:36:49.000000000
+0200
+++ new/mashumaro-3.17/requirements-dev.txt 2025-10-03 23:05:12.000000000
+0200
@@ -1,5 +1,5 @@
# extra
-msgpack>=0.5.6
+msgpack>=0.5.6;sys_platform != 'win32' or (sys_platform == 'win32' and
python_version<'3.14')
pyyaml>=3.13
tomli-w>=1.0
tomli>=1.1.0;python_version<'3.11'
@@ -19,14 +19,14 @@
# third party features
ciso8601>=2.1.3
-pendulum>=2.1.2;python_version<'3.13'
+pendulum>=2.1.2
# benchmark
pyperf>=2.6.1
termtables>=0.2.3
pytablewriter[html]>=0.58.0
cattrs==24.1.2
-pydantic==2.9.2
+pydantic==2.9.2;python_version<'3.14' # see
https://github.com/pydantic/pydantic/issues/11613
dacite==1.7.0 # see
https://github.com/konradhalas/dacite/issues/236#issuecomment-1613987368
marshmallow>=3.19.0
dataclasses-json==0.6.7
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/setup.py new/mashumaro-3.17/setup.py
--- old/mashumaro-3.16/setup.py 2025-05-20 20:36:49.000000000 +0200
+++ new/mashumaro-3.17/setup.py 1970-01-01 01:00:00.000000000 +0100
@@ -1,43 +0,0 @@
-#!/usr/bin/env python
-
-from setuptools import find_packages, setup
-
-setup(
- name="mashumaro",
- version="3.16",
- description="Fast and well tested serialization library",
- long_description=open("README.md", encoding="utf8").read(),
- long_description_content_type="text/markdown",
- platforms="all",
- classifiers=[
- "License :: OSI Approved :: Apache Software License",
- "Intended Audience :: Developers",
- "Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3.12",
- "Programming Language :: Python :: 3.13",
- "Development Status :: 5 - Production/Stable",
- ],
- license="Apache License, Version 2.0",
- author="Alexander Tikhonov",
- author_email="[email protected]",
- url="https://github.com/Fatal1ty/mashumaro",
- packages=find_packages(include=("mashumaro", "mashumaro.*")),
- package_data={"mashumaro": ["py.typed", "mixins/orjson.pyi"]},
- python_requires=">=3.9",
- install_requires=[
- "typing_extensions>=4.1.0",
- ],
- extras_require={
- "orjson": ["orjson"],
- "msgpack": ["msgpack>=0.5.6"],
- "yaml": ["pyyaml>=3.13"],
- "toml": [
- "tomli-w>=1.0",
- "tomli>=1.1.0;python_version<'3.11'",
- ],
- },
- zip_safe=False,
-)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/tests/conftest.py
new/mashumaro-3.17/tests/conftest.py
--- old/mashumaro-3.16/tests/conftest.py 2025-05-20 20:36:49.000000000
+0200
+++ new/mashumaro-3.17/tests/conftest.py 2025-10-03 23:05:12.000000000
+0200
@@ -1,6 +1,6 @@
from unittest.mock import patch
-from mashumaro.core.const import PY_312_MIN, PY_313_MIN
+from mashumaro.core.const import PY_312_MIN
if not PY_312_MIN:
collect_ignore = [
@@ -9,15 +9,6 @@
"test_recursive_union.py",
]
-if PY_313_MIN:
- collect_ignore = [
- "test_codecs/test_orjson_codec.py",
- "test_discriminated_unions/test_dialects.py",
- "test_orjson.py",
- "test_pep_563.py",
- "test_self.py",
- ]
-
add_unpack_method = patch(
"mashumaro.core.meta.code.builder.CodeBuilder.add_unpack_method",
lambda *args, **kwargs: ...,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/mashumaro-3.16/tests/test_jsonschema/test_jsonschema_generation.py
new/mashumaro-3.17/tests/test_jsonschema/test_jsonschema_generation.py
--- old/mashumaro-3.16/tests/test_jsonschema/test_jsonschema_generation.py
2025-05-20 20:36:49.000000000 +0200
+++ new/mashumaro-3.17/tests/test_jsonschema/test_jsonschema_generation.py
2025-10-03 23:05:12.000000000 +0200
@@ -32,6 +32,7 @@
OrderedDict,
Sequence,
Tuple,
+ TypeVar,
Union,
)
from uuid import UUID
@@ -143,7 +144,7 @@
def test_jsonschema_for_dataclass():
@dataclass
- class DataClass:
+ class MyClass:
a: int
b: float = 3.14
c: Optional[int] = field(default=None, metadata={"alias": "cc"})
@@ -156,8 +157,8 @@
class Config:
aliases = {"a": "aa", "d": "dd"}
- schema = JSONObjectSchema(
- title="DataClass",
+ assert build_json_schema(MyClass) == JSONObjectSchema(
+ title="MyClass",
properties={
"aa": JSONSchema(type=JSONSchemaInstanceType.INTEGER),
"b": JSONSchema(type=JSONSchemaInstanceType.NUMBER, default=3.14),
@@ -177,9 +178,35 @@
additionalProperties=False,
required=["aa"],
)
- assert build_json_schema(DataClass) == schema
- assert build_json_schema(DataClass, all_refs=True) == JSONSchema(
- reference="#/$defs/DataClass", definitions={"DataClass": schema}
+ assert build_json_schema(MyClass, all_refs=True) == JSONSchema(
+ reference="#/$defs/test_jsonschema_for_dataclass__locals__MyClass",
+ definitions={
+ "test_jsonschema_for_dataclass__locals__MyClass": JSONObjectSchema(
+ title="test_jsonschema_for_dataclass__locals__MyClass",
+ properties={
+ "aa": JSONSchema(type=JSONSchemaInstanceType.INTEGER),
+ "b": JSONSchema(
+ type=JSONSchemaInstanceType.NUMBER, default=3.14
+ ),
+ "cc": JSONSchema(
+ anyOf=[
+ JSONSchema(type=JSONSchemaInstanceType.INTEGER),
+ JSONSchema(type=JSONSchemaInstanceType.NULL),
+ ],
+ default=None,
+ ),
+ "dd": JSONSchema(
+ type=JSONSchemaInstanceType.STRING, default=""
+ ),
+ "f": JSONArraySchema(
+ items=JSONSchema(type=JSONSchemaInstanceType.INTEGER),
+ description="description for f",
+ ),
+ },
+ additionalProperties=False,
+ required=["aa"],
+ )
+ },
)
@@ -1156,7 +1183,12 @@
class DataClass:
pass
- schema = {"$ref": "#/components/responses/DataClass"}
+ schema = {
+ "$ref": (
+ "#/components/responses/"
+ "test_jsonschema_with_ref_prefix__locals__DataClass"
+ )
+ }
assert (
build_json_schema(
List[DataClass], all_refs=True, ref_prefix="#/components/responses"
@@ -1443,3 +1475,72 @@
required=["x", "y"],
additionalProperties=False,
)
+
+
+def test_jsonschema_for_generic_dataclass():
+ T = TypeVar("T")
+
+ @dataclass
+ class MyClass(Generic[T]):
+ x: T
+ y: list[T]
+
+ assert build_json_schema(MyClass) == JSONObjectSchema(
+ title="MyClass",
+ properties={
+ "x": EmptyJSONSchema(),
+ "y": JSONArraySchema(),
+ },
+ additionalProperties=False,
+ required=["x", "y"],
+ )
+ assert build_json_schema(MyClass[int]) == JSONObjectSchema(
+ title="MyClass",
+ properties={
+ "x": JSONSchema(type=JSONSchemaInstanceType.INTEGER),
+ "y": JSONArraySchema(
+ items=JSONSchema(type=JSONSchemaInstanceType.INTEGER)
+ ),
+ },
+ additionalProperties=False,
+ required=["x", "y"],
+ )
+
+ @dataclass
+ class MyClass2(Generic[T]):
+ z: MyClass[T]
+
+ assert build_json_schema(MyClass2) == JSONObjectSchema(
+ title="MyClass2",
+ properties={
+ "z": JSONObjectSchema(
+ title="MyClass",
+ properties={
+ "x": EmptyJSONSchema(),
+ "y": JSONArraySchema(),
+ },
+ additionalProperties=False,
+ required=["x", "y"],
+ )
+ },
+ additionalProperties=False,
+ required=["z"],
+ )
+ assert build_json_schema(MyClass2[str]) == JSONObjectSchema(
+ title="MyClass2",
+ properties={
+ "z": JSONObjectSchema(
+ title="MyClass",
+ properties={
+ "x": JSONSchema(type=JSONSchemaInstanceType.STRING),
+ "y": JSONArraySchema(
+ items=JSONSchema(type=JSONSchemaInstanceType.STRING)
+ ),
+ },
+ additionalProperties=False,
+ required=["x", "y"],
+ )
+ },
+ additionalProperties=False,
+ required=["z"],
+ )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/tests/test_metadata_options.py
new/mashumaro-3.17/tests/test_metadata_options.py
--- old/mashumaro-3.16/tests/test_metadata_options.py 2025-05-20
20:36:49.000000000 +0200
+++ new/mashumaro-3.17/tests/test_metadata_options.py 2025-10-03
23:05:12.000000000 +0200
@@ -7,7 +7,6 @@
import pytest
from mashumaro import DataClassDictMixin
-from mashumaro.core.const import PY_312_MIN, PY_313_MIN
from mashumaro.exceptions import (
UnserializableField,
UnsupportedDeserializationEngine,
@@ -58,7 +57,6 @@
assert instance == should_be
[email protected](PY_313_MIN, reason="pendulum doesn't install on 3.13")
def test_pendulum_datetime_parser():
@dataclass
class DataClass(DataClassDictMixin):
@@ -69,7 +67,6 @@
assert instance == should_be
[email protected](PY_313_MIN, reason="pendulum doesn't install on 3.13")
def test_pendulum_date_parser():
@dataclass
class DataClass(DataClassDictMixin):
@@ -80,7 +77,6 @@
assert instance == should_be
[email protected](PY_313_MIN, reason="pendulum doesn't install on 3.13")
def test_pendulum_time_parser():
@dataclass
class DataClass(DataClassDictMixin):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/mashumaro-3.16/tests/test_not_required_with_future_annotations.py
new/mashumaro-3.17/tests/test_not_required_with_future_annotations.py
--- old/mashumaro-3.16/tests/test_not_required_with_future_annotations.py
1970-01-01 01:00:00.000000000 +0100
+++ new/mashumaro-3.17/tests/test_not_required_with_future_annotations.py
2025-10-03 23:05:12.000000000 +0200
@@ -0,0 +1,56 @@
+from __future__ import annotations
+
+from dataclasses import dataclass
+from typing import TypedDict
+
+from typing_extensions import NotRequired
+
+from mashumaro import DataClassDictMixin
+from mashumaro.codecs.basic import decode, encode
+from mashumaro.jsonschema import build_json_schema
+
+
+class MyDict(TypedDict):
+ a: str
+ b: NotRequired[int]
+
+
+@dataclass
+class MyDataClass(DataClassDictMixin):
+ data: MyDict
+
+
+def test_typeddict_with_not_required_and_future_annotations():
+ # test workaround for https://github.com/Fatal1ty/mashumaro/issues/292
+ assert decode({"a": "test", "b": 42}, MyDict) == {"a": "test", "b": 42}
+ assert decode({"a": "test", "b": "42"}, MyDict) == {"a": "test", "b": 42}
+ assert decode({"a": "test"}, MyDict) == {"a": "test"}
+
+ assert encode(MyDict(a="test"), MyDict) == {"a": "test"}
+ assert encode({"a": "test"}, MyDict) == {"a": "test"}
+ assert encode(MyDict(a="test", b=42), MyDict) == {"a": "test", "b": 42}
+
+ assert MyDataClass(MyDict(a="test", b=42)).to_dict() == {
+ "data": {"a": "test", "b": 42}
+ }
+ assert MyDataClass(MyDict(a="test")).to_dict() == {"data": {"a": "test"}}
+
+ assert MyDataClass.from_dict(
+ {"data": {"a": "test", "b": 42}}
+ ) == MyDataClass(MyDict(a="test", b=42))
+ assert MyDataClass.from_dict({"data": {"a": "test"}}) == MyDataClass(
+ MyDict(a="test")
+ )
+
+
+def test_jsonschema_for_not_required_and_future_annotations():
+ schema = build_json_schema(MyDict).to_dict()
+ assert schema == {
+ "type": "object",
+ "properties": {
+ "a": {"type": "string"},
+ "b": {"type": "integer"},
+ },
+ "required": ["a"],
+ "additionalProperties": False,
+ }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/mashumaro-3.16/tests/test_pep_563.py
new/mashumaro-3.17/tests/test_pep_563.py
--- old/mashumaro-3.16/tests/test_pep_563.py 2025-05-20 20:36:49.000000000
+0200
+++ new/mashumaro-3.17/tests/test_pep_563.py 2025-10-03 23:05:12.000000000
+0200
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass
-from typing import Dict
+from typing import Any, Dict, Optional
import msgpack
import orjson
@@ -13,6 +13,7 @@
from mashumaro.exceptions import UnresolvedTypeReferenceError
from mashumaro.mixins.msgpack import DataClassMessagePackMixin
from mashumaro.mixins.orjson import DataClassORJSONMixin
+from mashumaro.types import SerializationStrategy
from .conftest import add_unpack_method
@@ -290,3 +291,23 @@
dumped = orjson.dumps({"a": {"b": 123000}, "x": 456000})
assert instance.to_jsonb(encoder=encoder) == dumped
assert A3ORJSON.from_json(dumped, decoder=decoder) == instance
+
+
+def test_postponed_serialization_strategy() -> None:
+ class Strategy(SerializationStrategy, use_annotations=True):
+ def serialize(self, value) -> dict[str, Any]:
+ return {"a": value}
+
+ def deserialize(self, value: dict[str, Any]):
+ return value.get("a")
+
+ @dataclass
+ class MyDataClass(DataClassDictMixin):
+ x: Optional[int]
+
+ class Config(BaseConfig):
+ serialization_strategy = {int: Strategy()}
+
+ obj = MyDataClass(x=2)
+ assert obj.to_dict() == {"x": {"a": 2}}
+ assert MyDataClass.from_dict({"x": {"a": 2}}) == obj