New Upstream Release - python-marshmallow

Ready changes

Summary

Merged new upstream version: 3.19.0 (was: 3.18.0).

Resulting package

Built on 2022-12-30T23:17 (took 3m8s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases python3-marshmallow-docapt install -t fresh-releases python3-marshmallow

Lintian Result

Diff

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3d06386
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,81 @@
+*.py[cod]
+
+# C extensions
+*.so
+
+# Packages
+*.egg
+*.egg-info
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+pip-wheel-metadata
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+htmlcov
+.tox
+nosetests.xml
+.cache
+.pytest_cache
+
+# Translations
+*.mo
+
+# Mr Developer
+.mr.developer.cfg
+
+# IDE
+.project
+.pydevproject
+.idea
+
+# Coverage
+cover
+.coveragerc
+
+# Sphinx
+docs/_build
+README.html
+
+*.ipynb
+.ipynb_checkpoints
+
+Vagrantfile
+.vagrant
+
+*.db
+*.ai
+.konchrc
+_sandbox
+pylintrc
+
+# Virtualenvs
+env
+venv
+
+# pyenv
+.python-version
+
+# pytest
+.pytest_cache
+
+# Other
+.directory
+*.pprof
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
diff --git a/AUTHORS.rst b/AUTHORS.rst
index f4aec16..a0a928a 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -170,3 +170,4 @@ Contributors (chronological)
 - Isira Seneviratne `@Isira-Seneviratne <https://github.com/Isira-Seneviratne>`_
 - Karthikeyan Singaravelan `@tirkarthi  <https://github.com/tirkarthi>`_
 - Marco Satti `@marcosatti  <https://github.com/marcosatti>`_
+- Ivo Reumkens `@vanHoi <https://github.com/vanHoi>`_
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ec2a084..2ae6c8a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,16 @@
 Changelog
 ---------
 
+3.19.0 (2022-11-11)
+*******************
+
+Features:
+
+- Add ``timestamp`` and ``timestamp_ms`` formats to `fields.DateTime`
+  (:issue:`612`).
+  Thanks :user:`vgavro` for the suggestion and thanks :user:`vanHoi` for
+  the PR.
+
 3.18.0 (2022-09-15)
 *******************
 
diff --git a/debian/changelog b/debian/changelog
index ecdafe2..667a2bc 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+python-marshmallow (3.19.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Fri, 30 Dec 2022 23:14:33 -0000
+
 python-marshmallow (3.18.0-1) unstable; urgency=medium
 
   * New upstream release
diff --git a/debian/patches/0001-remove-privacy-breaches b/debian/patches/0001-remove-privacy-breaches
index fb53228..e419ea4 100644
--- a/debian/patches/0001-remove-privacy-breaches
+++ b/debian/patches/0001-remove-privacy-breaches
@@ -1,9 +1,11 @@
 From: Federico Ceratto <federico@debian.org>
 Subject: Remove privacy breaches
 
---- a/docs/conf.py
-+++ b/docs/conf.py
-@@ -13,9 +13,6 @@
+Index: python-marshmallow.git/docs/conf.py
+===================================================================
+--- python-marshmallow.git.orig/docs/conf.py
++++ python-marshmallow.git/docs/conf.py
+@@ -13,9 +13,6 @@ extensions = [
      "sphinx.ext.intersphinx",
      "sphinx.ext.viewcode",
      "alabaster",
@@ -13,7 +15,7 @@ Subject: Remove privacy breaches
  ]
  
  primary_domain = "py"
-@@ -59,10 +56,6 @@
+@@ -59,10 +56,6 @@ html_theme_options = {
      "logo": "marshmallow-logo.png",
      "description": "Object serialization and deserialization, lightweight and fluffy.",
      "description_font_style": "italic",
@@ -24,9 +26,11 @@ Subject: Remove privacy breaches
      "opencollective": "marshmallow",
      "tidelift_url": (
          "https://tidelift.com/subscription/pkg/pypi-marshmallow"
---- a/docs/_templates/donate.html
-+++ b/docs/_templates/donate.html
-@@ -17,7 +17,6 @@
+Index: python-marshmallow.git/docs/_templates/donate.html
+===================================================================
+--- python-marshmallow.git.orig/docs/_templates/donate.html
++++ python-marshmallow.git/docs/_templates/donate.html
+@@ -17,7 +17,6 @@ If you find marshmallow useful, please c
  {% if theme_opencollective %}
  <p>
  <a class="badge" href="https://opencollective.com/{{ theme_opencollective }}" target="_blank">
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..50441ab
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,2 @@
+marshmallow.docset
+marshmallow.tgz
diff --git a/setup.py b/setup.py
index 286ec75..44e8546 100644
--- a/setup.py
+++ b/setup.py
@@ -4,13 +4,13 @@ from setuptools import setup, find_packages
 EXTRAS_REQUIRE = {
     "tests": ["pytest", "pytz", "simplejson"],
     "lint": [
-        "mypy==0.971",
+        "mypy==0.990",
         "flake8==5.0.4",
-        "flake8-bugbear==22.9.11",
+        "flake8-bugbear==22.10.25",
         "pre-commit~=2.4",
     ],
     "docs": [
-        "sphinx==5.1.1",
+        "sphinx==5.3.0",
         "sphinx-issues==3.0.1",
         "alabaster==0.7.12",
         "sphinx-version-warning==1.1.2",
diff --git a/src/marshmallow/__init__.py b/src/marshmallow/__init__.py
index 6ff7589..3ec24fe 100644
--- a/src/marshmallow/__init__.py
+++ b/src/marshmallow/__init__.py
@@ -16,7 +16,7 @@ from marshmallow.utils import EXCLUDE, INCLUDE, RAISE, missing, pprint
 
 from . import fields
 
-__version__ = "3.18.0"
+__version__ = "3.19.0"
 __parsed_version__ = Version(__version__)
 __version_info__: tuple[int, int, int] | tuple[
     int, int, int, str, int
diff --git a/src/marshmallow/fields.py b/src/marshmallow/fields.py
index f153b5f..b53562a 100644
--- a/src/marshmallow/fields.py
+++ b/src/marshmallow/fields.py
@@ -1218,11 +1218,14 @@ class DateTime(Field):
     Example: ``'2014-12-22T03:12:58.019077+00:00'``
 
     :param format: Either ``"rfc"`` (for RFC822), ``"iso"`` (for ISO8601),
-        or a date format string. If `None`, defaults to "iso".
+        ``"timestamp"``, ``"timestamp_ms"`` (for a POSIX timestamp) or a date format string.
+        If `None`, defaults to "iso".
     :param kwargs: The same keyword arguments that :class:`Field` receives.
 
     .. versionchanged:: 3.0.0rc9
         Does not modify timezone information on (de)serialization.
+    .. versionchanged:: 3.19
+        Add timestamp as a format.
     """
 
     SERIALIZATION_FUNCS = {
@@ -1230,13 +1233,17 @@ class DateTime(Field):
         "iso8601": utils.isoformat,
         "rfc": utils.rfcformat,
         "rfc822": utils.rfcformat,
-    }  # type: typing.Dict[str, typing.Callable[[typing.Any], str]]
+        "timestamp": utils.timestamp,
+        "timestamp_ms": utils.timestamp_ms,
+    }  # type: typing.Dict[str, typing.Callable[[typing.Any], str | float]]
 
     DESERIALIZATION_FUNCS = {
         "iso": utils.from_iso_datetime,
         "iso8601": utils.from_iso_datetime,
         "rfc": utils.from_rfc,
         "rfc822": utils.from_rfc,
+        "timestamp": utils.from_timestamp,
+        "timestamp_ms": utils.from_timestamp_ms,
     }  # type: typing.Dict[str, typing.Callable[[str], typing.Any]]
 
     DEFAULT_FORMAT = "iso"
@@ -1252,7 +1259,7 @@ class DateTime(Field):
         "format": '"{input}" cannot be formatted as a {obj_type}.',
     }
 
-    def __init__(self, format: str | None = None, **kwargs):
+    def __init__(self, format: str | None = None, **kwargs) -> None:
         super().__init__(**kwargs)
         # Allow this to be None. It may be set later in the ``_serialize``
         # or ``_deserialize`` methods. This allows a Schema to dynamically set the
@@ -1267,7 +1274,7 @@ class DateTime(Field):
             or self.DEFAULT_FORMAT
         )
 
-    def _serialize(self, value, attr, obj, **kwargs):
+    def _serialize(self, value, attr, obj, **kwargs) -> str | float | None:
         if value is None:
             return None
         data_format = self.format or self.DEFAULT_FORMAT
@@ -1277,7 +1284,7 @@ class DateTime(Field):
         else:
             return value.strftime(data_format)
 
-    def _deserialize(self, value, attr, data, **kwargs):
+    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
@@ -1298,7 +1305,7 @@ class DateTime(Field):
                 ) from error
 
     @staticmethod
-    def _make_object_from_format(value, data_format):
+    def _make_object_from_format(value, data_format) -> dt.datetime:
         return dt.datetime.strptime(value, data_format)
 
 
@@ -1323,11 +1330,11 @@ class NaiveDateTime(DateTime):
         *,
         timezone: dt.timezone | None = None,
         **kwargs,
-    ):
+    ) -> None:
         super().__init__(format=format, **kwargs)
         self.timezone = timezone
 
-    def _deserialize(self, value, attr, data, **kwargs):
+    def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
         ret = super()._deserialize(value, attr, data, **kwargs)
         if is_aware(ret):
             if self.timezone is None:
@@ -1360,11 +1367,11 @@ class AwareDateTime(DateTime):
         *,
         default_timezone: dt.tzinfo | None = None,
         **kwargs,
-    ):
+    ) -> None:
         super().__init__(format=format, **kwargs)
         self.default_timezone = default_timezone
 
-    def _deserialize(self, value, attr, data, **kwargs):
+    def _deserialize(self, value, attr, data, **kwargs) -> dt.datetime:
         ret = super()._deserialize(value, attr, data, **kwargs)
         if not is_aware(ret):
             if self.default_timezone is None:
diff --git a/src/marshmallow/utils.py b/src/marshmallow/utils.py
index fd65133..7a16ff6 100644
--- a/src/marshmallow/utils.py
+++ b/src/marshmallow/utils.py
@@ -190,6 +190,34 @@ def from_iso_date(value):
     return dt.date(**kw)
 
 
+def from_timestamp(value: typing.Any) -> dt.datetime:
+    value = float(value)
+    if value < 0:
+        raise ValueError("Not a valid POSIX timestamp")
+
+    # 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)
+
+
+def from_timestamp_ms(value: typing.Any) -> dt.datetime:
+    value = float(value)
+    return from_timestamp(value / 1000)
+
+
+def timestamp(
+    value: dt.datetime,
+) -> float:
+    if not is_aware(value):
+        # When a date is naive, use UTC as zone info to prevent using system timezone.
+        value = value.replace(tzinfo=dt.timezone.utc)
+    return value.timestamp()
+
+
+def timestamp_ms(value: dt.datetime) -> float:
+    return timestamp(value) * 1000
+
+
 def isoformat(datetime: dt.datetime) -> str:
     """Return the ISO8601-formatted representation of a datetime object.
 
diff --git a/tests/test_deserialization.py b/tests/test_deserialization.py
index c80a258..32aa099 100644
--- a/tests/test_deserialization.py
+++ b/tests/test_deserialization.py
@@ -526,6 +526,50 @@ class TestFieldDeserialization:
         else:
             assert field.deserialize(value) == expected
 
+    @pytest.mark.parametrize(
+        ("fmt", "value", "expected"),
+        [
+            ("timestamp", 1384043025, dt.datetime(2013, 11, 10, 0, 23, 45)),
+            ("timestamp", "1384043025", dt.datetime(2013, 11, 10, 0, 23, 45)),
+            ("timestamp", 1384043025, dt.datetime(2013, 11, 10, 0, 23, 45)),
+            ("timestamp", 1384043025.12, dt.datetime(2013, 11, 10, 0, 23, 45, 120000)),
+            (
+                "timestamp",
+                1384043025.123456,
+                dt.datetime(2013, 11, 10, 0, 23, 45, 123456),
+            ),
+            ("timestamp", 1, dt.datetime(1970, 1, 1, 0, 0, 1)),
+            ("timestamp_ms", 1384043025000, dt.datetime(2013, 11, 10, 0, 23, 45)),
+            ("timestamp_ms", 1000, dt.datetime(1970, 1, 1, 0, 0, 1)),
+        ],
+    )
+    def test_timestamp_field_deserialization(self, fmt, value, expected):
+        field = fields.DateTime(format=fmt)
+        assert field.deserialize(value) == expected
+
+        # By default, a datetime from a timestamp is never aware.
+        field = fields.NaiveDateTime(format=fmt)
+        assert field.deserialize(value) == expected
+
+        field = fields.AwareDateTime(format=fmt)
+        with pytest.raises(ValidationError, match="Not a valid aware datetime."):
+            field.deserialize(value)
+
+        # But it can be added by providing a default.
+        field = fields.AwareDateTime(format=fmt, default_timezone=central)
+        expected_aware = expected.replace(tzinfo=central)
+        assert field.deserialize(value) == expected_aware
+
+    @pytest.mark.parametrize("fmt", ["timestamp", "timestamp_ms"])
+    @pytest.mark.parametrize(
+        "in_value",
+        ["", "!@#", 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")
+        with pytest.raises(ValidationError, match="Not a valid datetime."):
+            field.deserialize(in_value)
+
     @pytest.mark.parametrize(
         ("fmt", "timezone", "value", "expected"),
         [
diff --git a/tests/test_serialization.py b/tests/test_serialization.py
index 51671dd..56f0773 100644
--- a/tests/test_serialization.py
+++ b/tests/test_serialization.py
@@ -579,6 +579,38 @@ class TestFieldSerialization:
         field = fields.DateTime(format=fmt)
         assert field.serialize("d", {"d": value}) == expected
 
+    @pytest.mark.parametrize(
+        ("fmt", "value", "expected"),
+        [
+            ("timestamp", dt.datetime(1970, 1, 1), 0),
+            ("timestamp", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025),
+            (
+                "timestamp",
+                dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc),
+                1384043025,
+            ),
+            (
+                "timestamp",
+                central.localize(dt.datetime(2013, 11, 10, 0, 23, 45), is_dst=False),
+                1384064625,
+            ),
+            ("timestamp_ms", dt.datetime(2013, 11, 10, 0, 23, 45), 1384043025000),
+            (
+                "timestamp_ms",
+                dt.datetime(2013, 11, 10, 0, 23, 45, tzinfo=dt.timezone.utc),
+                1384043025000,
+            ),
+            (
+                "timestamp_ms",
+                central.localize(dt.datetime(2013, 11, 10, 0, 23, 45), is_dst=False),
+                1384064625000,
+            ),
+        ],
+    )
+    def test_datetime_field_timestamp(self, fmt, value, expected):
+        field = fields.DateTime(format=fmt)
+        assert field.serialize("d", {"d": value}) == expected
+
     @pytest.mark.parametrize("fmt", ["iso", "iso8601", None])
     @pytest.mark.parametrize(
         ("value", "expected"),

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.19.0.egg-info/top_level.txt
lrwxrwxrwx  root/root   /usr/share/doc/python3-marshmallow/html/_static/_sphinx_javascript_frameworks_compat.js -> ../../../../javascript/sphinxdoc/1.0/_sphinx_javascript_frameworks_compat.js

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/PKG-INFO
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/dependency_links.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/not-zip-safe
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/requires.txt
-rw-r--r--  root/root   /usr/lib/python3/dist-packages/marshmallow-3.18.0.egg-info/top_level.txt

No differences were encountered between the control files of package python3-marshmallow

Control files of package python3-marshmallow-doc: lines which differ (wdiff format)

  • Depends: libjs-sphinxdoc (>= 4.3), 5.0), libjs-jquery, libjs-underscore

More details

Full run details