marshmallow 4.0.1__tar.gz → 4.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. {marshmallow-4.0.1 → marshmallow-4.1.1}/CHANGELOG.rst +19 -0
  2. {marshmallow-4.0.1 → marshmallow-4.1.1}/PKG-INFO +7 -7
  3. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/code_of_conduct.rst +3 -3
  4. {marshmallow-4.0.1 → marshmallow-4.1.1}/pyproject.toml +8 -7
  5. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/constants.py +3 -0
  6. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/decorators.py +17 -17
  7. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/fields.py +5 -7
  8. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/schema.py +3 -1
  9. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/types.py +3 -10
  10. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/utils.py +4 -10
  11. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/validate.py +2 -3
  12. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_decorators.py +2 -2
  13. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_deserialization.py +12 -2
  14. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_schema.py +1 -1
  15. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_utils.py +1 -1
  16. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_validate.py +8 -0
  17. {marshmallow-4.0.1 → marshmallow-4.1.1}/tox.ini +1 -1
  18. {marshmallow-4.0.1 → marshmallow-4.1.1}/CONTRIBUTING.rst +0 -0
  19. {marshmallow-4.0.1 → marshmallow-4.1.1}/LICENSE +0 -0
  20. {marshmallow-4.0.1 → marshmallow-4.1.1}/NOTICE +0 -0
  21. {marshmallow-4.0.1 → marshmallow-4.1.1}/README.rst +0 -0
  22. {marshmallow-4.0.1 → marshmallow-4.1.1}/SECURITY.md +0 -0
  23. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/.gitignore +0 -0
  24. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/apple-touch-icon.png +0 -0
  25. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/custom.css +0 -0
  26. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/favicon.ico +0 -0
  27. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/marshmallow-logo-200.png +0 -0
  28. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/marshmallow-logo-with-title-for-dark-theme.png +0 -0
  29. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/marshmallow-logo-with-title.png +0 -0
  30. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/_static/marshmallow-logo.png +0 -0
  31. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/api_reference.rst +0 -0
  32. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/authors.rst +0 -0
  33. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/changelog.rst +0 -0
  34. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/conf.py +0 -0
  35. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/contributing.rst +0 -0
  36. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/custom_fields.rst +0 -0
  37. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/dashing.json +0 -0
  38. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/donate.rst +0 -0
  39. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/examples/index.rst +0 -0
  40. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/examples/inflection.rst +0 -0
  41. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/examples/quotes_api.rst +0 -0
  42. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/examples/validating_package_json.rst +0 -0
  43. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/custom_error_handling.rst +0 -0
  44. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/custom_error_messages.rst +0 -0
  45. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/custom_options.rst +0 -0
  46. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/index.rst +0 -0
  47. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/overriding_attribute_access.rst +0 -0
  48. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/pre_and_post_processing_methods.rst +0 -0
  49. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/schema_validation.rst +0 -0
  50. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/extending/using_original_input_data.rst +0 -0
  51. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/index.rst +0 -0
  52. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/install.rst +0 -0
  53. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/kudos.rst +0 -0
  54. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/license.rst +0 -0
  55. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.class_registry.rst +0 -0
  56. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.decorators.rst +0 -0
  57. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.error_store.rst +0 -0
  58. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.exceptions.rst +0 -0
  59. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.experimental.context.rst +0 -0
  60. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.fields.rst +0 -0
  61. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.schema.rst +0 -0
  62. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.types.rst +0 -0
  63. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.utils.rst +0 -0
  64. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/marshmallow.validate.rst +0 -0
  65. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/nesting.rst +0 -0
  66. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/quickstart.rst +0 -0
  67. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/top_level.rst +0 -0
  68. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/upgrading.rst +0 -0
  69. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/whos_using.rst +0 -0
  70. {marshmallow-4.0.1 → marshmallow-4.1.1}/docs/why.rst +0 -0
  71. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/__init__.py +0 -0
  72. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/class_registry.py +0 -0
  73. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/error_store.py +0 -0
  74. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/exceptions.py +0 -0
  75. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/experimental/__init__.py +0 -0
  76. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/experimental/context.py +0 -0
  77. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/orderedset.py +0 -0
  78. {marshmallow-4.0.1 → marshmallow-4.1.1}/src/marshmallow/py.typed +0 -0
  79. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/__init__.py +0 -0
  80. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/base.py +0 -0
  81. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/conftest.py +0 -0
  82. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/foo_serializer.py +0 -0
  83. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/mypy_test_cases/test_class_registry.py +0 -0
  84. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/mypy_test_cases/test_schema.py +0 -0
  85. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/mypy_test_cases/test_validation_error.py +0 -0
  86. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_context.py +0 -0
  87. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_error_store.py +0 -0
  88. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_exceptions.py +0 -0
  89. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_fields.py +0 -0
  90. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_options.py +0 -0
  91. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_registry.py +0 -0
  92. {marshmallow-4.0.1 → marshmallow-4.1.1}/tests/test_serialization.py +0 -0
@@ -1,6 +1,25 @@
1
1
  Changelog
2
2
  ---------
3
3
 
4
+ 4.1.1 (2025-11-05)
5
+ ++++++++++++++++++
6
+
7
+ Bug fixes:
8
+
9
+ - Ensure ``URL`` validator is case-insensitive when using custom schemes (:pr:`2874`).
10
+ Thanks :user:`T90REAL` for the PR.
11
+
12
+ 4.1.0 (2025-11-01)
13
+ ++++++++++++++++++
14
+
15
+ Other changes:
16
+
17
+ - Add `__len__` implementation to `missing` so that it can be used with
18
+ `validate.Length <marshmallow.validate.Length>` (:pr:`2861`).
19
+ Thanks :user:`agentgodzilla` for the PR.
20
+ - Drop support for Python 3.9 (:pr:`2363`).
21
+ - Test against Python 3.14 (:pr:`2864`).
22
+
4
23
  4.0.1 (2025-08-28)
5
24
  ++++++++++++++++++
6
25
 
@@ -1,20 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marshmallow
3
- Version: 4.0.1
3
+ Version: 4.1.1
4
4
  Summary: A lightweight library for converting complex datatypes to and from native Python datatypes.
5
- Author-email: Steven Loria <sloria1@gmail.com>
6
- Maintainer-email: Steven Loria <sloria1@gmail.com>, Jérôme Lafréchoux <jerome@jolimont.fr>, Jared Deckard <jared@shademaps.com>
7
- Requires-Python: >=3.9
5
+ Author-email: Steven Loria <oss@stevenloria.com>
6
+ Maintainer-email: Steven Loria <oss@stevenloria.com>, Jérôme Lafréchoux <jerome@jolimont.fr>, Jared Deckard <jared@shademaps.com>
7
+ Requires-Python: >=3.10
8
8
  Description-Content-Type: text/x-rst
9
9
  Classifier: Development Status :: 5 - Production/Stable
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.9
14
13
  Classifier: Programming Language :: Python :: 3.10
15
14
  Classifier: Programming Language :: Python :: 3.11
16
15
  Classifier: Programming Language :: Python :: 3.12
17
16
  Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
18
  License-File: LICENSE
19
19
  Requires-Dist: backports-datetime-fromisoformat; python_version < '3.11'
20
20
  Requires-Dist: typing-extensions; python_version < '3.11'
@@ -22,11 +22,11 @@ Requires-Dist: marshmallow[tests] ; extra == "dev"
22
22
  Requires-Dist: tox ; extra == "dev"
23
23
  Requires-Dist: pre-commit>=3.5,<5.0 ; extra == "dev"
24
24
  Requires-Dist: autodocsumm==0.2.14 ; extra == "docs"
25
- Requires-Dist: furo==2025.7.19 ; extra == "docs"
25
+ Requires-Dist: furo==2025.9.25 ; extra == "docs"
26
26
  Requires-Dist: sphinx-copybutton==0.5.2 ; extra == "docs"
27
27
  Requires-Dist: sphinx-issues==5.0.1 ; extra == "docs"
28
28
  Requires-Dist: sphinx==8.2.3 ; extra == "docs"
29
- Requires-Dist: sphinxext-opengraph==0.12.0 ; extra == "docs"
29
+ Requires-Dist: sphinxext-opengraph==0.13.0 ; extra == "docs"
30
30
  Requires-Dist: pytest ; extra == "tests"
31
31
  Requires-Dist: simplejson ; extra == "tests"
32
32
  Project-URL: Changelog, https://marshmallow.readthedocs.io/en/latest/changelog.html
@@ -136,10 +136,10 @@ members. This section covers actual concrete steps.
136
136
  Contacting maintainers
137
137
  ~~~~~~~~~~~~~~~~~~~~~~
138
138
 
139
- As a small and young project, we don't yet have a Code of Conduct
139
+ As a small project, we don't yet have a Code of Conduct
140
140
  enforcement team. Hopefully that will be addressed as we grow, but for
141
- now, any issues should be addressed to `Steven Loria
142
- <https://github.com/sloria>`__, via `email <mailto:sloria1@gmail.com>`__
141
+ now, any issues should be addressed to `@sloria
142
+ <https://github.com/sloria>`__, via `email <mailto:oss@stevenloria.com>`__
143
143
  or any other medium that you feel comfortable with. Using words like
144
144
  "marshmallow code of conduct" in your subject will help make sure your
145
145
  message is noticed quickly.
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "marshmallow"
3
- version = "4.0.1"
3
+ version = "4.1.1"
4
4
  description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
5
5
  readme = "README.rst"
6
6
  license = { file = "LICENSE" }
7
- authors = [{ name = "Steven Loria", email = "sloria1@gmail.com" }]
7
+ authors = [{ name = "Steven Loria", email = "oss@stevenloria.com" }]
8
8
  maintainers = [
9
- { name = "Steven Loria", email = "sloria1@gmail.com" },
9
+ { name = "Steven Loria", email = "oss@stevenloria.com" },
10
10
  { name = "Jérôme Lafréchoux", email = "jerome@jolimont.fr" },
11
11
  { name = "Jared Deckard", email = "jared@shademaps.com" },
12
12
  ]
@@ -15,13 +15,13 @@ classifiers = [
15
15
  "Intended Audience :: Developers",
16
16
  "License :: OSI Approved :: MIT License",
17
17
  "Programming Language :: Python :: 3",
18
- "Programming Language :: Python :: 3.9",
19
18
  "Programming Language :: Python :: 3.10",
20
19
  "Programming Language :: Python :: 3.11",
21
20
  "Programming Language :: Python :: 3.12",
22
21
  "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
23
23
  ]
24
- requires-python = ">=3.9"
24
+ requires-python = ">=3.10"
25
25
  dependencies = [
26
26
  "backports-datetime-fromisoformat; python_version < '3.11'",
27
27
  "typing-extensions; python_version < '3.11'",
@@ -37,11 +37,11 @@ Tidelift = "https://tidelift.com/subscription/pkg/pypi-marshmallow?utm_source=py
37
37
  [project.optional-dependencies]
38
38
  docs = [
39
39
  "autodocsumm==0.2.14",
40
- "furo==2025.7.19",
40
+ "furo==2025.9.25",
41
41
  "sphinx-copybutton==0.5.2",
42
42
  "sphinx-issues==5.0.1",
43
43
  "sphinx==8.2.3",
44
- "sphinxext-opengraph==0.12.0",
44
+ "sphinxext-opengraph==0.13.0",
45
45
  ]
46
46
  tests = ["pytest", "simplejson"]
47
47
  dev = ["marshmallow[tests]", "tox", "pre-commit>=3.5,<5.0"]
@@ -108,6 +108,7 @@ ignore = [
108
108
  "PLR0915", # allow lots of statements
109
109
  "PT007", # ignore false positives due to https://github.com/astral-sh/ruff/issues/14743
110
110
  "PT011", # don't require match when using pytest.raises
111
+ "RUF043", # allow metacharacters in match patterns
111
112
  "S", # allow asserts
112
113
  "SIM117", # allow nested with statements because it's more readable sometimes
113
114
  "SLF001", # allow private attribute access
@@ -18,5 +18,8 @@ class _Missing:
18
18
  def __repr__(self):
19
19
  return "<marshmallow.missing>"
20
20
 
21
+ def __len__(self):
22
+ return 0
23
+
21
24
 
22
25
  missing: typing.Final = _Missing()
@@ -68,8 +68,8 @@ Example: ::
68
68
  from __future__ import annotations
69
69
 
70
70
  import functools
71
+ import typing
71
72
  from collections import defaultdict
72
- from typing import Any, Callable, cast
73
73
 
74
74
  PRE_DUMP = "pre_dump"
75
75
  POST_DUMP = "post_dump"
@@ -80,10 +80,10 @@ VALIDATES_SCHEMA = "validates_schema"
80
80
 
81
81
 
82
82
  class MarshmallowHook:
83
- __marshmallow_hook__: dict[str, list[tuple[bool, Any]]] | None = None
83
+ __marshmallow_hook__: dict[str, list[tuple[bool, typing.Any]]] | None = None
84
84
 
85
85
 
86
- def validates(*field_names: str) -> Callable[..., Any]:
86
+ def validates(*field_names: str) -> typing.Callable[..., typing.Any]:
87
87
  """Register a validator method for field(s).
88
88
 
89
89
  :param field_names: Names of the fields that the method validates.
@@ -95,12 +95,12 @@ def validates(*field_names: str) -> Callable[..., Any]:
95
95
 
96
96
 
97
97
  def validates_schema(
98
- fn: Callable[..., Any] | None = None,
98
+ fn: typing.Callable[..., typing.Any] | None = None,
99
99
  *,
100
100
  pass_collection: bool = False,
101
101
  pass_original: bool = False,
102
102
  skip_on_field_errors: bool = True,
103
- ) -> Callable[..., Any]:
103
+ ) -> typing.Callable[..., typing.Any]:
104
104
  """Register a schema-level validator.
105
105
 
106
106
  By default it receives a single object at a time, transparently handling the ``many``
@@ -131,10 +131,10 @@ def validates_schema(
131
131
 
132
132
 
133
133
  def pre_dump(
134
- fn: Callable[..., Any] | None = None,
134
+ fn: typing.Callable[..., typing.Any] | None = None,
135
135
  *,
136
136
  pass_collection: bool = False,
137
- ) -> Callable[..., Any]:
137
+ ) -> typing.Callable[..., typing.Any]:
138
138
  """Register a method to invoke before serializing an object. The method
139
139
  receives the object to be serialized and returns the processed object.
140
140
 
@@ -150,11 +150,11 @@ def pre_dump(
150
150
 
151
151
 
152
152
  def post_dump(
153
- fn: Callable[..., Any] | None = None,
153
+ fn: typing.Callable[..., typing.Any] | None = None,
154
154
  *,
155
155
  pass_collection: bool = False,
156
156
  pass_original: bool = False,
157
- ) -> Callable[..., Any]:
157
+ ) -> typing.Callable[..., typing.Any]:
158
158
  """Register a method to invoke after serializing an object. The method
159
159
  receives the serialized object and returns the processed object.
160
160
 
@@ -173,10 +173,10 @@ def post_dump(
173
173
 
174
174
 
175
175
  def pre_load(
176
- fn: Callable[..., Any] | None = None,
176
+ fn: typing.Callable[..., typing.Any] | None = None,
177
177
  *,
178
178
  pass_collection: bool = False,
179
- ) -> Callable[..., Any]:
179
+ ) -> typing.Callable[..., typing.Any]:
180
180
  """Register a method to invoke before deserializing an object. The method
181
181
  receives the data to be deserialized and returns the processed data.
182
182
 
@@ -194,11 +194,11 @@ def pre_load(
194
194
 
195
195
 
196
196
  def post_load(
197
- fn: Callable[..., Any] | None = None,
197
+ fn: typing.Callable[..., typing.Any] | None = None,
198
198
  *,
199
199
  pass_collection: bool = False,
200
200
  pass_original: bool = False,
201
- ) -> Callable[..., Any]:
201
+ ) -> typing.Callable[..., typing.Any]:
202
202
  """Register a method to invoke after deserializing an object. The method
203
203
  receives the deserialized data and returns the processed data.
204
204
 
@@ -219,12 +219,12 @@ def post_load(
219
219
 
220
220
 
221
221
  def set_hook(
222
- fn: Callable[..., Any] | None,
222
+ fn: typing.Callable[..., typing.Any] | None,
223
223
  tag: str,
224
224
  *,
225
225
  many: bool = False,
226
- **kwargs: Any,
227
- ) -> Callable[..., Any]:
226
+ **kwargs: typing.Any,
227
+ ) -> typing.Callable[..., typing.Any]:
228
228
  """Mark decorated function as a hook to be picked up later.
229
229
  You should not need to use this method directly.
230
230
 
@@ -241,7 +241,7 @@ def set_hook(
241
241
 
242
242
  # Set a __marshmallow_hook__ attribute instead of wrapping in some class,
243
243
  # because I still want this to end up as a normal (unbound) method.
244
- function = cast("MarshmallowHook", fn)
244
+ function = typing.cast("MarshmallowHook", fn)
245
245
  try:
246
246
  hook_config = function.__marshmallow_hook__
247
247
  except AttributeError:
@@ -685,7 +685,7 @@ class Pluck(Nested):
685
685
  return self._load(value, partial=partial)
686
686
 
687
687
 
688
- class List(Field[list[typing.Optional[_InternalT]]]):
688
+ class List(Field[list[_InternalT | None]]):
689
689
  """A list field, composed with another `Field` class or
690
690
  instance.
691
691
 
@@ -816,7 +816,7 @@ class Tuple(Field[tuple]):
816
816
 
817
817
  return tuple(
818
818
  field._serialize(each, attr, obj, **kwargs)
819
- for field, each in zip(self.tuple_fields, value)
819
+ for field, each in zip(self.tuple_fields, value, strict=True)
820
820
  )
821
821
 
822
822
  def _deserialize(
@@ -834,7 +834,7 @@ class Tuple(Field[tuple]):
834
834
  result = []
835
835
  errors = {}
836
836
 
837
- for idx, (field, each) in enumerate(zip(self.tuple_fields, value)):
837
+ for idx, (field, each) in enumerate(zip(self.tuple_fields, value, strict=True)):
838
838
  try:
839
839
  result.append(field.deserialize(each, **kwargs))
840
840
  except ValidationError as error:
@@ -1745,7 +1745,7 @@ class Email(String):
1745
1745
  self.validators.insert(0, validator)
1746
1746
 
1747
1747
 
1748
- class IP(Field[typing.Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]):
1748
+ class IP(Field[ipaddress.IPv4Address | ipaddress.IPv6Address]):
1749
1749
  """A IP address field.
1750
1750
 
1751
1751
  :param exploded: If `True`, serialize ipv6 address in long form, ie. with groups
@@ -1802,9 +1802,7 @@ class IPv6(IP):
1802
1802
  DESERIALIZATION_CLASS = ipaddress.IPv6Address
1803
1803
 
1804
1804
 
1805
- class IPInterface(
1806
- Field[typing.Union[ipaddress.IPv4Interface, ipaddress.IPv6Interface]]
1807
- ):
1805
+ class IPInterface(Field[ipaddress.IPv4Interface | ipaddress.IPv6Interface]):
1808
1806
  """A IPInterface field.
1809
1807
 
1810
1808
  IP interface is the non-strict form of the IPNetwork type where arbitrary host
@@ -1188,7 +1188,9 @@ class Schema(metaclass=SchemaMeta):
1188
1188
  pass_original = validator_kwargs.get("pass_original", False)
1189
1189
 
1190
1190
  if many and not pass_collection:
1191
- for idx, (item, orig) in enumerate(zip(data, original_data)):
1191
+ for idx, (item, orig) in enumerate(
1192
+ zip(data, original_data, strict=True)
1193
+ ):
1192
1194
  self._run_validator(
1193
1195
  validator,
1194
1196
  item,
@@ -7,23 +7,16 @@
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- try:
11
- from typing import TypeAlias
12
- except ImportError: # Remove when dropping Python 3.9
13
- from typing_extensions import TypeAlias
14
-
15
10
  import typing
16
11
 
17
12
  #: A type that can be either a sequence of strings or a set of strings
18
- StrSequenceOrSet: TypeAlias = typing.Union[
19
- typing.Sequence[str], typing.AbstractSet[str]
20
- ]
13
+ StrSequenceOrSet: typing.TypeAlias = typing.Sequence[str] | typing.AbstractSet[str]
21
14
 
22
15
  #: Type for validator functions
23
- Validator: TypeAlias = typing.Callable[[typing.Any], typing.Any]
16
+ Validator: typing.TypeAlias = typing.Callable[[typing.Any], typing.Any]
24
17
 
25
18
  #: A valid option for the ``unknown`` schema option and argument
26
- UnknownOption: TypeAlias = typing.Literal["exclude", "include", "raise"]
19
+ UnknownOption: typing.TypeAlias = typing.Literal["exclude", "include", "raise"]
27
20
 
28
21
 
29
22
  class SchemaValidator(typing.Protocol):
@@ -7,31 +7,25 @@ import inspect
7
7
  import typing
8
8
  from collections.abc import Mapping, Sequence
9
9
 
10
- # Remove when we drop Python 3.9
11
- try:
12
- from typing import TypeGuard
13
- except ImportError:
14
- from typing_extensions import TypeGuard
15
-
16
10
  from marshmallow.constants import missing
17
11
 
18
12
 
19
- def is_generator(obj) -> TypeGuard[typing.Generator]:
13
+ def is_generator(obj) -> typing.TypeGuard[typing.Generator]:
20
14
  """Return True if ``obj`` is a generator"""
21
15
  return inspect.isgeneratorfunction(obj) or inspect.isgenerator(obj)
22
16
 
23
17
 
24
- def is_iterable_but_not_string(obj) -> TypeGuard[typing.Iterable]:
18
+ def is_iterable_but_not_string(obj) -> typing.TypeGuard[typing.Iterable]:
25
19
  """Return True if ``obj`` is an iterable object that isn't a string."""
26
20
  return (hasattr(obj, "__iter__") and not hasattr(obj, "strip")) or is_generator(obj)
27
21
 
28
22
 
29
- def is_sequence_but_not_string(obj) -> TypeGuard[Sequence]:
23
+ def is_sequence_but_not_string(obj) -> typing.TypeGuard[Sequence]:
30
24
  """Return True if ``obj`` is a sequence that isn't a string."""
31
25
  return isinstance(obj, Sequence) and not isinstance(obj, (str, bytes))
32
26
 
33
27
 
34
- def is_collection(obj) -> TypeGuard[typing.Iterable]:
28
+ def is_collection(obj) -> typing.TypeGuard[typing.Iterable]:
35
29
  """Return True if ``obj`` is a collection type, e.g list, tuple, queryset."""
36
30
  return is_iterable_but_not_string(obj) and not isinstance(obj, Mapping)
37
31
 
@@ -187,7 +187,7 @@ class URL(Validator):
187
187
  self.relative = relative
188
188
  self.absolute = absolute
189
189
  self.error: str = error or self.default_message
190
- self.schemes = schemes or self.default_schemes
190
+ self.schemes = {s.lower() for s in schemes} if schemes else self.default_schemes
191
191
  self.require_tld = require_tld
192
192
 
193
193
  def _repr_args(self) -> str:
@@ -623,8 +623,7 @@ class OneOf(Validator):
623
623
  :param valuegetter: Can be a callable or a string. In the former case, it must
624
624
  be a one-argument callable which returns the value of a
625
625
  choice. In the latter case, the string specifies the name
626
- of an attribute of the choice objects. Defaults to `str()`
627
- or `str()`.
626
+ of an attribute of the choice objects. Defaults to `str()`.
628
627
  """
629
628
  valuegetter = valuegetter if callable(valuegetter) else attrgetter(valuegetter)
630
629
  pairs = zip_longest(self.choices, self.labels, fillvalue="")
@@ -165,7 +165,7 @@ class TestPassOriginal:
165
165
  def post_load(self, data, original, many, **kwargs):
166
166
  if many:
167
167
  ret = []
168
- for item, orig_item in zip(data, original):
168
+ for item, orig_item in zip(data, original, strict=True):
169
169
  item["_post_load"] = orig_item["sentinel"]
170
170
  ret.append(item)
171
171
  else:
@@ -177,7 +177,7 @@ class TestPassOriginal:
177
177
  def post_dump(self, data, original, many, **kwargs):
178
178
  if many:
179
179
  ret = []
180
- for item, orig_item in zip(data, original):
180
+ for item, orig_item in zip(data, original, strict=True):
181
181
  item["_post_dump"] = orig_item["sentinel"]
182
182
  ret.append(item)
183
183
  else:
@@ -14,6 +14,7 @@ from marshmallow import (
14
14
  RAISE,
15
15
  Schema,
16
16
  fields,
17
+ missing,
17
18
  validate,
18
19
  )
19
20
  from marshmallow.exceptions import ValidationError
@@ -1005,6 +1006,13 @@ class TestFieldDeserialization:
1005
1006
  field = fields.Function(lambda x: None, deserialize=lambda val: val.upper())
1006
1007
  assert field.deserialize("foo") == "FOO"
1007
1008
 
1009
+ def test_function_field_deserialization_missing_with_length_validator(self):
1010
+ field = fields.Function(
1011
+ deserialize=lambda value: value.get("some-key", missing),
1012
+ validate=validate.Length(min=0),
1013
+ )
1014
+ assert field.deserialize({}) is missing
1015
+
1008
1016
  def test_function_field_passed_deserialize_only_is_load_only(self):
1009
1017
  field = fields.Function(deserialize=lambda val: val.upper())
1010
1018
  assert field.load_only is True
@@ -1306,7 +1314,7 @@ class TestFieldDeserialization:
1306
1314
  field = fields.List(fields.DateTime())
1307
1315
  result = field.deserialize(dstrings)
1308
1316
  assert all(isinstance(each, dt.datetime) for each in result)
1309
- for actual, expected in zip(result, dtimes):
1317
+ for actual, expected in zip(result, dtimes, strict=True):
1310
1318
  assert_date_equal(actual, expected)
1311
1319
 
1312
1320
  def test_list_field_deserialize_invalid_item(self):
@@ -1347,7 +1355,9 @@ class TestFieldDeserialization:
1347
1355
 
1348
1356
  assert isinstance(result, tuple)
1349
1357
  assert len(result) == 2
1350
- for val, type_, true_val in zip(result, (dt.datetime, int), (dtime, 42)):
1358
+ for val, type_, true_val in zip(
1359
+ result, (dt.datetime, int), (dtime, 42), strict=True
1360
+ ):
1351
1361
  assert isinstance(val, type_)
1352
1362
  assert val == true_val
1353
1363
 
@@ -2265,7 +2265,7 @@ class TestGetAttribute:
2265
2265
  ]
2266
2266
  schema = UserDictSchema(many=True)
2267
2267
  results = schema.dump(user_dicts)
2268
- for result, user_dict in zip(results, user_dicts):
2268
+ for result, user_dict in zip(results, user_dicts, strict=True):
2269
2269
  assert result["name"] == user_dict["_name"]
2270
2270
  assert result["email"] == user_dict["_email"]
2271
2271
  # can't serialize User object
@@ -124,7 +124,7 @@ def test_from_timestamp_with_negative_value():
124
124
 
125
125
  def test_from_timestamp_with_overflow_value():
126
126
  value = 9223372036854775
127
- with pytest.raises(ValueError, match="out of range"):
127
+ with pytest.raises(ValueError, match=r"out of range|year must be in 1\.\.9999"):
128
128
  utils.from_timestamp(value)
129
129
 
130
130
 
@@ -205,6 +205,14 @@ def test_url_custom_scheme():
205
205
  assert validator(url) == url
206
206
 
207
207
 
208
+ def test_url_custom_scheme_case_insensitive():
209
+ validator = validate.URL(schemes={"HTTP"})
210
+ assert validator("http://example.com") == "http://example.com"
211
+
212
+ validator = validate.URL(schemes={"HtTp"})
213
+ assert validator("hTtP://example.com") == "hTtP://example.com"
214
+
215
+
208
216
  @pytest.mark.parametrize(
209
217
  "valid_url",
210
218
  (
@@ -1,5 +1,5 @@
1
1
  [tox]
2
- envlist = lint,mypy,py{39,310,311,312,313},docs
2
+ envlist = lint,mypy,py{310,311,312,313,314},docs
3
3
 
4
4
  [testenv]
5
5
  extras = tests
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes