Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-marshmallow for openSUSE:Factory checked in at 2023-12-11 21:49:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-marshmallow (Old) and /work/SRC/openSUSE:Factory/.python-marshmallow.new.25432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-marshmallow" Mon Dec 11 21:49:40 2023 rev:23 rq:1132365 version:3.20.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-marshmallow/python-marshmallow.changes 2023-05-26 20:15:35.840306959 +0200 +++ /work/SRC/openSUSE:Factory/.python-marshmallow.new.25432/python-marshmallow.changes 2023-12-11 21:49:41.593621335 +0100 @@ -1,0 +2,16 @@ +Sun Dec 10 21:27:36 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 3.20.1: + * Fix call to ``get_declared_fields``: pass ``dict_cls`` again + * Add ``absolute`` parameter to ``URL`` validator and ``Url`` + * Use Abstract Base Classes to define ``FieldABC`` and + ``SchemaABC`` + * Use `OrderedSet` as default `set_class`. Schemas are now + ordered by default. + * Handle ``OSError`` and ``OverflowError`` in + ``utils.from_timestamp`` (:pr:`2102`). + * Fix the default inheritance of nested partial schemas + * Officially support Python 3.11 (:pr:`2067`). + * Drop support for Python 3.7 (:pr:`2135`). + +------------------------------------------------------------------- Old: ---- marshmallow-3.19.0.tar.gz New: ---- marshmallow-3.20.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-marshmallow.spec ++++++ --- /var/tmp/diff_new_pack.yftqPI/_old 2023-12-11 21:49:43.297684441 +0100 +++ /var/tmp/diff_new_pack.yftqPI/_new 2023-12-11 21:49:43.297684441 +0100 @@ -16,10 +16,9 @@ # -%{?!python_module:%define python_module() python3-%{**}} %{?sle15_python_module_pythons} Name: python-marshmallow -Version: 3.19.0 +Version: 3.20.1 Release: 0 Summary: ORM/ODM/framework-agnostic library to convert datatypes from/to Python types License: BSD-3-Clause AND MIT @@ -29,7 +28,7 @@ # https://github.com/humitos/sphinx-version-warning/issues/22 Patch0: python-marshmallow-no-version-warning.patch BuildRequires: %{python_module autodocsumm} -BuildRequires: %{python_module base >= 3.7} +BuildRequires: %{python_module base >= 3.8} BuildRequires: %{python_module setuptools} BuildRequires: fdupes BuildRequires: python-rpm-macros ++++++ marshmallow-3.19.0.tar.gz -> marshmallow-3.20.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/AUTHORS.rst new/marshmallow-3.20.1/AUTHORS.rst --- old/marshmallow-3.19.0/AUTHORS.rst 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/AUTHORS.rst 2023-07-21 00:08:04.000000000 +0200 @@ -171,3 +171,5 @@ - Karthikeyan Singaravelan `@tirkarthi <https://github.com/tirkarthi>`_ - Marco Satti `@marcosatti <https://github.com/marcosatti>`_ - Ivo Reumkens `@vanHoi <https://github.com/vanHoi>`_ +- Aditya Tewary `@aditkumar72 <https://github.com/aditkumar72>`_ +- Sebastien Lovergne `@TheBigRoomXXL <https://github.com/TheBigRoomXXL>`_ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/CHANGELOG.rst new/marshmallow-3.20.1/CHANGELOG.rst --- old/marshmallow-3.19.0/CHANGELOG.rst 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/CHANGELOG.rst 2023-07-21 00:08:04.000000000 +0200 @@ -1,12 +1,44 @@ Changelog --------- +3.20.1 (2023-07-20) +******************* + +Bug fixes: + +- Fix call to ``get_declared_fields``: pass ``dict_cls`` again (:issue:`2152`). + Thanks :user:`Cheaterman` for reporting. + +3.20.0 (2023-07-20) +******************* + +Features: + +- Add ``absolute`` parameter to ``URL`` validator and ``Url`` field (:pr:`2123`). + Thanks :user:`sirosen` for the PR. +- Use Abstract Base Classes to define ``FieldABC`` and ``SchemaABC`` + (:issue:`1449`). Thanks :user:`aditkumar72` for the PR. +- Use `OrderedSet` as default `set_class`. Schemas are now ordered by default. + (:issue:`1744`) + +Bug fixes: + +- Handle ``OSError`` and ``OverflowError`` in ``utils.from_timestamp`` (:pr:`2102`). + Thanks :user:`TheBigRoomXXL` for the PR. +- Fix the default inheritance of nested partial schemas (:issue:`2149`). + Thanks :user:`matejsp` for reporting. + +Other changes: + +- Officially support Python 3.11 (:pr:`2067`). +- Drop support for Python 3.7 (:pr:`2135`). + 3.19.0 (2022-11-11) ******************* Features: -- Add ``timestamp`` and ``timestamp_ms`` formats to `fields.DateTime` +- Add ``timestamp`` and ``timestamp_ms`` formats to ``fields.DateTime`` (:issue:`612`). Thanks :user:`vgavro` for the suggestion and thanks :user:`vanHoi` for the PR. @@ -93,7 +125,7 @@ Other changes: -- Fix type-hints for ```data``` arg in ```Schema.validate``` to accept +- Fix type-hints for ``data`` arg in ``Schema.validate`` to accept list of dictionaries (:issue:`1790`, :pr:`1868`). Thanks :user:`yourun-proger` for PR. - Improve warning when passing metadata as keyword arguments (:pr:`1882`). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/PKG-INFO new/marshmallow-3.20.1/PKG-INFO --- old/marshmallow-3.19.0/PKG-INFO 2022-11-11 17:10:43.928643500 +0100 +++ new/marshmallow-3.20.1/PKG-INFO 2023-07-21 00:08:15.534859200 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: marshmallow -Version: 3.19.0 +Version: 3.20.1 Summary: A lightweight library for converting complex datatypes to and from native Python datatypes. Home-page: https://github.com/marshmallow-code/marshmallow Author: Steven Loria @@ -15,11 +15,11 @@ Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 -Requires-Python: >=3.7 +Classifier: Programming Language :: Python :: 3.11 +Requires-Python: >=3.8 Provides-Extra: tests Provides-Extra: lint Provides-Extra: docs @@ -34,10 +34,14 @@ :target: https://pypi.org/project/marshmallow/ :alt: Latest version -.. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.marshmallow?branchName=dev - :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=5&branchName=dev +.. image:: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml/badge.svg + :target: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml :alt: Build status +.. image:: https://results.pre-commit.ci/badge/github/marshmallow-code/marshmallow/dev.svg + :target: https://results.pre-commit.ci/latest/github/marshmallow-code/marshmallow/dev + :alt: pre-commit.ci status + .. image:: https://readthedocs.org/projects/marshmallow/badge/ :target: https://marshmallow.readthedocs.io/ :alt: Documentation @@ -100,7 +104,7 @@ Requirements ============ -- Python >= 3.7 +- Python >= 3.8 Ecosystem ========= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/README.rst new/marshmallow-3.20.1/README.rst --- old/marshmallow-3.19.0/README.rst 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/README.rst 2023-07-21 00:08:04.000000000 +0200 @@ -6,10 +6,14 @@ :target: https://pypi.org/project/marshmallow/ :alt: Latest version -.. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.marshmallow?branchName=dev - :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=5&branchName=dev +.. image:: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml/badge.svg + :target: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml :alt: Build status +.. image:: https://results.pre-commit.ci/badge/github/marshmallow-code/marshmallow/dev.svg + :target: https://results.pre-commit.ci/latest/github/marshmallow-code/marshmallow/dev + :alt: pre-commit.ci status + .. image:: https://readthedocs.org/projects/marshmallow/badge/ :target: https://marshmallow.readthedocs.io/ :alt: Documentation @@ -72,7 +76,7 @@ Requirements ============ -- Python >= 3.7 +- Python >= 3.8 Ecosystem ========= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/docs/conf.py new/marshmallow-3.20.1/docs/conf.py --- old/marshmallow-3.19.0/docs/conf.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/docs/conf.py 2023-07-21 00:08:04.000000000 +0200 @@ -81,14 +81,14 @@ } html_sidebars = { - "index": ["about.html", "donate.html", "useful-links.html", "searchbox.html"], + "index": ["about.html", "searchbox.html", "donate.html", "useful-links.html"], "**": [ "about.html", + "searchbox.html", "donate.html", "useful-links.html", "localtoc.html", "relations.html", - "searchbox.html", ], } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/docs/install.rst new/marshmallow-3.20.1/docs/install.rst --- old/marshmallow-3.19.0/docs/install.rst 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/docs/install.rst 2023-07-21 00:08:04.000000000 +0200 @@ -3,7 +3,7 @@ Installation ============ -**marshmallow** requires Python >= 3.7. It has no external dependencies other than the `packaging` library. +**marshmallow** requires Python >= 3.8. It has no external dependencies other than the `packaging` library. Installing/Upgrading from the PyPI ---------------------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/docs/quickstart.rst new/marshmallow-3.20.1/docs/quickstart.rst --- old/marshmallow-3.19.0/docs/quickstart.rst 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/docs/quickstart.rst 2023-07-21 00:08:04.000000000 +0200 @@ -524,37 +524,6 @@ # No need to include 'uppername' additional = ("name", "email", "created_at") -Ordering Output ---------------- - -To maintain field ordering, set the ``ordered`` option to `True`. This will instruct marshmallow to serialize data to a `collections.OrderedDict`. - -.. code-block:: python - - from collections import OrderedDict - from pprint import pprint - - from marshmallow import Schema, fields - - - class UserSchema(Schema): - first_name = fields.String() - last_name = fields.String() - email = fields.Email() - - class Meta: - ordered = True - - - u = User("Charlie", "Stones", "char...@stones.com") - schema = UserSchema() - result = schema.dump(u) - assert isinstance(result, OrderedDict) - pprint(result, indent=2) - #  OrderedDict([('first_name', 'Charlie'), - # ('last_name', 'Stones'), - # ('email', 'char...@stones.com')]) - Next Steps ---------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/pyproject.toml new/marshmallow-3.20.1/pyproject.toml --- old/marshmallow-3.19.0/pyproject.toml 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/pyproject.toml 2023-07-21 00:08:04.000000000 +0200 @@ -1,3 +1,3 @@ [tool.black] line-length = 88 -target-version = ['py37', 'py38', 'py39', 'py310'] +target-version = ['py38', 'py39', 'py310', 'py311'] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/setup.cfg new/marshmallow-3.20.1/setup.cfg --- old/marshmallow-3.19.0/setup.cfg 2022-11-11 17:10:43.932643400 +0100 +++ new/marshmallow-3.20.1/setup.cfg 2023-07-21 00:08:15.534859200 +0200 @@ -2,10 +2,9 @@ license_files = LICENSE [flake8] -extend-ignore = E203, E266, E501, E731, B903 max-line-length = 90 max-complexity = 18 -select = B,C,E,F,W,T4,B9 +extend-ignore = E203, E266, E501, E731, B903 [tool:pytest] norecursedirs = .git .ropeproject .tox docs env venv tests/mypy_test_cases diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/setup.py new/marshmallow-3.20.1/setup.py --- old/marshmallow-3.19.0/setup.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/setup.py 2023-07-21 00:08:04.000000000 +0200 @@ -4,17 +4,17 @@ EXTRAS_REQUIRE = { "tests": ["pytest", "pytz", "simplejson"], "lint": [ - "mypy==0.990", - "flake8==5.0.4", - "flake8-bugbear==22.10.25", - "pre-commit~=2.4", + "mypy==1.4.1", + "flake8==6.0.0", + "flake8-bugbear==23.7.10", + "pre-commit>=2.4,<4.0", ], "docs": [ - "sphinx==5.3.0", + "sphinx==7.0.1", "sphinx-issues==3.0.1", - "alabaster==0.7.12", + "alabaster==0.7.13", "sphinx-version-warning==1.1.2", - "autodocsumm==0.2.9", + "autodocsumm==0.2.11", ], } EXTRAS_REQUIRE["dev"] = EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["lint"] + ["tox"] @@ -73,16 +73,16 @@ "validation", "schema", ], - python_requires=">=3.7", + python_requires=">=3.8", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ], test_suite="tests", project_urls={ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/__init__.py new/marshmallow-3.20.1/src/marshmallow/__init__.py --- old/marshmallow-3.19.0/src/marshmallow/__init__.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/__init__.py 2023-07-21 00:08:04.000000000 +0200 @@ -16,7 +16,7 @@ from . import fields -__version__ = "3.19.0" +__version__ = "3.20.1" __parsed_version__ = Version(__version__) __version_info__: tuple[int, int, int] | tuple[ int, int, int, str, int diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/base.py new/marshmallow-3.20.1/src/marshmallow/base.py --- old/marshmallow-3.19.0/src/marshmallow/base.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/base.py 2023-07-21 00:08:04.000000000 +0200 @@ -1,6 +1,6 @@ """Abstract base classes. -These are necessary to avoid circular imports between core.py and fields.py. +These are necessary to avoid circular imports between schema.py and fields.py. .. warning:: @@ -8,40 +8,49 @@ Users should not need to use this module directly. """ from __future__ import annotations +from abc import ABC, abstractmethod -class FieldABC: +class FieldABC(ABC): """Abstract base class from which all Field classes inherit.""" parent = None name = None root = None + @abstractmethod def serialize(self, attr, obj, accessor=None): - raise NotImplementedError + pass + @abstractmethod def deserialize(self, value): - raise NotImplementedError + pass + @abstractmethod def _serialize(self, value, attr, obj, **kwargs): - raise NotImplementedError + pass + @abstractmethod def _deserialize(self, value, attr, data, **kwargs): - raise NotImplementedError + pass -class SchemaABC: +class SchemaABC(ABC): """Abstract base class from which all Schemas inherit.""" + @abstractmethod def dump(self, obj, *, many: bool | None = None): - raise NotImplementedError + pass + @abstractmethod def dumps(self, obj, *, many: bool | None = None): - raise NotImplementedError + pass + @abstractmethod def load(self, data, *, many: bool | None = None, partial=None, unknown=None): - raise NotImplementedError + pass + @abstractmethod def loads( self, json_data, @@ -51,4 +60,4 @@ unknown=None, **kwargs, ): - raise NotImplementedError + pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/decorators.py new/marshmallow-3.20.1/src/marshmallow/decorators.py --- old/marshmallow-3.19.0/src/marshmallow/decorators.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/decorators.py 2023-07-21 00:08:04.000000000 +0200 @@ -61,7 +61,7 @@ from __future__ import annotations import functools -from typing import Any, Callable, Dict, Optional, Tuple, Union, cast +from typing import Any, Callable, cast PRE_DUMP = "pre_dump" POST_DUMP = "post_dump" @@ -72,9 +72,7 @@ class MarshmallowHook: - __marshmallow_hook__ = ( - None - ) # type: Optional[Dict[Union[Tuple[str, bool], str], Any]] + __marshmallow_hook__: dict[tuple[str, bool] | str, Any] | None = None def validates(field_name: str) -> Callable[..., Any]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/fields.py new/marshmallow-3.20.1/src/marshmallow/fields.py --- old/marshmallow-3.19.0/src/marshmallow/fields.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/fields.py 2023-07-21 00:08:04.000000000 +0200 @@ -137,7 +137,6 @@ # to exist as attributes on the objects to serialize. Set this to False # for those fields _CHECK_ATTRIBUTE = True - _creation_index = 0 # Used for sorting #: Default error messages for various kinds of errors. The keys in this dictionary #: are passed to `Field.make_error`. The values are error messages passed to @@ -157,9 +156,9 @@ default: typing.Any = missing_, data_key: str | None = None, attribute: str | None = None, - validate: None - | ( - typing.Callable[[typing.Any], typing.Any] + validate: ( + None + | typing.Callable[[typing.Any], typing.Any] | typing.Iterable[typing.Callable[[typing.Any], typing.Any]] ) = None, required: bool = False, @@ -227,9 +226,6 @@ stacklevel=2, ) - self._creation_index = Field._creation_index - Field._creation_index += 1 - # Collect default error message from self and parent classes messages = {} # type: dict[str, str] for cls in reversed(self.__class__.__mro__): @@ -920,8 +916,7 @@ try: if isinstance(value, bytes) and len(value) == 16: return uuid.UUID(bytes=value) - else: - return uuid.UUID(value) + return uuid.UUID(value) except (ValueError, AttributeError, TypeError) as error: raise self.make_error("invalid_uuid") from error @@ -1281,28 +1276,21 @@ format_func = self.SERIALIZATION_FUNCS.get(data_format) if format_func: return format_func(value) - else: - return value.strftime(data_format) + return value.strftime(data_format) def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime: if not value: # Falsy values, e.g. '', None, [] are not valid raise self.make_error("invalid", input=value, obj_type=self.OBJ_TYPE) data_format = self.format or self.DEFAULT_FORMAT func = self.DESERIALIZATION_FUNCS.get(data_format) - if func: - try: + try: + if func: return func(value) - except (TypeError, AttributeError, ValueError) as error: - raise self.make_error( - "invalid", input=value, obj_type=self.OBJ_TYPE - ) from error - else: - try: - return self._make_object_from_format(value, data_format) - except (TypeError, AttributeError, ValueError) as error: - raise self.make_error( - "invalid", input=value, obj_type=self.OBJ_TYPE - ) from error + return self._make_object_from_format(value, data_format) + except (TypeError, AttributeError, ValueError) as error: + raise self.make_error( + "invalid", input=value, obj_type=self.OBJ_TYPE + ) from error @staticmethod def _make_object_from_format(value, data_format) -> dt.datetime: @@ -1525,9 +1513,8 @@ delta = utils.timedelta_to_microseconds(value) unit = utils.timedelta_to_microseconds(base_unit) return delta // unit - else: - assert self.serialization_type is float - return value.total_seconds() / base_unit.total_seconds() + assert self.serialization_type is float + return value.total_seconds() / base_unit.total_seconds() def _deserialize(self, value, attr, data, **kwargs): try: @@ -1710,6 +1697,7 @@ self, *, relative: bool = False, + absolute: bool = True, schemes: types.StrSequenceOrSet | None = None, require_tld: bool = True, **kwargs, @@ -1717,10 +1705,12 @@ super().__init__(**kwargs) self.relative = relative + self.absolute = absolute self.require_tld = require_tld # Insert validation into self.validators so that multiple errors can be stored. validator = validate.URL( relative=self.relative, + absolute=self.absolute, schemes=schemes, require_tld=self.require_tld, error=self.error_messages["invalid"], @@ -2024,14 +2014,14 @@ def __init__( self, - serialize: None - | ( - typing.Callable[[typing.Any], typing.Any] + serialize: ( + None + | typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] ) = None, - deserialize: None - | ( - typing.Callable[[typing.Any], typing.Any] + deserialize: ( + None + | typing.Callable[[typing.Any], typing.Any] | typing.Callable[[typing.Any, dict], typing.Any] ) = None, **kwargs, @@ -2057,8 +2047,7 @@ msg = f"No context available for Function field {attr!r}" raise ValidationError(msg) return func(value, self.parent.context) - else: - return func(value) + return func(value) class Constant(Field): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/schema.py new/marshmallow-3.20.1/src/marshmallow/schema.py --- old/marshmallow-3.19.0/src/marshmallow/schema.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/schema.py 2023-07-21 00:08:04.000000000 +0200 @@ -1,5 +1,6 @@ """The :class:`Schema` class, including its metaclass and options (class Meta).""" from __future__ import annotations +from abc import ABCMeta from collections import defaultdict, OrderedDict from collections.abc import Mapping @@ -41,25 +42,21 @@ _T = typing.TypeVar("_T") -def _get_fields(attrs, ordered=False): - """Get fields from a class. If ordered=True, fields will sorted by creation index. +def _get_fields(attrs): + """Get fields from a class :param attrs: Mapping of class attributes - :param bool ordered: Sort fields by creation index """ - fields = [ + return [ (field_name, field_value) for field_name, field_value in attrs.items() if is_instance_or_subclass(field_value, base.FieldABC) ] - if ordered: - fields.sort(key=lambda pair: pair[1]._creation_index) - return fields # This function allows Schemas to inherit from non-Schema classes and ensures # inheritance according to the MRO -def _get_fields_by_mro(klass, ordered=False): +def _get_fields_by_mro(klass): """Collect fields from a class, following its method resolution order. The class itself is excluded from the search; only its parents are checked. Get fields from ``_declared_fields`` if available, else use ``__dict__``. @@ -72,7 +69,6 @@ ( _get_fields( getattr(base, "_declared_fields", base.__dict__), - ordered=ordered, ) for base in mro[:0:-1] ), @@ -80,7 +76,7 @@ ) -class SchemaMeta(type): +class SchemaMeta(ABCMeta): """Metaclass for the Schema class. Binds the declared fields to a ``_declared_fields`` attribute, which is a dictionary mapping attribute names to field objects. Also sets the ``opts`` class attribute, which is @@ -101,13 +97,13 @@ break else: ordered = False - cls_fields = _get_fields(attrs, ordered=ordered) + cls_fields = _get_fields(attrs) # Remove fields from list of class attributes to avoid shadowing # Schema attributes/methods in case of name conflict for field_name, _ in cls_fields: del attrs[field_name] klass = super().__new__(mcs, name, bases, attrs) - inherited_fields = _get_fields_by_mro(klass, ordered=ordered) + inherited_fields = _get_fields_by_mro(klass) meta = klass.Meta # Set klass.opts in __new__ rather than __init__ so that it is accessible in @@ -116,13 +112,12 @@ # Add fields specified in the `include` class Meta option cls_fields += list(klass.opts.include.items()) - dict_cls = OrderedDict if ordered else dict # Assign _declared_fields on class klass._declared_fields = mcs.get_declared_fields( klass=klass, cls_fields=cls_fields, inherited_fields=inherited_fields, - dict_cls=dict_cls, + dict_cls=dict, ) return klass @@ -132,7 +127,7 @@ klass: type, cls_fields: list, inherited_fields: list, - dict_cls: type, + dict_cls: type = dict, ): """Returns a dictionary of field_name => `Field` pairs declared on the class. This is exposed mainly so that plugins can add additional fields, e.g. fields @@ -142,8 +137,7 @@ :param cls_fields: The fields declared on the class, including those added by the ``include`` class Meta option. :param inherited_fields: Inherited fields. - :param dict_cls: Either `dict` or `OrderedDict`, depending on whether - the user specified `ordered=True`. + :param dict_cls: dict-like class to use for dict output Default to ``dict``. """ return dict_cls(inherited_fields + cls_fields) @@ -318,6 +312,8 @@ OPTIONS_CLASS = SchemaOpts # type: type + set_class = OrderedSet + # These get set by SchemaMeta opts = None # type: SchemaOpts _declared_fields = {} # type: typing.Dict[str, ma_fields.Field] @@ -349,9 +345,7 @@ - ``timeformat``: Default format for `Time <fields.Time>` fields. - ``render_module``: Module to use for `loads <Schema.loads>` and `dumps <Schema.dumps>`. Defaults to `json` from the standard library. - - ``ordered``: If `True`, order serialization output according to the - order in which fields were declared. Output of `Schema.dump` will be a - `collections.OrderedDict`. + - ``ordered``: If `True`, output of `Schema.dump` will be a `collections.OrderedDict`. - ``index_errors``: If `True`, errors dictionaries will include the index of invalid items in a collection. - ``load_only``: Tuple or list of fields to exclude from serialized results. @@ -373,7 +367,7 @@ context: dict | None = None, load_only: types.StrSequenceOrSet = (), dump_only: types.StrSequenceOrSet = (), - partial: bool | types.StrSequenceOrSet = False, + partial: bool | types.StrSequenceOrSet | None = None, unknown: str | None = None, ): # Raise error if only or exclude is passed as string, not list of strings @@ -385,7 +379,9 @@ self.declared_fields = copy.deepcopy(self._declared_fields) self.many = many self.only = only - self.exclude = set(self.opts.exclude) | set(exclude) + self.exclude: set[typing.Any] | typing.MutableSet[typing.Any] = set( + self.opts.exclude + ) | set(exclude) self.ordered = self.opts.ordered self.load_only = set(load_only) or set(self.opts.load_only) self.dump_only = set(dump_only) or set(self.opts.dump_only) @@ -418,10 +414,6 @@ def dict_class(self) -> type: return OrderedDict if self.ordered else dict - @property - def set_class(self) -> type: - return OrderedSet if self.ordered else set - @classmethod def from_dict( cls, @@ -589,7 +581,7 @@ *, error_store: ErrorStore, many: bool = False, - partial=False, + partial=None, unknown=RAISE, index=None, ) -> _T | list[_T]: @@ -656,11 +648,19 @@ f[len_prefix:] for f in partial if f.startswith(prefix) ] d_kwargs["partial"] = sub_partial - else: + elif partial is not None: d_kwargs["partial"] = partial - getter = lambda val: field_obj.deserialize( - val, field_name, data, **d_kwargs - ) + + def getter( + val, field_obj=field_obj, field_name=field_name, d_kwargs=d_kwargs + ): + return field_obj.deserialize( + val, + field_name, + data, + **d_kwargs, + ) + value = self._call_and_store( getter_func=getter, data=raw_value, @@ -961,7 +961,7 @@ if self.only is not None: # Return only fields specified in only option - field_names = self.set_class(self.only) + field_names: typing.AbstractSet[typing.Any] = self.set_class(self.only) invalid_fields |= field_names - available_field_names else: @@ -1047,15 +1047,15 @@ # the type checker's perspective. if isinstance(field_obj, type) and issubclass(field_obj, base.FieldABC): msg = ( - 'Field for "{}" must be declared as a ' + 'Field for "{field_name}" must be declared as a ' "Field instance, not a class. " - 'Did you mean "fields.{}()"?'.format(field_name, field_obj.__name__) + 'Did you mean "fields.{field_obj.__name__}()"?' ) raise TypeError(msg) from error raise error self.on_bind_field(field_name, field_obj) - @lru_cache(maxsize=8) + @lru_cache(maxsize=8) # noqa (https://github.com/PyCQA/flake8-bugbear/issues/310) def _has_processors(self, tag) -> bool: return bool(self._hooks[(tag, True)] or self._hooks[(tag, False)]) @@ -1080,7 +1080,7 @@ *, many: bool, original_data, - partial: bool | types.StrSequenceOrSet, + partial: bool | types.StrSequenceOrSet | None, ): # This has to invert the order of the dump processors, so run the pass_many # processors first. @@ -1157,7 +1157,7 @@ data, original_data, many: bool, - partial: bool | types.StrSequenceOrSet, + partial: bool | types.StrSequenceOrSet | None, field_errors: bool = False, ): for attr_name in self._hooks[(VALIDATES_SCHEMA, pass_many)]: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/types.py new/marshmallow-3.20.1/src/marshmallow/types.py --- old/marshmallow-3.19.0/src/marshmallow/types.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/types.py 2023-07-21 00:08:04.000000000 +0200 @@ -6,6 +6,6 @@ """ import typing -StrSequenceOrSet = typing.Union[typing.Sequence[str], typing.Set[str]] +StrSequenceOrSet = typing.Union[typing.Sequence[str], typing.AbstractSet[str]] Tag = typing.Union[str, typing.Tuple[str, bool]] Validator = typing.Callable[[typing.Any], typing.Any] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/utils.py new/marshmallow-3.20.1/src/marshmallow/utils.py --- old/marshmallow-3.19.0/src/marshmallow/utils.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/utils.py 2023-07-21 00:08:04.000000000 +0200 @@ -197,7 +197,12 @@ # Load a timestamp with utc as timezone to prevent using system timezone. # Then set timezone to None, to let the Field handle adding timezone info. - return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).replace(tzinfo=None) + try: + return dt.datetime.fromtimestamp(value, tz=dt.timezone.utc).replace(tzinfo=None) + except OverflowError as exc: + raise ValueError("Timestamp is too large") from exc + except OSError as exc: + raise ValueError("Error converting value to datetime") from exc def from_timestamp_ms(value: typing.Any) -> dt.datetime: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow/validate.py new/marshmallow-3.20.1/src/marshmallow/validate.py --- old/marshmallow-3.19.0/src/marshmallow/validate.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow/validate.py 2023-07-21 00:08:04.000000000 +0200 @@ -94,6 +94,7 @@ """Validate a URL. :param relative: Whether to allow relative URLs. + :param absolute: Whether to allow absolute URLs. :param error: Error message to raise in case of a validation error. Can be interpolated with `{input}`. :param schemes: Valid schemes. By default, ``http``, ``https``, @@ -105,38 +106,68 @@ def __init__(self): self._memoized = {} - def _regex_generator(self, relative: bool, require_tld: bool) -> typing.Pattern: - return re.compile( - r"".join( - ( - r"^", - r"(" if relative else r"", - r"(?:[a-z0-9\.\-\+]*)://", # scheme is validated separately - r"(?:[^:@]+?(:[^:@]*?)?@|)", # basic auth - r"(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+", - r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|", # domain... - r"localhost|", # localhost... - ( - r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)|" - if not require_tld - else r"" - ), # allow dotless hostnames - r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|", # ...or ipv4 - r"\[[A-F0-9]*:[A-F0-9:]+\])", # ...or ipv6 - r"(?::\d+)?", # optional port - r")?" - if relative - else r"", # host is optional, allow for relative URLs - r"(?:/?|[/?]\S+)\Z", - ) + def _regex_generator( + self, relative: bool, absolute: bool, require_tld: bool + ) -> typing.Pattern: + hostname_variants = [ + # a normal domain name, expressed in [A-Z0-9] chars with hyphens allowed only in the middle + # note that the regex will be compiled with IGNORECASE, so these are upper and lowercase chars + ( + r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+" + r"(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)" ), - re.IGNORECASE, + # or the special string 'localhost' + r"localhost", + # or IPv4 + r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", + # or IPv6 + r"\[[A-F0-9]*:[A-F0-9:]+\]", + ] + if not require_tld: + # allow dotless hostnames + hostname_variants.append(r"(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.?)") + + absolute_part = "".join( + ( + # scheme (e.g. 'https://', 'ftp://', etc) + # this is validated separately against allowed schemes, so in the regex + # we simply want to capture its existence + r"(?:[a-z0-9\.\-\+]*)://", + # basic_auth, for URLs encoding a username:password + # e.g. 'ftp://foo:b...@ftp.example.org/' + r"(?:[^:@]+?(:[^:@]*?)?@|)", + # netloc, the hostname/domain part of the URL plus the optional port + r"(?:", + "|".join(hostname_variants), + r")", + r"(?::\d+)?", + ) ) + relative_part = r"(?:/?|[/?]\S+)\Z" - def __call__(self, relative: bool, require_tld: bool) -> typing.Pattern: - key = (relative, require_tld) + if relative: + if absolute: + parts: tuple[str, ...] = ( + r"^(", + absolute_part, + r")?", + relative_part, + ) + else: + parts = (r"^", relative_part) + else: + parts = (r"^", absolute_part, relative_part) + + return re.compile("".join(parts), re.IGNORECASE) + + def __call__( + self, relative: bool, absolute: bool, require_tld: bool + ) -> typing.Pattern: + key = (relative, absolute, require_tld) if key not in self._memoized: - self._memoized[key] = self._regex_generator(relative, require_tld) + self._memoized[key] = self._regex_generator( + relative, absolute, require_tld + ) return self._memoized[key] @@ -149,17 +180,23 @@ self, *, relative: bool = False, + absolute: bool = True, schemes: types.StrSequenceOrSet | None = None, require_tld: bool = True, error: str | None = None, ): + if not relative and not absolute: + raise ValueError( + "URL validation cannot set both relative and absolute to False." + ) self.relative = relative + self.absolute = absolute self.error = error or self.default_message # type: str self.schemes = schemes or self.default_schemes self.require_tld = require_tld def _repr_args(self) -> str: - return f"relative={self.relative!r}" + return f"relative={self.relative!r}, absolute={self.absolute!r}" def _format_error(self, value) -> str: return self.error.format(input=value) @@ -175,7 +212,7 @@ if scheme not in self.schemes: raise ValidationError(message) - regex = self._regex(self.relative, self.require_tld) + regex = self._regex(self.relative, self.absolute, self.require_tld) if not regex.search(value): raise ValidationError(message) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow.egg-info/PKG-INFO new/marshmallow-3.20.1/src/marshmallow.egg-info/PKG-INFO --- old/marshmallow-3.19.0/src/marshmallow.egg-info/PKG-INFO 2022-11-11 17:10:43.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow.egg-info/PKG-INFO 2023-07-21 00:08:15.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: marshmallow -Version: 3.19.0 +Version: 3.20.1 Summary: A lightweight library for converting complex datatypes to and from native Python datatypes. Home-page: https://github.com/marshmallow-code/marshmallow Author: Steven Loria @@ -15,11 +15,11 @@ Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 -Requires-Python: >=3.7 +Classifier: Programming Language :: Python :: 3.11 +Requires-Python: >=3.8 Provides-Extra: tests Provides-Extra: lint Provides-Extra: docs @@ -34,10 +34,14 @@ :target: https://pypi.org/project/marshmallow/ :alt: Latest version -.. image:: https://dev.azure.com/sloria/sloria/_apis/build/status/marshmallow-code.marshmallow?branchName=dev - :target: https://dev.azure.com/sloria/sloria/_build/latest?definitionId=5&branchName=dev +.. image:: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml/badge.svg + :target: https://github.com/marshmallow-code/marshmallow/actions/workflows/build-release.yml :alt: Build status +.. image:: https://results.pre-commit.ci/badge/github/marshmallow-code/marshmallow/dev.svg + :target: https://results.pre-commit.ci/latest/github/marshmallow-code/marshmallow/dev + :alt: pre-commit.ci status + .. image:: https://readthedocs.org/projects/marshmallow/badge/ :target: https://marshmallow.readthedocs.io/ :alt: Documentation @@ -100,7 +104,7 @@ Requirements ============ -- Python >= 3.7 +- Python >= 3.8 Ecosystem ========= diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/src/marshmallow.egg-info/requires.txt new/marshmallow-3.20.1/src/marshmallow.egg-info/requires.txt --- old/marshmallow-3.19.0/src/marshmallow.egg-info/requires.txt 2022-11-11 17:10:43.000000000 +0100 +++ new/marshmallow-3.20.1/src/marshmallow.egg-info/requires.txt 2023-07-21 00:08:15.000000000 +0200 @@ -4,24 +4,24 @@ pytest pytz simplejson -mypy==0.990 -flake8==5.0.4 -flake8-bugbear==22.10.25 -pre-commit~=2.4 +mypy==1.4.1 +flake8==6.0.0 +flake8-bugbear==23.7.10 +pre-commit<4.0,>=2.4 tox [docs] -sphinx==5.3.0 +sphinx==7.0.1 sphinx-issues==3.0.1 -alabaster==0.7.12 +alabaster==0.7.13 sphinx-version-warning==1.1.2 -autodocsumm==0.2.9 +autodocsumm==0.2.11 [lint] -mypy==0.990 -flake8==5.0.4 -flake8-bugbear==22.10.25 -pre-commit~=2.4 +mypy==1.4.1 +flake8==6.0.0 +flake8-bugbear==23.7.10 +pre-commit<4.0,>=2.4 [tests] pytest diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_deserialization.py new/marshmallow-3.20.1/tests/test_deserialization.py --- old/marshmallow-3.19.0/tests/test_deserialization.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_deserialization.py 2023-07-21 00:08:04.000000000 +0200 @@ -4,6 +4,8 @@ import decimal import math +from unittest.mock import patch + import pytest from marshmallow import EXCLUDE, INCLUDE, RAISE, fields, Schema, validate @@ -21,6 +23,20 @@ ) +class MockDateTimeOverflowError(dt.datetime): + """Used to simulate the possible OverflowError of datetime.fromtimestamp""" + + def fromtimestamp(self, *args, **kwargs): + raise OverflowError() + + +class MockDateTimeOSError(dt.datetime): + """Used to simulate the possible OSError of datetime.fromtimestamp""" + + def fromtimestamp(self, *args, **kwargs): + raise OSError() + + class TestDeserializingNone: @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_fields_allow_none_deserialize_to_none(self, FieldClass): @@ -258,7 +274,6 @@ @pytest.mark.parametrize("allow_nan", (None, False, True)) @pytest.mark.parametrize("value", ("nan", "-nan", "inf", "-inf")) def test_float_field_allow_nan(self, value, allow_nan): - if allow_nan is None: # Test default case is False field = fields.Float() @@ -566,10 +581,21 @@ ["", "!@#", 0, -1, dt.datetime(2013, 11, 10, 1, 23, 45)], ) def test_invalid_timestamp_field_deserialization(self, fmt, in_value): - field = fields.DateTime(format="timestamp") + field = fields.DateTime(format=fmt) with pytest.raises(ValidationError, match="Not a valid datetime."): field.deserialize(in_value) + #  Regression test for https://github.com/marshmallow-code/marshmallow/pull/2102 + @pytest.mark.parametrize("fmt", ["timestamp", "timestamp_ms"]) + @pytest.mark.parametrize( + "mock_fromtimestamp", [MockDateTimeOSError, MockDateTimeOverflowError] + ) + def test_oversized_timestamp_field_deserialization(self, fmt, mock_fromtimestamp): + with patch("datetime.datetime", mock_fromtimestamp): + field = fields.DateTime(format=fmt) + with pytest.raises(ValidationError, match="Not a valid datetime."): + field.deserialize(99999999999999999) + @pytest.mark.parametrize( ("fmt", "timezone", "value", "expected"), [ @@ -1546,7 +1572,7 @@ name = fields.Str() class StoreSchema(Schema): - pets = fields.Nested(PetSchema(), allow_none=False, many=True) + pets = fields.Nested(PetSchema, allow_none=False, many=True) sch = StoreSchema() errors = sch.validate({"pets": None}) @@ -1570,7 +1596,7 @@ name = fields.Str() class StoreSchema(Schema): - pets = fields.Nested(PetSchema(), required=True, many=True) + pets = fields.Nested(PetSchema, required=True, many=True) sch = StoreSchema() errors = sch.validate({}) @@ -2210,6 +2236,21 @@ with pytest.raises(ValidationError): SchemaB().load(b_dict, partial=("z.y",)) + def test_nested_partial_default(self): + class SchemaA(Schema): + x = fields.Integer(required=True) + y = fields.Integer(required=True) + + class SchemaB(Schema): + z = fields.Nested(SchemaA(partial=("x",))) + + b_dict = {"z": {"y": 42}} + # Nested partial args should be respected. + result = SchemaB().load(b_dict) + assert result["z"]["y"] == 42 + with pytest.raises(ValidationError): + SchemaB().load({"z": {"x": 0}}) + @pytest.mark.parametrize("FieldClass", ALL_FIELDS) def test_required_field_failure(FieldClass): # noqa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_fields.py new/marshmallow-3.20.1/tests/test_fields.py --- old/marshmallow-3.19.0/tests/test_fields.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_fields.py 2023-07-21 00:08:04.000000000 +0200 @@ -9,6 +9,7 @@ RAISE, missing, ) +from marshmallow.orderedset import OrderedSet from marshmallow.exceptions import StringNotCollectionError from tests.base import ALL_FIELDS @@ -380,14 +381,14 @@ @pytest.mark.parametrize( ("param", "fields_list"), [("only", ["foo"]), ("exclude", ["bar"])] ) - def test_ordered_instanced_nested_schema_only_and_exclude(self, param, fields_list): + def test_nested_schema_only_and_exclude(self, param, fields_list): class NestedSchema(Schema): + # We mean to test the use of OrderedSet to specify it explicitly + # even if it is default + set_class = OrderedSet foo = fields.String() bar = fields.String() - class Meta: - ordered = True - class MySchema(Schema): nested = fields.Nested(NestedSchema(), **{param: fields_list}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_options.py new/marshmallow-3.20.1/tests/test_options.py --- old/marshmallow-3.19.0/tests/test_options.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_options.py 2023-07-21 00:08:04.000000000 +0200 @@ -178,9 +178,6 @@ def test_nested_field_order_with_exclude_arg_is_maintained(self, user): class HasNestedExclude(Schema): - class Meta: - ordered = True - user = fields.Nested(KeepOrder, exclude=("birthdate",)) ser = HasNestedExclude() @@ -231,7 +228,7 @@ result = s.load({"name": "Steve", "from": "Oskosh"}) assert result == in_data - def test_ordered_included(self): + def test_included_fields_ordered_after_declared_fields(self): class AddFieldsOrdered(Schema): name = fields.Str() email = fields.Str() @@ -242,7 +239,6 @@ "in": fields.Str(), "@at": fields.Str(), } - ordered = True s = AddFieldsOrdered() in_data = { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_registry.py new/marshmallow-3.20.1/tests/test_registry.py --- old/marshmallow-3.19.0/tests/test_registry.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_registry.py 2023-07-21 00:08:04.000000000 +0200 @@ -52,7 +52,6 @@ def test_serializer_class_registry_register_same_classname_different_module(): - reglen = len(class_registry._registry) type("MyTestRegSchema", (Schema,), {"__module__": "modA"}) @@ -83,7 +82,6 @@ def test_serializer_class_registry_override_if_same_classname_same_module(): - reglen = len(class_registry._registry) type("MyTestReg2Schema", (Schema,), {"__module__": "SameModulePath"}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_schema.py new/marshmallow-3.20.1/tests/test_schema.py --- old/marshmallow-3.19.0/tests/test_schema.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_schema.py 2023-07-21 00:08:04.000000000 +0200 @@ -2933,3 +2933,16 @@ MySchema(unknown="badval") else: MySchema().load({"foo": "bar"}, unknown="badval") + + +@pytest.mark.parametrize("dict_cls", (dict, OrderedDict)) +def test_set_dict_class(dict_cls): + """Demonstrate how to specify dict_class as class attribute""" + + class MySchema(Schema): + dict_class = dict_cls + foo = fields.String() + + result = MySchema().dump({"foo": "bar"}) + assert result == {"foo": "bar"} + assert isinstance(result, dict_cls) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_serialization.py new/marshmallow-3.20.1/tests/test_serialization.py --- old/marshmallow-3.19.0/tests/test_serialization.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_serialization.py 2023-07-21 00:08:04.000000000 +0200 @@ -146,7 +146,6 @@ assert field.serialize("uuid2", user) is None def test_ip_address_field(self, user): - ipv4_string = "192.168.0.1" ipv6_string = "ffff::ffff" ipv6_exploded_string = ipaddress.ip_address("ffff::ffff").exploded @@ -167,7 +166,6 @@ assert field_exploded.serialize("ipv6", user) == ipv6_exploded_string def test_ipv4_address_field(self, user): - ipv4_string = "192.168.0.1" user.ipv4 = ipaddress.ip_address(ipv4_string) @@ -179,7 +177,6 @@ assert field.serialize("empty_ip", user) is None def test_ipv6_address_field(self, user): - ipv6_string = "ffff::ffff" ipv6_exploded_string = ipaddress.ip_address("ffff::ffff").exploded @@ -196,7 +193,6 @@ assert field_exploded.serialize("ipv6", user) == ipv6_exploded_string def test_ip_interface_field(self, user): - ipv4interface_string = "192.168.0.1/24" ipv6interface_string = "ffff::ffff/128" ipv6interface_exploded_string = ipaddress.ip_interface( @@ -222,7 +218,6 @@ ) def test_ipv4_interface_field(self, user): - ipv4interface_string = "192.168.0.1/24" user.ipv4interface = ipaddress.ip_interface(ipv4interface_string) @@ -234,7 +229,6 @@ assert field.serialize("empty_ipinterface", user) is None def test_ipv6_interface_field(self, user): - ipv6interface_string = "ffff::ffff/128" ipv6interface_exploded_string = ipaddress.ip_interface( "ffff::ffff/128" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_utils.py new/marshmallow-3.20.1/tests/test_utils.py --- old/marshmallow-3.19.0/tests/test_utils.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_utils.py 2023-07-21 00:08:04.000000000 +0200 @@ -228,6 +228,31 @@ assert_date_equal(result, d) +@pytest.mark.parametrize( + ("value", "expected"), + [ + (1676386740, dt.datetime(2023, 2, 14, 14, 59, 00)), + (1676386740.58, dt.datetime(2023, 2, 14, 14, 59, 00, 580000)), + ], +) +def test_from_timestamp(value, expected): + result = utils.from_timestamp(value) + assert type(result) == dt.datetime + assert result == expected + + +def test_from_timestamp_with_negative_value(): + value = -10 + with pytest.raises(ValueError, match=r"Not a valid POSIX timestamp"): + utils.from_timestamp(value) + + +def test_from_timestamp_with_overflow_value(): + value = 9223372036854775 + with pytest.raises(ValueError): + utils.from_timestamp(value) + + def test_get_func_args(): def f1(foo, bar): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/marshmallow-3.19.0/tests/test_validate.py new/marshmallow-3.20.1/tests/test_validate.py --- old/marshmallow-3.19.0/tests/test_validate.py 2022-11-11 17:10:31.000000000 +0100 +++ new/marshmallow-3.20.1/tests/test_validate.py 2023-07-21 00:08:04.000000000 +0200 @@ -75,6 +75,9 @@ "http://example.com/./icons/logo.gif", "ftp://example.com/../../../../g", "http://example.com/g?y/./x", + "/foo/bar", + "/foo?bar", + "/foo?bar#baz", ], ) def test_url_relative_valid(valid_url): @@ -107,6 +110,48 @@ @pytest.mark.parametrize( "valid_url", [ + "/foo/bar", + "/foo?bar", + "?bar", + "/foo?bar#baz", + ], +) +def test_url_relative_only_valid(valid_url): + validator = validate.URL(relative=True, absolute=False) + assert validator(valid_url) == valid_url + + +@pytest.mark.parametrize( + "invalid_url", + [ + "http//example.org", + "http://example.org\n", + "suppliers.html", + "../icons/logo.gif", + "icons/logo.gif", + "../.../g", + "...", + "\\", + " ", + "", + "http://example.org", + "http://123.45.67.8/", + "http://example.com/foo/bar/../baz", + "https://example.com/../icons/logo.gif", + "http://example.com/./icons/logo.gif", + "ftp://example.com/../../../../g", + "http://example.com/g?y/./x", + ], +) +def test_url_relative_only_invalid(invalid_url): + validator = validate.URL(relative=True, absolute=False) + with pytest.raises(ValidationError): + validator(invalid_url) + + +@pytest.mark.parametrize( + "valid_url", + [ "http://example.org", "http://123.45.67.8/", "http://example", @@ -170,10 +215,21 @@ def test_url_repr(): assert repr( validate.URL(relative=False, error=None) - ) == "<URL(relative=False, error={!r})>".format("Not a valid URL.") + ) == "<URL(relative=False, absolute=True, error={!r})>".format("Not a valid URL.") assert repr( validate.URL(relative=True, error="foo") - ) == "<URL(relative=True, error={!r})>".format("foo") + ) == "<URL(relative=True, absolute=True, error={!r})>".format("foo") + assert repr( + validate.URL(relative=True, absolute=False, error="foo") + ) == "<URL(relative=True, absolute=False, error={!r})>".format("foo") + + +def test_url_rejects_invalid_relative_usage(): + with pytest.raises( + ValueError, + match="URL validation cannot set both relative and absolute to False", + ): + validate.URL(relative=False, absolute=False) @pytest.mark.parametrize(