Skip to content

Commit d4c087e

Browse files
authored
Support Python 3.9-3.13 and marshmallow 4 (#347)
* Drop Python 3.8 and support 3.13 * Support marshmallow 4.0 * Update tox config * Update annotations * Attempt to fix test on GHA * Update changelog * Update doc
1 parent 029917b commit d4c087e

15 files changed

+70
-49
lines changed

Diff for: .github/workflows/build-release.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@ jobs:
1313
fail-fast: false
1414
matrix:
1515
include:
16-
- { name: "3.8", python: "3.8", tox: py38 }
17-
- { name: "3.12", python: "3.12", tox: py312 }
18-
- { name: "lowest", python: "3.8", tox: py38-lowest }
19-
- { name: "dev", python: "3.12", tox: py312-marshmallowdev }
16+
- { name: "3.9", python: "3.9", tox: py39 }
17+
- { name: "3.13", python: "3.13", tox: py313 }
18+
- { name: "lowest", python: "3.9", tox: py39-lowest }
19+
- { name: "dev", python: "3.13", tox: py313-marshmallowdev }
2020
steps:
2121
- uses: actions/[email protected]
2222
- uses: actions/setup-python@v5
@@ -31,7 +31,7 @@ jobs:
3131
- uses: actions/checkout@v4
3232
- uses: actions/setup-python@v5
3333
with:
34-
python-version: "3.11"
34+
python-version: "3.13"
3535
- name: Install pypa/build
3636
run: python -m pip install build
3737
- name: Build a binary wheel and a source tarball
@@ -54,7 +54,7 @@ jobs:
5454
- uses: actions/[email protected]
5555
- uses: actions/setup-python@v5
5656
with:
57-
python-version: "3.11"
57+
python-version: "3.13"
5858
- run: python -m pip install tox
5959
- run: python -m tox -elint
6060
publish-to-pypi:

Diff for: .pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ repos:
1515
rev: 1.19.1
1616
hooks:
1717
- id: blacken-docs
18-
additional_dependencies: [black==23.12.1]
18+
additional_dependencies: [black==24.10.0]
1919
- repo: https://github.com/pre-commit/mirrors-mypy
2020
rev: v1.13.0
2121
hooks:

Diff for: .readthedocs.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ formats:
66
build:
77
os: ubuntu-22.04
88
tools:
9-
python: "3.11"
9+
python: "3.13"
1010
python:
1111
install:
1212
- method: pip

Diff for: CHANGELOG.rst

+8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
=========
33

4+
1.3.0 (unreleased)
5+
******************
6+
7+
Support:
8+
9+
* Support Python 3.9-3.13 (:pr:`347`).
10+
* Support marshmallow 4.0.0 (:pr:`347`).
11+
412
1.2.1 (2024-03-18)
513
******************
614

Diff for: README.rst

+5-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Flask-Marshmallow
33
*****************
44

5-
|pypi-package| |build-status| |docs| |marshmallow3|
5+
|pypi-package| |build-status| |docs| |marshmallow-support|
66

77
Flask + marshmallow for beautiful APIs
88
======================================
@@ -45,9 +45,8 @@ Define your output format with marshmallow.
4545
4646
4747
class UserSchema(ma.Schema):
48-
class Meta:
49-
# Fields to expose
50-
fields = ("email", "date_created", "_links")
48+
email = ma.Email()
49+
date_created = ma.DateTime()
5150
5251
# Smart hyperlinking
5352
_links = ma.Hyperlinks(
@@ -127,6 +126,6 @@ MIT licensed. See the bundled `LICENSE <https://github.com/marshmallow-code/flas
127126
:target: https://flask-marshmallow.readthedocs.io/
128127
:alt: Documentation
129128

130-
.. |marshmallow3| image:: https://badgen.net/badge/marshmallow/3
129+
.. |marshmallow-support| image:: https://badgen.net/badge/marshmallow/3,4?list=1
131130
:target: https://marshmallow.readthedocs.io/en/latest/upgrading.html
132-
:alt: marshmallow 3 compatible
131+
:alt: marshmallow 3|4 compatible

Diff for: docs/index.rst

+2-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,8 @@ Define your output format with marshmallow.
4949
5050
5151
class UserSchema(ma.Schema):
52-
class Meta:
53-
# Fields to expose
54-
fields = ("email", "date_created", "_links")
52+
email = ma.Email()
53+
date_created = ma.DateTime()
5554
5655
# Smart hyperlinking
5756
_links = ma.Hyperlinks(

Diff for: pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ classifiers = [
1414
"License :: OSI Approved :: MIT License",
1515
"Natural Language :: English",
1616
"Programming Language :: Python :: 3",
17-
"Programming Language :: Python :: 3.8",
1817
"Programming Language :: Python :: 3.9",
1918
"Programming Language :: Python :: 3.10",
2019
"Programming Language :: Python :: 3.11",
2120
"Programming Language :: Python :: 3.12",
21+
"Programming Language :: Python :: 3.13",
2222
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
2323
]
24-
requires-python = ">=3.8"
24+
requires-python = ">=3.9"
2525
dependencies = ["Flask>=2.2", "marshmallow>=3.0.0"]
2626

2727
[project.urls]

Diff for: src/flask_marshmallow/__init__.py

+13-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,15 @@
99
import typing
1010
import warnings
1111

12-
from marshmallow import exceptions, pprint
12+
from marshmallow import exceptions
13+
14+
try:
15+
# Available in marshmallow 3 only
16+
from marshmallow import pprint # noqa: F401
17+
except ImportError:
18+
_has_pprint = False
19+
else:
20+
_has_pprint = True
1321
from marshmallow import fields as base_fields
1422

1523
from . import fields
@@ -41,8 +49,9 @@
4149
"Schema",
4250
"fields",
4351
"exceptions",
44-
"pprint",
4552
]
53+
if _has_pprint:
54+
__all__.append("pprint")
4655

4756
EXTENSION_NAME = "flask-marshmallow"
4857

@@ -75,9 +84,8 @@ class Marshmallow:
7584
You can declare schema like so::
7685
7786
class BookSchema(ma.Schema):
78-
class Meta:
79-
fields = ("id", "title", "author", "links")
80-
87+
id = ma.Integer(dump_only=True)
88+
title = ma.String(required=True)
8189
author = ma.Nested(AuthorSchema)
8290
8391
links = ma.Hyperlinks(

Diff for: src/flask_marshmallow/fields.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
marshmallow library.
99
"""
1010

11+
from __future__ import annotations
12+
1113
import re
1214
import typing
1315
from collections.abc import Sequence
@@ -29,7 +31,7 @@
2931
_tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*")
3032

3133

32-
def _tpl(val: str) -> typing.Optional[str]:
34+
def _tpl(val: str) -> str | None:
3335
"""Return value within ``< >`` if possible, else return ``None``."""
3436
match = _tpl_pattern.match(val)
3537
if match:
@@ -95,7 +97,7 @@ class URLFor(fields.Field):
9597
def __init__(
9698
self,
9799
endpoint: str,
98-
values: typing.Optional[typing.Dict[str, typing.Any]] = None,
100+
values: dict[str, typing.Any] | None = None,
99101
**kwargs,
100102
):
101103
self.endpoint = endpoint
@@ -133,7 +135,7 @@ class AbsoluteURLFor(URLFor):
133135
def __init__(
134136
self,
135137
endpoint: str,
136-
values: typing.Optional[typing.Dict[str, typing.Any]] = None,
138+
values: dict[str, typing.Any] | None = None,
137139
**kwargs,
138140
):
139141
if values:
@@ -146,9 +148,7 @@ def __init__(
146148
AbsoluteUrlFor = AbsoluteURLFor
147149

148150

149-
def _rapply(
150-
d: typing.Union[dict, typing.Iterable], func: typing.Callable, *args, **kwargs
151-
):
151+
def _rapply(d: dict | typing.Iterable, func: typing.Callable, *args, **kwargs):
152152
"""Apply a function to all values in a dictionary or
153153
list of dictionaries, recursively.
154154
"""
@@ -201,7 +201,7 @@ class Hyperlinks(fields.Field):
201201

202202
_CHECK_ATTRIBUTE = False
203203

204-
def __init__(self, schema: typing.Dict[str, typing.Union[URLFor, str]], **kwargs):
204+
def __init__(self, schema: dict[str, URLFor | str], **kwargs):
205205
self.schema = schema
206206
fields.Field.__init__(self, **kwargs)
207207

@@ -229,8 +229,8 @@ def __init__(self, *args, **kwargs):
229229
def deserialize(
230230
self,
231231
value: typing.Any,
232-
attr: typing.Optional[str] = None,
233-
data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
232+
attr: str | None = None,
233+
data: typing.Mapping[str, typing.Any] | None = None,
234234
**kwargs,
235235
):
236236
if isinstance(value, Sequence) and len(value) == 0:

Diff for: src/flask_marshmallow/schema.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from __future__ import annotations
2+
13
import typing
24

35
import flask
@@ -14,8 +16,8 @@ class Schema(ma.Schema):
1416
"""
1517

1618
def jsonify(
17-
self, obj: typing.Any, many: typing.Optional[bool] = None, *args, **kwargs
18-
) -> "Response":
19+
self, obj: typing.Any, many: bool | None = None, *args, **kwargs
20+
) -> Response:
1921
"""Return a JSON response containing the serialized data.
2022
2123

Diff for: src/flask_marshmallow/sqla.py

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
that use the scoped session from Flask-SQLAlchemy.
99
"""
1010

11+
from __future__ import annotations
12+
1113
from urllib import parse
1214

1315
import marshmallow_sqlalchemy as msqla

Diff for: src/flask_marshmallow/validate.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
Custom validation classes for various types of data.
66
"""
77

8+
from __future__ import annotations
9+
810
import io
911
import os
1012
import re
@@ -91,11 +93,11 @@ class ImageSchema(Schema):
9193

9294
def __init__(
9395
self,
94-
min: typing.Optional[str] = None,
95-
max: typing.Optional[str] = None,
96+
min: str | None = None,
97+
max: str | None = None,
9698
min_inclusive: bool = True,
9799
max_inclusive: bool = True,
98-
error: typing.Optional[str] = None,
100+
error: str | None = None,
99101
):
100102
self.min = min
101103
self.max = max
@@ -171,7 +173,7 @@ class ImageSchema(Schema):
171173
def __init__(
172174
self,
173175
accept: typing.Iterable[str],
174-
error: typing.Optional[str] = None,
176+
error: str | None = None,
175177
):
176178
self.allowed_types = {ext.lower() for ext in accept}
177179
self.error = error or self.default_message

Diff for: tests/conftest.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,8 @@ def ma(app):
8484
@pytest.fixture
8585
def schemas(ma):
8686
class AuthorSchema(ma.Schema):
87-
class Meta:
88-
fields = ("id", "name", "absolute_url", "links")
89-
87+
id = ma.Integer()
88+
name = ma.String()
9089
absolute_url = ma.AbsoluteURLFor("author", values={"id": "<id>"})
9190

9291
links = ma.Hyperlinks(
@@ -97,9 +96,8 @@ class Meta:
9796
)
9897

9998
class BookSchema(ma.Schema):
100-
class Meta:
101-
fields = ("id", "title", "author", "links")
102-
99+
id = ma.Integer()
100+
title = ma.String()
103101
author = ma.Nested(AuthorSchema)
104102

105103
links = ma.Hyperlinks(

Diff for: tests/test_sqla.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,10 @@ def book(id):
4848

4949
@pytest.fixture
5050
def db(self, extapp):
51-
return extapp.extensions["sqlalchemy"]
51+
db = extapp.extensions["sqlalchemy"]
52+
yield db
53+
db.session.close()
54+
db.engine.dispose()
5255

5356
@pytest.fixture
5457
def extma(self, extapp):

Diff for: tox.ini

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
[tox]
22
envlist=
33
lint
4-
py{38,39,310,311,312}
5-
py312-marshmallowdev
6-
py38-lowest
4+
py{39,310,311,312,313}
5+
py313-marshmallowdev
6+
py39-lowest
77
docs
88

99
[testenv]

0 commit comments

Comments
 (0)