Skip to content

Commit

Permalink
Support Python 3.9-3.13 and marshmallow 4 (#347)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sloria authored Jan 7, 2025
1 parent 029917b commit d4c087e
Show file tree
Hide file tree
Showing 15 changed files with 70 additions and 49 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ jobs:
fail-fast: false
matrix:
include:
- { name: "3.8", python: "3.8", tox: py38 }
- { name: "3.12", python: "3.12", tox: py312 }
- { name: "lowest", python: "3.8", tox: py38-lowest }
- { name: "dev", python: "3.12", tox: py312-marshmallowdev }
- { name: "3.9", python: "3.9", tox: py39 }
- { name: "3.13", python: "3.13", tox: py313 }
- { name: "lowest", python: "3.9", tox: py39-lowest }
- { name: "dev", python: "3.13", tox: py313-marshmallowdev }
steps:
- uses: actions/[email protected]
- uses: actions/setup-python@v5
Expand All @@ -31,7 +31,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.13"
- name: Install pypa/build
run: python -m pip install build
- name: Build a binary wheel and a source tarball
Expand All @@ -54,7 +54,7 @@ jobs:
- uses: actions/[email protected]
- uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.13"
- run: python -m pip install tox
- run: python -m tox -elint
publish-to-pypi:
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ repos:
rev: 1.19.1
hooks:
- id: blacken-docs
additional_dependencies: [black==23.12.1]
additional_dependencies: [black==24.10.0]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ formats:
build:
os: ubuntu-22.04
tools:
python: "3.11"
python: "3.13"
python:
install:
- method: pip
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
=========

1.3.0 (unreleased)
******************

Support:

* Support Python 3.9-3.13 (:pr:`347`).
* Support marshmallow 4.0.0 (:pr:`347`).

1.2.1 (2024-03-18)
******************

Expand Down
11 changes: 5 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Flask-Marshmallow
*****************

|pypi-package| |build-status| |docs| |marshmallow3|
|pypi-package| |build-status| |docs| |marshmallow-support|

Flask + marshmallow for beautiful APIs
======================================
Expand Down Expand Up @@ -45,9 +45,8 @@ Define your output format with marshmallow.
class UserSchema(ma.Schema):
class Meta:
# Fields to expose
fields = ("email", "date_created", "_links")
email = ma.Email()
date_created = ma.DateTime()
# Smart hyperlinking
_links = ma.Hyperlinks(
Expand Down Expand Up @@ -127,6 +126,6 @@ MIT licensed. See the bundled `LICENSE <https://github.com/marshmallow-code/flas
:target: https://flask-marshmallow.readthedocs.io/
:alt: Documentation

.. |marshmallow3| image:: https://badgen.net/badge/marshmallow/3
.. |marshmallow-support| image:: https://badgen.net/badge/marshmallow/3,4?list=1
:target: https://marshmallow.readthedocs.io/en/latest/upgrading.html
:alt: marshmallow 3 compatible
:alt: marshmallow 3|4 compatible
5 changes: 2 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,8 @@ Define your output format with marshmallow.
class UserSchema(ma.Schema):
class Meta:
# Fields to expose
fields = ("email", "date_created", "_links")
email = ma.Email()
date_created = ma.DateTime()
# Smart hyperlinking
_links = ma.Hyperlinks(
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ classifiers = [
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"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",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
]
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = ["Flask>=2.2", "marshmallow>=3.0.0"]

[project.urls]
Expand Down
18 changes: 13 additions & 5 deletions src/flask_marshmallow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,15 @@
import typing
import warnings

from marshmallow import exceptions, pprint
from marshmallow import exceptions

try:
# Available in marshmallow 3 only
from marshmallow import pprint # noqa: F401
except ImportError:
_has_pprint = False
else:
_has_pprint = True
from marshmallow import fields as base_fields

from . import fields
Expand Down Expand Up @@ -41,8 +49,9 @@
"Schema",
"fields",
"exceptions",
"pprint",
]
if _has_pprint:
__all__.append("pprint")

EXTENSION_NAME = "flask-marshmallow"

Expand Down Expand Up @@ -75,9 +84,8 @@ class Marshmallow:
You can declare schema like so::
class BookSchema(ma.Schema):
class Meta:
fields = ("id", "title", "author", "links")
id = ma.Integer(dump_only=True)
title = ma.String(required=True)
author = ma.Nested(AuthorSchema)
links = ma.Hyperlinks(
Expand Down
18 changes: 9 additions & 9 deletions src/flask_marshmallow/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
marshmallow library.
"""

from __future__ import annotations

import re
import typing
from collections.abc import Sequence
Expand All @@ -29,7 +31,7 @@
_tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*")


def _tpl(val: str) -> typing.Optional[str]:
def _tpl(val: str) -> str | None:
"""Return value within ``< >`` if possible, else return ``None``."""
match = _tpl_pattern.match(val)
if match:
Expand Down Expand Up @@ -95,7 +97,7 @@ class URLFor(fields.Field):
def __init__(
self,
endpoint: str,
values: typing.Optional[typing.Dict[str, typing.Any]] = None,
values: dict[str, typing.Any] | None = None,
**kwargs,
):
self.endpoint = endpoint
Expand Down Expand Up @@ -133,7 +135,7 @@ class AbsoluteURLFor(URLFor):
def __init__(
self,
endpoint: str,
values: typing.Optional[typing.Dict[str, typing.Any]] = None,
values: dict[str, typing.Any] | None = None,
**kwargs,
):
if values:
Expand All @@ -146,9 +148,7 @@ def __init__(
AbsoluteUrlFor = AbsoluteURLFor


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

_CHECK_ATTRIBUTE = False

def __init__(self, schema: typing.Dict[str, typing.Union[URLFor, str]], **kwargs):
def __init__(self, schema: dict[str, URLFor | str], **kwargs):
self.schema = schema
fields.Field.__init__(self, **kwargs)

Expand Down Expand Up @@ -229,8 +229,8 @@ def __init__(self, *args, **kwargs):
def deserialize(
self,
value: typing.Any,
attr: typing.Optional[str] = None,
data: typing.Optional[typing.Mapping[str, typing.Any]] = None,
attr: str | None = None,
data: typing.Mapping[str, typing.Any] | None = None,
**kwargs,
):
if isinstance(value, Sequence) and len(value) == 0:
Expand Down
6 changes: 4 additions & 2 deletions src/flask_marshmallow/schema.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

import typing

import flask
Expand All @@ -14,8 +16,8 @@ class Schema(ma.Schema):
"""

def jsonify(
self, obj: typing.Any, many: typing.Optional[bool] = None, *args, **kwargs
) -> "Response":
self, obj: typing.Any, many: bool | None = None, *args, **kwargs
) -> Response:
"""Return a JSON response containing the serialized data.
Expand Down
2 changes: 2 additions & 0 deletions src/flask_marshmallow/sqla.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
that use the scoped session from Flask-SQLAlchemy.
"""

from __future__ import annotations

from urllib import parse

import marshmallow_sqlalchemy as msqla
Expand Down
10 changes: 6 additions & 4 deletions src/flask_marshmallow/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
Custom validation classes for various types of data.
"""

from __future__ import annotations

import io
import os
import re
Expand Down Expand Up @@ -91,11 +93,11 @@ class ImageSchema(Schema):

def __init__(
self,
min: typing.Optional[str] = None,
max: typing.Optional[str] = None,
min: str | None = None,
max: str | None = None,
min_inclusive: bool = True,
max_inclusive: bool = True,
error: typing.Optional[str] = None,
error: str | None = None,
):
self.min = min
self.max = max
Expand Down Expand Up @@ -171,7 +173,7 @@ class ImageSchema(Schema):
def __init__(
self,
accept: typing.Iterable[str],
error: typing.Optional[str] = None,
error: str | None = None,
):
self.allowed_types = {ext.lower() for ext in accept}
self.error = error or self.default_message
Expand Down
10 changes: 4 additions & 6 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,8 @@ def ma(app):
@pytest.fixture
def schemas(ma):
class AuthorSchema(ma.Schema):
class Meta:
fields = ("id", "name", "absolute_url", "links")

id = ma.Integer()
name = ma.String()
absolute_url = ma.AbsoluteURLFor("author", values={"id": "<id>"})

links = ma.Hyperlinks(
Expand All @@ -97,9 +96,8 @@ class Meta:
)

class BookSchema(ma.Schema):
class Meta:
fields = ("id", "title", "author", "links")

id = ma.Integer()
title = ma.String()
author = ma.Nested(AuthorSchema)

links = ma.Hyperlinks(
Expand Down
5 changes: 4 additions & 1 deletion tests/test_sqla.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ def book(id):

@pytest.fixture
def db(self, extapp):
return extapp.extensions["sqlalchemy"]
db = extapp.extensions["sqlalchemy"]
yield db
db.session.close()
db.engine.dispose()

@pytest.fixture
def extma(self, extapp):
Expand Down
6 changes: 3 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[tox]
envlist=
lint
py{38,39,310,311,312}
py312-marshmallowdev
py38-lowest
py{39,310,311,312,313}
py313-marshmallowdev
py39-lowest
docs

[testenv]
Expand Down

0 comments on commit d4c087e

Please sign in to comment.