flask-marshmallow 1.2.0__py3-none-any.whl → 1.3.0__py3-none-any.whl

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.
@@ -1,14 +1,23 @@
1
1
  """
2
- flask_marshmallow
3
- ~~~~~~~~~~~~~~~~~
2
+ flask_marshmallow
3
+ ~~~~~~~~~~~~~~~~~
4
4
 
5
- Integrates the marshmallow serialization/deserialization library
6
- with your Flask application.
5
+ Integrates the marshmallow serialization/deserialization library
6
+ with your Flask application.
7
7
  """
8
+
8
9
  import typing
9
10
  import warnings
10
11
 
11
- 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
12
21
  from marshmallow import fields as base_fields
13
22
 
14
23
  from . import fields
@@ -40,8 +49,9 @@ __all__ = [
40
49
  "Schema",
41
50
  "fields",
42
51
  "exceptions",
43
- "pprint",
44
52
  ]
53
+ if _has_pprint:
54
+ __all__.append("pprint")
45
55
 
46
56
  EXTENSION_NAME = "flask-marshmallow"
47
57
 
@@ -74,15 +84,16 @@ class Marshmallow:
74
84
  You can declare schema like so::
75
85
 
76
86
  class BookSchema(ma.Schema):
77
- class Meta:
78
- fields = ('id', 'title', 'author', 'links')
79
-
87
+ id = ma.Integer(dump_only=True)
88
+ title = ma.String(required=True)
80
89
  author = ma.Nested(AuthorSchema)
81
90
 
82
- links = ma.Hyperlinks({
83
- 'self': ma.URLFor('book_detail', values=dict(id='<id>')),
84
- 'collection': ma.URLFor('book_list')
85
- })
91
+ links = ma.Hyperlinks(
92
+ {
93
+ "self": ma.URLFor("book_detail", values=dict(id="<id>")),
94
+ "collection": ma.URLFor("book_list"),
95
+ }
96
+ )
86
97
 
87
98
 
88
99
  In order to integrate with Flask-SQLAlchemy, this extension must be initialized
@@ -1,14 +1,18 @@
1
1
  """
2
- flask_marshmallow.fields
3
- ~~~~~~~~~~~~~~~~~~~~~~~~
2
+ flask_marshmallow.fields
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~
4
4
 
5
- Custom, Flask-specific fields.
5
+ Custom, Flask-specific fields.
6
6
 
7
- See the `marshmallow.fields` module for the list of all fields available from the
8
- marshmallow library.
7
+ See the `marshmallow.fields` module for the list of all fields available from the
8
+ marshmallow library.
9
9
  """
10
+
11
+ from __future__ import annotations
12
+
10
13
  import re
11
14
  import typing
15
+ from collections.abc import Sequence
12
16
 
13
17
  from flask import current_app, url_for
14
18
  from marshmallow import fields, missing
@@ -27,7 +31,7 @@ __all__ = [
27
31
  _tpl_pattern = re.compile(r"\s*<\s*(\S*)\s*>\s*")
28
32
 
29
33
 
30
- def _tpl(val: str) -> typing.Optional[str]:
34
+ def _tpl(val: str) -> str | None:
31
35
  """Return value within ``< >`` if possible, else return ``None``."""
32
36
  match = _tpl_pattern.match(val)
33
37
  if match:
@@ -75,10 +79,10 @@ class URLFor(fields.Field):
75
79
 
76
80
  Usage: ::
77
81
 
78
- url = URLFor('author_get', values=dict(id='<id>'))
82
+ url = URLFor("author_get", values=dict(id="<id>"))
79
83
  https_url = URLFor(
80
- 'author_get',
81
- values=dict(id='<id>', _scheme='https', _external=True),
84
+ "author_get",
85
+ values=dict(id="<id>", _scheme="https", _external=True),
82
86
  )
83
87
 
84
88
  :param str endpoint: Flask endpoint name.
@@ -93,7 +97,7 @@ class URLFor(fields.Field):
93
97
  def __init__(
94
98
  self,
95
99
  endpoint: str,
96
- values: typing.Optional[typing.Dict[str, typing.Any]] = None,
100
+ values: dict[str, typing.Any] | None = None,
97
101
  **kwargs,
98
102
  ):
99
103
  self.endpoint = endpoint
@@ -131,7 +135,7 @@ class AbsoluteURLFor(URLFor):
131
135
  def __init__(
132
136
  self,
133
137
  endpoint: str,
134
- values: typing.Optional[typing.Dict[str, typing.Any]] = None,
138
+ values: dict[str, typing.Any] | None = None,
135
139
  **kwargs,
136
140
  ):
137
141
  if values:
@@ -144,9 +148,7 @@ class AbsoluteURLFor(URLFor):
144
148
  AbsoluteUrlFor = AbsoluteURLFor
145
149
 
146
150
 
147
- def _rapply(
148
- d: typing.Union[dict, typing.Iterable], func: typing.Callable, *args, **kwargs
149
- ):
151
+ def _rapply(d: dict | typing.Iterable, func: typing.Callable, *args, **kwargs):
150
152
  """Apply a function to all values in a dictionary or
151
153
  list of dictionaries, recursively.
152
154
  """
@@ -175,19 +177,23 @@ class Hyperlinks(fields.Field):
175
177
 
176
178
  Example: ::
177
179
 
178
- _links = Hyperlinks({
179
- 'self': URLFor('author', values=dict(id='<id>')),
180
- 'collection': URLFor('author_list'),
181
- })
180
+ _links = Hyperlinks(
181
+ {
182
+ "self": URLFor("author", values=dict(id="<id>")),
183
+ "collection": URLFor("author_list"),
184
+ }
185
+ )
182
186
 
183
187
  `URLFor` objects can be nested within the dictionary. ::
184
188
 
185
- _links = Hyperlinks({
186
- 'self': {
187
- 'href': URLFor('book', values=dict(id='<id>')),
188
- 'title': 'book detail'
189
+ _links = Hyperlinks(
190
+ {
191
+ "self": {
192
+ "href": URLFor("book", values=dict(id="<id>")),
193
+ "title": "book detail",
194
+ }
189
195
  }
190
- })
196
+ )
191
197
 
192
198
  :param dict schema: A dict that maps names to
193
199
  :class:`~flask_marshmallow.fields.URLFor` fields.
@@ -195,7 +201,7 @@ class Hyperlinks(fields.Field):
195
201
 
196
202
  _CHECK_ATTRIBUTE = False
197
203
 
198
- def __init__(self, schema: typing.Dict[str, typing.Union[URLFor, str]], **kwargs):
204
+ def __init__(self, schema: dict[str, URLFor | str], **kwargs):
199
205
  self.schema = schema
200
206
  fields.Field.__init__(self, **kwargs)
201
207
 
@@ -220,6 +226,17 @@ class File(fields.Field):
220
226
 
221
227
  default_error_messages = {"invalid": "Not a valid file."}
222
228
 
229
+ def deserialize(
230
+ self,
231
+ value: typing.Any,
232
+ attr: str | None = None,
233
+ data: typing.Mapping[str, typing.Any] | None = None,
234
+ **kwargs,
235
+ ):
236
+ if isinstance(value, Sequence) and len(value) == 0:
237
+ value = missing
238
+ return super().deserialize(value, attr, data, **kwargs)
239
+
223
240
  def _deserialize(self, value, attr, data, **kwargs):
224
241
  from werkzeug.datastructures import FileStorage
225
242
 
@@ -236,11 +253,12 @@ class Config(fields.Field):
236
253
  from flask import Flask
237
254
 
238
255
  app = Flask(__name__)
239
- app.config['API_TITLE'] = 'Pet API'
256
+ app.config["API_TITLE"] = "Pet API"
257
+
240
258
 
241
259
  class FooSchema(Schema):
242
260
  user = String()
243
- title = Config('API_TITLE')
261
+ title = Config("API_TITLE")
244
262
 
245
263
  This field should only be used in an output schema. A ``ValueError`` will
246
264
  be raised if the config key is not found in the app config.
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import typing
2
4
 
3
5
  import flask
@@ -14,8 +16,8 @@ class Schema(ma.Schema):
14
16
  """
15
17
 
16
18
  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:
19
21
  """Return a JSON response containing the serialized data.
20
22
 
21
23
 
flask_marshmallow/sqla.py CHANGED
@@ -1,12 +1,15 @@
1
1
  """
2
- flask_marshmallow.sqla
3
- ~~~~~~~~~~~~~~~~~~~~~~
2
+ flask_marshmallow.sqla
3
+ ~~~~~~~~~~~~~~~~~~~~~~
4
4
 
5
- Integration with Flask-SQLAlchemy and marshmallow-sqlalchemy. Provides
6
- `SQLAlchemySchema <marshmallow_sqlalchemy.SQLAlchemySchema>` and
7
- `SQLAlchemyAutoSchema <marshmallow_sqlalchemy.SQLAlchemyAutoSchema>` classes
8
- that use the scoped session from Flask-SQLAlchemy.
5
+ Integration with Flask-SQLAlchemy and marshmallow-sqlalchemy. Provides
6
+ `SQLAlchemySchema <marshmallow_sqlalchemy.SQLAlchemySchema>` and
7
+ `SQLAlchemyAutoSchema <marshmallow_sqlalchemy.SQLAlchemyAutoSchema>` classes
8
+ that use the scoped session from Flask-SQLAlchemy.
9
9
  """
10
+
11
+ from __future__ import annotations
12
+
10
13
  from urllib import parse
11
14
 
12
15
  import marshmallow_sqlalchemy as msqla
@@ -1,12 +1,17 @@
1
1
  """
2
- flask_marshmallow.validate
3
- ~~~~~~~~~~~~~~~~~~~~~~~~~~
2
+ flask_marshmallow.validate
3
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
4
4
 
5
- Custom validation classes for various types of data.
5
+ Custom validation classes for various types of data.
6
6
  """
7
+
8
+ from __future__ import annotations
9
+
10
+ import io
7
11
  import os
8
12
  import re
9
13
  import typing
14
+ from tempfile import SpooledTemporaryFile
10
15
 
11
16
  from marshmallow.exceptions import ValidationError
12
17
  from marshmallow.validate import Validator as Validator
@@ -15,7 +20,15 @@ from werkzeug.datastructures import FileStorage
15
20
 
16
21
  def _get_filestorage_size(file: FileStorage) -> int:
17
22
  """Return the size of the FileStorage object in bytes."""
18
- size: int = file.stream.getbuffer().nbytes # type: ignore
23
+ stream = file.stream
24
+ if isinstance(stream, io.BytesIO):
25
+ return stream.getbuffer().nbytes
26
+
27
+ if isinstance(stream, SpooledTemporaryFile):
28
+ return os.stat(stream.fileno()).st_size
29
+
30
+ size = len(file.read())
31
+ file.stream.seek(0)
19
32
  return size
20
33
 
21
34
 
@@ -57,7 +70,7 @@ class FileSize(Validator):
57
70
  Example: ::
58
71
 
59
72
  class ImageSchema(Schema):
60
- image = File(required=True, validate=FileSize(min='1 MiB', max='2 MiB'))
73
+ image = File(required=True, validate=FileSize(min="1 MiB", max="2 MiB"))
61
74
 
62
75
  :param min: The minimum size (lower bound). If not provided, minimum
63
76
  size will not be checked.
@@ -80,11 +93,11 @@ class FileSize(Validator):
80
93
 
81
94
  def __init__(
82
95
  self,
83
- min: typing.Optional[str] = None,
84
- max: typing.Optional[str] = None,
96
+ min: str | None = None,
97
+ max: str | None = None,
85
98
  min_inclusive: bool = True,
86
99
  max_inclusive: bool = True,
87
- error: typing.Optional[str] = None,
100
+ error: str | None = None,
88
101
  ):
89
102
  self.min = min
90
103
  self.max = max
@@ -106,8 +119,10 @@ class FileSize(Validator):
106
119
  )
107
120
 
108
121
  def _repr_args(self):
109
- return "min={!r}, max={!r}, min_inclusive={!r}, max_inclusive={!r}".format(
110
- self.min, self.max, self.min_inclusive, self.max_inclusive
122
+ return (
123
+ f"min={self.min!r}, max={self.max!r}, "
124
+ f"min_inclusive={self.min_inclusive!r}, "
125
+ f"max_inclusive={self.max_inclusive!r}"
111
126
  )
112
127
 
113
128
  def _format_error(self, value, message):
@@ -141,11 +156,12 @@ class FileSize(Validator):
141
156
 
142
157
  class FileType(Validator):
143
158
  """Validator which succeeds if the uploaded file is allowed by a given list
144
- of extensions.
159
+ of extensions.
145
160
 
146
161
  Example: ::
162
+
147
163
  class ImageSchema(Schema):
148
- image = File(required=True, validate=FileType(['.png']))
164
+ image = File(required=True, validate=FileType([".png"]))
149
165
 
150
166
  :param accept: A sequence of allowed extensions.
151
167
  :param error: Error message to raise in case of a validation error.
@@ -157,7 +173,7 @@ class FileType(Validator):
157
173
  def __init__(
158
174
  self,
159
175
  accept: typing.Iterable[str],
160
- error: typing.Optional[str] = None,
176
+ error: str | None = None,
161
177
  ):
162
178
  self.allowed_types = {ext.lower() for ext in accept}
163
179
  self.error = error or self.default_message
@@ -1,29 +1,29 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: flask-marshmallow
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: Flask + marshmallow for beautiful APIs
5
5
  Maintainer-email: Steven Loria <sloria1@gmail.com>, Stephen Rosen <sirosen0@gmail.com>
6
- Requires-Python: >=3.8
6
+ Requires-Python: >=3.9
7
7
  Description-Content-Type: text/x-rst
8
8
  Classifier: Environment :: Web Environment
9
9
  Classifier: Intended Audience :: Developers
10
10
  Classifier: License :: OSI Approved :: MIT License
11
11
  Classifier: Natural Language :: English
12
12
  Classifier: Programming Language :: Python :: 3
13
- Classifier: Programming Language :: Python :: 3.8
14
13
  Classifier: Programming Language :: Python :: 3.9
15
14
  Classifier: Programming Language :: Python :: 3.10
16
15
  Classifier: Programming Language :: Python :: 3.11
17
16
  Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
18
  Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
19
19
  Requires-Dist: Flask>=2.2
20
20
  Requires-Dist: marshmallow>=3.0.0
21
21
  Requires-Dist: flask-marshmallow[tests] ; extra == "dev"
22
22
  Requires-Dist: tox ; extra == "dev"
23
- Requires-Dist: pre-commit~=3.5 ; extra == "dev"
23
+ Requires-Dist: pre-commit>=3.5,<5.0 ; extra == "dev"
24
24
  Requires-Dist: marshmallow-sqlalchemy>=0.19.0 ; extra == "docs"
25
- Requires-Dist: Sphinx==7.2.6 ; extra == "docs"
26
- Requires-Dist: sphinx-issues==4.0.0 ; extra == "docs"
25
+ Requires-Dist: Sphinx==8.1.3 ; extra == "docs"
26
+ Requires-Dist: sphinx-issues==5.0.0 ; extra == "docs"
27
27
  Requires-Dist: flask-sqlalchemy>=3.0.0 ; extra == "sqlalchemy"
28
28
  Requires-Dist: marshmallow-sqlalchemy>=0.29.0 ; extra == "sqlalchemy"
29
29
  Requires-Dist: flask-marshmallow[sqlalchemy] ; extra == "tests"
@@ -39,7 +39,7 @@ Provides-Extra: tests
39
39
  Flask-Marshmallow
40
40
  *****************
41
41
 
42
- |pypi-package| |build-status| |docs| |marshmallow3|
42
+ |pypi-package| |build-status| |docs| |marshmallow-support|
43
43
 
44
44
  Flask + marshmallow for beautiful APIs
45
45
  ======================================
@@ -82,9 +82,8 @@ Define your output format with marshmallow.
82
82
 
83
83
 
84
84
  class UserSchema(ma.Schema):
85
- class Meta:
86
- # Fields to expose
87
- fields = ("email", "date_created", "_links")
85
+ email = ma.Email()
86
+ date_created = ma.DateTime()
88
87
 
89
88
  # Smart hyperlinking
90
89
  _links = ma.Hyperlinks(
@@ -164,7 +163,7 @@ MIT licensed. See the bundled `LICENSE <https://github.com/marshmallow-code/flas
164
163
  :target: https://flask-marshmallow.readthedocs.io/
165
164
  :alt: Documentation
166
165
 
167
- .. |marshmallow3| image:: https://badgen.net/badge/marshmallow/3
166
+ .. |marshmallow-support| image:: https://badgen.net/badge/marshmallow/3,4?list=1
168
167
  :target: https://marshmallow.readthedocs.io/en/latest/upgrading.html
169
- :alt: marshmallow 3 compatible
168
+ :alt: marshmallow 3|4 compatible
170
169
 
@@ -0,0 +1,10 @@
1
+ flask_marshmallow/__init__.py,sha256=koKpA3E3MtgQ8SomgVv0pVHoYkgPDqVoFbEby4Dswd0,3871
2
+ flask_marshmallow/fields.py,sha256=gKFJuIpo6jYCBe3WpeDrvhP1EUozakx3WXYisQGubf0,7869
3
+ flask_marshmallow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ flask_marshmallow/schema.py,sha256=qC6jF_peb8WvQAJpe14MQaLmqRahAReoP7GKcC4nNfI,1436
5
+ flask_marshmallow/sqla.py,sha256=RXiV5EZwg8g3XWN5ZsqOK3xrhmZ5FlgeVmzwDExC1LU,3984
6
+ flask_marshmallow/validate.py,sha256=MNFcCBHwSEkfZGadr7WvwG6dF4aQ1s6yWYhxIk2JmWc,6812
7
+ flask_marshmallow-1.3.0.dist-info/LICENSE,sha256=kGtdkFHkJhRMsXOtkRZnuOvQWpxYTCwmwTWzKj7RIAE,1064
8
+ flask_marshmallow-1.3.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
9
+ flask_marshmallow-1.3.0.dist-info/METADATA,sha256=R-Z0GAkzvbVQyAeS105faz5qmyYt1F_3CotHUwEdR6k,5177
10
+ flask_marshmallow-1.3.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: flit 3.9.0
2
+ Generator: flit 3.10.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,10 +0,0 @@
1
- flask_marshmallow/__init__.py,sha256=2s46vkrP0wX1wXBT0OF9oTUNIaoX5-uu_AogfhGXupg,3650
2
- flask_marshmallow/fields.py,sha256=0_mk3dKpyDKT66Tp4KoDZ0uNCxmvGVH3Rk4OAwjR8f4,7486
3
- flask_marshmallow/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- flask_marshmallow/schema.py,sha256=Z6uJVkqi-gq1QfdPbPuy5Fw3UnXGE9vKiD6IJkPOMR4,1412
5
- flask_marshmallow/sqla.py,sha256=QKEpKDfrBJ5m1KpnRuEB5Txfxql-aN_pyHznYxzNd6Y,3971
6
- flask_marshmallow/validate.py,sha256=cJDooGyaEqhTmnIBEpOKAR8Y-WzPcNdvtDcAZUCI030,6565
7
- flask_marshmallow-1.2.0.dist-info/LICENSE,sha256=kGtdkFHkJhRMsXOtkRZnuOvQWpxYTCwmwTWzKj7RIAE,1064
8
- flask_marshmallow-1.2.0.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
9
- flask_marshmallow-1.2.0.dist-info/METADATA,sha256=SnUTf1VP6m6L83PHJK6QZ6tbsQwuCP4yAAvLwqPdMf0,5190
10
- flask_marshmallow-1.2.0.dist-info/RECORD,,